Newer
Older
DNA / corlib / System.IO / Path.cs
@Chris Bacon Chris Bacon on 21 Jan 2012 22 KB First commit
#if !LOCALTEST

namespace System.IO {
	public static class Path {

		/*		static Path() {
					InvalidPathChars = GetInvalidPathChars();
					if (Environment.Platform == PlatformID.Unix) {
						AltDirectorySeparatorChar = '/';
						DirectorySeparatorChar = '/';
						PathSeparator = ';';
						VolumeSeparatorChar = '/';
					} else {
						AltDirectorySeparatorChar = '/';
						DirectorySeparatorChar = '\\';
						PathSeparator = ';';
						VolumeSeparatorChar = ':';
					}
					DirectorySeparatorStr = DirectorySeparatorChar.ToString();
					dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
				}

				public static readonly char AltDirectorySeparatorChar;
				public static readonly char DirectorySeparatorChar;
				internal static readonly string DirectorySeparatorStr;
				public static readonly char[] InvalidPathChars;
				public static readonly char PathSeparator;
				public static readonly char VolumeSeparatorChar;
				private static bool dirEqualsVolume;

				public static char[] GetInvalidPathChars() {
					// return a new array as we do not want anyone to be able to change the values
					if (Environment.Platform == PlatformID.Unix) {
						return new char[] { '\x00' };
					} else {
						return new char[] {
							'\x22', '\x3C', '\x3E', '\x7C', '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
							'\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12', 
							'\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', 
							'\x1E', '\x1F' };
					}
				}

				private static bool IsDsc(char c) {
					return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
				}

				private static string WindowsDriveAdjustment(string path) {
					// two special cases to consider when a drive is specified
					if (path.Length < 2) {
						return path;
					}
					if ((path[1] != ':') || !Char.IsLetter(path[0])) {
						return path;
					}
					string current = Directory.GetCurrentDirectory();
					// first, only the drive is specified
					if (path.Length == 2) {
						// then if the current directory is on the same drive
						if (current[0] == path[0]) {
							path = current; // we return it
						} else {
							path += '\\';
						}
					} else if ((path[2] != Path.DirectorySeparatorChar) && (path[2] != Path.AltDirectorySeparatorChar)) {
						// second, the drive + a directory is specified *without* a separator between them (e.g. C:dir).
						// If the current directory is on the specified drive...
						if (current[0] == path[0]) {
							// then specified directory is appended to the current drive directory
							path = Path.Combine(current, path.Substring(2, path.Length - 2));
						} else {
							// if not, then just pretend there was a separator (Path.Combine won't work in this case)
							path = String.Concat(path.Substring(0, 2), DirectorySeparatorStr, path.Substring(2, path.Length - 2));
						}
					}
					return path;
				}

				public static bool IsPathRooted(string path) {
					if (path == null || path.Length == 0) {
						return false;
					}
					if (path.IndexOfAny(InvalidPathChars) != -1) {
						throw new ArgumentException("Illegal characters in path", "path");
					}
					char c = path[0];
					return (c == DirectorySeparatorChar ||
						c == AltDirectorySeparatorChar ||
						(!dirEqualsVolume && path.Length > 1 && path[1] == VolumeSeparatorChar));
				}

				static string CanonicalizePath(string path) {
					// STEP 1: Check for empty string
					if (path == null) {
						return path;
					}
					if (Environment.IsRunningOnWindows) {
						path = path.Trim();
					}
					if (path.Length == 0) {
						return path;
					}
					// STEP 2: Check to see if this is only a root
					string root = Path.GetPathRoot(path);
					// it will return '\' for path '\', while it should return 'c:\' or so.
					// Note: commenting this out makes the ened for the (target == 1...) check in step 5
					//if (root == path) return path;

					// STEP 3: split the directories, this gets rid of consecutative "/"'s
					string[] dirs = path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
					// STEP 4: Get rid of directories containing . and ..
					int target = 0;

					for (int i = 0; i < dirs.Length; i++) {
						if (dirs[i] == "." || (i != 0 && dirs[i].Length == 0)) {
							continue;
						} else if (dirs[i] == "..") {
							if (target != 0) {
								target--;
							}
						} else {
							dirs[target++] = dirs[i];
						}
					}

					// STEP 5: Combine everything.
					if (target == 0 || (target == 1 && dirs[0] == "")) {
						return root;
					} else {
						string ret = String.Join(DirectorySeparatorStr, dirs, 0, target);
						if (Environment.IsRunningOnWindows) {
							if (!SameRoot(root, ret)) {
								ret = root + ret;
							}
							// In GetFullPath(), it is assured that here never comes UNC. So this must only applied to such path that starts with '\', without drive specification.
							if (!IsDsc(path[0]) && SameRoot(root, path)) {
								if (ret.Length <= 2 && !ret.EndsWith(DirectorySeparatorStr)) { // '\' after "c:"
									ret += Path.DirectorySeparatorChar;
								}
								return ret;
							} else {
								string current = Directory.GetCurrentDirectory();
								if (current.Length > 1 && current[1] == Path.VolumeSeparatorChar) {
									// DOS local file path
									if (ret.Length == 0 || IsDsc(ret[0])) {
										ret += '\\';
									}
									return current.Substring(0, 2) + ret;
								} else if (IsDsc(current[current.Length - 1]) && IsDsc(ret[0])) {
									return current + ret.Substring(1);
								} else {
									return current + ret;
								}
							}
						}
						return ret;
					}
				}

				public static string GetFullPath(string path) {
					if (path == null) {
						throw new ArgumentNullException("path");
					}
					if (path.Trim().Length == 0) {
						string msg = "The specified path is not of a legal form (empty).";
						throw new ArgumentException(msg, "path");
					}

					// adjust for drives, i.e. a special case for windows
					if (Environment.IsRunningOnWindows) {
						path = WindowsDriveAdjustment(path);
					}
					// if the supplied path ends with a separator...
					char end = path[path.Length - 1];

					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.");
						}
						if (path[0] != DirectorySeparatorChar) {
							path = path.Replace(AltDirectorySeparatorChar, DirectorySeparatorChar);
						}
					} else {
						if (!IsPathRooted(path)) {
							path = Directory.GetCurrentDirectory() + DirectorySeparatorStr + path;
						} else if (DirectorySeparatorChar == '\\' && path.Length >= 2 && IsDsc(path[0]) && !IsDsc(path[1])) { // like `\abc\def'
							string current = Directory.GetCurrentDirectory();
							if (current[1] == VolumeSeparatorChar) {
								path = current.Substring(0, 2) + path;
							} else {
								path = current.Substring(0, current.IndexOf('\\', current.IndexOf("\\\\") + 1));
							}
						}
						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)) {
						path += DirectorySeparatorChar;
					}

					return path;
				}*/

