1 //------------------------------------------------------------------------------
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
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
15 //------------------------------------------------------------------------------
18 // Copyright (C) 2004-2005 Novell, Inc (http://www.novell.com)
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:
28 // The above copyright notice and this permission notice shall be
29 // included in all copies or substantial portions of the Software.
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.
40 using System.Runtime.CompilerServices;
41 using System.Security;
42 using System.Security.Permissions;
46 public sealed class Path
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;
55 private static readonly char[] PathSeparatorChars;
56 private static readonly bool dirEqualsVolume;
63 public static string ChangeExtension (string path, string extension)
68 if (path.IndexOfAny (InvalidPathChars) != -1)
69 throw new ArgumentException ("Illegal characters in path", "path");
71 int iExt = findExtension (path);
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);
78 else if (path.Length != 0) {
79 if (extension.Length > 0 && extension [0] != '.')
80 extension = "." + extension;
82 extension = String.Empty;
85 return path + extension;
86 } else if (iExt > 0) {
87 string temp = path.Substring (0, iExt);
88 return temp + extension;
94 public static string Combine (string path1, string path2)
97 throw new ArgumentNullException ("path1");
100 throw new ArgumentNullException ("path2");
102 if (path1 == String.Empty)
105 if (path2 == String.Empty)
108 if (path1.IndexOfAny (InvalidPathChars) != -1)
109 throw new ArgumentException ("Illegal characters in path", "path1");
111 if (path2.IndexOfAny (InvalidPathChars) != -1)
112 throw new ArgumentException ("Illegal characters in path", "path2");
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))
120 char p1end = path1 [path1.Length - 1];
121 if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
122 return path1 + DirectorySeparatorChar + path2;
124 return path1 + path2;
127 public static string GetDirectoryName (string path)
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();
134 if (path == null || GetPathRoot (path) == path)
137 CheckArgument.WhitespaceOnly (path);
138 CheckArgument.PathChars (path);
140 int nLast = path.LastIndexOfAny (PathSeparatorChars);
145 string ret = path.Substring (0, nLast);
147 if (l >= 2 && ret [l - 1] == VolumeSeparatorChar)
148 return ret + DirectorySeparatorChar;
156 public static string GetExtension (string path)
161 if (path.IndexOfAny (InvalidPathChars) != -1)
162 throw new ArgumentException ("Illegal characters in path", "path");
164 int iExt = findExtension (path);
168 if (iExt < path.Length - 1)
169 return path.Substring (iExt);
174 public static string GetFileName (string path)
176 if (path == null || path == String.Empty)
179 if (path.IndexOfAny (InvalidPathChars) != -1)
180 throw new ArgumentException ("Illegal characters in path", "path");
182 int nLast = path.LastIndexOfAny (PathSeparatorChars);
184 return path.Substring (nLast + 1);
189 public static string GetFileNameWithoutExtension (string path)
191 return ChangeExtension (GetFileName (path), null);
194 public static string GetFullPath (string path)
196 string fullpath = InsecureGetFullPath (path);
197 if (SecurityManager.SecurityEnabled) {
198 new FileIOPermission (FileIOPermissionAccess.PathDiscovery, fullpath).Demand ();
203 // insecure - do not call directly
204 internal static string InsecureGetFullPath (string path)
207 throw (new ArgumentNullException (
209 "You must specify a path when calling System.IO.Path.GetFullPath"));
211 if (path.Trim () == String.Empty)
212 throw new ArgumentException ("The path is not of a legal form", "path");
214 if (path.Length >= 2 &&
217 if (path.Length == 2 || path.IndexOf (path [0], 2) < 0)
218 throw new ArgumentException ("UNC pass should be of the form \\\\server\\share.");
220 if (path [0] != DirectorySeparatorChar)
221 path = path.Replace (AltDirectorySeparatorChar, DirectorySeparatorChar);
226 if (!IsPathRooted (path))
227 path = Directory.GetCurrentDirectory () + DirectorySeparatorStr + path;
228 else if (DirectorySeparatorChar == '\\' &&
231 !IsDsc (path [1])) { // like `\abc\def'
232 string current = Directory.GetCurrentDirectory ();
233 if (current [1] == VolumeSeparatorChar)
234 path = current.Substring (0, 2) + path;
236 path = current.Substring (0, current.IndexOf ('\\', current.IndexOf ("\\\\") + 1));
238 return CanonicalizePath (path);
241 static bool IsDsc (char c) {
242 return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
245 public static string GetPathRoot (string path)
250 if (path == String.Empty)
251 throw new ArgumentException ("This specified path is invalid.");
253 if (!IsPathRooted (path))
256 if (DirectorySeparatorChar == '/') {
258 return IsDsc (path [0]) ? DirectorySeparatorStr : String.Empty;
263 if (path.Length == 1 && IsDsc (path [0]))
264 return DirectorySeparatorStr;
265 else if (path.Length < 2)
268 if (IsDsc (path [0]) && IsDsc (path[1])) {
269 // UNC: \\server or \\server\share
271 while (len < path.Length && !IsDsc (path [len])) len++;
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) {
282 if (path.Length >= 3 && (IsDsc (path [2]))) len++;
284 return Directory.GetCurrentDirectory ().Substring (0, 2);// + path.Substring (0, len);
285 return path.Substring (0, len);
289 // FIXME: Further limit the assertion when imperative Assert is implemented
290 [FileIOPermission (SecurityAction.Assert, Unrestricted = true)]
291 public static string GetTempFileName ()
302 path = Path.Combine (GetTempPath(), "tmp" + num.ToString("x"));
305 f = new FileStream (path, FileMode.CreateNew);
307 catch (SecurityException) {
308 // avoid an endless loop
319 [EnvironmentPermission (SecurityAction.Demand, Unrestricted = true)]
320 public static string GetTempPath ()
322 string p = get_temp_path ();
323 if (p.Length > 0 && p [p.Length - 1] != DirectorySeparatorChar)
324 return p + DirectorySeparatorChar;
329 [MethodImplAttribute(MethodImplOptions.InternalCall)]
330 private static extern string get_temp_path ();
332 public static bool HasExtension (string path)
334 if (path == null || path.Trim () == String.Empty)
337 int pos = findExtension (path);
338 return 0 <= pos && pos < path.Length - 1;
341 public static bool IsPathRooted (string path)
343 if (path == null || path.Length == 0)
346 if (path.IndexOfAny (InvalidPathChars) != -1)
347 throw new ArgumentException ("Illegal characters in path", "path");
350 return (c == DirectorySeparatorChar ||
351 c == AltDirectorySeparatorChar ||
352 (!dirEqualsVolume && path.Length > 1 && path [1] == VolumeSeparatorChar));
355 // private class methods
357 private static int findExtension (string path)
359 // method should return the index of the path extension
360 // start or -1 if no valid extension
362 int iLastDot = path.LastIndexOf ('.');
363 int iLastSep = path.LastIndexOfAny ( PathSeparatorChars );
365 if (iLastDot > iLastSep)
372 VolumeSeparatorChar = MonoIO.VolumeSeparatorChar;
373 DirectorySeparatorChar = MonoIO.DirectorySeparatorChar;
374 AltDirectorySeparatorChar = MonoIO.AltDirectorySeparatorChar;
376 PathSeparator = MonoIO.PathSeparator;
377 InvalidPathChars = MonoIO.InvalidPathChars;
381 DirectorySeparatorStr = DirectorySeparatorChar.ToString ();
382 PathSeparatorChars = new char [] {
383 DirectorySeparatorChar,
384 AltDirectorySeparatorChar,
388 dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
392 static string CanonicalizePath (string path) {
394 // STEP 1: Check for empty string
395 if (path == null) return path;
397 if (path == String.Empty) return path;
399 // STEP 2: Check to see if this is only a root
400 string root = GetPathRoot (path);
401 // it will return '\' for path '\', while it should return 'c:\' or so.
402 // Note: commenting this out makes the ened for the (target == 1...) check in step 5
403 //if (root == path) return path;
405 // STEP 3: split the directories, this gets rid of consecutative "/"'s
406 string [] dirs = path.Split (DirectorySeparatorChar, AltDirectorySeparatorChar);
407 // STEP 4: Get rid of directories containing . and ..
410 for (int i = 0; i < dirs.Length; i++) {
411 if (dirs [i] == "." || (i != 0 && dirs [i] == String.Empty)) continue;
412 else if (dirs [i] == "..") {
413 if (target != 0) target--;
416 dirs [target++] = dirs [i];
419 // STEP 5: Combine everything.
420 if (target == 0 || (target == 1 && dirs [0] == ""))
423 string ret = String.Join (DirectorySeparatorStr, dirs, 0, target);
424 switch (DirectorySeparatorChar) {
425 case '\\': // Windows
426 // In GetFullPath(), it is assured that here never comes UNC. So this must only applied to such path that starts with '\', without drive specification.
427 if (path [0] != DirectorySeparatorChar && path.StartsWith (root)) {
428 if (ret.Length <= 2 && !ret.EndsWith (DirectorySeparatorStr)) // '\' after "c:"
429 ret += DirectorySeparatorChar;
432 string current = Directory.GetCurrentDirectory ();
433 if (current.Length > 1 && current [1] == VolumeSeparatorChar) {
434 // DOS local file path
435 if (ret.Length == 0 || IsDsc (ret [0]))
437 return current.Substring (0, 2) + ret;
439 else if (IsDsc (current [current.Length - 1]) && IsDsc (ret [0]))
440 return current + ret.Substring (1);
442 return current + ret;