Merge pull request #4063 from ntherning/fix-culture-info-problem-in-RepeatInfoTest
[mono.git] / mcs / class / corlib / System.IO / Path.cs
1 //------------------------------------------------------------------------------
2 // 
3 // System.IO.Path.cs 
4 //
5 // Copyright (C) 2001 Moonlight Enterprises, All Rights Reserved
6 // Copyright (C) 2002 Ximian, Inc. (http://www.ximian.com)
7 // Copyright (C) 2003 Ben Maurer
8 // Copyright 2011 Xamarin Inc (http://www.xamarin.com).
9 // 
10 // Author:         Jim Richardson, develop@wtfo-guru.com
11 //                 Dan Lewis (dihlewis@yahoo.co.uk)
12 //                 Gonzalo Paniagua Javier (gonzalo@ximian.com)
13 //                 Ben Maurer (bmaurer@users.sourceforge.net)
14 //                 Sebastien Pouliot  <sebastien@ximian.com>
15 // Created:        Saturday, August 11, 2001 
16 //
17 //------------------------------------------------------------------------------
18
19 //
20 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
21 //
22 // Permission is hereby granted, free of charge, to any person obtaining
23 // a copy of this software and associated documentation files (the
24 // "Software"), to deal in the Software without restriction, including
25 // without limitation the rights to use, copy, modify, merge, publish,
26 // distribute, sublicense, and/or sell copies of the Software, and to
27 // permit persons to whom the Software is furnished to do so, subject to
28 // the following conditions:
29 // 
30 // The above copyright notice and this permission notice shall be
31 // included in all copies or substantial portions of the Software.
32 // 
33 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
34 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
35 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
36 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
37 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
38 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
39 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
40 //
41
42 using System.Globalization;
43 using System.Runtime.CompilerServices;
44 using System.Runtime.InteropServices;
45 using System.Security;
46 using System.Security.Cryptography;
47 using System.Security.Permissions;
48 using System.Text;
49
50 namespace System.IO {
51
52         [ComVisible (true)]
53         public static class Path {
54
55                 [Obsolete ("see GetInvalidPathChars and GetInvalidFileNameChars methods.")]
56                 public static readonly char[] InvalidPathChars;
57                 public static readonly char AltDirectorySeparatorChar;
58                 public static readonly char DirectorySeparatorChar;
59                 public static readonly char PathSeparator;
60                 internal static readonly string DirectorySeparatorStr;
61                 public static readonly char VolumeSeparatorChar;
62
63                 internal static readonly char[] PathSeparatorChars;
64                 private static readonly bool dirEqualsVolume;
65
66                 // class methods
67                 public static string ChangeExtension (string path, string extension)
68                 {
69                         if (path == null)
70                                 return null;
71
72                         if (path.IndexOfAny (InvalidPathChars) != -1)
73                                 throw new ArgumentException ("Illegal characters in path.");
74
75                         int iExt = findExtension (path);
76
77                         if (extension == null)
78                                 return iExt < 0 ? path : path.Substring (0, iExt);
79                         else if (extension.Length == 0)
80                                 return iExt < 0 ? path + '.' : path.Substring (0, iExt + 1);
81
82                         else if (path.Length != 0) {
83                                 if (extension.Length > 0 && extension [0] != '.')
84                                         extension = "." + extension;
85                         } else
86                                 extension = String.Empty;
87                         
88                         if (iExt < 0) {
89                                 return path + extension;
90                         } else if (iExt > 0) {
91                                 string temp = path.Substring (0, iExt);
92                                 return temp + extension;
93                         }
94
95                         return extension;
96                 }
97
98                 public static string Combine (string path1, string path2)
99                 {
100                         if (path1 == null)
101                                 throw new ArgumentNullException ("path1");
102
103                         if (path2 == null)
104                                 throw new ArgumentNullException ("path2");
105
106                         if (path1.Length == 0)
107                                 return path2;
108
109                         if (path2.Length == 0)
110                                 return path1;
111
112                         if (path1.IndexOfAny (InvalidPathChars) != -1)
113                                 throw new ArgumentException ("Illegal characters in path.");
114
115                         if (path2.IndexOfAny (InvalidPathChars) != -1)
116                                 throw new ArgumentException ("Illegal characters in path.");
117
118                         //TODO???: UNC names
119                         if (IsPathRooted (path2))
120                                 return path2;
121                         
122                         char p1end = path1 [path1.Length - 1];
123                         if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
124                                 return path1 + DirectorySeparatorStr + path2;
125
126                         return path1 + path2;
127                 }
128         
129                 //
130                 // This routine:
131                 //   * Removes duplicat path separators from a string
132                 //   * If the string starts with \\, preserves the first two (hostname on Windows)
133                 //   * Removes the trailing path separator.
134                 //   * Returns the DirectorySeparatorChar for the single input DirectorySeparatorChar or AltDirectorySeparatorChar
135                 //
136                 // Unlike CanonicalizePath, this does not do any path resolution
137                 // (which GetDirectoryName is not supposed to do).
138                 //
139                 internal static string CleanPath (string s)
140                 {
141                         int l = s.Length;
142                         int sub = 0;
143                         int alt = 0;
144                         int start = 0;
145
146                         // Host prefix?
147                         char s0 = s [0];
148                         if (l > 2 && s0 == '\\' && s [1] == '\\'){
149                                 start = 2;
150                         }
151
152                         // We are only left with root
153                         if (l == 1 && (s0 == DirectorySeparatorChar || s0 == AltDirectorySeparatorChar))
154                                 return s;
155
156                         // Cleanup
157                         for (int i = start; i < l; i++){
158                                 char c = s [i];
159                                 
160                                 if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar)
161                                         continue;
162                                 if (DirectorySeparatorChar != AltDirectorySeparatorChar && c == AltDirectorySeparatorChar)
163                                         alt++;
164                                 if (i+1 == l)
165                                         sub++;
166                                 else {
167                                         c = s [i + 1];
168                                         if (c == DirectorySeparatorChar || c == AltDirectorySeparatorChar)
169                                                 sub++;
170                                 }
171                         }
172
173                         if (sub == 0 && alt == 0)
174                                 return s;
175
176                         char [] copy = new char [l-sub];
177                         if (start != 0){
178                                 copy [0] = '\\';
179                                 copy [1] = '\\';
180                         }
181                         for (int i = start, j = start; i < l && j < copy.Length; i++){
182                                 char c = s [i];
183
184                                 if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar){
185                                         copy [j++] = c;
186                                         continue;
187                                 }
188
189                                 // For non-trailing cases.
190                                 if (j+1 != copy.Length){
191                                         copy [j++] = DirectorySeparatorChar;
192                                         for (;i < l-1; i++){
193                                                 c = s [i+1];
194                                                 if (c != DirectorySeparatorChar && c != AltDirectorySeparatorChar)
195                                                         break;
196                                         }
197                                 }
198                         }
199                         return new String (copy);
200                 }
201
202                 public static string GetDirectoryName (string path)
203                 {
204                         // LAMESPEC: For empty string MS docs say both
205                         // return null AND throw exception.  Seems .NET throws.
206                         if (path == String.Empty)
207                                 throw new ArgumentException("Invalid path");
208
209                         if (path == null || GetPathRoot (path) == path)
210                                 return null;
211
212                         if (path.Trim ().Length == 0)
213                                 throw new ArgumentException ("Argument string consists of whitespace characters only.");
214
215                         if (path.IndexOfAny (System.IO.Path.InvalidPathChars) > -1)
216                                 throw new ArgumentException ("Path contains invalid characters");
217
218                         int nLast = path.LastIndexOfAny (PathSeparatorChars);
219                         if (nLast == 0)
220                                 nLast++;
221
222                         if (nLast > 0) {
223                                 string ret = path.Substring (0, nLast);
224                                 int l = ret.Length;
225
226                                 if (l >= 2 && DirectorySeparatorChar == '\\' && ret [l - 1] == VolumeSeparatorChar)
227                                         return ret + DirectorySeparatorChar;
228                                 else if (l == 1 && DirectorySeparatorChar == '\\' && path.Length >= 2 && path [nLast] == VolumeSeparatorChar)
229                                         return ret + VolumeSeparatorChar;
230                                 else {
231                                         //
232                                         // Important: do not use CanonicalizePath here, use
233                                         // the custom CleanPath here, as this should not
234                                         // return absolute paths
235                                         //
236                                         return CleanPath (ret);
237                                 }
238                         }
239
240                         return String.Empty;
241                 }
242
243                 public static string GetExtension (string path)
244                 {
245                         if (path == null)
246                                 return null;
247
248                         if (path.IndexOfAny (InvalidPathChars) != -1)
249                                 throw new ArgumentException ("Illegal characters in path.");
250
251                         int iExt = findExtension (path);
252
253                         if (iExt > -1)
254                         {
255                                 if (iExt < path.Length - 1)
256                                         return path.Substring (iExt);
257                         }
258                         return string.Empty;
259                 }
260
261                 public static string GetFileName (string path)
262                 {
263                         if (path == null || path.Length == 0)
264                                 return path;
265
266                         if (path.IndexOfAny (InvalidPathChars) != -1)
267                                 throw new ArgumentException ("Illegal characters in path.");
268
269                         int nLast = path.LastIndexOfAny (PathSeparatorChars);
270                         if (nLast >= 0)
271                                 return path.Substring (nLast + 1);
272
273                         return path;
274                 }
275
276                 public static string GetFileNameWithoutExtension (string path)
277                 {
278                         return ChangeExtension (GetFileName (path), null);
279                 }
280
281                 public static string GetFullPath (string path)
282                 {
283                         string fullpath = InsecureGetFullPath (path);
284
285                         SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
286
287 #if !MOBILE
288                         if (SecurityManager.SecurityEnabled) {
289                                 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, fullpath).Demand ();
290                         }
291 #endif
292                         return fullpath;
293                 }
294
295                 internal static String GetFullPathInternal(String path)
296                 {
297                         return InsecureGetFullPath (path);
298                 }
299
300 #if !MOBILE
301                 // http://msdn.microsoft.com/en-us/library/windows/desktop/aa364963%28v=vs.85%29.aspx
302                 [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
303                 private static extern int GetFullPathName(string path, int numBufferChars, StringBuilder buffer, ref IntPtr lpFilePartOrNull); 
304
305                 internal static string GetFullPathName(string path)
306                 {
307                         const int MAX_PATH = 260;
308                         StringBuilder buffer = new StringBuilder(MAX_PATH);
309                         IntPtr ptr = IntPtr.Zero;
310                         int length = GetFullPathName(path, MAX_PATH, buffer, ref ptr);
311                         if (length == 0)
312                         {
313                                 int error = Marshal.GetLastWin32Error();
314                                 throw new IOException("Windows API call to GetFullPathName failed, Windows error code: " + error);
315                         }
316                         else if (length > MAX_PATH)
317                         {
318                                 buffer = new StringBuilder(length);
319                                 GetFullPathName(path, length, buffer, ref ptr);
320                         }
321                         return buffer.ToString();
322                 }
323
324                 internal static string WindowsDriveAdjustment (string path)
325                 {
326                         // three special cases to consider when a drive is specified
327                         if (path.Length < 2) {
328                                 if (path.Length == 1 && (path[0] == '\\' || path[0] == '/'))
329                                         return Path.GetPathRoot(Directory.GetCurrentDirectory());
330                                 return path;
331                         }
332                         if ((path [1] != ':') || !Char.IsLetter (path [0]))
333                                 return path;
334
335                         string current = Directory.InsecureGetCurrentDirectory ();
336                         // first, only the drive is specified
337                         if (path.Length == 2) {
338                                 // then if the current directory is on the same drive
339                                 if (current [0] == path [0])
340                                         path = current; // we return it
341                                 else
342                                         path = GetFullPathName(path); // we have to use the GetFullPathName Windows API
343                         } else if ((path [2] != Path.DirectorySeparatorChar) && (path [2] != Path.AltDirectorySeparatorChar)) {
344                                 // second, the drive + a directory is specified *without* a separator between them (e.g. C:dir).
345                                 // If the current directory is on the specified drive...
346                                 if (current [0] == path [0]) {
347                                         // then specified directory is appended to the current drive directory
348                                         path = Path.Combine (current, path.Substring (2, path.Length - 2));
349                                 } else {
350                                         // we have to use the GetFullPathName Windows API
351                                         path = GetFullPathName(path);
352                                 }
353                         }
354                         return path;
355                 }
356 #endif
357
358                 // insecure - do not call directly
359                 internal static string InsecureGetFullPath (string path)
360                 {
361                         if (path == null)
362                                 throw new ArgumentNullException ("path");
363
364                         if (path.Trim ().Length == 0) {
365                                 string msg = Locale.GetText ("The specified path is not of a legal form (empty).");
366                                 throw new ArgumentException (msg);
367                         }
368 #if !MOBILE
369                         // adjust for drives, i.e. a special case for windows
370                         if (Environment.IsRunningOnWindows)
371                                 path = WindowsDriveAdjustment (path);
372 #endif
373                         // if the supplied path ends with a separator...
374                         char end = path [path.Length - 1];
375
376                         var canonicalize = true;
377                         if (path.Length >= 2 &&
378                                 IsDirectorySeparator (path [0]) &&
379                                 IsDirectorySeparator (path [1])) {
380                                 if (path.Length == 2 || path.IndexOf (path [0], 2) < 0)
381                                         throw new ArgumentException ("UNC paths should be of the form \\\\server\\share.");
382
383                                 if (path [0] != DirectorySeparatorChar)
384                                         path = path.Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
385
386                         } else {
387                                 if (!IsPathRooted (path)) {
388                                         
389                                         // avoid calling expensive CanonicalizePath when possible
390                                         if (!Environment.IsRunningOnWindows) {
391                                                 var start = 0;
392                                                 while ((start = path.IndexOf ('.', start)) != -1) {
393                                                         if (++start == path.Length || path [start] == DirectorySeparatorChar || path [start] == AltDirectorySeparatorChar)
394                                                                 break;
395                                                 }
396                                                 canonicalize = start > 0;
397                                         }
398
399                                         var cwd = Directory.InsecureGetCurrentDirectory();
400                                         if (cwd [cwd.Length - 1] == DirectorySeparatorChar)
401                                                 path = cwd + path;
402                                         else
403                                                 path = cwd + DirectorySeparatorChar + path;                                     
404                                 } else if (DirectorySeparatorChar == '\\' &&
405                                         path.Length >= 2 &&
406                                         IsDirectorySeparator (path [0]) &&
407                                         !IsDirectorySeparator (path [1])) { // like `\abc\def'
408                                         string current = Directory.InsecureGetCurrentDirectory();
409                                         if (current [1] == VolumeSeparatorChar)
410                                                 path = current.Substring (0, 2) + path;
411                                         else
412                                                 path = current.Substring (0, current.IndexOf ('\\', current.IndexOfUnchecked ("\\\\", 0, current.Length) + 1));
413                                 }
414                         }
415                         
416                         if (canonicalize)
417                             path = CanonicalizePath (path);
418
419                         // if the original ended with a [Alt]DirectorySeparatorChar then ensure the full path also ends with one
420                         if (IsDirectorySeparator (end) && (path [path.Length - 1] != DirectorySeparatorChar))
421                                 path += DirectorySeparatorChar;
422
423                         return path;
424                 }
425
426                 internal static bool IsDirectorySeparator (char c) {
427                         return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
428                 }
429
430                 public static string GetPathRoot (string path)
431                 {
432                         if (path == null)
433                                 return null;
434
435                         if (path.Trim ().Length == 0)
436                                 throw new ArgumentException ("The specified path is not of a legal form.");
437
438                         if (!IsPathRooted (path))
439                                 return String.Empty;
440                         
441                         if (DirectorySeparatorChar == '/') {
442                                 // UNIX
443                                 return IsDirectorySeparator (path [0]) ? DirectorySeparatorStr : String.Empty;
444                         } else {
445                                 // Windows
446                                 int len = 2;
447
448                                 if (path.Length == 1 && IsDirectorySeparator (path [0]))
449                                         return DirectorySeparatorStr;
450                                 else if (path.Length < 2)
451                                         return String.Empty;
452
453                                 if (IsDirectorySeparator (path [0]) && IsDirectorySeparator (path[1])) {
454                                         // UNC: \\server or \\server\share
455                                         // Get server
456                                         while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
457
458                                         // Get share
459                                         if (len < path.Length) {
460                                                 len++;
461                                                 while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
462                                         }
463
464                                         return DirectorySeparatorStr +
465                                                 DirectorySeparatorStr +
466                                                 path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
467                                 } else if (IsDirectorySeparator (path [0])) {
468                                         // path starts with '\' or '/'
469                                         return DirectorySeparatorStr;
470                                 } else if (path[1] == VolumeSeparatorChar) {
471                                         // C:\folder
472                                         if (path.Length >= 3 && (IsDirectorySeparator (path [2]))) len++;
473                                 } else
474                                         return Directory.GetCurrentDirectory ().Substring (0, 2);// + path.Substring (0, len);
475                                 return path.Substring (0, len);
476                         }
477                 }
478
479                 // FIXME: Further limit the assertion when imperative Assert is implemented
480                 [FileIOPermission (SecurityAction.Assert, Unrestricted = true)]
481                 public static string GetTempFileName ()
482                 {
483                         FileStream f = null;
484                         string path;
485                         Random rnd;
486                         int num;
487                         int count = 0;
488
489                         SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
490
491                         rnd = new Random ();
492                         var tmp_path = GetTempPath ();
493                         do {
494                                 num = rnd.Next ();
495                                 num++;
496                                 path = Path.Combine (tmp_path, "tmp" + num.ToString ("x", CultureInfo.InvariantCulture) + ".tmp");
497
498                                 try {
499                                         f = new FileStream (path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.Read,
500                                                             8192, false, (FileOptions) 1);
501                                 } catch (IOException ex){
502                                         if (ex._HResult != MonoIO.FileAlreadyExistsHResult || count ++ > 65536)
503                                                 throw;
504                                 } catch (UnauthorizedAccessException ex) {
505                                         if (count ++ > 65536)
506                                                 throw new IOException (ex.Message, ex);
507                                 }
508                         } while (f == null);
509                         
510                         f.Close();
511                         return path;
512                 }
513
514                 [EnvironmentPermission (SecurityAction.Demand, Unrestricted = true)]
515                 public static string GetTempPath ()
516                 {
517                         SecurityManager.EnsureElevatedPermissions (); // this is a no-op outside moonlight
518
519                         string p = get_temp_path ();
520                         if (p.Length > 0 && p [p.Length - 1] != DirectorySeparatorChar)
521                                 return p + DirectorySeparatorChar;
522
523                         return p;
524                 }
525
526                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
527                 private static extern string get_temp_path ();
528
529                 public static bool HasExtension (string path)
530                 {
531                         if (path == null || path.Trim ().Length == 0)
532                                 return false;
533
534                         if (path.IndexOfAny (InvalidPathChars) != -1)
535                                 throw new ArgumentException ("Illegal characters in path.");
536
537                         int pos = findExtension (path);
538                         return 0 <= pos && pos < path.Length - 1;
539                 }
540
541                 public static bool IsPathRooted (string path)
542                 {
543                         if (path == null || path.Length == 0)
544                                 return false;
545
546                         if (path.IndexOfAny (InvalidPathChars) != -1)
547                                 throw new ArgumentException ("Illegal characters in path.");
548
549                         char c = path [0];
550                         return (c == DirectorySeparatorChar     ||
551                                 c == AltDirectorySeparatorChar  ||
552                                 (!dirEqualsVolume && path.Length > 1 && path [1] == VolumeSeparatorChar));
553                 }
554
555                 public static char[] GetInvalidFileNameChars ()
556                 {
557                         // return a new array as we do not want anyone to be able to change the values
558                         if (Environment.IsRunningOnWindows) {
559                                 return new char [41] { '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
560                                         '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12', 
561                                         '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', 
562                                         '\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' };
563                         } else {
564                                 return new char [2] { '\x00', '/' };
565                         }
566                 }
567
568                 public static char[] GetInvalidPathChars ()
569                 {
570                         // return a new array as we do not want anyone to be able to change the values
571                         if (Environment.IsRunningOnWindows) {
572                                 return new char [36] { '\x22', '\x3C', '\x3E', '\x7C', '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
573                                         '\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12', 
574                                         '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', 
575                                         '\x1E', '\x1F' };
576                         } else {
577                                 return new char [1] { '\x00' };
578                         }
579                 }
580
581                 public static string GetRandomFileName ()
582                 {
583                         // returns a 8.3 filename (total size 12)
584                         StringBuilder sb = new StringBuilder (12);
585                         // using strong crypto but without creating the file
586                         RandomNumberGenerator rng = RandomNumberGenerator.Create ();
587                         byte [] buffer = new byte [11];
588                         rng.GetBytes (buffer);
589
590                         for (int i = 0; i < buffer.Length; i++) {
591                                 if (sb.Length == 8)
592                                         sb.Append ('.');
593
594                                 // restrict to length of range [a..z0..9]
595                                 int b = (buffer [i] % 36);
596                                 char c = (char) (b < 26 ? (b + 'a') : (b - 26 + '0'));
597                                 sb.Append (c);
598                         }
599
600                         return sb.ToString ();
601                 }
602
603                 // private class methods
604
605                 private static int findExtension (string path)
606                 {
607                         // method should return the index of the path extension
608                         // start or -1 if no valid extension
609                         if (path != null){
610                                 int iLastDot = path.LastIndexOf ('.');
611                                 int iLastSep = path.LastIndexOfAny ( PathSeparatorChars );
612
613                                 if (iLastDot > iLastSep)
614                                         return iLastDot;
615                         }
616                         return -1;
617                 }
618
619                 static Path ()
620                 {
621                         VolumeSeparatorChar = MonoIO.VolumeSeparatorChar;
622                         DirectorySeparatorChar = MonoIO.DirectorySeparatorChar;
623                         AltDirectorySeparatorChar = MonoIO.AltDirectorySeparatorChar;
624
625                         PathSeparator = MonoIO.PathSeparator;
626                         // this copy will be modifiable ("by design")
627                         InvalidPathChars = GetInvalidPathChars ();
628                         // internal fields
629
630                         DirectorySeparatorStr = DirectorySeparatorChar.ToString ();
631                         PathSeparatorChars = new char [] {
632                                 DirectorySeparatorChar,
633                                 AltDirectorySeparatorChar,
634                                 VolumeSeparatorChar
635                         };
636
637                         dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
638                 }
639
640                 // returns the server and share part of a UNC. Assumes "path" is a UNC.
641                 static string GetServerAndShare (string path)
642                 {
643                         int len = 2;
644                         while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
645
646                         if (len < path.Length) {
647                                 len++;
648                                 while (len < path.Length && !IsDirectorySeparator (path [len])) len++;
649                         }
650
651                         return path.Substring (2, len - 2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
652                 }
653
654                 // assumes Environment.IsRunningOnWindows == true
655                 static bool SameRoot (string root, string path)
656                 {
657                         // compare root - if enough details are available
658                         if ((root.Length < 2) || (path.Length < 2))
659                                 return false;
660
661                         // UNC handling
662                         if (IsDirectorySeparator (root[0]) && IsDirectorySeparator (root[1])) {
663                                 if (!(IsDirectorySeparator (path[0]) && IsDirectorySeparator (path[1])))
664                                         return false;
665
666                                 string rootShare = GetServerAndShare (root);
667                                 string pathShare = GetServerAndShare (path);
668
669                                 return String.Compare (rootShare, pathShare, true, CultureInfo.InvariantCulture) == 0;
670                         }
671                         
672                         // same volume/drive
673                         if (!root [0].Equals (path [0]))
674                                 return false;
675                         // presence of the separator
676                         if (path[1] != Path.VolumeSeparatorChar)
677                                 return false;
678                         if ((root.Length > 2) && (path.Length > 2)) {
679                                 // but don't directory compare the directory separator
680                                 return (IsDirectorySeparator (root[2]) && IsDirectorySeparator (path[2]));
681                         }
682                         return true;
683                 }
684
685                 static string CanonicalizePath (string path)
686                 {
687                         // STEP 1: Check for empty string
688                         if (path == null)
689                                 return path;
690                         if (Environment.IsRunningOnWindows)
691                                 path = path.Trim ();
692
693                         if (path.Length == 0)
694                                 return path;
695
696                         // STEP 2: Check to see if this is only a root
697                         string root = Path.GetPathRoot (path);
698                         // it will return '\' for path '\', while it should return 'c:\' or so.
699                         // Note: commenting this out makes the need for the (target == 1...) check in step 5
700                         //if (root == path) return path;
701
702                         // STEP 3: split the directories, this gets rid of consecutative "/"'s
703                         string[] dirs = path.Split (Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
704                         // STEP 4: Get rid of directories containing . and ..
705                         int target = 0;
706
707                         bool isUnc = Environment.IsRunningOnWindows &&
708                                 root.Length > 2 && IsDirectorySeparator (root[0]) && IsDirectorySeparator (root[1]);
709
710                         // Set an overwrite limit for UNC paths since '\' + server + share
711                         // must not be eliminated by the '..' elimination algorithm.
712                         int limit = isUnc ? 3 : 0;
713
714                         for (int i = 0; i < dirs.Length; i++) {
715                                 // WIN32 path components must be trimmed
716                                 if (Environment.IsRunningOnWindows)
717                                         dirs[i] = dirs[i].TrimEnd ();
718                                 
719                                 if (dirs[i] == "." || (i != 0 && dirs[i].Length == 0))
720                                         continue;
721                                 else if (dirs[i] == "..") {
722                                         // don't overwrite path segments below the limit
723                                         if (target > limit)
724                                                 target--;
725                                 } else
726                                         dirs[target++] = dirs[i];
727                         }
728
729                         // STEP 5: Combine everything.
730                         if (target == 0 || (target == 1 && dirs[0] == ""))
731                                 return root;
732                         else {
733                                 string ret = String.Join (DirectorySeparatorStr, dirs, 0, target);
734                                 if (Environment.IsRunningOnWindows) {
735 #if !MOBILE                                     
736                                         // append leading '\' of the UNC path that was lost in STEP 3.
737                                         if (isUnc)
738                                                 ret = Path.DirectorySeparatorStr + ret;
739
740                                         if (!SameRoot (root, ret))
741                                                 ret = root + ret;
742
743                                         if (isUnc) {
744                                                 return ret;
745                                         } else if (!IsDirectorySeparator (path[0]) && SameRoot (root, path)) {
746                                                 if (ret.Length <= 2 && !ret.EndsWith (DirectorySeparatorStr)) // '\' after "c:"
747                                                         ret += Path.DirectorySeparatorChar;
748                                                 return ret;
749                                         } else {
750                                                 string current = Directory.GetCurrentDirectory ();
751                                                 if (current.Length > 1 && current[1] == Path.VolumeSeparatorChar) {
752                                                         // DOS local file path
753                                                         if (ret.Length == 0 || IsDirectorySeparator (ret[0]))
754                                                                 ret += '\\';
755                                                         return current.Substring (0, 2) + ret;
756                                                 } else if (IsDirectorySeparator (current[current.Length - 1]) && IsDirectorySeparator (ret[0]))
757                                                         return current + ret.Substring (1);
758                                                 else
759                                                         return current + ret;
760                                         }
761 #endif
762                                 } else {
763                                         if (root != "" && ret.Length > 0 && ret [0] != '/')
764                                                 ret = root + ret;
765                                 }
766                                 return ret;
767                         }
768                 }
769
770                 // required for FileIOPermission (and most proibably reusable elsewhere too)
771                 // both path MUST be "full paths"
772                 static internal bool IsPathSubsetOf (string subset, string path)
773                 {
774                         if (subset.Length > path.Length)
775                                 return false;
776
777                         // check that everything up to the last separator match
778                         int slast = subset.LastIndexOfAny (PathSeparatorChars);
779                         if (String.Compare (subset, 0, path, 0, slast) != 0)
780                                 return false;
781
782                         slast++;
783                         // then check if the last segment is identical
784                         int plast = path.IndexOfAny (PathSeparatorChars, slast);
785                         if (plast >= slast) {
786                                 return String.Compare (subset, slast, path, slast, path.Length - plast) == 0;
787                         }
788                         if (subset.Length != path.Length)
789                                 return false;
790
791                         return String.Compare (subset, slast, path, slast, subset.Length - slast) == 0;
792                 }
793
794                 public
795                 static string Combine (params string [] paths)
796                 {
797                         if (paths == null)
798                                 throw new ArgumentNullException ("paths");
799
800                         bool need_sep;
801                         var ret = new StringBuilder ();
802                         int pathsLen = paths.Length;
803                         int slen;
804                         need_sep = false;
805
806                         foreach (var s in paths) {
807                                 if (s == null)
808                                         throw new ArgumentNullException ("One of the paths contains a null value", "paths");
809                                 if (s.Length == 0)
810                                         continue;
811                                 if (s.IndexOfAny (InvalidPathChars) != -1)
812                                         throw new ArgumentException ("Illegal characters in path.");
813
814                                 if (need_sep) {
815                                         need_sep = false;
816                                         ret.Append (DirectorySeparatorStr);
817                                 }
818
819                                 pathsLen--;
820                                 if (IsPathRooted (s))
821                                         ret.Length = 0;
822                                 
823                                 ret.Append (s);
824                                 slen = s.Length;
825                                 if (slen > 0 && pathsLen > 0) {
826                                         char p1end = s [slen - 1];
827                                         if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
828                                                 need_sep = true;
829                                 }
830                         }
831
832                         return ret.ToString ();
833                 }
834
835                 public
836                 static string Combine (string path1, string path2, string path3)
837                 {
838                         if (path1 == null)
839                                 throw new ArgumentNullException ("path1");
840
841                         if (path2 == null)
842                                 throw new ArgumentNullException ("path2");
843
844                         if (path3 == null)
845                                 throw new ArgumentNullException ("path3");
846                         
847                         return Combine (new string [] { path1, path2, path3 });
848                 }
849
850                 public
851                 static string Combine (string path1, string path2, string path3, string path4)
852                 {
853                         if (path1 == null)
854                                 throw new ArgumentNullException ("path1");
855
856                         if (path2 == null)
857                                 throw new ArgumentNullException ("path2");
858
859                         if (path3 == null)
860                                 throw new ArgumentNullException ("path3");
861
862                         if (path4 == null)
863                                 throw new ArgumentNullException ("path4");
864                         
865                         return Combine (new string [] { path1, path2, path3, path4 });
866                 }
867
868                 internal static void Validate (string path)
869                 {
870                         Validate (path, "path");
871                 }
872
873                 internal static void Validate (string path, string parameterName)
874                 {
875                         if (path == null)
876                                 throw new ArgumentNullException (parameterName);
877                         if (String.IsNullOrWhiteSpace (path))
878                                 throw new ArgumentException (Locale.GetText ("Path is empty"));
879                         if (path.IndexOfAny (Path.InvalidPathChars) != -1)
880                                 throw new ArgumentException (Locale.GetText ("Path contains invalid chars"));
881 #if !MOBILE                             
882                         if (Environment.IsRunningOnWindows) {
883                                 int idx = path.IndexOf (':');
884                                 if (idx >= 0 && idx != 1)
885                                         throw new ArgumentException (parameterName);
886                         }
887 #endif
888                 }
889
890                 internal static string DirectorySeparatorCharAsString {
891                         get {
892                                 return DirectorySeparatorStr;
893                         }
894                 }
895
896                 internal const int MAX_PATH = 260;  // From WinDef.h
897
898 #region Copied from referencesource
899                 // this was copied from corefx since it's not available in referencesource
900                 internal static readonly char[] trimEndCharsWindows = { (char)0x9, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20, (char)0x85, (char)0xA0 };
901                 internal static readonly char[] trimEndCharsUnix = { };
902
903                 internal static char[] TrimEndChars => Environment.IsRunningOnWindows ? trimEndCharsWindows : trimEndCharsUnix;
904
905         // ".." can only be used if it is specified as a part of a valid File/Directory name. We disallow
906         //  the user being able to use it to move up directories. Here are some examples eg 
907         //    Valid: a..b  abc..d
908         //    Invalid: ..ab   ab..  ..   abc..d\abc..
909         //
910         internal static void CheckSearchPattern(String searchPattern)
911         {
912             int index;
913             while ((index = searchPattern.IndexOf("..", StringComparison.Ordinal)) != -1) {
914                     
915                  if (index + 2 == searchPattern.Length) // Terminal ".." . Files names cannot end in ".."
916                     throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern"));
917                 
918                  if ((searchPattern[index+2] ==  DirectorySeparatorChar)
919                     || (searchPattern[index+2] == AltDirectorySeparatorChar))
920                     throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern"));
921                 
922                 searchPattern = searchPattern.Substring(index + 2);
923             }
924         }
925
926         internal static void CheckInvalidPathChars(string path, bool checkAdditional = false)
927         {
928             if (path == null)
929                 throw new ArgumentNullException("path");
930
931             if (PathInternal.HasIllegalCharacters(path, checkAdditional))
932                 throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
933         }
934
935         internal static String InternalCombine(String path1, String path2) {
936             if (path1==null || path2==null)
937                 throw new ArgumentNullException((path1==null) ? "path1" : "path2");
938             CheckInvalidPathChars(path1);
939             CheckInvalidPathChars(path2);
940             
941             if (path2.Length == 0)
942                 throw new ArgumentException(Environment.GetResourceString("Argument_PathEmpty"), "path2");
943             if (IsPathRooted(path2))
944                 throw new ArgumentException(Environment.GetResourceString("Arg_Path2IsRooted"), "path2");
945             int i = path1.Length;
946             if (i == 0) return path2;
947             char ch = path1[i - 1];
948             if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar) 
949                 return path1 + DirectorySeparatorCharAsString + path2;
950             return path1 + path2;
951         }
952 #endregion
953         }
954 }