		public static readonly char[] InvalidPathChars;
		public static readonly char AltDirectorySeparatorChar;
		public static readonly char DirectorySeparatorChar;
		public static readonly char PathSeparator;
		internal static readonly string DirectorySeparatorStr;
		public static readonly char VolumeSeparatorChar;

		private static readonly char[] PathSeparatorChars;
		private static readonly bool dirEqualsVolume;

		// class methods
		public static string ChangeExtension(string path, string extension) {
			if (path == null)
				return null;

			if (path.IndexOfAny(InvalidPathChars) != -1)
				throw new ArgumentException("Illegal characters in path", "path");

			int iExt = findExtension(path);

			if (extension == null)
				return iExt < 0 ? path : path.Substring(0, iExt);
			else if (extension == String.Empty)
				return iExt < 0 ? path + '.' : path.Substring(0, iExt + 1);

			else if (path.Length != 0) {
				if (extension.Length > 0 && extension[0] != '.')
					extension = "." + extension;
			} else
				extension = String.Empty;

			if (iExt < 0) {
				return path + extension;
			} else if (iExt > 0) {
				string temp = path.Substring(0, iExt);
				return temp + extension;
			}

			return extension;
		}

		public static string Combine(string path1, string path2) {
			if (path1 == null)
				throw new ArgumentNullException("path1");

			if (path2 == null)
				throw new ArgumentNullException("path2");

			if (path1 == String.Empty)
				return path2;

			if (path2 == String.Empty)
				return path1;

			if (path1.IndexOfAny(InvalidPathChars) != -1)
				throw new ArgumentException("Illegal characters in path", "path1");

			if (path2.IndexOfAny(InvalidPathChars) != -1)
				throw new ArgumentException("Illegal characters in path", "path2");

			//TODO???: UNC names
			// LAMESPEC: MS says that if path1 is not empty and path2 is a full path
			// it should throw ArgumentException
			if (IsPathRooted(path2))
				return path2;

			char p1end = path1[path1.Length - 1];
			if (p1end != DirectorySeparatorChar && p1end != AltDirectorySeparatorChar && p1end != VolumeSeparatorChar)
				return path1 + DirectorySeparatorChar + path2;

			return path1 + path2;
		}

