Fix URi tests (and class) to be omcpatible with both 2.0 and 4.0 (and SL4)
[mono.git] / mcs / class / System / System / Uri.cs
index 3659e871513073e709714e89f76fed3b240ef802..101f6c94d813e7cfa3eac7e08cdc95ad062276b3 100644 (file)
@@ -13,7 +13,8 @@
 // (C) 2001 Garrett Rooney
 // (C) 2003 Ian MacLean
 // (C) 2003 Ben Maurer
-// Copyright (C) 2003,2005 Novell, Inc (http://www.novell.com)
+// Copyright (C) 2003,2009 Novell, Inc (http://www.novell.com)
+// Copyright (c) 2009 Stephane Delcroix
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
@@ -54,12 +55,8 @@ using System.Globalization;
 namespace System {
 
        [Serializable]
-#if NET_2_0
        [TypeConverter (typeof (UriTypeConverter))]
        public class Uri : ISerializable {
-#else
-       public class Uri : MarshalByRefObject, ISerializable {
-#endif
                // NOTES:
                // o  scheme excludes the scheme delimiter
                // o  port is -1 to indicate no port is defined
@@ -105,24 +102,37 @@ namespace System {
                public static readonly string UriSchemeMailto = "mailto";
                public static readonly string UriSchemeNews = "news";
                public static readonly string UriSchemeNntp = "nntp";
-#if NET_2_0
                public static readonly string UriSchemeNetPipe = "net.pipe";
                public static readonly string UriSchemeNetTcp = "net.tcp";
-#endif
 
                // Constructors         
 
+#if MOONLIGHT
+               public Uri (string uriString) : this (uriString, UriKind.Absolute) 
+               {
+               }
+#else
                public Uri (string uriString) : this (uriString, false) 
                {
                }
-
-               protected Uri (SerializationInfo serializationInfo, 
-                              StreamingContext streamingContext) :
-                       this (serializationInfo.GetString ("AbsoluteUri"), true)
+#endif
+               protected Uri (SerializationInfo serializationInfo, StreamingContext streamingContext)
                {
+                       string uri = serializationInfo.GetString ("AbsoluteUri");
+                       if (uri.Length > 0) {
+                               source = uri;
+                               ParseUri(UriKind.Absolute);
+                       } else {
+                               uri = serializationInfo.GetString ("RelativeUri");
+                               if (uri.Length > 0) {
+                                       source = uri;
+                                       ParseUri(UriKind.Relative);
+                               } else {
+                                       throw new ArgumentException("Uri string was null or empty.");
+                               }
+                       }
                }
 
-#if NET_2_0
                public Uri (string uriString, UriKind uriKind)
                {
                        source = uriString;
@@ -143,13 +153,55 @@ namespace System {
                                break;
                        default:
                                string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
-                               throw new ArgumentException ("uriKind", msg);
+                               throw new ArgumentException (msg);
+                       }
+               }
+
+               //
+               // An exception-less constructor, returns success
+               // condition on the out parameter `success'.
+               //
+               Uri (string uriString, UriKind uriKind, out bool success)
+               {
+                       if (uriString == null) {
+                               success = false;
+                               return;
+                       }
+
+                       if (uriKind != UriKind.RelativeOrAbsolute &&
+                               uriKind != UriKind.Absolute &&
+                               uriKind != UriKind.Relative) {
+                               string msg = Locale.GetText ("Invalid UriKind value '{0}'.", uriKind);
+                               throw new ArgumentException (msg);
+                       }
+
+                       source = uriString;
+                       if (ParseNoExceptions (uriKind, uriString) != null)
+                               success = false;
+                       else {
+                               success = true;
+                               
+                               switch (uriKind) {
+                               case UriKind.Absolute:
+                                       if (!IsAbsoluteUri)
+                                               success = false;
+                                       break;
+                               case UriKind.Relative:
+                                       if (IsAbsoluteUri)
+                                               success = false;
+                                       break;
+                               case UriKind.RelativeOrAbsolute:
+                                       break;
+                               default:
+                                       success = false;
+                                       break;
+                               }
                        }
                }
 
                public Uri (Uri baseUri, Uri relativeUri)
-                       : this (baseUri, relativeUri.OriginalString, false)
                {
+                       Merge (baseUri, relativeUri == null ? String.Empty : relativeUri.OriginalString);
                        // FIXME: this should call UriParser.Resolve
                }
 
@@ -164,50 +216,35 @@ namespace System {
                                throw new UriFormatException("Invalid URI: The format of the URI could not be "
                                        + "determined: " + uriString);
                }
-#else
-               public Uri (string uriString, bool dontEscape) 
-               {
-                       userEscaped = dontEscape;
-                       source = uriString;
-                       Parse ();
-                       if (!isAbsoluteUri)
-                               throw new UriFormatException("Invalid URI: The format of the URI could not be "
-                                       + "determined.");
-               }
-#endif
 
                public Uri (Uri baseUri, string relativeUri) 
-                       : this (baseUri, relativeUri, false) 
                {
+                       Merge (baseUri, relativeUri);
                        // FIXME: this should call UriParser.Resolve
                }
 
-#if NET_2_0
                [Obsolete ("dontEscape is always false")]
-#endif
                public Uri (Uri baseUri, string relativeUri, bool dontEscape) 
                {
-#if NET_2_0
+                       userEscaped = dontEscape;
+                       Merge (baseUri, relativeUri);
+               }
+
+               private void Merge (Uri baseUri, string relativeUri)
+               {
                        if (baseUri == null)
                                throw new ArgumentNullException ("baseUri");
+                       if (!baseUri.IsAbsoluteUri)
+                               throw new ArgumentOutOfRangeException ("baseUri");
                        if (relativeUri == null)
                                relativeUri = String.Empty;
-#else
-                       if (baseUri == null)
-                               throw new NullReferenceException ("baseUri");
-#endif
-                       // See RFC 2396 Par 5.2 and Appendix C
 
-                       userEscaped = dontEscape;
+                       // See RFC 2396 Par 5.2 and Appendix C
 
                        // Check Windows UNC (for // it is scheme/host separator)
                        if (relativeUri.Length >= 2 && relativeUri [0] == '\\' && relativeUri [1] == '\\') {
                                source = relativeUri;
-#if NET_2_0
                                ParseUri (UriKind.Absolute);
-#else
-                               Parse ();
-#endif
                                return;
                        }
 
@@ -232,11 +269,7 @@ namespace System {
                                            relativeUri.Length > pos + 1 &&
                                            relativeUri [pos + 1] == '/') {
                                                source = relativeUri;
-#if NET_2_0
                                                ParseUri (UriKind.Absolute);
-#else
-                                               Parse ();
-#endif
                                                return;
                                        }
                                        else
@@ -252,13 +285,13 @@ namespace System {
                        this.isUnixFilePath = baseUri.isUnixFilePath;
                        this.isOpaquePart = baseUri.isOpaquePart;
 
-                       if (relativeUri == String.Empty) {
+                       if (relativeUri.Length == 0) {
                                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 ('#');
@@ -267,26 +300,30 @@ namespace System {
                                        fragment = relativeUri.Substring (pos);
                                else
                                        fragment = "#" + EscapeString (relativeUri.Substring (pos+1));
-                               relativeUri = relativeUri.Substring (0, pos);
+                               relativeUri = pos == 0 ? String.Empty : relativeUri.Substring (0, pos);
                        }
 
+                       bool consider_query = false;
+
                        // 6 query
                        pos = relativeUri.IndexOf ('?');
                        if (pos != -1) {
                                query = relativeUri.Substring (pos);
                                if (!userEscaped)
                                        query = EscapeString (query);
-                               relativeUri = relativeUri.Substring (0, pos);
+#if !NET_4_0 && !MOONLIGHT
+                               consider_query = query.Length > 0;
+#endif
+                               relativeUri = pos == 0 ? String.Empty : relativeUri.Substring (0, pos);
+                       } else if (relativeUri.Length == 0) {
+                               // if there is no relative path then we keep the Query and Fragment from the absolute
+                               query = baseUri.query;
                        }
 
                        if (relativeUri.Length > 0 && relativeUri [0] == '/') {
                                if (relativeUri.Length > 1 && relativeUri [1] == '/') {
                                        source = scheme + ':' + relativeUri;
-#if NET_2_0
                                        ParseUri (UriKind.Absolute);
-#else
-                                       Parse ();
-#endif
                                        return;
                                } else {
                                        path = relativeUri;
@@ -298,7 +335,7 @@ namespace System {
                        
                        // par 5.2 step 6 a)
                        path = baseUri.path;
-                       if (relativeUri.Length > 0 || query.Length > 0) {
+                       if ((relativeUri.Length > 0) || consider_query) {
                                pos = path.LastIndexOf ('/');
                                if (pos >= 0) 
                                        path = path.Substring (0, pos + 1);
@@ -359,6 +396,10 @@ namespace System {
                                                path = path.Remove (pos + 1, path.Length - pos - 1);
                        }
                        
+                       // 6 g)
+                       while (path.StartsWith ("/../"))
+                               path = path.Substring (3);
+                       
                        if (!userEscaped)
                                path = EscapeString (path);
                }               
@@ -367,7 +408,6 @@ namespace System {
                
                public string AbsolutePath { 
                        get {
-#if NET_2_0
                                EnsureAbsoluteUri ();
                                switch (Scheme) {
                                case "mailto":
@@ -384,9 +424,6 @@ namespace System {
                                        }
                                        return path;
                                }
-#else
-                               return path;
-#endif
                        }
                }
 
@@ -432,17 +469,12 @@ namespace System {
                                UriHostNameType ret = CheckHostName (Host);
                                if (ret != UriHostNameType.Unknown)
                                        return ret;
-#if NET_2_0
                                switch (Scheme) {
                                case "mailto":
                                        return UriHostNameType.Basic;
                                default:
                                        return (IsFile) ? UriHostNameType.Basic : ret;
                                }
-#else
-                               // looks it always returns Basic...
-                               return UriHostNameType.Basic; //.Unknown;
-#endif
                        } 
                }
 
@@ -465,25 +497,23 @@ namespace System {
                                EnsureAbsoluteUri ();
                                
                                if (Host.Length == 0) {
-#if NET_2_0
                                        return IsFile;
-#else
-                                       return false;
-#endif
                                }
 
                                if (host == "loopback" || host == "localhost") 
                                        return true;
-                                       
-                               try {
-                                       if (IPAddress.Loopback.Equals (IPAddress.Parse (host)))
+
+                               IPAddress result;
+                               if (IPAddress.TryParse (host, out result))
+                                       if (IPAddress.Loopback.Equals (result))
                                                return true;
-                               } catch (FormatException) {}
 
-                               try {
-                                       return IPv6Address.IsLoopback (IPv6Address.Parse (host));
-                               } catch (FormatException) {}
-                               
+                               IPv6Address result6;
+                               if (IPv6Address.TryParse (host, out result6)){
+                                       if (IPv6Address.IsLoopback (result6))
+                                               return true;
+                               }
+
                                return false;
                        } 
                }
@@ -531,13 +561,9 @@ namespace System {
                                        else if (System.IO.Path.DirectorySeparatorChar == '\\') {
                                                string h = host;
                                                if (path.Length > 0) {
-#if NET_2_0
                                                        if ((path.Length > 1) || (path[0] != '/')) {
                                                                h += path.Replace ('/', '\\');
                                                        }
-#else
-                                                       h += path.Replace ('/', '\\');
-#endif
                                                }
                                                cachedLocalPath = "\\\\" + Unescape (h);
                                        }  else
@@ -628,7 +654,6 @@ namespace System {
                        }
                }
                
-#if NET_2_0
                [MonoTODO ("add support for IPv6 address")]
                public string DnsSafeHost {
                        get {
@@ -637,7 +662,12 @@ namespace System {
                        }
                }
 
-               public bool IsAbsoluteUri {
+#if NET_2_0
+               public
+#else
+               internal
+#endif
+               bool IsAbsoluteUri {
                        get { return isAbsoluteUri; }
                }
 
@@ -649,7 +679,6 @@ namespace System {
                public string OriginalString {
                        get { return source != null ? source : ToString (); }
                }
-#endif
 
                // Methods              
 
@@ -664,27 +693,30 @@ namespace System {
                        if (IsDomainAddress (name))
                                return UriHostNameType.Dns;                             
                                
-                       try {
-                               IPv6Address.Parse (name);
+                       IPv6Address addr;
+                       if (IPv6Address.TryParse (name, out addr))
                                return UriHostNameType.IPv6;
-                       } catch (FormatException) {}
                        
                        return UriHostNameType.Unknown;
                }
                
                internal static bool IsIPv4Address (string name)
-               {               
+               {
                        string [] captures = name.Split (new char [] {'.'});
                        if (captures.Length != 4)
                                return false;
+
                        for (int i = 0; i < 4; i++) {
-                               try {
-                                       int d = Int32.Parse (captures [i], CultureInfo.InvariantCulture);
-                                       if (d < 0 || d > 255)
-                                               return false;
-                               } catch (Exception) {
+                               int length;
+
+                               length = captures [i].Length;
+                               if (length == 0)
+                                       return false;
+                               uint number;
+                               if (!UInt32.TryParse (captures [i], out number))
+                                       return false;
+                               if (number > 255)
                                        return false;
-                               }
                        }
                        return true;
                }                       
@@ -710,10 +742,9 @@ namespace System {
                        
                        return true;
                }
+#if !NET_2_1
 
-#if NET_2_0
                [Obsolete("This method does nothing, it has been obsoleted")]
-#endif
                protected virtual void Canonicalize ()
                {
                        //
@@ -722,6 +753,14 @@ namespace System {
                        //
                }
 
+               [MonoTODO ("Find out what this should do")]
+               [Obsolete]
+               protected virtual void CheckSecurity ()
+               {
+               }
+
+#endif // NET_2_1
+
                // defined in RFC3986 as = ALPHA *( ALPHA / DIGIT / "+" / "-" / ".")
                public static bool CheckSchemeName (string schemeName) 
                {
@@ -743,23 +782,10 @@ namespace System {
 
                private static bool IsAlpha (char c)
                {
-#if NET_2_0
                        // as defined in rfc2234
                        // %x41-5A / %x61-7A (A-Z / a-z)
                        int i = (int) c;
                        return (((i >= 0x41) && (i <= 0x5A)) || ((i >= 0x61) && (i <= 0x7A)));
-#else
-                       // Fx 1.x got this too large
-                       return Char.IsLetter (c);
-#endif
-               }
-
-               [MonoTODO ("Find out what this should do")]
-#if NET_2_0
-               [Obsolete]
-#endif
-               protected virtual void CheckSecurity ()
-               {
                }
 
                public override bool Equals (object comparant) 
@@ -781,27 +807,19 @@ namespace System {
                // Assumes: uri != null
                bool InternalEquals (Uri uri)
                {
-#if NET_2_0
                        if (this.isAbsoluteUri != uri.isAbsoluteUri)
                                return false;
                        if (!this.isAbsoluteUri)
                                return this.source == uri.source;
-#endif
 
                        CultureInfo inv = CultureInfo.InvariantCulture;
                        return this.scheme.ToLower (inv) == uri.scheme.ToLower (inv)
                                && this.host.ToLower (inv) == uri.host.ToLower (inv)
                                && this.port == uri.port
-#if NET_2_0
                                && this.query == uri.query
-#else
-                               // Note: MS.NET 1.x has bug - ignores query check altogether
-                               && this.query.ToLower (inv) == uri.query.ToLower (inv)
-#endif
                                && this.path == uri.path;
                }
 
-#if NET_2_0
                public static bool operator == (Uri u1, Uri u2)
                {
                        return object.Equals(u1, u2);
@@ -811,7 +829,6 @@ namespace System {
                {
                        return !(u1 == u2);
                }
-#endif
 
                public override int GetHashCode () 
                {
@@ -821,11 +838,7 @@ namespace System {
                                        cachedHashCode = scheme.ToLower (inv).GetHashCode ()
                                                ^ host.ToLower (inv).GetHashCode ()
                                                ^ port
-#if NET_2_0
                                                ^ query.GetHashCode ()
-#else
-                                               ^ query.ToLower (inv).GetHashCode ()
-#endif
                                                ^ path.GetHashCode ();
                                }
                                else {
@@ -872,19 +885,15 @@ namespace System {
                                        sb.Append (':').Append (port);
 
                                if (path.Length > 0) {
-#if NET_2_0
                                        switch (Scheme) {
                                        case "mailto":
                                        case "news":
                                                sb.Append (path);
                                                break;
                                        default:
-                                               sb.Append (Reduce (path));
+                                               sb.Append (Reduce (path, CompactEscaped (Scheme)));
                                                break;
                                        }
-#else
-                                       sb.Append (path);
-#endif
                                }
                                return sb.ToString ();
                        }
@@ -950,7 +959,6 @@ namespace System {
                                IsHexDigit (pattern [index]));
                }
 
-#if NET_2_0
                //
                // Implemented by copying most of the MakeRelative code
                //
@@ -985,7 +993,6 @@ namespace System {
                }
 
                [Obsolete ("Use MakeRelativeUri(Uri uri) instead.")]
-#endif
                public string MakeRelative (Uri toUri) 
                {
                        if ((this.Scheme != toUri.Scheme) ||
@@ -1016,7 +1023,7 @@ namespace System {
                void AppendQueryAndFragment (ref string result)
                {
                        if (query.Length > 0) {
-                               string q = query [0] == '?' ? '?' + Unescape (query.Substring (1), false) : Unescape (query, false);
+                               string q = query [0] == '?' ? '?' + Unescape (query.Substring (1), true) : Unescape (query, false);
                                result += q;
                        }
                        if (fragment.Length > 0)
@@ -1039,33 +1046,36 @@ namespace System {
                        return cachedToString;
                }
 
-#if NET_2_0
                protected void GetObjectData (SerializationInfo info, StreamingContext context)
                {
-                       info.AddValue ("AbsoluteUri", this.AbsoluteUri);
+                       if (this.isAbsoluteUri) {
+                               info.AddValue ("AbsoluteUri", this.AbsoluteUri);
+                       } else {
+                               info.AddValue("AbsoluteUri", String.Empty);
+                               info.AddValue("RelativeUri", this.OriginalString);
+                       }
                }
-#endif
 
                void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
                {
-                       info.AddValue ("AbsoluteUri", this.AbsoluteUri);
+                       GetObjectData (info, context);
                }
 
 
                // Internal Methods             
 
-#if NET_2_0
                [Obsolete]
-#endif
                protected virtual void Escape ()
                {
                        path = EscapeString (path);
                }
 
-#if NET_2_0
+#if MOONLIGHT
+               static string EscapeString (string str)
+#else
                [Obsolete]
-#endif
                protected static string EscapeString (string str) 
+#endif
                {
                        return EscapeString (str, false, true, true);
                }
@@ -1118,14 +1128,9 @@ namespace System {
                // On .NET 1.x, this method is called from .ctor(). When overriden, we 
                // can avoid the "absolute uri" constraints of the .ctor() by
                // overriding with custom code.
-#if NET_2_0
                [Obsolete("The method has been deprecated. It is not used by the system.")]
-#endif
                protected virtual void Parse ()
                {
-#if !NET_2_0
-                       ParseUri (UriKind.Absolute);
-#endif
                }
 
                private void ParseUri (UriKind kind)
@@ -1146,10 +1151,12 @@ namespace System {
                        }
                }
 
-#if NET_2_0
+#if MOONLIGHT
+               string Unescape (string str)
+#else
                [Obsolete]
-#endif
                protected virtual string Unescape (string str)
+#endif
                {
                        return Unescape (str, false);
                }
@@ -1206,17 +1213,21 @@ namespace System {
                        path = path.Replace ("\\", "/");
                }
 
-               private void ParseAsWindowsAbsoluteFilePath (string uriString)
+               //
+               // Returns null on success, string with error on failure
+               //
+               private string ParseAsWindowsAbsoluteFilePath (string uriString)
                {
-                       if (uriString.Length > 2 && uriString [2] != '\\'
-                                       && uriString [2] != '/')
-                               throw new UriFormatException ("Relative file path is not allowed.");
+                       if (uriString.Length > 2 && uriString [2] != '\\' && uriString [2] != '/')
+                               return "Relative file path is not allowed.";
                        scheme = UriSchemeFile;
                        host = String.Empty;
                        port = -1;
                        path = uriString.Replace ("\\", "/");
                        fragment = String.Empty;
                        query = String.Empty;
+
+                       return null;
                }
 
                private void ParseAsUnixAbsoluteFilePath (string uriString)
@@ -1248,10 +1259,26 @@ namespace System {
                                path = uriString;
                }
 
-               // this parse method is as relaxed as possible about the format
-               // it will hardly ever throw a UriFormatException
+               //
+               // This parse method will throw exceptions on failure
+               //  
                private void Parse (UriKind kind, string uriString)
                {                       
+                       if (uriString == null)
+                               throw new ArgumentNullException ("uriString");
+
+                       string s = ParseNoExceptions (kind, uriString);
+                       if (s != null)
+                               throw new UriFormatException (s);
+               }
+
+               //
+               // This parse method will not throw exceptions on failure
+               //
+               // Returns null on success, or a description of the error in the parsing
+               //
+               private string ParseNoExceptions (UriKind kind, string uriString)
+               {
                        //
                        // From RFC 2396 :
                        //
@@ -1259,50 +1286,65 @@ namespace System {
                        //       12            3  4          5       6  7        8 9
                        //                      
                        
-                       if (uriString == null)
-                               throw new ArgumentNullException ("uriString");
-
                        uriString = uriString.Trim();
                        int len = uriString.Length;
 
                        if (len == 0){
                                if (kind == UriKind.Relative || kind == UriKind.RelativeOrAbsolute){
                                        isAbsoluteUri = false;
-                                       return;
+                                       return null;
                                }
                        }
                        
-                       if (len <= 1 && (kind != UriKind.Relative))
-                               throw new UriFormatException ();
+                       if (len <= 1 && (kind == UriKind.Absolute))
+                               return "Absolute URI is too short";
 
                        int pos = 0;
 
                        // 1, 2
                        // Identify Windows path, unix path, or standard URI.
+                       if (uriString [0] == '/' && Path.DirectorySeparatorChar == '/'){
+                               //Unix Path
+                               ParseAsUnixAbsoluteFilePath (uriString);
+#if MOONLIGHT
+                               isAbsoluteUri = false;
+#else
+                               if (kind == UriKind.Relative)
+                                       isAbsoluteUri = false;
+#endif
+                               return null;
+                       } else if (uriString.Length >= 2 && uriString [0] == '\\' && uriString [1] == '\\') {
+                               //Windows UNC
+                               ParseAsWindowsUNC (uriString);
+                               return null;
+                       }
+
+
                        pos = uriString.IndexOf (':');
                        if (pos == 0) {
-                               throw new UriFormatException("Invalid URI: The format of the URI could not be determined.");
+                               if (kind == UriKind.Absolute)
+                                       return "Invalid URI: The format of the URI could not be determined.";
+                               isAbsoluteUri = false;
+                               path = uriString;
+                               return null;
                        } else if (pos < 0) {
-                               // It must be Unix file path or Windows UNC
-                               if (uriString [0] == '/' && Path.DirectorySeparatorChar == '/'){
-                                       ParseAsUnixAbsoluteFilePath (uriString);
-                                       if (kind == UriKind.Relative)
-                                               isAbsoluteUri = false;
-                                       
-                               } else if (uriString.Length >= 2 && uriString [0] == '\\' && uriString [1] == '\\')
-                                       ParseAsWindowsUNC (uriString);
-                               else {
-                                       /* Relative path */
+                               /* Relative path */
+                               isAbsoluteUri = false;
+                               path = uriString;
+                               return null;
+                       } else if (pos == 1) {
+                               if (!IsAlpha (uriString [0])) {
+                                       if (kind == UriKind.Absolute)
+                                               return "URI scheme must start with a letter.";
                                        isAbsoluteUri = false;
                                        path = uriString;
+                                       return null;
                                }
-                               return;
-                       } else if (pos == 1) {
-                               if (!IsAlpha (uriString [0]))
-                                       throw new UriFormatException ("URI scheme must start with a letter.");
                                // This means 'a:' == windows full path.
-                               ParseAsWindowsAbsoluteFilePath (uriString);
-                               return;
+                               string msg = ParseAsWindowsAbsoluteFilePath (uriString);
+                               if (msg != null)
+                                       return msg;
+                               return null;
                        } 
 
                        // scheme
@@ -1311,8 +1353,11 @@ namespace System {
                        // Check scheme name characters as specified in RFC2396.
                        // Note: different checks in 1.x and 2.0
                        if (!CheckSchemeName (scheme)) {
-                               string msg = Locale.GetText ("URI scheme must start with a letter and must consist of one of alphabet, digits, '+', '-' or '.' character.");
-                               throw new UriFormatException (msg);
+                               if (kind == UriKind.Absolute)
+                                       return Locale.GetText ("URI scheme must start with a letter and must consist of one of alphabet, digits, '+', '-' or '.' character.");
+                               isAbsoluteUri = false;
+                               path = uriString;
+                               return null;
                        }
 
                        // from here we're practically working on uriString.Substring(startpos,endpos-startpos)
@@ -1343,14 +1388,15 @@ namespace System {
                        if (IsPredefinedScheme (scheme) && scheme != UriSchemeMailto && scheme != UriSchemeNews && (
                                (endpos-startpos < 2) ||
                                (endpos-startpos >= 2 && uriString [startpos] == '/' && uriString [startpos+1] != '/')))                                
-                               throw new UriFormatException ("Invalid URI: The Authority/Host could not be parsed.");
+                               return "Invalid URI: The Authority/Host could not be parsed.";
                        
                        
                        bool startsWithSlashSlash = endpos-startpos >= 2 && uriString [startpos] == '/' && uriString [startpos+1] == '/';
                        bool unixAbsPath = scheme == UriSchemeFile && startsWithSlashSlash && (endpos-startpos == 2 || uriString [startpos+2] == '/');
+                       bool windowsFilePath = false;
                        if (startsWithSlashSlash) {
                                if (kind == UriKind.Relative)
-                                       throw new UriFormatException ("Absolute URI when we expected a relative one");
+                                       return "Absolute URI when we expected a relative one";
                                
                                if (scheme != UriSchemeMailto && scheme != UriSchemeNews)
                                        startpos += 2;
@@ -1372,19 +1418,26 @@ namespace System {
                                        }
                                }
                                
-                               if (endpos - startpos > 1 && uriString [startpos + 1] == ':')
+                               if (endpos - startpos > 1 && uriString [startpos + 1] == ':') {
                                        unixAbsPath = false;
+                                       windowsFilePath = true;
+                               }
 
                        } else if (!IsPredefinedScheme (scheme)) {
                                path = uriString.Substring(startpos, endpos-startpos);
                                isOpaquePart = true;
-                               return;
+                               return null;
                        }
 
                        // 5 path
-                       pos = uriString.IndexOf ('/', startpos, endpos-startpos);
-                       if (unixAbsPath)
+                       if (unixAbsPath) {
                                pos = -1;
+                       } else {
+                               pos = uriString.IndexOf ('/', startpos, endpos-startpos);
+                               if (pos == -1 && windowsFilePath)
+                                       pos = uriString.IndexOf ('\\', startpos, endpos-startpos);
+                       }
+
                        if (pos == -1) {
                                if ((scheme != Uri.UriSchemeMailto) &&
 #if ONLY_1_1
@@ -1398,7 +1451,10 @@ namespace System {
                        }
 
                        // 4.a user info
-                       pos = uriString.IndexOf ('@', startpos, endpos-startpos);
+                       if (unixAbsPath)
+                               pos = -1;
+                       else
+                               pos = uriString.IndexOf ('@', startpos, endpos-startpos);
                        if (pos != -1) {
                                userinfo = uriString.Substring (startpos, pos-startpos);
                                startpos = pos + 1;
@@ -1406,22 +1462,17 @@ namespace System {
 
                        // 4.b port
                        port = -1;
-                       pos = uriString.LastIndexOf (':', endpos-1, endpos-startpos);
                        if (unixAbsPath)
                                pos = -1;
+                       else
+                               pos = uriString.LastIndexOf (':', endpos-1, endpos-startpos);
                        if (pos != -1 && pos != endpos - 1) {
                                string portStr = uriString.Substring(pos + 1, endpos - (pos + 1));
                                if (portStr.Length > 0 && portStr[portStr.Length - 1] != ']') {
-                                       try {
-#if NET_2_0
-                                               port = (int) UInt16.Parse (portStr, NumberStyles.Integer, CultureInfo.InvariantCulture);
-#else
-                                               port = (int) UInt32.Parse (portStr, CultureInfo.InvariantCulture);
-#endif
-                                               endpos = pos;
-                                       } catch (Exception) {
-                                               throw new UriFormatException ("Invalid URI: Invalid port number");
-                                       }
+                                       if (!Int32.TryParse (portStr, NumberStyles.Integer, CultureInfo.InvariantCulture, out port) ||
+                                           port < 0 || port > UInt16.MaxValue)
+                                               return "Invalid URI: Invalid port number";
+                                       endpos = pos;
                                } else {
                                        if (port == -1) {
                                                port = GetDefaultPort (scheme);
@@ -1438,7 +1489,7 @@ namespace System {
                        host = uriString;
 
                        if (unixAbsPath) {
-                               path = '/' + uriString;
+                               path = Reduce ('/' + uriString, true);
                                host = String.Empty;
                        } else if (host.Length == 2 && host [1] == ':') {
                                // windows filepath
@@ -1456,48 +1507,107 @@ namespace System {
                                        host = String.Empty;
                                }
                        } else if (host.Length == 0 &&
-                               (scheme == UriSchemeHttp || scheme == UriSchemeGopher || scheme == UriSchemeNntp
-                                || scheme == UriSchemeHttps || scheme == UriSchemeFtp)) {
-                               throw new UriFormatException ("Invalid URI: The hostname could not be parsed");
+                                  (scheme == UriSchemeHttp || scheme == UriSchemeGopher || scheme == UriSchemeNntp ||
+                                   scheme == UriSchemeHttps || scheme == UriSchemeFtp)) {
+                               return "Invalid URI: The hostname could not be parsed";
                        }
 
                        bool badhost = ((host.Length > 0) && (CheckHostName (host) == UriHostNameType.Unknown));
                        if (!badhost && (host.Length > 1) && (host[0] == '[') && (host[host.Length - 1] == ']')) {
-                               try {
-                                       host = "[" + IPv6Address.Parse (host).ToString (true) + "]";
-                               }
-                               catch (Exception) {
+                               IPv6Address ipv6addr;
+                               
+                               if (IPv6Address.TryParse (host, out ipv6addr))
+                                       host = "[" + ipv6addr.ToString (true) + "]";
+                               else
                                        badhost = true;
-                               }
-                       }
-                       if (badhost) {
-                               string msg = Locale.GetText ("Invalid URI: The hostname could not be parsed.");
-                               throw new UriFormatException (msg);
                        }
+                       if (badhost && (Parser is DefaultUriParser || Parser == null))
+                               return Locale.GetText ("Invalid URI: The hostname could not be parsed. (" + host + ")");
+
+                       UriFormatException ex = null;
+                       if (Parser != null)
+                               Parser.InitializeAndValidate (this, out ex);
+                       if (ex != null)
+                               return ex.Message;
 
                        if ((scheme != Uri.UriSchemeMailto) &&
                                        (scheme != Uri.UriSchemeNews) &&
                                        (scheme != Uri.UriSchemeFile)) {
-                               path = Reduce (path);
+                               path = Reduce (path, CompactEscaped (scheme));
                        }
+
+                       return null;
                }
 
-               private static string Reduce (string path)
+               private static bool CompactEscaped (string scheme)
                {
-                       path = path.Replace ('\\','/');
+                       switch (scheme) {
+                       case "file":
+                       case "http":
+                       case "https":
+                       case "net.pipe":
+                       case "net.tcp":
+                               return true;
+                       }
+                       return false;
+               }
+
+               // This is called "compacting" in the MSDN documentation
+               private static string Reduce (string path, bool compact_escaped)
+               {
+                       // quick out, allocation-free, for a common case
+                       if (path == "/")
+                               return path;
+
+                       StringBuilder res = new StringBuilder();
+
+                       if (compact_escaped) {
+                               // replace '\', %5C ('\') and %2f ('/') into '/'
+                               // other escaped values seems to survive this step
+                               for (int i=0; i < path.Length; i++) {
+                                       char c = path [i];
+                                       switch (c) {
+                                       case '\\':
+                                               res.Append ('/');
+                                               break;
+                                       case '%':
+                                               if (i < path.Length - 2) {
+                                                       char c1 = path [i + 1];
+                                                       char c2 = Char.ToUpper (path [i + 2]);
+                                                       if (((c1 == '2') && (c2 == 'F')) || ((c1 == '5') && (c2 == 'C'))) {
+                                                               res.Append ('/');
+                                                               i += 2;
+                                                       } else {
+                                                               res.Append (c);
+                                                       }
+                                               } else {
+                                                       res.Append (c);
+                                               }
+                                               break;
+                                       default:
+                                               res.Append (c);
+                                               break;
+                                       }
+                               }
+                               path = res.ToString ();
+                       } else {
+                               path = path.Replace ('\\', '/');
+                       }
+
                        ArrayList result = new ArrayList ();
 
+                       bool begin = true;
                        for (int startpos = 0; startpos < path.Length; ) {
                                int endpos = path.IndexOf('/', startpos);
                                if (endpos == -1) endpos = path.Length;
                                string current = path.Substring (startpos, endpos-startpos);
                                startpos = endpos + 1;
-                               if (current.Length == 0 || current == "." )
+                               if ((begin && current.Length == 0) || current == "." )
                                        continue;
 
+                               begin = false;
                                if (current == "..") {
                                        int resultCount = result.Count;
-#if NET_2_0
                                        // in 2.0 profile, skip leading ".." parts
                                        if (resultCount == 0) {
                                                continue;
@@ -1505,16 +1615,6 @@ namespace System {
 
                                        result.RemoveAt (resultCount - 1);
                                        continue;
-#else
-                                       // in 1.x profile, retain leading ".." parts, and only reduce
-                                       // URI is previous part is not ".."
-                                       if (resultCount > 0) {
-                                               if ((string) result[resultCount - 1] != "..") {
-                                                       result.RemoveAt (resultCount - 1);
-                                                       continue;
-                                               }
-                                       }
-#endif
                                }
 
                                result.Add (current);
@@ -1523,7 +1623,7 @@ namespace System {
                        if (result.Count == 0)
                                return "/";
 
-                       StringBuilder res = new StringBuilder();
+                       res.Length = 0;
                        if (path [0] == '/')
                                res.Append ('/');
 
@@ -1667,17 +1767,10 @@ namespace System {
                
                internal static int GetDefaultPort (string scheme)
                {
-#if NET_2_0
                        UriParser parser = UriParser.GetParser (scheme);
                        if (parser == null)
                                return -1;
                        return parser.DefaultPort;
-#else
-                       for (int i = 0; i < schemes.Length; i++) 
-                               if (schemes [i].scheme == scheme)
-                                       return schemes [i].defaultPort;
-                       return -1;
-#endif
                }
 
                private string GetOpaqueWiseSchemeDelimiter ()
@@ -1688,9 +1781,7 @@ namespace System {
                                return GetSchemeDelimiter (scheme);
                }
 
-#if NET_2_0
                [Obsolete]
-#endif
                protected virtual bool IsBadFileSystemCharacter (char ch)
                {
                        // It does not always overlap with InvalidPathChars.
@@ -1713,9 +1804,7 @@ namespace System {
                        return false;
                }
 
-#if NET_2_0
                [Obsolete]
-#endif
                protected static bool IsExcludedCharacter (char ch)
                {
                        if (ch <= 32 || ch >= 127)
@@ -1729,6 +1818,18 @@ namespace System {
                        return false;
                }
 
+               internal static bool MaybeUri (string s)
+               {
+                       int p = s.IndexOf (':');
+                       if (p == -1)
+                               return false;
+
+                       if (p >= 10)
+                               return false;
+
+                       return IsPredefinedScheme (s.Substring (0, p));
+               }
+               
                private static bool IsPredefinedScheme (string scheme)
                {
                        switch (scheme) {
@@ -1740,19 +1841,15 @@ namespace System {
                        case "gopher":
                        case "mailto":
                        case "news":
-#if NET_2_0
                        case "net.pipe":
                        case "net.tcp":
-#endif
                                return true;
                        default:
                                return false;
                        }
                }
 
-#if NET_2_0
                [Obsolete]
-#endif
                protected virtual bool IsReservedCharacter (char ch)
                {
                        if (ch == '$' || ch == '&' || ch == '+' || ch == ',' ||
@@ -1761,14 +1858,18 @@ namespace System {
                                return true;
                        return false;
                }
-#if NET_2_0
+
                [NonSerialized]
                private UriParser parser;
 
                private UriParser Parser {
                        get {
-                               if (parser == null)
+                               if (parser == null) {
                                        parser = UriParser.GetParser (Scheme);
+                                       // no specific parser ? then use a default one
+                                       if (parser == null)
+                                               parser = new DefaultUriParser ("*");
+                               }
                                return parser;
                        }
                        set { parser = value; }
@@ -1781,13 +1882,18 @@ namespace System {
 
                public bool IsBaseOf (Uri uri)
                {
+#if NET_4_0
+                       if (uri == null)
+                               throw new ArgumentNullException ("uri");
+#endif
                        return Parser.IsBaseOf (this, uri);
                }
 
                public bool IsWellFormedOriginalString ()
                {
                        // funny, but it does not use the Parser's IsWellFormedOriginalString().
-                       return EscapeString (OriginalString) == OriginalString;
+                       // Also, it seems we need to *not* escape hex.
+                       return EscapeString (OriginalString, false, false, true) == OriginalString;
                }
 
                // static methods
@@ -1899,21 +2005,24 @@ namespace System {
                {
                        if (uriString == null)
                                return false;
-                       Uri uri = new Uri (uriString, uriKind);
-                       return uri.IsWellFormedOriginalString ();
+
+                       Uri uri;
+                       if (Uri.TryCreate (uriString, uriKind, out uri))
+                               return uri.IsWellFormedOriginalString ();
+                       return false;
                }
 
-               // [MonoTODO ("rework code to avoid exception catching")]
                public static bool TryCreate (string uriString, UriKind uriKind, out Uri result)
                {
-                       try {
-                               result = new Uri (uriString, uriKind);
+                       bool success;
+
+                       Uri r = new Uri (uriString, uriKind, out success);
+                       if (success) {
+                               result = r;
                                return true;
                        }
-                       catch (UriFormatException) {
-                               result = null;
-                               return false;
-                       }
+                       result = null;
+                       return false;
                }
 
                // [MonoTODO ("rework code to avoid exception catching")]
@@ -1923,8 +2032,7 @@ namespace System {
                                // FIXME: this should call UriParser.Resolve
                                result = new Uri (baseUri, relativeUri);
                                return true;
-                       }
-                       catch (UriFormatException) {
+                       } catch (UriFormatException) {
                                result = null;
                                return false;
                        }
@@ -1933,12 +2041,17 @@ namespace System {
                //[MonoTODO ("rework code to avoid exception catching")]
                public static bool TryCreate (Uri baseUri, Uri relativeUri, out Uri result)
                {
+#if NET_4_0
+                       if (relativeUri == null) {
+                               result = null;
+                               return false;
+                       }
+#endif
                        try {
                                // FIXME: this should call UriParser.Resolve
-                               result = new Uri (baseUri, relativeUri);
+                               result = new Uri (baseUri, relativeUri.OriginalString);
                                return true;
-                       }
-                       catch (UriFormatException) {
+                       } catch (UriFormatException) {
                                result = null;
                                return false;
                        }
@@ -2044,10 +2157,5 @@ namespace System {
                        if (!IsAbsoluteUri)
                                throw new InvalidOperationException ("This operation is not supported for a relative URI.");
                }
-#else
-               private void EnsureAbsoluteUri ()
-               {
-               }
-#endif
        }
 }