updating to the latest module.
[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 // 
9 // Author:         Jim Richardson, develop@wtfo-guru.com
10 //                 Dan Lewis (dihlewis@yahoo.co.uk)
11 //                 Gonzalo Paniagua Javier (gonzalo@ximian.com)
12 //                 Ben Maurer (bmaurer@users.sourceforge.net)
13 // Created:        Saturday, August 11, 2001 
14 //
15 //------------------------------------------------------------------------------
16
17 //
18 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
19 //
20 // Permission is hereby granted, free of charge, to any person obtaining
21 // a copy of this software and associated documentation files (the
22 // "Software"), to deal in the Software without restriction, including
23 // without limitation the rights to use, copy, modify, merge, publish,
24 // distribute, sublicense, and/or sell copies of the Software, and to
25 // permit persons to whom the Software is furnished to do so, subject to
26 // the following conditions:
27 // 
28 // The above copyright notice and this permission notice shall be
29 // included in all copies or substantial portions of the Software.
30 // 
31 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
32 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
33 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
34 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
35 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
36 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
37 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
38 //
39
40 using System.Runtime.CompilerServices;
41 using System.Security;
42 using System.Security.Permissions;
43
44 namespace System.IO
45 {
46         public sealed class Path
47         {
48                 public static readonly char AltDirectorySeparatorChar;
49                 public static readonly char DirectorySeparatorChar;
50                 public static readonly char[] InvalidPathChars;
51                 public static readonly char PathSeparator;
52                 internal static readonly string DirectorySeparatorStr;
53                 public static readonly char VolumeSeparatorChar;
54
55                 private static readonly char[] PathSeparatorChars;
56                 private static readonly bool dirEqualsVolume;
57
58                 private Path ()
59                 {
60                 }
61
62                 // class methods
63                 public static string ChangeExtension (string path, string extension)
64                 {
65                         if (path == null)
66                                 return null;
67
68                         if (path.IndexOfAny (InvalidPathChars) != -1)
69                                 throw new ArgumentException ("Illegal characters in path", "path");
70
71                         int iExt = findExtension (path);
72
73                         if (extension == null)
74                                 return iExt < 0 ? path : path.Substring (0, iExt);
75                         else if (extension == String.Empty)
76                                 return iExt < 0 ? path + '.' : path.Substring (0, iExt + 1);
77
78                         else if (path.Length != 0) {
79                                 if (extension.Length > 0 && extension [0] != '.')
80                                         extension = "." + extension;
81                         } else
82                                 extension = String.Empty;
83                         
84                         if (iExt < 0) {
85                                 return path + extension;
86                         } else if (iExt > 0) {
87                                 string temp = path.Substring (0, iExt);
88                                 return temp + extension;
89                         }
90
91                         return extension;
92                 }
93
94                 public static string Combine (string path1, string path2)
95                 {
96                         if (path1 == null)
97                                 throw new ArgumentNullException ("path1");
98
99                         if (path2 == null)
100                                 throw new ArgumentNullException ("path2");
101
102                         if (path1 == String.Empty)
103                                 return path2;
104
105                         if (path2 == String.Empty)
106                                 return path1;
107
108                         if (path1.IndexOfAny (InvalidPathChars) != -1)
109                                 throw new ArgumentException ("Illegal characters in path", "path1");
110
111                         if (path2.IndexOfAny (InvalidPathChars) != -1)
112                                 throw new ArgumentException ("Illegal characters in path", "path2");
113
114                         //TODO???: UNC names
115                         // LAMESPEC: MS says that if path1 is not empty and path2 is a full path
116                         // it should throw ArgumentException
117                         if (IsPathRooted (path2))
118                                 return path2;
119                         
120                         char p1end = path1 [path1.Length - 1];
121                         if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
122                                 return path1 + DirectorySeparatorChar + path2;
123
124                         return path1 + path2;
125                 }
126
127                 public static string GetDirectoryName (string path)
128                 {
129                         // LAMESPEC: For empty string MS docs say both
130                         // return null AND throw exception.  Seems .NET throws.
131                         if (path == String.Empty)
132                                 throw new ArgumentException();
133
134                         if (path == null || GetPathRoot (path) == path)
135                                 return null;
136
137                         CheckArgument.WhitespaceOnly (path);
138                         CheckArgument.PathChars (path);
139
140                         int nLast = path.LastIndexOfAny (PathSeparatorChars);
141                         if (nLast == 0)
142                                 nLast++;
143
144                         if (nLast > 0) {
145                                 string ret = path.Substring (0, nLast);
146                                 int l = ret.Length;
147                                 if (l >= 2 && ret [l - 1] == VolumeSeparatorChar)
148                                         return ret + DirectorySeparatorChar;
149                                 else
150                                         return ret;
151                         }
152
153                         return String.Empty;
154                 }
155
156                 public static string GetExtension (string path)
157                 {
158                         if (path == null)
159                                 return null;
160
161                         if (path.IndexOfAny (InvalidPathChars) != -1)
162                                 throw new ArgumentException ("Illegal characters in path", "path");
163
164                         int iExt = findExtension (path);
165
166                         if (iExt > -1)
167                         {
168                                 if (iExt < path.Length - 1)
169                                         return path.Substring (iExt);
170                         }
171                         return string.Empty;
172                 }
173
174                 public static string GetFileName (string path)
175                 {
176                         if (path == null || path == String.Empty)
177                                 return path;
178
179                         if (path.IndexOfAny (InvalidPathChars) != -1)
180                                 throw new ArgumentException ("Illegal characters in path", "path");
181
182                         int nLast = path.LastIndexOfAny (PathSeparatorChars);
183                         if (nLast >= 0)
184                                 return path.Substring (nLast + 1);
185
186                         return path;
187                 }
188
189                 public static string GetFileNameWithoutExtension (string path)
190                 {
191                         return ChangeExtension (GetFileName (path), null);
192                 }
193
194                 public static string GetFullPath (string path)
195                 {
196                         string fullpath = InsecureGetFullPath (path);
197                         if (SecurityManager.SecurityEnabled) {
198                                 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, fullpath).Demand ();
199                         }
200                         return fullpath;
201                 }
202
203                 // insecure - do not call directly
204                 internal static string InsecureGetFullPath (string path)
205                 {
206                         if (path == null)
207                                 throw (new ArgumentNullException (
208                                         "path",
209                                         "You must specify a path when calling System.IO.Path.GetFullPath"));
210
211                         if (path.Trim () == String.Empty)
212                                 throw new ArgumentException ("The path is not of a legal form", "path");
213
214                         if (path.Length >= 2 &&
215                                 IsDsc (path [0]) &&
216                                 IsDsc (path [1])) {
217                                 if (path.Length == 2 || path.IndexOf (path [0], 2) < 0)
218                                         throw new ArgumentException ("UNC pass should be of the form \\\\server\\share.");
219
220                                 if (path [0] != DirectorySeparatorChar)
221                                         path = path.Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
222
223                                 return path;
224                         }
225
226                         if (!IsPathRooted (path))
227                                 path = Directory.GetCurrentDirectory () + DirectorySeparatorStr + path;
228                         else if (DirectorySeparatorChar == '\\' &&
229                                 path.Length >= 2 &&
230                                 IsDsc (path [0]) &&
231                                 !IsDsc (path [1])) { // like `\abc\def'
232                                 string current = Directory.GetCurrentDirectory ();
233                                 if (current [1] == VolumeSeparatorChar)
234                                         path = current.Substring (0, 2) + path;
235                                 else
236                                         path = current.Substring (0, current.IndexOf ('\\', current.IndexOf ("\\\\") + 1));
237                         }
238                         return CanonicalizePath (path);
239                 }
240
241                 static bool IsDsc (char c) {
242                         return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
243                 }
244                 
245                 public static string GetPathRoot (string path)
246                 {
247                         if (path == null)
248                                 return null;
249
250                         if (path == String.Empty)
251                                 throw new ArgumentException ("This specified path is invalid.");
252
253                         if (!IsPathRooted (path))
254                                 return String.Empty;
255                         
256                         if (DirectorySeparatorChar == '/') {
257                                 // UNIX
258                                 return IsDsc (path [0]) ? DirectorySeparatorStr : String.Empty;
259                         } else {
260                                 // Windows
261                                 int len = 2;
262
263                                 if (path.Length == 1 && IsDsc (path [0]))
264                                         return DirectorySeparatorStr;
265                                 else if (path.Length < 2)
266                                         return String.Empty;
267
268                                 if (IsDsc (path [0]) && IsDsc (path[1])) {
269                                         // UNC: \\server or \\server\share
270                                         // Get server
271                                         while (len < path.Length && !IsDsc (path [len])) len++;
272                                         // Get share
273                                         while (len < path.Length && !IsDsc (path [len])) len++;
274                                         return DirectorySeparatorStr +
275                                                 DirectorySeparatorStr +
276                                                 path.Substring (2).Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
277                                 } else if (IsDsc (path [0])) {
278                                         // path starts with '\' or '/'
279                                         return DirectorySeparatorStr;
280                                 } else if (path[1] == VolumeSeparatorChar) {
281                                         // C:\folder
282                                         if (path.Length >= 3 && (IsDsc (path [2]))) len++;
283                                 } else
284                                         return Directory.GetCurrentDirectory ().Substring (0, 2);// + path.Substring (0, len);
285                                 return path.Substring (0, len);
286                         }
287                 }
288
289                 // FIXME: Further limit the assertion when imperative Assert is implemented
290                 [FileIOPermission (SecurityAction.Assert, Unrestricted = true)]
291                 public static string GetTempFileName ()
292                 {
293                         FileStream f = null;
294                         string path;
295                         Random rnd;
296                         int num = 0;
297
298                         rnd = new Random ();
299                         do {
300                                 num = rnd.Next ();
301                                 num++;
302                                 path = Path.Combine (GetTempPath(), "tmp" + num.ToString("x"));
303
304                                 try {
305                                         f = new FileStream (path, FileMode.CreateNew);
306                                 }
307                                 catch (SecurityException) {
308                                         // avoid an endless loop
309                                         throw;
310                                 }
311                                 catch {
312                                 }
313                         } while (f == null);
314                         
315                         f.Close();
316                         return path;
317                 }
318
319                 [EnvironmentPermission (SecurityAction.Demand, Unrestricted = true)]
320                 public static string GetTempPath ()
321                 {
322                         string p = get_temp_path ();
323                         if (p.Length > 0 && p [p.Length - 1] != DirectorySeparatorChar)
324                                 return p + DirectorySeparatorChar;
325
326                         return p;
327                 }
328
329                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
330                 private static extern string get_temp_path ();
331
332                 public static bool HasExtension (string path)
333                 {  
334                         if (path == null || path.Trim () == String.Empty)
335                                 return false;
336
337                         int pos = findExtension (path);
338                         return 0 <= pos && pos < path.Length - 1;
339                 }
340
341                 public static bool IsPathRooted (string path)
342                 {
343                         if (path == null || path.Length == 0)
344                                 return false;
345
346                         if (path.IndexOfAny (InvalidPathChars) != -1)
347                                 throw new ArgumentException ("Illegal characters in path", "path");
348
349                         char c = path [0];
350                         return (c == DirectorySeparatorChar     ||
351                                 c == AltDirectorySeparatorChar  ||
352                                 (!dirEqualsVolume && path.Length > 1 && path [1] == VolumeSeparatorChar));
353                 }
354
355                 // private class methods
356
357                 private static int findExtension (string path)
358                 {
359                         // method should return the index of the path extension
360                         // start or -1 if no valid extension
361                         if (path != null){
362                                 int iLastDot = path.LastIndexOf ('.');
363                                 int iLastSep = path.LastIndexOfAny ( PathSeparatorChars );
364
365                                 if (iLastDot > iLastSep)
366                                         return iLastDot;
367                         }
368                         return -1;
369                 }
370
371                 static Path () {
372                         VolumeSeparatorChar = MonoIO.VolumeSeparatorChar;
373                         DirectorySeparatorChar = MonoIO.DirectorySeparatorChar;
374                         AltDirectorySeparatorChar = MonoIO.AltDirectorySeparatorChar;
375
376                         PathSeparator = MonoIO.PathSeparator;
377                         InvalidPathChars = MonoIO.InvalidPathChars;
378
379                         // internal fields
380
381                         DirectorySeparatorStr = DirectorySeparatorChar.ToString ();
382                         PathSeparatorChars = new char [] {
383                                 DirectorySeparatorChar,
384                                 AltDirectorySeparatorChar,
385                                 VolumeSeparatorChar
386                         };
387
388                         dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
389                 }
390                 
391                 
392                 static string CanonicalizePath (string path) {
393                         
394                         // STEP 1: Check for empty string
395                         if (path == null) return path;
396                         if (Environment.IsRunningOnWindows)
397                                 path = path.Trim ();
398
399                         if (path == String.Empty) return path;
400                         
401                         // STEP 2: Check to see if this is only a root
402                         string root = GetPathRoot (path);
403                         // it will return '\' for path '\', while it should return 'c:\' or so.
404                         // Note: commenting this out makes the ened for the (target == 1...) check in step 5
405                         //if (root == path) return path;
406                                 
407                         // STEP 3: split the directories, this gets rid of consecutative "/"'s
408                         string [] dirs = path.Split (DirectorySeparatorChar, AltDirectorySeparatorChar);
409                         // STEP 4: Get rid of directories containing . and ..
410                         int target = 0;
411                         
412                         for (int i = 0; i < dirs.Length; i++) {
413                                 if (dirs [i] == "." || (i != 0 && dirs [i] == String.Empty)) continue;
414                                 else if (dirs [i] == "..") {
415                                         if (target != 0) target--;
416                                 }
417                                 else
418                                         dirs [target++] = dirs [i];
419                         }
420
421                         // STEP 5: Combine everything.
422                         if (target == 0 || (target == 1 && dirs [0] == ""))
423                                 return root;
424                         else {
425                                 string ret = String.Join (DirectorySeparatorStr, dirs, 0, target);
426                                 switch (DirectorySeparatorChar) {
427                                 case '\\': // Windows
428                                         // In GetFullPath(), it is assured that here never comes UNC. So this must only applied to such path that starts with '\', without drive specification.
429                                         if (path [0] != DirectorySeparatorChar && path.StartsWith (root)) {
430                                                 if (ret.Length <= 2 && !ret.EndsWith (DirectorySeparatorStr)) // '\' after "c:"
431                                                         ret += DirectorySeparatorChar;
432                                                 return ret;
433                                         } else {
434                                                 string current = Directory.GetCurrentDirectory ();
435                                                 if (current.Length > 1 && current [1] == VolumeSeparatorChar) {
436                                                         // DOS local file path
437                                                         if (ret.Length == 0 || IsDsc (ret [0]))
438                                                                 ret += '\\';
439                                                         return current.Substring (0, 2) + ret;
440                                                 }
441                                                 else if (IsDsc (current [current.Length - 1]) && IsDsc (ret [0]))
442                                                         return current + ret.Substring (1);
443                                                 else
444                                                         return current + ret;
445                                         }
446                                 default: // Unix/Mac
447                                         return ret;
448                                 }
449                         }
450                 }
451         }
452 }