		public static string GetDirectoryName(string path) {
			// LAMESPEC: For empty string MS docs say both
			// return null AND throw exception.  Seems .NET throws.
			if (path == String.Empty) {
				throw new ArgumentException();
			}
			if (path == null || GetPathRoot(path) == path) {
				return null;
			}
			CheckArgument.WhitespaceOnly(path);
			CheckArgument.PathChars(path);

			int nLast = path.LastIndexOfAny(PathSeparatorChars);
			if (nLast == 0) {
				nLast++;
			}
			if (nLast > 0) {
				string ret = path.Substring(0, nLast);
				int l = ret.Length;
				if (l >= 2 && ret[l - 1] == VolumeSeparatorChar) {
					return ret + DirectorySeparatorChar;
				} else {
					return ret;
				}
			}

			return String.Empty;
		}

		public static string GetExtension(string path) {
			if (path == null) {
				return null;
			}
			if (path.IndexOfAny(InvalidPathChars) != -1) {
				throw new ArgumentException("Illegal characters in path", "path");
			}
			int iExt = findExtension(path);

			if (iExt > -1) {
				if (iExt < path.Length - 1) {
					return path.Substring(iExt);
				}
			}
			return string.Empty;
		}

		public static string GetFileName(string path) {
			if (path == null || path == String.Empty) {
				return path;
			}
			if (path.IndexOfAny(InvalidPathChars) != -1) {
				throw new ArgumentException("Illegal characters in path", "path");
			}
			int nLast = path.LastIndexOfAny(PathSeparatorChars);
			if (nLast >= 0) {
				return path.Substring(nLast + 1);
			}
			return path;
		}

		public static string GetFileNameWithoutExtension(string path) {
			return ChangeExtension(GetFileName(path), null);
		}

		public static string GetFullPath(string path) {
			string fullpath = InsecureGetFullPath(path);
			return fullpath;
		}

		internal static string WindowsDriveAdjustment(string path) {
			// two special cases to consider when a drive is specified
			if (path.Length < 2) {
				return path;
			}
			if ((path[1] != ':') || !Char.IsLetter(path[0])) {
				return path;
			}
			string current = Directory.GetCurrentDirectory();
			// first, only the drive is specified
			if (path.Length == 2) {
				// then if the current directory is on the same drive
				if (current[0] == path[0]) {
					path = current; // we return it
				} else {
					path += '\\';
				}
			} else if ((path[2] != Path.DirectorySeparatorChar) && (path[2] != Path.AltDirectorySeparatorChar)) {
				// second, the drive + a directory is specified *without* a separator between them (e.g. C:dir).
				// If the current directory is on the specified drive...
				if (current[0] == path[0]) {
					// then specified directory is appended to the current drive directory
					path = Path.Combine(current, path.Substring(2, path.Length - 2));
				} else {
					// if not, then just pretend there was a separator (Path.Combine won't work in this case)
					path = String.Concat(path.Substring(0, 2), DirectorySeparatorStr, path.Substring(2, path.Length - 2));
				}
			}
			return path;
		}

