New tests.
[mono.git] / mcs / class / corlib / System.IO / Path.cs
index 100ff3851f0883f404ec0af6958078d46bf99bf2..1dd4706255704a3b4e12c75dac0b901b66ecb8b6 100644 (file)
@@ -48,28 +48,18 @@ using System.Text;
 
 namespace System.IO {
 
-#if NET_2_0
        [ComVisible (true)]
        public static class Path {
 
                [Obsolete ("see GetInvalidPathChars and GetInvalidFileNameChars methods.")]
                public static readonly char[] InvalidPathChars;
-#else
-       public sealed class Path {
-
-               private Path ()
-               {
-               }
-
-               public static readonly char[] InvalidPathChars;
-#endif
                public static readonly char AltDirectorySeparatorChar;
                public static readonly char DirectorySeparatorChar;
                public static readonly char PathSeparator;
                internal static readonly string DirectorySeparatorStr;
                public static readonly char VolumeSeparatorChar;
 
-               private static readonly char[] PathSeparatorChars;
+               internal static readonly char[] PathSeparatorChars;
                private static readonly bool dirEqualsVolume;
 
                // class methods
@@ -79,7 +69,7 @@ namespace System.IO {
                                return null;
 
                        if (path.IndexOfAny (InvalidPathChars) != -1)
-                               throw new ArgumentException ("Illegal characters in path", "path");
+                               throw new ArgumentException ("Illegal characters in path.");
 
                        int iExt = findExtension (path);
 
@@ -119,20 +109,18 @@ namespace System.IO {
                                return path1;
 
                        if (path1.IndexOfAny (InvalidPathChars) != -1)
-                               throw new ArgumentException ("Illegal characters in path", "path1");
+                               throw new ArgumentException ("Illegal characters in path.");
 
                        if (path2.IndexOfAny (InvalidPathChars) != -1)
-                               throw new ArgumentException ("Illegal characters in path", "path2");
+                               throw new ArgumentException ("Illegal characters in path.");
 
                        //TODO???: UNC names
-                       // LAMESPEC: MS says that if path1 is not empty and path2 is a full path
-                       // it should throw ArgumentException
                        if (IsPathRooted (path2))
                                return path2;
                        
                        char p1end = path1 [path1.Length - 1];
                        if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
-                               return path1 + DirectorySeparatorChar.ToString () + path2;
+                               return path1 + DirectorySeparatorStr + path2;
 
                        return path1 + path2;
                }
@@ -217,8 +205,11 @@ namespace System.IO {
                        if (path == null || GetPathRoot (path) == path)
                                return null;
 
-                       CheckArgument.WhitespaceOnly (path);
-                       CheckArgument.PathChars (path);
+                       if (path.Trim ().Length == 0)
+                               throw new ArgumentException ("Argument string consists of whitespace characters only.");
+
+                       if (path.IndexOfAny (System.IO.Path.InvalidPathChars) > -1)
+                               throw new ArgumentException ("Path contains invalid characters");
 
                        int nLast = path.LastIndexOfAny (PathSeparatorChars);
                        if (nLast == 0)
@@ -228,7 +219,7 @@ namespace System.IO {
                                string ret = path.Substring (0, nLast);
                                int l = ret.Length;
 
-                               if (l >= 2 && ret [l - 1] == VolumeSeparatorChar)
+                               if (l >= 2 && DirectorySeparatorChar == '\\' && ret [l - 1] == VolumeSeparatorChar)
                                        return ret + DirectorySeparatorChar;
                                else {
                                        //
@@ -249,7 +240,7 @@ namespace System.IO {
                                return null;
 
                        if (path.IndexOfAny (InvalidPathChars) != -1)
-                               throw new ArgumentException ("Illegal characters in path", "path");
+                               throw new ArgumentException ("Illegal characters in path.");
 
                        int iExt = findExtension (path);
 
@@ -267,7 +258,7 @@ namespace System.IO {
                                return path;
 
                        if (path.IndexOfAny (InvalidPathChars) != -1)
-                               throw new ArgumentException ("Illegal characters in path", "path");
+                               throw new ArgumentException ("Illegal characters in path.");
 
                        int nLast = path.LastIndexOfAny (PathSeparatorChars);
                        if (nLast >= 0)
@@ -284,9 +275,14 @@ namespace System.IO {
                public static string GetFullPath (string path)
                {
                        string fullpath = InsecureGetFullPath (path);
+
+                       SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
+
+#if !NET_2_1
                        if (SecurityManager.SecurityEnabled) {
                                new FileIOPermission (FileIOPermissionAccess.PathDiscovery, fullpath).Demand ();
                        }
+#endif
                        return fullpath;
                }
 
@@ -338,6 +334,7 @@ namespace System.IO {
                        // if the supplied path ends with a separator...
                        char end = path [path.Length - 1];
 
+                       var canonicalize = true;
                        if (path.Length >= 2 &&
                                IsDsc (path [0]) &&
                                IsDsc (path [1])) {
@@ -346,10 +343,20 @@ namespace System.IO {
 
                                if (path [0] != DirectorySeparatorChar)
                                        path = path.Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
+
                        } else {
-                               if (!IsPathRooted (path))
+                               if (!IsPathRooted (path)) {
+                                       
+                                       // avoid calling expensive CanonicalizePath when possible
+                                       var start = 0;
+                                       while ((start = path.IndexOf ('.', start)) != -1) {
+                                               if (++start == path.Length || path [start] == DirectorySeparatorChar || path [start] == AltDirectorySeparatorChar)
+                                                       break;
+                                       }
+                                       canonicalize = start > 0;
+                                       
                                        path = Directory.GetCurrentDirectory () + DirectorySeparatorStr + path;
-                               else if (DirectorySeparatorChar == '\\' &&
+                               else if (DirectorySeparatorChar == '\\' &&
                                        path.Length >= 2 &&
                                        IsDsc (path [0]) &&
                                        !IsDsc (path [1])) { // like `\abc\def'
@@ -359,8 +366,10 @@ namespace System.IO {
                                        else
                                                path = current.Substring (0, current.IndexOf ('\\', current.IndexOf ("\\\\") + 1));
                                }
-                               path = CanonicalizePath (path);
                        }
+                       
+                       if (canonicalize)
+                           path = CanonicalizePath (path);
 
                        // if the original ended with a [Alt]DirectorySeparatorChar then ensure the full path also ends with one
                        if (IsDsc (end) && (path [path.Length - 1] != DirectorySeparatorChar))
@@ -378,8 +387,8 @@ namespace System.IO {
                        if (path == null)
                                return null;
 
-                       if (path.Length == 0)
-                               throw new ArgumentException ("This specified path is invalid.");
+                       if (path.Trim ().Length == 0)
+                               throw new ArgumentException ("The specified path is not of a legal form.");
 
                        if (!IsPathRooted (path))
                                return String.Empty;
@@ -431,6 +440,8 @@ namespace System.IO {
                        Random rnd;
                        int num = 0;
 
+                       SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
+
                        rnd = new Random ();
                        do {
                                num = rnd.Next ();
@@ -445,6 +456,10 @@ namespace System.IO {
                                        // avoid an endless loop
                                        throw;
                                }
+                               catch (UnauthorizedAccessException) {
+                                       // This can happen if we don't have write permission to /tmp
+                                       throw;
+                               }
                                catch {
                                }
                        } while (f == null);
@@ -456,6 +471,8 @@ namespace System.IO {
                [EnvironmentPermission (SecurityAction.Demand, Unrestricted = true)]
                public static string GetTempPath ()
                {
+                       SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
+
                        string p = get_temp_path ();
                        if (p.Length > 0 && p [p.Length - 1] != DirectorySeparatorChar)
                                return p + DirectorySeparatorChar;
@@ -467,10 +484,13 @@ namespace System.IO {
                private static extern string get_temp_path ();
 
                public static bool HasExtension (string path)
-               {  
+               {
                        if (path == null || path.Trim ().Length == 0)
                                return false;
 
+                       if (path.IndexOfAny (InvalidPathChars) != -1)
+                               throw new ArgumentException ("Illegal characters in path.");
+
                        int pos = findExtension (path);
                        return 0 <= pos && pos < path.Length - 1;
                }
@@ -489,7 +509,6 @@ namespace System.IO {
                                (!dirEqualsVolume && path.Length > 1 && path [1] == VolumeSeparatorChar));
                }
 
-#if NET_2_0
                public static char[] GetInvalidFileNameChars ()
                {
                        // return a new array as we do not want anyone to be able to change the values
@@ -537,7 +556,7 @@ namespace System.IO {
 
                        return sb.ToString ();
                }
-#endif
+
                // private class methods
 
                private static int findExtension (string path)
@@ -561,17 +580,8 @@ namespace System.IO {
                        AltDirectorySeparatorChar = MonoIO.AltDirectorySeparatorChar;
 
                        PathSeparator = MonoIO.PathSeparator;
-#if NET_2_0
                        // this copy will be modifiable ("by design")
                        InvalidPathChars = GetInvalidPathChars ();
-#else
-                       if (Environment.IsRunningOnWindows) {
-                               InvalidPathChars = new char [15] { '\x00', '\x08', '\x10', '\x11', '\x12', '\x14', '\x15', '\x16',
-                                       '\x17', '\x18', '\x19', '\x22', '\x3C', '\x3E', '\x7C' };
-                       } else {
-                               InvalidPathChars = new char [1] { '\x00' };
-                       }
-#endif
                        // internal fields
 
                        DirectorySeparatorStr = DirectorySeparatorChar.ToString ();
@@ -583,16 +593,43 @@ namespace System.IO {
 
                        dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
                }
-               
+
+               // returns the server and share part of a UNC. Assumes "path" is a UNC.
+               static string GetServerAndShare (string path)
+               {
+                       int len = 2;
+                       while (len < path.Length && !IsDsc (path [len])) len++;
+
+                       if (len < path.Length) {
+                               len++;
+                               while (len < path.Length && !IsDsc (path [len])) len++;
+                       }
+
+                       return path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
+               }
+
+               // assumes Environment.IsRunningOnWindows == true
                static bool SameRoot (string root, string path)
                {
                        // compare root - if enough details are available
                        if ((root.Length < 2) || (path.Length < 2))
                                return false;
+
+                       // UNC handling
+                       if (IsDsc (root[0]) && IsDsc (root[1])) {
+                               if (!(IsDsc (path[0]) && IsDsc (path[1])))
+                                       return false;
+
+                               string rootShare = GetServerAndShare (root);
+                               string pathShare = GetServerAndShare (path);
+
+                               return String.Compare (rootShare, pathShare, true, CultureInfo.InvariantCulture) == 0;
+                       }
+                       
                        // same volume/drive
                        if (!root [0].Equals (path [0]))
                                return false;
-                       // presence if the separator
+                       // presence of the separator
                        if (path[1] != Path.VolumeSeparatorChar)
                                return false;
                        if ((root.Length > 2) && (path.Length > 2)) {
@@ -616,7 +653,7 @@ namespace System.IO {
                        // STEP 2: Check to see if this is only a root
                        string root = Path.GetPathRoot (path);
                        // it will return '\' for path '\', while it should return 'c:\' or so.
-                       // Note: commenting this out makes the ened for the (target == 1...) check in step 5
+                       // Note: commenting this out makes the need for the (target == 1...) check in step 5
                        //if (root == path) return path;
 
                        // STEP 3: split the directories, this gets rid of consecutative "/"'s
@@ -624,11 +661,23 @@ namespace System.IO {
                        // STEP 4: Get rid of directories containing . and ..
                        int target = 0;
 
+                       bool isUnc = Environment.IsRunningOnWindows &&
+                               root.Length > 2 && IsDsc (root[0]) && IsDsc (root[1]);
+
+                       // Set an overwrite limit for UNC paths since '\' + server + share
+                       // must not be eliminated by the '..' elimination algorithm.
+                       int limit = isUnc ? 3 : 0;
+
                        for (int i = 0; i < dirs.Length; i++) {
+                               // WIN32 path components must be trimmed
+                               if (Environment.IsRunningOnWindows)
+                                       dirs[i] = dirs[i].TrimEnd ();
+                               
                                if (dirs[i] == "." || (i != 0 && dirs[i].Length == 0))
                                        continue;
                                else if (dirs[i] == "..") {
-                                       if (target != 0)
+                                       // don't overwrite path segments below the limit
+                                       if (target > limit)
                                                target--;
                                } else
                                        dirs[target++] = dirs[i];
@@ -640,10 +689,16 @@ namespace System.IO {
                        else {
                                string ret = String.Join (DirectorySeparatorStr, dirs, 0, target);
                                if (Environment.IsRunningOnWindows) {
+                                       // append leading '\' of the UNC path that was lost in STEP 3.
+                                       if (isUnc)
+                                               ret = Path.DirectorySeparatorStr + ret;
+
                                        if (!SameRoot (root, ret))
                                                ret = root + ret;
-                                       // In GetFullPath(), it is assured that here never comes UNC. So this must only applied to such path that starts with '\', without drive specification.
-                                       if (!IsDsc (path[0]) && SameRoot (root, path)) {
+
+                                       if (isUnc) {
+                                               return ret;
+                                       } else if (!IsDsc (path[0]) && SameRoot (root, path)) {
                                                if (ret.Length <= 2 && !ret.EndsWith (DirectorySeparatorStr)) // '\' after "c:"
                                                        ret += Path.DirectorySeparatorChar;
                                                return ret;
@@ -687,5 +742,71 @@ namespace System.IO {
 
                        return String.Compare (subset, slast, path, slast, subset.Length - slast) == 0;
                }
+
+#if NET_4_0 || MOONLIGHT
+               public static string Combine (params string [] paths)
+               {
+                       if (paths == null)
+                               throw new ArgumentNullException ("paths");
+
+                       int l = 0;
+                       bool need_sep = false;
+                       foreach (var s in paths){
+                               if (s == null)
+                                       throw new ArgumentNullException ("One of the paths contains a null value", "paths");
+                               if (s.IndexOfAny (InvalidPathChars) != -1)
+                                       throw new ArgumentException ("Illegal characters in path.");
+                               if (l == 0 && s.Length > 0){
+                                       char p1end = s [s.Length - 1];
+                                       if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar){
+                                               need_sep = true;
+                                               l += DirectorySeparatorStr.Length;
+                                       }
+                               }
+                       }
+                       var ret = new StringBuilder (l);
+                       l = 0;
+                       foreach (var s in paths){
+                               if (IsPathRooted (s))
+                                       ret.Length = l = 0;
+                               ret.Append (s);
+                               if (l == 0 && need_sep)
+                                       ret.Append (DirectorySeparatorStr);
+                               l = 1;
+                       }
+
+                       return ret.ToString ();
+               }
+
+               public static string Combine (string path1, string path2, string path3)
+               {
+                       return Combine (new string [] { path1, path2, path3 });
+               }
+
+               public static string Combine (string path1, string path2, string path3, string path4)
+               {
+                       return Combine (new string [] { path1, path2, path3, path4 });
+               }
+#endif
+
+               internal static void Validate (string path)
+               {
+                       Validate (path, "path");
+               }
+
+               internal static void Validate (string path, string parameterName)
+               {
+                       if (path == null)
+                               throw new ArgumentNullException (parameterName);
+                       if (String.IsNullOrWhiteSpace (path))
+                               throw new ArgumentException (Locale.GetText ("Path is empty"));
+                       if (path.IndexOfAny (Path.InvalidPathChars) != -1)
+                               throw new ArgumentException (Locale.GetText ("Path contains invalid chars"));
+#if MOONLIGHT
+                       // On Moonlight (SL4+) there are some limitations in "Elevated Trust"
+                       if (SecurityManager.HasElevatedPermissions) {
+                       }
+#endif
+               }
        }
 }