summaryrefslogtreecommitdiff
path: root/src/mscorlib/src/System/IO/Path.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/mscorlib/src/System/IO/Path.cs')
-rw-r--r--src/mscorlib/src/System/IO/Path.cs1435
1 files changed, 1435 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/IO/Path.cs b/src/mscorlib/src/System/IO/Path.cs
new file mode 100644
index 0000000000..4f7993633b
--- /dev/null
+++ b/src/mscorlib/src/System/IO/Path.cs
@@ -0,0 +1,1435 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+/*============================================================
+**
+**
+**
+**
+**
+** Purpose: A collection of path manipulation methods.
+**
+**
+===========================================================*/
+
+using System;
+using System.Security.Permissions;
+using Win32Native = Microsoft.Win32.Win32Native;
+using System.Text;
+using System.Runtime.InteropServices;
+using System.Security;
+#if FEATURE_LEGACYSURFACE
+using System.Security.Cryptography;
+#endif
+using System.Runtime.CompilerServices;
+using System.Globalization;
+using System.Runtime.Versioning;
+using System.Diagnostics.Contracts;
+
+namespace System.IO {
+ // Provides methods for processing directory strings in an ideally
+ // cross-platform manner. Most of the methods don't do a complete
+ // full parsing (such as examining a UNC hostname), but they will
+ // handle most string operations.
+ [ComVisible(true)]
+ public static class Path
+ {
+ // Platform specific directory separator character. This is backslash
+ // ('\') on Windows and slash ('/') on Unix.
+ //
+#if !PLATFORM_UNIX
+ public static readonly char DirectorySeparatorChar = '\\';
+ internal const string DirectorySeparatorCharAsString = "\\";
+#else
+ public static readonly char DirectorySeparatorChar = '/';
+ internal const string DirectorySeparatorCharAsString = "/";
+#endif // !PLATFORM_UNIX
+
+ // Platform specific alternate directory separator character.
+ // There is only one directory separator char on Unix,
+ // so the same definition is used for both Unix and Windows.
+ public static readonly char AltDirectorySeparatorChar = '/';
+
+ // Platform specific volume separator character. This is colon (':')
+ // on Windows and MacOS, and slash ('/') on Unix. This is mostly
+ // useful for parsing paths like "c:\windows" or "MacVolume:System Folder".
+ //
+#if !PLATFORM_UNIX
+ public static readonly char VolumeSeparatorChar = ':';
+#else
+ public static readonly char VolumeSeparatorChar = '/';
+#endif // !PLATFORM_UNIX
+
+ // Platform specific invalid list of characters in a path.
+ // See the "Naming a File" MSDN conceptual docs for more details on
+ // what is valid in a file name (which is slightly different from what
+ // is legal in a path name).
+ // Note: This list is duplicated in CheckInvalidPathChars
+ [Obsolete("Please use GetInvalidPathChars or GetInvalidFileNameChars instead.")]
+#if !PLATFORM_UNIX
+ public static readonly char[] InvalidPathChars = { '\"', '<', '>', '|', '\0', (Char)1, (Char)2, (Char)3, (Char)4, (Char)5, (Char)6, (Char)7, (Char)8, (Char)9, (Char)10, (Char)11, (Char)12, (Char)13, (Char)14, (Char)15, (Char)16, (Char)17, (Char)18, (Char)19, (Char)20, (Char)21, (Char)22, (Char)23, (Char)24, (Char)25, (Char)26, (Char)27, (Char)28, (Char)29, (Char)30, (Char)31 };
+#else
+ public static readonly char[] InvalidPathChars = { '\0' };
+#endif // !PLATFORM_UNIX
+
+ // Trim trailing white spaces, tabs etc but don't be aggressive in removing everything that has UnicodeCategory of trailing space.
+ // String.WhitespaceChars will trim aggressively than what the underlying FS does (for ex, NTFS, FAT).
+ internal static readonly char[] TrimEndChars =
+ {
+ (char)0x09, // Horizontal tab
+ (char)0x0A, // Line feed
+ (char)0x0B, // Vertical tab
+ (char)0x0C, // Form feed
+ (char)0x0D, // Carriage return
+ (char)0x20, // Space
+ (char)0x85, // Next line
+ (char)0xA0 // Non breaking space
+ };
+
+#if !PLATFORM_UNIX
+ private static readonly char[] RealInvalidPathChars = PathInternal.InvalidPathChars;
+
+ private static readonly char[] InvalidFileNameChars = { '\"', '<', '>', '|', '\0', (Char)1, (Char)2, (Char)3, (Char)4, (Char)5, (Char)6, (Char)7, (Char)8, (Char)9, (Char)10, (Char)11, (Char)12, (Char)13, (Char)14, (Char)15, (Char)16, (Char)17, (Char)18, (Char)19, (Char)20, (Char)21, (Char)22, (Char)23, (Char)24, (Char)25, (Char)26, (Char)27, (Char)28, (Char)29, (Char)30, (Char)31, ':', '*', '?', '\\', '/' };
+#else
+ private static readonly char[] RealInvalidPathChars = { '\0' };
+
+ private static readonly char[] InvalidFileNameChars = { '\0', '/' };
+#endif // !PLATFORM_UNIX
+
+#if !PLATFORM_UNIX
+ public static readonly char PathSeparator = ';';
+#else
+ public static readonly char PathSeparator = ':';
+#endif // !PLATFORM_UNIX
+
+
+ // The max total path is 260, and the max individual component length is 255.
+ // For example, D:\<256 char file name> isn't legal, even though it's under 260 chars.
+ internal static readonly int MaxPath = PathInternal.MaxShortPath;
+
+ internal static readonly int MaxPathComponentLength = PathInternal.MaxComponentLength;
+
+ // Windows API definitions
+ internal const int MAX_PATH = 260; // From WinDef.h
+ internal const int MAX_DIRECTORY_PATH = 248; // cannot create directories greater than 248 characters
+
+ // Changes the extension of a file path. The path parameter
+ // specifies a file path, and the extension parameter
+ // specifies a file extension (with a leading period, such as
+ // ".exe" or ".cs").
+ //
+ // The function returns a file path with the same root, directory, and base
+ // name parts as path, but with the file extension changed to
+ // the specified extension. If path is null, the function
+ // returns null. If path does not contain a file extension,
+ // the new file extension is appended to the path. If extension
+ // is null, any exsiting extension is removed from path.
+ //
+ public static String ChangeExtension(String path, String extension) {
+ if (path != null) {
+ CheckInvalidPathChars(path);
+
+ String s = path;
+ for (int i = path.Length; --i >= 0;) {
+ char ch = path[i];
+ if (ch == '.') {
+ s = path.Substring(0, i);
+ break;
+ }
+ if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar) break;
+ }
+ if (extension != null && path.Length != 0) {
+ if (extension.Length == 0 || extension[0] != '.') {
+ s = s + ".";
+ }
+ s = s + extension;
+ }
+ return s;
+ }
+ return null;
+ }
+
+ // Returns the directory path of a file path. This method effectively
+ // removes the last element of the given file path, i.e. it returns a
+ // string consisting of all characters up to but not including the last
+ // backslash ("\") in the file path. The returned value is null if the file
+ // path is null or if the file path denotes a root (such as "\", "C:", or
+ // "\\server\share").
+ public static String GetDirectoryName(String path)
+ {
+ return GetDirectoryNameInternal(path);
+ }
+
+ [System.Security.SecuritySafeCritical]
+ private static string GetDirectoryNameInternal(string path)
+ {
+ if (path != null)
+ {
+ CheckInvalidPathChars(path);
+
+ // Expanding short paths is dangerous in this case as the results will change with the current directory.
+ //
+ // Suppose you have a path called "PICTUR~1\Foo". Now suppose you have two folders on disk "C:\Mine\Pictures Of Me"
+ // and "C:\Yours\Pictures of You". If the current directory is neither you'll get back "PICTUR~1". If it is "C:\Mine"
+ // get back "Pictures Of Me". "C:\Yours" would give back "Pictures of You".
+ //
+ // Because of this and as it isn't documented that short paths are expanded we will not expand short names unless
+ // we're in legacy mode.
+ string normalizedPath = NormalizePath(path, fullCheck: false, expandShortPaths:
+#if FEATURE_PATHCOMPAT
+ AppContextSwitches.UseLegacyPathHandling
+#else
+ false
+#endif
+ );
+
+ // If there are no permissions for PathDiscovery to this path, we should NOT expand the short paths
+ // as this would leak information about paths to which the user would not have access to.
+ if (path.Length > 0
+#if FEATURE_CAS_POLICY
+ // Only do the extra logic if we're not in full trust
+ && !CodeAccessSecurityEngine.QuickCheckForAllDemands()
+#endif
+ )
+ {
+ try
+ {
+ // If we were passed in a path with \\?\ we need to remove it as FileIOPermission does not like it.
+ string tempPath = RemoveLongPathPrefix(path);
+
+ // FileIOPermission cannot handle paths that contain ? or *
+ // So we only pass to FileIOPermission the text up to them.
+ int pos = 0;
+ while (pos < tempPath.Length && (tempPath[pos] != '?' && tempPath[pos] != '*'))
+ pos++;
+
+ // GetFullPath will Demand that we have the PathDiscovery FileIOPermission and thus throw
+ // SecurityException if we don't.
+ // While we don't use the result of this call we are using it as a consistent way of
+ // doing the security checks.
+ if (pos > 0)
+ GetFullPath(tempPath.Substring(0, pos));
+ }
+ catch (SecurityException)
+ {
+ // If the user did not have permissions to the path, make sure that we don't leak expanded short paths
+ // Only re-normalize if the original path had a ~ in it.
+ if (path.IndexOf("~", StringComparison.Ordinal) != -1)
+ {
+ normalizedPath = NormalizePath(path, fullCheck: false, expandShortPaths: false);
+ }
+ }
+ catch (PathTooLongException) { }
+ catch (NotSupportedException) { } // Security can throw this on "c:\foo:"
+ catch (IOException) { }
+ catch (ArgumentException) { } // The normalizePath with fullCheck will throw this for file: and http:
+ }
+
+ path = normalizedPath;
+
+ int root = GetRootLength(path);
+ int i = path.Length;
+ if (i > root)
+ {
+ i = path.Length;
+ if (i == root) return null;
+ while (i > root && path[--i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar);
+ return path.Substring(0, i);
+ }
+ }
+ return null;
+ }
+
+ // Gets the length of the root DirectoryInfo or whatever DirectoryInfo markers
+ // are specified for the first part of the DirectoryInfo name.
+ //
+ internal static int GetRootLength(string path)
+ {
+ CheckInvalidPathChars(path);
+
+#if !PLATFORM_UNIX && FEATURE_PATHCOMPAT
+ if (AppContextSwitches.UseLegacyPathHandling)
+ {
+ int i = 0;
+ int length = path.Length;
+
+ if (length >= 1 && (IsDirectorySeparator(path[0])))
+ {
+ // handles UNC names and directories off current drive's root.
+ i = 1;
+ if (length >= 2 && (IsDirectorySeparator(path[1])))
+ {
+ i = 2;
+ int n = 2;
+ while (i < length && ((path[i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar) || --n > 0)) i++;
+ }
+ }
+ else if (length >= 2 && path[1] == VolumeSeparatorChar)
+ {
+ // handles A:\foo.
+ i = 2;
+ if (length >= 3 && (IsDirectorySeparator(path[2]))) i++;
+ }
+ return i;
+ }
+ else
+#endif // !PLATFORM_UNIX && FEATURE_PATHCOMPAT
+ {
+ return PathInternal.GetRootLength(path);
+ }
+ }
+
+ internal static bool IsDirectorySeparator(char c) {
+ return (c==DirectorySeparatorChar || c == AltDirectorySeparatorChar);
+ }
+
+ public static char[] GetInvalidPathChars()
+ {
+ return (char[]) RealInvalidPathChars.Clone();
+ }
+
+ public static char[] GetInvalidFileNameChars()
+ {
+ return (char[]) InvalidFileNameChars.Clone();
+ }
+
+ // Returns the extension of the given path. The returned value includes the
+ // period (".") character of the extension except when you have a terminal period when you get String.Empty, such as ".exe" or
+ // ".cpp". The returned value is null if the given path is
+ // null or if the given path does not include an extension.
+ //
+ [Pure]
+ public static String GetExtension(String path) {
+ if (path==null)
+ return null;
+
+ CheckInvalidPathChars(path);
+ int length = path.Length;
+ for (int i = length; --i >= 0;) {
+ char ch = path[i];
+ if (ch == '.')
+ {
+ if (i != length - 1)
+ return path.Substring(i, length - i);
+ else
+ return String.Empty;
+ }
+ if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar)
+ break;
+ }
+ return String.Empty;
+ }
+
+ // Expands the given path to a fully qualified path. The resulting string
+ // consists of a drive letter, a colon, and a root relative path. This
+ // function does not verify that the resulting path
+ // refers to an existing file or directory on the associated volume.
+ [Pure]
+ [System.Security.SecuritySafeCritical]
+ public static String GetFullPath(String path) {
+ String fullPath = GetFullPathInternal(path);
+#if FEATURE_CORECLR
+ FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, path, fullPath);
+ state.EnsureState();
+#else
+ FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, fullPath, false, false);
+#endif
+ return fullPath;
+ }
+
+ [System.Security.SecurityCritical]
+ internal static String UnsafeGetFullPath(String path)
+ {
+ String fullPath = GetFullPathInternal(path);
+#if !FEATURE_CORECLR
+ FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, fullPath, false, false);
+#endif
+ return fullPath;
+ }
+
+ // This method is package access to let us quickly get a string name
+ // while avoiding a security check. This also serves a slightly
+ // different purpose - when we open a file, we need to resolve the
+ // path into a fully qualified, non-relative path name. This
+ // method does that, finding the current drive &; directory. But
+ // as long as we don't return this info to the user, we're good. However,
+ // the public GetFullPath does need to do a security check.
+ internal static string GetFullPathInternal(string path)
+ {
+ if (path == null)
+ throw new ArgumentNullException("path");
+ Contract.EndContractBlock();
+
+ string newPath = NormalizePath(path, fullCheck: true);
+ return newPath;
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal unsafe static string NormalizePath(string path, bool fullCheck)
+ {
+ return NormalizePath(path, fullCheck,
+#if FEATURE_PATHCOMPAT
+ AppContextSwitches.BlockLongPaths ? PathInternal.MaxShortPath :
+#endif
+ PathInternal.MaxLongPath);
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal unsafe static string NormalizePath(string path, bool fullCheck, bool expandShortPaths)
+ {
+ return NormalizePath(path, fullCheck,
+#if FEATURE_PATHCOMPAT
+ AppContextSwitches.BlockLongPaths ? PathInternal.MaxShortPath :
+#endif
+ PathInternal.MaxLongPath,
+ expandShortPaths);
+ }
+
+ [System.Security.SecuritySafeCritical] // auto-generated
+ internal static string NormalizePath(string path, bool fullCheck, int maxPathLength)
+ {
+ return NormalizePath(path, fullCheck, maxPathLength, expandShortPaths: true);
+ }
+
+ [System.Security.SecuritySafeCritical]
+ internal static string NormalizePath(string path, bool fullCheck, int maxPathLength, bool expandShortPaths)
+ {
+#if FEATURE_PATHCOMPAT
+ if (AppContextSwitches.UseLegacyPathHandling)
+ {
+ return LegacyNormalizePath(path, fullCheck, maxPathLength, expandShortPaths);
+ }
+ else
+#endif // FEATURE_APPCOMPAT
+ {
+ if (PathInternal.IsExtended(path))
+ {
+ // We can't really know what is valid for all cases of extended paths.
+ //
+ // - object names can include other characters as well (':', '/', etc.)
+ // - even file objects have different rules (pipe names can contain most characters)
+ //
+ // As such we will do no further analysis of extended paths to avoid blocking known and unknown
+ // scenarios as well as minimizing compat breaks should we block now and need to unblock later.
+ return path;
+ }
+
+ string normalizedPath = null;
+
+ if (fullCheck == false)
+ {
+ // Disabled fullCheck is only called by GetDirectoryName and GetPathRoot.
+ // Avoid adding addtional callers and try going direct to lighter weight NormalizeDirectorySeparators.
+ normalizedPath = NewNormalizePathLimitedChecks(path, maxPathLength, expandShortPaths);
+ }
+ else
+ {
+ normalizedPath = NewNormalizePath(path, maxPathLength, expandShortPaths: true);
+ }
+
+ if (string.IsNullOrWhiteSpace(normalizedPath))
+ throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
+ return normalizedPath;
+ }
+ }
+
+ [System.Security.SecuritySafeCritical]
+ private static string NewNormalizePathLimitedChecks(string path, int maxPathLength, bool expandShortPaths)
+ {
+ string normalized = PathInternal.NormalizeDirectorySeparators(path);
+
+ if (PathInternal.IsPathTooLong(normalized) || PathInternal.AreSegmentsTooLong(normalized))
+ throw new PathTooLongException();
+
+#if !PLATFORM_UNIX
+ if (!PathInternal.IsDevice(normalized) && PathInternal.HasInvalidVolumeSeparator(path))
+ throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
+
+ if (expandShortPaths && normalized.IndexOf('~') != -1)
+ {
+ try
+ {
+ return LongPathHelper.GetLongPathName(normalized);
+ }
+ catch
+ {
+ // Don't care if we can't get the long path- might not exist, etc.
+ }
+ }
+#endif
+
+ return normalized;
+ }
+
+ /// <summary>
+ /// Normalize the path and check for bad characters or other invalid syntax.
+ /// </summary>
+ [System.Security.SecuritySafeCritical]
+ [ResourceExposure(ResourceScope.Machine)]
+ [ResourceConsumption(ResourceScope.Machine)]
+ private static string NewNormalizePath(string path, int maxPathLength, bool expandShortPaths)
+ {
+ Contract.Requires(path != null, "path can't be null");
+
+ // Embedded null characters are the only invalid character case we want to check up front.
+ // This is because the nulls will signal the end of the string to Win32 and therefore have
+ // unpredictable results. Other invalid characters we give a chance to be normalized out.
+ if (path.IndexOf('\0') != -1)
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
+
+#if !PLATFORM_UNIX
+ // Note that colon and wildcard checks happen in FileIOPermissions
+
+ // Technically this doesn't matter but we used to throw for this case
+ if (string.IsNullOrWhiteSpace(path))
+ throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
+
+ // We don't want to check invalid characters for device format- see comments for extended above
+ return LongPathHelper.Normalize(path, (uint)maxPathLength, checkInvalidCharacters: !PathInternal.IsDevice(path), expandShortPaths: expandShortPaths);
+#else
+ if (path.Length == 0)
+ throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
+
+ // Expand with current directory if necessary
+ if (!IsPathRooted(path))
+ path = Combine(Directory.GetCurrentDirectory(), path);
+
+ // We would ideally use realpath to do this, but it resolves symlinks, requires that the file actually exist,
+ // and turns it into a full path, which we only want if fullCheck is true.
+ string collapsedString = PathInternal.RemoveRelativeSegments(path);
+
+ if (collapsedString.Length > maxPathLength)
+ throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
+
+ return collapsedString.Length == 0 ? "/" : collapsedString;
+#endif // PLATFORM_UNIX
+ }
+
+#if FEATURE_PATHCOMPAT
+ [System.Security.SecurityCritical] // auto-generated
+ internal unsafe static String LegacyNormalizePath(String path, bool fullCheck, int maxPathLength, bool expandShortPaths) {
+
+ Contract.Requires(path != null, "path can't be null");
+ // If we're doing a full path check, trim whitespace and look for
+ // illegal path characters.
+ if (fullCheck) {
+ // Trim whitespace off the end of the string.
+ // Win32 normalization trims only U+0020.
+ path = path.TrimEnd(TrimEndChars);
+
+ // Look for illegal path characters.
+ if (PathInternal.AnyPathHasIllegalCharacters(path))
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
+ }
+
+ int index = 0;
+ // We prefer to allocate on the stack for workingset/perf gain. If the
+ // starting path is less than MaxPath then we can stackalloc; otherwise we'll
+ // use a StringBuilder (PathHelper does this under the hood). The latter may
+ // happen in 2 cases:
+ // 1. Starting path is greater than MaxPath but it normalizes down to MaxPath.
+ // This is relevant for paths containing escape sequences. In this case, we
+ // attempt to normalize down to MaxPath, but the caller pays a perf penalty
+ // since StringBuilder is used.
+ // 2. IsolatedStorage, which supports paths longer than MaxPath (value given
+ // by maxPathLength.
+ PathHelper newBuffer;
+ if (path.Length + 1 <= MaxPath) {
+ char* m_arrayPtr = stackalloc char[MaxPath];
+ newBuffer = new PathHelper(m_arrayPtr, MaxPath);
+ } else {
+ newBuffer = new PathHelper(path.Length + Path.MaxPath, maxPathLength);
+ }
+
+ uint numSpaces = 0;
+ uint numDots = 0;
+ bool fixupDirectorySeparator = false;
+ // Number of significant chars other than potentially suppressible
+ // dots and spaces since the last directory or volume separator char
+ uint numSigChars = 0;
+ int lastSigChar = -1; // Index of last significant character.
+ // Whether this segment of the path (not the complete path) started
+ // with a volume separator char. Reject "c:...".
+ bool startedWithVolumeSeparator = false;
+ bool firstSegment = true;
+ int lastDirectorySeparatorPos = 0;
+
+#if !PLATFORM_UNIX
+ bool mightBeShortFileName = false;
+
+ // LEGACY: This code is here for backwards compatibility reasons. It
+ // ensures that \\foo.cs\bar.cs stays \\foo.cs\bar.cs instead of being
+ // turned into \foo.cs\bar.cs.
+ if (path.Length > 0 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar)) {
+ newBuffer.Append('\\');
+ index++;
+ lastSigChar = 0;
+ }
+#endif
+
+ // Normalize the string, stripping out redundant dots, spaces, and
+ // slashes.
+ while (index < path.Length) {
+ char currentChar = path[index];
+
+ // We handle both directory separators and dots specially. For
+ // directory separators, we consume consecutive appearances.
+ // For dots, we consume all dots beyond the second in
+ // succession. All other characters are added as is. In
+ // addition we consume all spaces after the last other char
+ // in a directory name up until the directory separator.
+
+ if (currentChar == DirectorySeparatorChar || currentChar == AltDirectorySeparatorChar) {
+ // If we have a path like "123.../foo", remove the trailing dots.
+ // However, if we found "c:\temp\..\bar" or "c:\temp\...\bar", don't.
+ // Also remove trailing spaces from both files & directory names.
+ // This was agreed on with the OS team to fix undeletable directory
+ // names ending in spaces.
+
+ // If we saw a '\' as the previous last significant character and
+ // are simply going to write out dots, suppress them.
+ // If we only contain dots and slashes though, only allow
+ // a string like [dot]+ [space]*. Ignore everything else.
+ // Legal: "\.. \", "\...\", "\. \"
+ // Illegal: "\.. .\", "\. .\", "\ .\"
+ if (numSigChars == 0) {
+ // Dot and space handling
+ if (numDots > 0) {
+ // Look for ".[space]*" or "..[space]*"
+ int start = lastSigChar + 1;
+ if (path[start] != '.')
+ throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
+
+ // Only allow "[dot]+[space]*", and normalize the
+ // legal ones to "." or ".."
+ if (numDots >= 2) {
+ // Reject "C:..."
+ if (startedWithVolumeSeparator && numDots > 2)
+
+ throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
+
+ if (path[start + 1] == '.') {
+ // Search for a space in the middle of the
+ // dots and throw
+ for(int i=start + 2; i < start + numDots; i++) {
+ if (path[i] != '.')
+ throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
+ }
+
+ numDots = 2;
+ }
+ else {
+ if (numDots > 1)
+ throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
+ numDots = 1;
+ }
+ }
+
+ if (numDots == 2) {
+ newBuffer.Append('.');
+ }
+
+ newBuffer.Append('.');
+ fixupDirectorySeparator = false;
+
+ // Continue in this case, potentially writing out '\'.
+ }
+
+ if (numSpaces > 0 && firstSegment) {
+ // Handle strings like " \\server\share".
+ if (index + 1 < path.Length &&
+ (path[index + 1] == DirectorySeparatorChar || path[index + 1] == AltDirectorySeparatorChar))
+ {
+ newBuffer.Append(DirectorySeparatorChar);
+ }
+ }
+ }
+ numDots = 0;
+ numSpaces = 0; // Suppress trailing spaces
+
+ if (!fixupDirectorySeparator) {
+ fixupDirectorySeparator = true;
+ newBuffer.Append(DirectorySeparatorChar);
+ }
+ numSigChars = 0;
+ lastSigChar = index;
+ startedWithVolumeSeparator = false;
+ firstSegment = false;
+
+#if !PLATFORM_UNIX
+ // For short file names, we must try to expand each of them as
+ // soon as possible. We need to allow people to specify a file
+ // name that doesn't exist using a path with short file names
+ // in it, such as this for a temp file we're trying to create:
+ // C:\DOCUME~1\USERNA~1.RED\LOCALS~1\Temp\bg3ylpzp
+ // We could try doing this afterwards piece by piece, but it's
+ // probably a lot simpler to do it here.
+ if (mightBeShortFileName) {
+ newBuffer.TryExpandShortFileName();
+ mightBeShortFileName = false;
+ }
+#endif
+ int thisPos = newBuffer.Length - 1;
+ if (thisPos - lastDirectorySeparatorPos > MaxPathComponentLength)
+ {
+ throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
+ }
+ lastDirectorySeparatorPos = thisPos;
+ } // if (Found directory separator)
+ else if (currentChar == '.') {
+ // Reduce only multiple .'s only after slash to 2 dots. For
+ // instance a...b is a valid file name.
+ numDots++;
+ // Don't flush out non-terminal spaces here, because they may in
+ // the end not be significant. Turn "c:\ . .\foo" -> "c:\foo"
+ // which is the conclusion of removing trailing dots & spaces,
+ // as well as folding multiple '\' characters.
+ }
+ else if (currentChar == ' ') {
+ numSpaces++;
+ }
+ else { // Normal character logic
+#if !PLATFORM_UNIX
+ if (currentChar == '~' && expandShortPaths)
+ mightBeShortFileName = true;
+#endif
+
+ fixupDirectorySeparator = false;
+
+#if !PLATFORM_UNIX
+ // To reject strings like "C:...\foo" and "C :\foo"
+ if (firstSegment && currentChar == VolumeSeparatorChar) {
+ // Only accept "C:", not "c :" or ":"
+ // Get a drive letter or ' ' if index is 0.
+ char driveLetter = (index > 0) ? path[index-1] : ' ';
+ bool validPath = ((numDots == 0) && (numSigChars >= 1) && (driveLetter != ' '));
+ if (!validPath)
+ throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
+
+ startedWithVolumeSeparator = true;
+ // We need special logic to make " c:" work, we should not fix paths like " foo::$DATA"
+ if (numSigChars > 1) { // Common case, simply do nothing
+ int spaceCount = 0; // How many spaces did we write out, numSpaces has already been reset.
+ while((spaceCount < newBuffer.Length) && newBuffer[spaceCount] == ' ')
+ spaceCount++;
+ if (numSigChars - spaceCount == 1) {
+ //Safe to update stack ptr directly
+ newBuffer.Length = 0;
+ newBuffer.Append(driveLetter); // Overwrite spaces, we need a special case to not break " foo" as a relative path.
+ }
+ }
+ numSigChars = 0;
+ }
+ else
+#endif // !PLATFORM_UNIX
+ {
+ numSigChars += 1 + numDots + numSpaces;
+ }
+
+ // Copy any spaces & dots since the last significant character
+ // to here. Note we only counted the number of dots & spaces,
+ // and don't know what order they're in. Hence the copy.
+ if (numDots > 0 || numSpaces > 0) {
+ int numCharsToCopy = (lastSigChar >= 0) ? index - lastSigChar - 1 : index;
+ if (numCharsToCopy > 0) {
+ for (int i=0; i<numCharsToCopy; i++) {
+ newBuffer.Append(path[lastSigChar + 1 + i]);
+ }
+ }
+ numDots = 0;
+ numSpaces = 0;
+ }
+
+ newBuffer.Append(currentChar);
+ lastSigChar = index;
+ }
+
+ index++;
+ } // end while
+
+ if (newBuffer.Length - 1 - lastDirectorySeparatorPos > MaxPathComponentLength)
+ {
+ throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
+ }
+
+ // Drop any trailing dots and spaces from file & directory names, EXCEPT
+ // we MUST make sure that "C:\foo\.." is correctly handled.
+ // Also handle "C:\foo\." -> "C:\foo", while "C:\." -> "C:\"
+ if (numSigChars == 0) {
+ if (numDots > 0) {
+ // Look for ".[space]*" or "..[space]*"
+ int start = lastSigChar + 1;
+ if (path[start] != '.')
+ throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
+
+ // Only allow "[dot]+[space]*", and normalize the
+ // legal ones to "." or ".."
+ if (numDots >= 2) {
+ // Reject "C:..."
+ if (startedWithVolumeSeparator && numDots > 2)
+ throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
+
+ if (path[start + 1] == '.') {
+ // Search for a space in the middle of the
+ // dots and throw
+ for(int i=start + 2; i < start + numDots; i++) {
+ if (path[i] != '.')
+ throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
+ }
+
+ numDots = 2;
+ }
+ else {
+ if (numDots > 1)
+ throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
+ numDots = 1;
+ }
+ }
+
+ if (numDots == 2) {
+ newBuffer.Append('.');
+ }
+
+ newBuffer.Append('.');
+ }
+ } // if (numSigChars == 0)
+
+ // If we ended up eating all the characters, bail out.
+ if (newBuffer.Length == 0)
+ throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
+
+ // Disallow URL's here. Some of our other Win32 API calls will reject
+ // them later, so we might be better off rejecting them here.
+ // Note we've probably turned them into "file:\D:\foo.tmp" by now.
+ // But for compatibility, ensure that callers that aren't doing a
+ // full check aren't rejected here.
+ if (fullCheck) {
+ if ( newBuffer.OrdinalStartsWith("http:", false) ||
+ newBuffer.OrdinalStartsWith("file:", false))
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_PathUriFormatNotSupported"));
+ }
+ }
+
+#if !PLATFORM_UNIX
+ // If the last part of the path (file or directory name) had a tilde,
+ // expand that too.
+ if (mightBeShortFileName) {
+ newBuffer.TryExpandShortFileName();
+ }
+#endif
+
+ // Call the Win32 API to do the final canonicalization step.
+ int result = 1;
+
+ if (fullCheck) {
+ // NOTE: Win32 GetFullPathName requires the input buffer to be big enough to fit the initial
+ // path which is a concat of CWD and the relative path, this can be of an arbitrary
+ // size and could be > MAX_PATH (which becomes an artificial limit at this point),
+ // even though the final normalized path after fixing up the relative path syntax
+ // might be well within the MAX_PATH restriction. For ex,
+ // "c:\SomeReallyLongDirName(thinkGreaterThan_MAXPATH)\..\foo.txt" which actually requires a
+ // buffer well with in the MAX_PATH as the normalized path is just "c:\foo.txt"
+ // This buffer requirement seems wrong, it could be a bug or a perf optimization
+ // like returning required buffer length quickly or avoid stratch buffer etc.
+ // Ideally we would get the required buffer length first by calling GetFullPathName
+ // once without the buffer and use that in the later call but this doesn't always work
+ // due to Win32 GetFullPathName bug. For instance, in Win2k, when the path we are trying to
+ // fully qualify is a single letter name (such as "a", "1", ",") GetFullPathName
+ // fails to return the right buffer size (i.e, resulting in insufficient buffer).
+ // To workaround this bug we will start with MAX_PATH buffer and grow it once if the
+ // return value is > MAX_PATH.
+
+ result = newBuffer.GetFullPathName();
+
+#if !PLATFORM_UNIX
+ // If we called GetFullPathName with something like "foo" and our
+ // command window was in short file name mode (ie, by running edlin or
+ // DOS versions of grep, etc), we might have gotten back a short file
+ // name. So, check to see if we need to expand it.
+ mightBeShortFileName = false;
+ for(int i=0; i < newBuffer.Length && !mightBeShortFileName; i++) {
+ if (newBuffer[i] == '~' && expandShortPaths)
+ mightBeShortFileName = true;
+ }
+
+ if (mightBeShortFileName) {
+ bool r = newBuffer.TryExpandShortFileName();
+ // Consider how the path "Doesn'tExist" would expand. If
+ // we add in the current directory, it too will need to be
+ // fully expanded, which doesn't happen if we use a file
+ // name that doesn't exist.
+ if (!r) {
+ int lastSlash = -1;
+
+ for (int i = newBuffer.Length - 1; i >= 0; i--) {
+ if (newBuffer[i] == DirectorySeparatorChar) {
+ lastSlash = i;
+ break;
+ }
+ }
+
+ if (lastSlash >= 0) {
+
+ // This bounds check is for safe memcpy but we should never get this far
+ if (newBuffer.Length >= maxPathLength)
+ throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
+
+ int lenSavedName = newBuffer.Length - lastSlash - 1;
+ Contract.Assert(lastSlash < newBuffer.Length, "path unexpectedly ended in a '\'");
+
+ newBuffer.Fixup(lenSavedName, lastSlash);
+ }
+ }
+ }
+#endif // PLATFORM_UNIX
+ }
+
+ if (result != 0) {
+ /* Throw an ArgumentException for paths like \\, \\server, \\server\
+ This check can only be properly done after normalizing, so
+ \\foo\.. will be properly rejected. Also, reject \\?\GLOBALROOT\
+ (an internal kernel path) because it provides aliases for drives. */
+ if (newBuffer.Length > 1 && newBuffer[0] == '\\' && newBuffer[1] == '\\') {
+ int startIndex = 2;
+ while (startIndex < result) {
+ if (newBuffer[startIndex] == '\\') {
+ startIndex++;
+ break;
+ }
+ else {
+ startIndex++;
+ }
+ }
+ if (startIndex == result)
+ throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC"));
+
+ // Check for \\?\Globalroot, an internal mechanism to the kernel
+ // that provides aliases for drives and other undocumented stuff.
+ // The kernel team won't even describe the full set of what
+ // is available here - we don't want managed apps mucking
+ // with this for security reasons.
+ if ( newBuffer.OrdinalStartsWith("\\\\?\\globalroot", true))
+ throw new ArgumentException(Environment.GetResourceString("Arg_PathGlobalRoot"));
+ }
+ }
+
+ // Check our result and form the managed string as necessary.
+ if (newBuffer.Length >= maxPathLength)
+ throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
+
+ if (result == 0) {
+ int errorCode = Marshal.GetLastWin32Error();
+ if (errorCode == 0)
+ errorCode = Win32Native.ERROR_BAD_PATHNAME;
+ __Error.WinIOError(errorCode, path);
+ return null; // Unreachable - silence a compiler error.
+ }
+
+ return newBuffer.ToStringOrExisting(path);
+ }
+#endif // FEATURE_PATHCOMPAT
+
+ internal const int MaxLongPath = PathInternal.MaxLongPath;
+
+ private const string LongPathPrefix = PathInternal.ExtendedPathPrefix;
+ private const string UNCPathPrefix = PathInternal.UncPathPrefix;
+ private const string UNCLongPathPrefixToInsert = PathInternal.UncExtendedPrefixToInsert;
+ private const string UNCLongPathPrefix = PathInternal.UncExtendedPathPrefix;
+
+ internal static bool HasLongPathPrefix(string path)
+ {
+#if FEATURE_PATHCOMPAT
+ if (AppContextSwitches.UseLegacyPathHandling)
+ return path.StartsWith(LongPathPrefix, StringComparison.Ordinal);
+ else
+#endif
+ return PathInternal.IsExtended(path);
+ }
+
+ internal static string AddLongPathPrefix(string path)
+ {
+#if FEATURE_PATHCOMPAT
+ if (AppContextSwitches.UseLegacyPathHandling)
+ {
+ if (path.StartsWith(LongPathPrefix, StringComparison.Ordinal))
+ return path;
+
+ if (path.StartsWith(UNCPathPrefix, StringComparison.Ordinal))
+ return path.Insert(2, UNCLongPathPrefixToInsert); // Given \\server\share in longpath becomes \\?\UNC\server\share => UNCLongPathPrefix + path.SubString(2); => The actual command simply reduces the operation cost.
+
+ return LongPathPrefix + path;
+ }
+ else
+#endif
+ {
+ return PathInternal.EnsureExtendedPrefix(path);
+ }
+ }
+
+ internal static string RemoveLongPathPrefix(string path)
+ {
+#if FEATURE_PATHCOMPAT
+ if (AppContextSwitches.UseLegacyPathHandling)
+ {
+ if (!path.StartsWith(LongPathPrefix, StringComparison.Ordinal))
+ return path;
+
+ if (path.StartsWith(UNCLongPathPrefix, StringComparison.OrdinalIgnoreCase))
+ return path.Remove(2, 6); // Given \\?\UNC\server\share we return \\server\share => @'\\' + path.SubString(UNCLongPathPrefix.Length) => The actual command simply reduces the operation cost.
+
+ return path.Substring(4);
+ }
+ else
+#endif
+ {
+ return PathInternal.RemoveExtendedPrefix(path);
+ }
+ }
+
+ internal static StringBuilder RemoveLongPathPrefix(StringBuilder pathSB)
+ {
+#if FEATURE_PATHCOMPAT
+ if (AppContextSwitches.UseLegacyPathHandling)
+ {
+ if (!PathInternal.StartsWithOrdinal(pathSB, LongPathPrefix))
+ return pathSB;
+
+ // Given \\?\UNC\server\share we return \\server\share => @'\\' + path.SubString(UNCLongPathPrefix.Length) => The actual command simply reduces the operation cost.
+ if (PathInternal.StartsWithOrdinal(pathSB, UNCLongPathPrefix, ignoreCase: true))
+ return pathSB.Remove(2, 6);
+
+ return pathSB.Remove(0, 4);
+ }
+ else
+#endif
+ {
+ return PathInternal.RemoveExtendedPrefix(pathSB);
+ }
+ }
+
+
+ // Returns the name and extension parts of the given path. The resulting
+ // string contains the characters of path that follow the last
+ // backslash ("\"), slash ("/"), or colon (":") character in
+ // path. The resulting string is the entire path if path
+ // contains no backslash after removing trailing slashes, slash, or colon characters. The resulting
+ // string is null if path is null.
+ //
+ [Pure]
+ public static String GetFileName(String path) {
+ if (path != null) {
+ CheckInvalidPathChars(path);
+
+ int length = path.Length;
+ for (int i = length; --i >= 0;) {
+ char ch = path[i];
+ if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar)
+ return path.Substring(i + 1, length - i - 1);
+
+ }
+ }
+ return path;
+ }
+
+ [Pure]
+ public static String GetFileNameWithoutExtension(String path) {
+ path = GetFileName(path);
+ if (path != null)
+ {
+ int i;
+ if ((i=path.LastIndexOf('.')) == -1)
+ return path; // No path extension found
+ else
+ return path.Substring(0,i);
+ }
+ return null;
+ }
+
+
+
+ // Returns the root portion of the given path. The resulting string
+ // consists of those rightmost characters of the path that constitute the
+ // root of the path. Possible patterns for the resulting string are: An
+ // empty string (a relative path on the current drive), "\" (an absolute
+ // path on the current drive), "X:" (a relative path on a given drive,
+ // where X is the drive letter), "X:\" (an absolute path on a given drive),
+ // and "\\server\share" (a UNC path for a given server and share name).
+ // The resulting string is null if path is null.
+ //
+ [Pure]
+ public static String GetPathRoot(String path) {
+ if (path == null) return null;
+
+ // Expanding short paths has no impact on the path root- there is no such thing as an
+ // 8.3 volume or server/share name.
+ path = NormalizePath(path, fullCheck: false, expandShortPaths: false);
+ return path.Substring(0, GetRootLength(path));
+ }
+
+ [System.Security.SecuritySafeCritical]
+ public static String GetTempPath()
+ {
+#if !FEATURE_CORECLR
+ new EnvironmentPermission(PermissionState.Unrestricted).Demand();
+#endif
+ StringBuilder sb = new StringBuilder(PathInternal.MaxShortPath);
+ uint r = Win32Native.GetTempPath(PathInternal.MaxShortPath, sb);
+ String path = sb.ToString();
+ if (r==0) __Error.WinIOError();
+ path = GetFullPathInternal(path);
+#if FEATURE_CORECLR
+ FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Write, String.Empty, path);
+ state.EnsureState();
+#endif
+ return path;
+ }
+
+ internal static bool IsRelative(string path)
+ {
+ Contract.Assert(path != null, "path can't be null");
+ return PathInternal.IsPartiallyQualified(path);
+ }
+
+ // Returns a cryptographically strong random 8.3 string that can be
+ // used as either a folder name or a file name.
+#if FEATURE_CORECLR
+ [System.Security.SecuritySafeCritical]
+#endif
+ public static String GetRandomFileName()
+ {
+ // 5 bytes == 40 bits == 40/5 == 8 chars in our encoding
+ // This gives us exactly 8 chars. We want to avoid the 8.3 short name issue
+ byte[] key = new byte[10];
+
+#if FEATURE_CORECLR
+ Win32Native.Random(true, key, key.Length);
+#else
+ // RNGCryptoServiceProvider is disposable in post-Orcas desktop mscorlibs, but not in CoreCLR's
+ // mscorlib, so we need to do a manual using block for it.
+ RNGCryptoServiceProvider rng = null;
+ try
+ {
+ rng = new RNGCryptoServiceProvider();
+
+ rng.GetBytes(key);
+ }
+ finally
+ {
+ if (rng != null)
+ {
+ rng.Dispose();
+ }
+ }
+#endif
+
+ // rndCharArray is expected to be 16 chars
+ char[] rndCharArray = Path.ToBase32StringSuitableForDirName(key).ToCharArray();
+ rndCharArray[8] = '.';
+ return new String(rndCharArray, 0, 12);
+ }
+
+ // Returns a unique temporary file name, and creates a 0-byte file by that
+ // name on disk.
+ [System.Security.SecuritySafeCritical]
+ public static String GetTempFileName()
+ {
+ return InternalGetTempFileName(true);
+ }
+
+ [System.Security.SecurityCritical]
+ internal static String UnsafeGetTempFileName()
+ {
+ return InternalGetTempFileName(false);
+ }
+
+ [System.Security.SecurityCritical]
+ private static String InternalGetTempFileName(bool checkHost)
+ {
+ String path = GetTempPath();
+
+ // Since this can write to the temp directory and theoretically
+ // cause a denial of service attack, demand FileIOPermission to
+ // that directory.
+
+#if FEATURE_CORECLR
+ if (checkHost)
+ {
+ FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Write, String.Empty, path);
+ state.EnsureState();
+ }
+#else
+ FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, path);
+#endif
+ StringBuilder sb = new StringBuilder(MaxPath);
+ uint r = Win32Native.GetTempFileName(path, "tmp", 0, sb);
+ if (r==0) __Error.WinIOError();
+ return sb.ToString();
+ }
+
+ // Tests if a path includes a file extension. The result is
+ // true if the characters that follow the last directory
+ // separator ('\\' or '/') or volume separator (':') in the path include
+ // a period (".") other than a terminal period. The result is false otherwise.
+ //
+ [Pure]
+ public static bool HasExtension(String path) {
+ if (path != null) {
+ CheckInvalidPathChars(path);
+
+ for (int i = path.Length; --i >= 0;) {
+ char ch = path[i];
+ if (ch == '.') {
+ if ( i != path.Length - 1)
+ return true;
+ else
+ return false;
+ }
+ if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar) break;
+ }
+ }
+ return false;
+ }
+
+ // Tests if the given path contains a root. A path is considered rooted
+ // if it starts with a backslash ("\") or a drive letter and a colon (":").
+ //
+ [Pure]
+ public static bool IsPathRooted(String path) {
+ if (path != null) {
+ CheckInvalidPathChars(path);
+
+ int length = path.Length;
+#if !PLATFORM_UNIX
+ if ((length >= 1 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar)) || (length >= 2 && path[1] == VolumeSeparatorChar))
+ return true;
+#else
+ if (length >= 1 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar))
+ return true;
+#endif
+ }
+ return false;
+ }
+
+ public static String Combine(String path1, String path2) {
+ if (path1==null || path2==null)
+ throw new ArgumentNullException((path1==null) ? "path1" : "path2");
+ Contract.EndContractBlock();
+ CheckInvalidPathChars(path1);
+ CheckInvalidPathChars(path2);
+
+ return CombineNoChecks(path1, path2);
+ }
+
+ public static String Combine(String path1, String path2, String path3) {
+ if (path1 == null || path2 == null || path3 == null)
+ throw new ArgumentNullException((path1 == null) ? "path1" : (path2 == null) ? "path2" : "path3");
+ Contract.EndContractBlock();
+ CheckInvalidPathChars(path1);
+ CheckInvalidPathChars(path2);
+ CheckInvalidPathChars(path3);
+
+ return CombineNoChecks(CombineNoChecks(path1, path2), path3);
+ }
+
+ public static String Combine(String path1, String path2, String path3, String path4) {
+ if (path1 == null || path2 == null || path3 == null || path4 == null)
+ throw new ArgumentNullException((path1 == null) ? "path1" : (path2 == null) ? "path2" : (path3 == null) ? "path3" : "path4");
+ Contract.EndContractBlock();
+ CheckInvalidPathChars(path1);
+ CheckInvalidPathChars(path2);
+ CheckInvalidPathChars(path3);
+ CheckInvalidPathChars(path4);
+
+ return CombineNoChecks(CombineNoChecks(CombineNoChecks(path1, path2), path3), path4);
+ }
+
+ public static String Combine(params String[] paths) {
+ if (paths == null) {
+ throw new ArgumentNullException("paths");
+ }
+ Contract.EndContractBlock();
+
+ int finalSize = 0;
+ int firstComponent = 0;
+
+ // We have two passes, the first calcuates how large a buffer to allocate and does some precondition
+ // checks on the paths passed in. The second actually does the combination.
+
+ for (int i = 0; i < paths.Length; i++) {
+ if (paths[i] == null) {
+ throw new ArgumentNullException("paths");
+ }
+
+ if (paths[i].Length == 0) {
+ continue;
+ }
+
+ CheckInvalidPathChars(paths[i]);
+
+ if (Path.IsPathRooted(paths[i])) {
+ firstComponent = i;
+ finalSize = paths[i].Length;
+ } else {
+ finalSize += paths[i].Length;
+ }
+
+ char ch = paths[i][paths[i].Length - 1];
+ if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar)
+ finalSize++;
+ }
+
+ StringBuilder finalPath = StringBuilderCache.Acquire(finalSize);
+
+ for (int i = firstComponent; i < paths.Length; i++) {
+ if (paths[i].Length == 0) {
+ continue;
+ }
+
+ if (finalPath.Length == 0) {
+ finalPath.Append(paths[i]);
+ } else {
+ char ch = finalPath[finalPath.Length - 1];
+ if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar) {
+ finalPath.Append(DirectorySeparatorChar);
+ }
+
+ finalPath.Append(paths[i]);
+ }
+ }
+
+ return StringBuilderCache.GetStringAndRelease(finalPath);
+ }
+
+ private static String CombineNoChecks(String path1, String path2) {
+ if (path2.Length == 0)
+ return path1;
+
+ if (path1.Length == 0)
+ return path2;
+
+ if (IsPathRooted(path2))
+ return path2;
+
+ char ch = path1[path1.Length - 1];
+ if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar)
+ return path1 + DirectorySeparatorCharAsString + path2;
+ return path1 + path2;
+ }
+
+ private static readonly Char[] s_Base32Char = {
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
+ 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
+ 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+ 'y', 'z', '0', '1', '2', '3', '4', '5'};
+
+ internal static String ToBase32StringSuitableForDirName(byte[] buff)
+ {
+ // This routine is optimised to be used with buffs of length 20
+ Contract.Assert(((buff.Length % 5) == 0), "Unexpected hash length");
+
+ StringBuilder sb = StringBuilderCache.Acquire();
+ byte b0, b1, b2, b3, b4;
+ int l, i;
+
+ l = buff.Length;
+ i = 0;
+
+ // Create l chars using the last 5 bits of each byte.
+ // Consume 3 MSB bits 5 bytes at a time.
+
+ do
+ {
+ b0 = (i < l) ? buff[i++] : (byte)0;
+ b1 = (i < l) ? buff[i++] : (byte)0;
+ b2 = (i < l) ? buff[i++] : (byte)0;
+ b3 = (i < l) ? buff[i++] : (byte)0;
+ b4 = (i < l) ? buff[i++] : (byte)0;
+
+ // Consume the 5 Least significant bits of each byte
+ sb.Append(s_Base32Char[b0 & 0x1F]);
+ sb.Append(s_Base32Char[b1 & 0x1F]);
+ sb.Append(s_Base32Char[b2 & 0x1F]);
+ sb.Append(s_Base32Char[b3 & 0x1F]);
+ sb.Append(s_Base32Char[b4 & 0x1F]);
+
+ // Consume 3 MSB of b0, b1, MSB bits 6, 7 of b3, b4
+ sb.Append(s_Base32Char[(
+ ((b0 & 0xE0) >> 5) |
+ ((b3 & 0x60) >> 2))]);
+
+ sb.Append(s_Base32Char[(
+ ((b1 & 0xE0) >> 5) |
+ ((b4 & 0x60) >> 2))]);
+
+ // Consume 3 MSB bits of b2, 1 MSB bit of b3, b4
+
+ b2 >>= 5;
+
+ Contract.Assert(((b2 & 0xF8) == 0), "Unexpected set bits");
+
+ if ((b3 & 0x80) != 0)
+ b2 |= 0x08;
+ if ((b4 & 0x80) != 0)
+ b2 |= 0x10;
+
+ sb.Append(s_Base32Char[b2]);
+
+ } while (i < l);
+
+ return StringBuilderCache.GetStringAndRelease(sb);
+ }
+
+ // ".." can only be used if it is specified as a part of a valid File/Directory name. We disallow
+ // the user being able to use it to move up directories. Here are some examples eg
+ // Valid: a..b abc..d
+ // Invalid: ..ab ab.. .. abc..d\abc..
+ //
+ internal static void CheckSearchPattern(String searchPattern)
+ {
+ int index;
+ while ((index = searchPattern.IndexOf("..", StringComparison.Ordinal)) != -1) {
+
+ if (index + 2 == searchPattern.Length) // Terminal ".." . Files names cannot end in ".."
+ throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern"));
+
+ if ((searchPattern[index+2] == DirectorySeparatorChar)
+ || (searchPattern[index+2] == AltDirectorySeparatorChar))
+ throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern"));
+
+ searchPattern = searchPattern.Substring(index + 2);
+ }
+
+ }
+
+ internal static void CheckInvalidPathChars(String path, bool checkAdditional = false)
+ {
+ if (path == null)
+ throw new ArgumentNullException("path");
+
+ if (PathInternal.HasIllegalCharacters(path, checkAdditional))
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
+ }
+
+ internal static String InternalCombine(String path1, String path2) {
+ if (path1==null || path2==null)
+ throw new ArgumentNullException((path1==null) ? "path1" : "path2");
+ Contract.EndContractBlock();
+ CheckInvalidPathChars(path1);
+ CheckInvalidPathChars(path2);
+
+ if (path2.Length == 0)
+ throw new ArgumentException(Environment.GetResourceString("Argument_PathEmpty"), "path2");
+ if (IsPathRooted(path2))
+ throw new ArgumentException(Environment.GetResourceString("Arg_Path2IsRooted"), "path2");
+ int i = path1.Length;
+ if (i == 0) return path2;
+ char ch = path1[i - 1];
+ if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar)
+ return path1 + DirectorySeparatorCharAsString + path2;
+ return path1 + path2;
+ }
+
+ }
+}