		// insecure - do not call directly
		internal static string InsecureGetFullPath(string path) {
			if (path == null) {
				throw new ArgumentNullException("path");
			}
			if (path.Trim().Length == 0) {
				string msg = "The specified path is not of a legal form (empty).";
				throw new ArgumentException(msg, "path");
			}

			// adjust for drives, i.e. a special case for windows
			if (Environment.IsRunningOnWindows) {
				path = WindowsDriveAdjustment(path);
			}
			// if the supplied path ends with a separator...
			char end = path[path.Length - 1];

			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.");
				}
				if (path[0] != DirectorySeparatorChar) {
					path = path.Replace(AltDirectorySeparatorChar, DirectorySeparatorChar);
				}
			} else {
				if (!IsPathRooted(path)) {
					path = Directory.GetCurrentDirectory() + DirectorySeparatorStr + path;
				} else if (DirectorySeparatorChar == '\\' &&
					path.Length >= 2 &&
					IsDsc(path[0]) &&
					!IsDsc(path[1])) { // like `\abc\def'
					string current = Directory.GetCurrentDirectory();
					if (current[1] == VolumeSeparatorChar) {
						path = current.Substring(0, 2) + path;
					} else {
						path = current.Substring(0, current.IndexOf('\\', current.IndexOf("\\\\") + 1));
					}
				}
				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)) {
				path += DirectorySeparatorChar;
			}

			return path;
		}

		static bool IsDsc(char c) {
			return c == DirectorySeparatorChar || c == AltDirectorySeparatorChar;
		}

		public static string GetPathRoot(string path) {
			if (path == null)
				return null;

			if (path == String.Empty)
				throw new ArgumentException("This specified path is invalid.");

			if (!IsPathRooted(path))
				return String.Empty;

			if (DirectorySeparatorChar == '/') {
				// UNIX
				return IsDsc(path[0]) ? DirectorySeparatorStr : String.Empty;
			} else {
				// Windows
				int len = 2;

				if (path.Length == 1 && IsDsc(path[0]))
					return DirectorySeparatorStr;
				else if (path.Length < 2)
					return String.Empty;

				if (IsDsc(path[0]) && IsDsc(path[1])) {
					// UNC: \\server or \\server\share
					// Get server
					while (len < path.Length && !IsDsc(path[len])) len++;

					// Get share
					if (len < path.Length) {
						len++;
						while (len < path.Length && !IsDsc(path[len])) len++;
					}

					return DirectorySeparatorStr +
						DirectorySeparatorStr +
						path.Substring(2, len - 2).Replace(AltDirectorySeparatorChar, DirectorySeparatorChar);
				} else if (IsDsc(path[0])) {
					// path starts with '\' or '/'
					return DirectorySeparatorStr;
				} else if (path[1] == VolumeSeparatorChar) {
					// C:\folder
					if (path.Length >= 3 && (IsDsc(path[2]))) len++;
				} else
					return Directory.GetCurrentDirectory().Substring(0, 2);// + path.Substring (0, len);
				return path.Substring(0, len);
			}
		}

		public static string GetTempFileName() {
			throw new NotImplementedException();
		}

		/*public static string GetTempPath() {
			string p = get_temp_path();
			if (p.Length > 0 && p[p.Length - 1] != DirectorySeparatorChar)
				return p + DirectorySeparatorChar;

			return p;
		}

		//private static extern string get_temp_path();
		*/

		public static bool HasExtension(string path) {
			if (path == null || path.Trim() == String.Empty)
				return false;

			int pos = findExtension(path);
			return 0 <= pos && pos < path.Length - 1;
		}

		public static bool IsPathRooted(string path) {
			if (path == null || path.Length == 0)
				return false;

			if (path.IndexOfAny(InvalidPathChars) != -1)
				throw new ArgumentException("Illegal characters in path", "path");

			char c = path[0];
			return (c == DirectorySeparatorChar ||
				c == AltDirectorySeparatorChar ||
				(!dirEqualsVolume && path.Length > 1 && path[1] == VolumeSeparatorChar));
		}

		public static char[] GetInvalidFileNameChars ()
		{
			// return a new array as we do not want anyone to be able to change the values
			if (Environment.IsRunningOnWindows) {
				return new char [41] { '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
					'\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12', 
					'\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', 
					'\x1E', '\x1F', '\x22', '\x3C', '\x3E', '\x7C', ':', '*', '?', '\\', '/' };
			} else {
				return new char [2] { '\x00', '/' };
			}
		}

		public static char[] GetInvalidPathChars ()
		{
			// return a new array as we do not want anyone to be able to change the values
			if (Environment.IsRunningOnWindows) {
				return new char [36] { '\x22', '\x3C', '\x3E', '\x7C', '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
					'\x08', '\x09', '\x0A', '\x0B', '\x0C', '\x0D', '\x0E', '\x0F', '\x10', '\x11', '\x12', 
					'\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1A', '\x1B', '\x1C', '\x1D', 
					'\x1E', '\x1F' };
			} else {
				return new char [1] { '\x00' };
			}
		}

		public static string GetRandomFileName ()
		{
			throw new NotImplementedException();
		}
		// private class methods

		private static int findExtension(string path) {
			// method should return the index of the path extension
			// start or -1 if no valid extension
			if (path != null) {
				int iLastDot = path.LastIndexOf('.');
				int iLastSep = path.LastIndexOfAny(PathSeparatorChars);

				if (iLastDot > iLastSep)
					return iLastDot;
			}
			return -1;
		}

		static Path() {
			if (Environment.Platform == PlatformID.Unix) {
				AltDirectorySeparatorChar = '/';
				DirectorySeparatorChar = '/';
				PathSeparator = ';';
				VolumeSeparatorChar = '/';
			} else {
				AltDirectorySeparatorChar = '/';
				DirectorySeparatorChar = '\\';
				PathSeparator = ';';
				VolumeSeparatorChar = ':';
			}

			// this copy will be modifiable ("by design")
			InvalidPathChars = GetInvalidPathChars ();

			// internal fields
			DirectorySeparatorStr = DirectorySeparatorChar.ToString();
			PathSeparatorChars = new char[] {
				DirectorySeparatorChar,
				AltDirectorySeparatorChar,
				VolumeSeparatorChar
			};

			dirEqualsVolume = (DirectorySeparatorChar == VolumeSeparatorChar);
		}

		static bool SameRoot(string root, string path) {
			// compare root - if enough details are available
			if ((root.Length < 2) || (path.Length < 2))
				return false;
			// same volume/drive
			if (!root[0].Equals(path[0]))
				return false;
			// presence if the separator
			if (path[1] != Path.VolumeSeparatorChar)
				return false;
			if ((root.Length > 2) && (path.Length > 2)) {
				// but don't directory compare the directory separator
				return (IsDsc(root[2]) && IsDsc(path[2]));
			}
			return true;
		}

		static string CanonicalizePath(string path) {
			// STEP 1: Check for empty string
			if (path == null)
				return path;
			if (Environment.IsRunningOnWindows)
				path = path.Trim();

			if (path.Length == 0)
				return path;

			// STEP 2: Check to see if this is only a root
			string root = Path.GetPathRoot(path);
			// it will return '\' for path '\', while it should return 'c:\' or so.
			// Note: commenting this out makes the ened for the (target == 1...) check in step 5
			//if (root == path) return path;

			// STEP 3: split the directories, this gets rid of consecutative "/"'s
			string[] dirs = path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
			// STEP 4: Get rid of directories containing . and ..
			int target = 0;

			for (int i = 0; i < dirs.Length; i++) {
				if (dirs[i] == "." || (i != 0 && dirs[i].Length == 0))
					continue;
				else if (dirs[i] == "..") {
					if (target != 0)
						target--;
				} else
					dirs[target++] = dirs[i];
			}

			// STEP 5: Combine everything.
			if (target == 0 || (target == 1 && dirs[0] == ""))
				return root;
			else {
				string ret = String.Join(DirectorySeparatorStr, dirs, 0, target);
				if (Environment.IsRunningOnWindows) {
					if (!SameRoot(root, ret))
						ret = root + ret;
					// In GetFullPath(), it is assured that here never comes UNC. So this must only applied to such path that starts with '\', without drive specification.
					if (!IsDsc(path[0]) && SameRoot(root, path)) {
						if (ret.Length <= 2 && !ret.EndsWith(DirectorySeparatorStr)) // '\' after "c:"
							ret += Path.DirectorySeparatorChar;
						return ret;
					} else {
						string current = Directory.GetCurrentDirectory();
						if (current.Length > 1 && current[1] == Path.VolumeSeparatorChar) {
							// DOS local file path
							if (ret.Length == 0 || IsDsc(ret[0]))
								ret += '\\';
							return current.Substring(0, 2) + ret;
						} else if (IsDsc(current[current.Length - 1]) && IsDsc(ret[0]))
							return current + ret.Substring(1);
						else
							return current + ret;
					}
				}
				return ret;
			}
		}

		// required for FileIOPermission (and most proibably reusable elsewhere too)
		// both path MUST be "full paths"
		static internal bool IsPathSubsetOf(string subset, string path) {
			if (subset.Length > path.Length)
				return false;

			// check that everything up to the last separator match
			int slast = subset.LastIndexOfAny(PathSeparatorChars);
			if (String.Compare(subset, 0, path, 0, slast) != 0)
				return false;

			slast++;
			// then check if the last segment is identical
			int plast = path.IndexOfAny(PathSeparatorChars, slast);
			if (plast >= slast) {
				return String.Compare(subset, slast, path, slast, path.Length - plast) == 0;
			}
			if (subset.Length != path.Length)
				return false;

			return String.Compare(subset, slast, path, slast, subset.Length - slast) == 0;
		}

	}
}

#endif