[corlib] Don't throw UnauthorizedAccessException from Path.GetTempFileName
[mono.git] / mcs / class / corlib / System.IO / Path.cs
index 6d936fe8ca0447f43b472b3d0024a5b2885a2a53..0585986e83d623a64cfac6c625a2fa050c8486e7 100644 (file)
@@ -5,6 +5,7 @@
 // Copyright (C) 2001 Moonlight Enterprises, All Rights Reserved
 // Copyright (C) 2002 Ximian, Inc. (http://www.ximian.com)
 // Copyright (C) 2003 Ben Maurer
+// Copyright 2011 Xamarin Inc (http://www.xamarin.com).
 // 
 // Author:         Jim Richardson, develop@wtfo-guru.com
 //                 Dan Lewis (dihlewis@yahoo.co.uk)
@@ -48,21 +49,11 @@ 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;
@@ -231,6 +222,8 @@ namespace System.IO {
 
                                if (l >= 2 && DirectorySeparatorChar == '\\' && ret [l - 1] == VolumeSeparatorChar)
                                        return ret + DirectorySeparatorChar;
+                               else if (l == 1 && DirectorySeparatorChar == '\\' && path.Length >= 2 && path [nLast] == VolumeSeparatorChar)
+                                       return ret + VolumeSeparatorChar;
                                else {
                                        //
                                        // Important: do not use CanonicalizePath here, use
@@ -285,9 +278,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;
                }
 
@@ -299,7 +297,7 @@ namespace System.IO {
                        if ((path [1] != ':') || !Char.IsLetter (path [0]))
                                return path;
 
-                       string current = Directory.GetCurrentDirectory ();
+                       string current = Directory.InsecureGetCurrentDirectory ();
                        // first, only the drive is specified
                        if (path.Length == 2) {
                                // then if the current directory is on the same drive
@@ -339,31 +337,44 @@ 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])) {
                                if (path.Length == 2 || path.IndexOf (path [0], 2) < 0)
-                                       throw new ArgumentException ("UNC pass should be of the form \\\\server\\share.");
+                                       throw new ArgumentException ("UNC paths should be of the form \\\\server\\share.");
 
                                if (path [0] != DirectorySeparatorChar)
                                        path = path.Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
 
-                               path = CanonicalizePath (path);
                        } else {
-                               if (!IsPathRooted (path))
-                                       path = Directory.GetCurrentDirectory () + DirectorySeparatorStr + path;
-                               else if (DirectorySeparatorChar == '\\' &&
+                               if (!IsPathRooted (path)) {
+                                       
+                                       // avoid calling expensive CanonicalizePath when possible
+                                       if (!Environment.IsRunningOnWindows) {
+                                               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.InsecureGetCurrentDirectory() + DirectorySeparatorStr + path;
+                               } else if (DirectorySeparatorChar == '\\' &&
                                        path.Length >= 2 &&
                                        IsDsc (path [0]) &&
                                        !IsDsc (path [1])) { // like `\abc\def'
-                                       string current = Directory.GetCurrentDirectory ();
+                                       string current = Directory.InsecureGetCurrentDirectory();
                                        if (current [1] == VolumeSeparatorChar)
                                                path = current.Substring (0, 2) + path;
                                        else
-                                               path = current.Substring (0, current.IndexOf ('\\', current.IndexOf ("\\\\") + 1));
+                                               path = current.Substring (0, current.IndexOf ('\\', current.IndexOfOrdinalUnchecked ("\\\\") + 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))
@@ -433,6 +444,9 @@ namespace System.IO {
                        string path;
                        Random rnd;
                        int num = 0;
+                       int count = 0;
+
+                       SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
 
                        rnd = new Random ();
                        do {
@@ -444,11 +458,13 @@ namespace System.IO {
                                        f = new FileStream (path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read,
                                                            8192, false, (FileOptions) 1);
                                }
-                               catch (SecurityException) {
-                                       // avoid an endless loop
-                                       throw;
+                               catch (IOException ex){
+                                       if (ex.hresult != MonoIO.FileAlreadyExistsHResult || count ++ > 65536)
+                                               throw;
                                }
-                               catch {
+                               catch (UnauthorizedAccessException ex) {
+                                       if (count ++ > 65536)
+                                               throw new IOException (ex.Message, ex);
                                }
                        } while (f == null);
                        
@@ -459,6 +475,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;
@@ -495,7 +513,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
@@ -543,7 +560,7 @@ namespace System.IO {
 
                        return sb.ToString ();
                }
-#endif
+
                // private class methods
 
                private static int findExtension (string path)
@@ -567,17 +584,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 ();
@@ -667,7 +675,7 @@ namespace System.IO {
                        for (int i = 0; i < dirs.Length; i++) {
                                // WIN32 path components must be trimmed
                                if (Environment.IsRunningOnWindows)
-                                       dirs[i] = dirs[i].Trim ();
+                                       dirs[i] = dirs[i].TrimEnd ();
                                
                                if (dirs[i] == "." || (i != 0 && dirs[i].Length == 0))
                                        continue;
@@ -738,5 +746,106 @@ namespace System.IO {
 
                        return String.Compare (subset, slast, path, slast, subset.Length - slast) == 0;
                }
+
+#if NET_4_0
+               public
+#else
+                internal
+#endif
+               static string Combine (params string [] paths)
+               {
+                       if (paths == null)
+                               throw new ArgumentNullException ("paths");
+
+                       bool need_sep;
+                       var ret = new StringBuilder ();
+                       int pathsLen = paths.Length;
+                       int slen;
+                       foreach (var s in paths) {
+                               need_sep = false;
+                               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.");
+                               
+                               pathsLen--;
+                               if (IsPathRooted (s))
+                                       ret.Length = 0;
+                               
+                               ret.Append (s);
+                               slen = s.Length;
+                               if (slen > 0 && pathsLen > 0) {
+                                       char p1end = s [slen - 1];
+                                       if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
+                                               need_sep = true;
+                               }
+                               
+                               if (need_sep)
+                                       ret.Append (DirectorySeparatorStr);
+                       }
+
+                       return ret.ToString ();
+               }
+
+#if NET_4_0
+               public
+#else
+                internal
+#endif
+               static string Combine (string path1, string path2, string path3)
+               {
+                       if (path1 == null)
+                               throw new ArgumentNullException ("path1");
+
+                       if (path2 == null)
+                               throw new ArgumentNullException ("path2");
+
+                       if (path3 == null)
+                               throw new ArgumentNullException ("path3");
+                       
+                       return Combine (new string [] { path1, path2, path3 });
+               }
+
+#if NET_4_0
+               public
+#else
+                internal
+#endif
+               static string Combine (string path1, string path2, string path3, string path4)
+               {
+                       if (path1 == null)
+                               throw new ArgumentNullException ("path1");
+
+                       if (path2 == null)
+                               throw new ArgumentNullException ("path2");
+
+                       if (path3 == null)
+                               throw new ArgumentNullException ("path3");
+
+                       if (path4 == null)
+                               throw new ArgumentNullException ("path4");
+                       
+                       return Combine (new string [] { path1, path2, path3, path4 });
+               }
+
+               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 (Environment.IsRunningOnWindows) {
+                               int idx = path.IndexOf (':');
+                               if (idx >= 0 && idx != 1)
+                                       throw new ArgumentException (parameterName);
+                       }
+               }
        }
 }