Allows long paths in mscorlib.
Fixes wraps for FEATURE_PATHCOMPAT and enables for desktop builds.
Add feature FEATURE_IMPLICIT_LONGPATH for implicit long path support (adding \\?\).
Implicit support added to Path.GetFullPath, which allows execution over MAX_PATH.
Without this change corerun would fail here:
HOSTLOG: AppDomainCompatSwitch=UseLatestBehaviorWhenTFMNotSpecified
HOSTLOG: APP_LOCAL_WINMETADATA=
HOSTLOG: Failed call to CreateAppDomainWithManager. ERRORCODE: 0x800700ce
HOSTLOG: Execution failed
<FeatureCoreFxGlobalization>true</FeatureCoreFxGlobalization>
</PropertyGroup>
+
+ <PropertyGroup Condition="'$(TargetsUnix)' != 'true'">
+ <FeatureImplicitLongPath>true</FeatureImplicitLongPath>
+ </PropertyGroup>
</Project>
<CDefines Condition="'$(FeatureAppdomainmanagerInitoptions)' == 'true'">$(CDefines);FEATURE_APPDOMAINMANAGER_INITOPTIONS</CDefines>
<CDefines Condition="'$(FeatureAppX)' == 'true'">$(CDefines);FEATURE_APPX</CDefines>
<CDefines Condition="'$(FeatureAppXBinder)' == 'true'">$(CDefines);FEATURE_APPX_BINDER</CDefines>
- <CDefines Condition="'$(FeatureAptca)' == 'true'">$(CDefines);FEATURE_APTCA</CDefines>
+ <CDefines Condition="'$(FeatureAptca)' == 'true'">$(CDefines);FEATURE_APTCA</CDefines>
<CDefines Condition="'$(FeatureArrayStubAsIL)' == 'true'">$(CDefines);FEATURE_ARRAYSTUB_AS_IL</CDefines>
<CDefines Condition="'$(FeatureStubsAsIL)' == 'true'">$(CDefines);FEATURE_STUBS_AS_IL</CDefines>
<CDefines Condition="'$(FeatureBclFormatting)' == 'true'">$(CDefines);FEATURE_BCL_FORMATTING</CDefines>
<DefineConstants Condition="'$(FeatureIdentityReference)' == 'true'">$(DefineConstants);FEATURE_IDENTITY_REFERENCE</DefineConstants>
<DefineConstants Condition="'$(FeatureImpersonation)' == 'true'">$(DefineConstants);FEATURE_IMPERSONATION</DefineConstants>
<DefineConstants Condition="'$(FeatureIncludeAllInterfaces)' == 'true'">$(DefineConstants);FEATURE_INCLUDE_ALL_INTERFACES</DefineConstants>
+ <DefineConstants Condition="'$(FeatureImplicitLongPath)' == 'true'">$(DefineConstants);FEATURE_IMPLICIT_LONGPATH</DefineConstants>
<DefineConstants Condition="'$(FeatureIsolatedStorageQuotaEnforcement)' == 'true'">$(DefineConstants);FEATURE_ISOLATED_STORAGE_QUOTA_ENFORCEMENT</DefineConstants>
<DefineConstants Condition="'$(FeatureIsostore)' == 'true'">$(DefineConstants);FEATURE_ISOSTORE</DefineConstants>
<DefineConstants Condition="'$(FeatureIsostoreLight)' == 'true'">$(DefineConstants);FEATURE_ISOSTORE_LIGHT</DefineConstants>
<DefineConstants Condition="'$(FeatureNongenericCollections)' == 'true'">$(DefineConstants);FEATURE_NONGENERIC_COLLECTIONS</DefineConstants>
<DefineConstants Condition="'$(FeatureNormIdnaOnly)' == 'true'">$(DefineConstants);FEATURE_NORM_IDNA_ONLY</DefineConstants>
<DefineConstants Condition="'$(FeaturePal)' == 'true'">$(DefineConstants);FEATURE_PAL</DefineConstants>
+ <DefineConstants Condition="'$(FeaturePathCompat)' == 'true'">$(DefineConstants);FEATURE_PATHCOMPAT</DefineConstants>
<DefineConstants Condition="'$(FeatureXplatEventSource)' == 'true'">$(DefineConstants);FEATURE_EVENTSOURCE_XPLAT</DefineConstants>
<DefineConstants Condition="'$(FeaturePerfmon)' == 'true'">$(DefineConstants);FEATURE_PERFMON</DefineConstants>
<DefineConstants Condition="'$(FeaturePls)' == 'true'">$(DefineConstants);FEATURE_PLS</DefineConstants>
<FeatureMultiModuleAssemblies>true</FeatureMultiModuleAssemblies>
<FeatureNativeImageGeneration>true</FeatureNativeImageGeneration>
<FeatureNongenericCollections>true</FeatureNongenericCollections>
+ <FeaturePathCompat>true</FeaturePathCompat>
<FeaturePerfmon>true</FeaturePerfmon>
<FeaturePls>true</FeaturePls>
<FeaturePrejit>true</FeaturePrejit>
<IoSources Include="$(BclSourcesRoot)\System\IO\MemoryStream.cs" />
<IoSources Include="$(BclSourcesRoot)\System\IO\Path.cs" />
<IoSources Include="$(BclSourcesRoot)\System\IO\PathHelper.cs" />
- <IoSources Include="$(BclSourcesRoot)\System\IO\LongPathHelper.cs" />
+ <IoSources Condition="'$(TargetsUnix)' != 'true'" Include="$(BclSourcesRoot)\System\IO\LongPathHelper.cs" />
<IoSources Include="$(BclSourcesRoot)\System\IO\PathInternal.cs" />
<IoSources Include="$(BclSourcesRoot)\System\IO\PathTooLongException.cs" />
<IoSources Include="$(BclSourcesRoot)\System\IO\PinnedBufferMemoryStream.cs" />
int nBufferLength,
[Out]StringBuilder lpBuffer);
+ [DllImport(KERNEL32, SetLastError = true, ExactSpelling = true)]
+ internal static extern uint GetCurrentDirectoryW(uint nBufferLength, SafeHandle lpBuffer);
+
[DllImport(KERNEL32, SetLastError=true, CharSet=CharSet.Auto, BestFitMapping=false)]
internal static extern bool GetFileAttributesEx(String name, int fileInfoLevel, ref WIN32_FILE_ATTRIBUTE_DATA lpFileInformation);
}
}
+#if FEATURE_PATHCOMPAT
+ private static int _useLegacyPathHandling;
+
+ /// <summary>
+ /// Use legacy path normalization logic and blocking of extended syntax.
+ /// </summary>
+ public static bool UseLegacyPathHandling
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ return GetCachedSwitchValue(AppContextDefaultValues.SwitchUseLegacyPathHandling, ref _useLegacyPathHandling);
+ }
+ }
+
+ private static int _blockLongPaths;
+
+ /// <summary>
+ /// Throw PathTooLongException for paths greater than MAX_PATH or directories greater than 248 (as per CreateDirectory Win32 limitations)
+ /// </summary>
+ public static bool BlockLongPaths
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ return GetCachedSwitchValue(AppContextDefaultValues.SwitchBlockLongPaths, ref _blockLongPaths);
+ }
+ }
+#endif // FEATURE_PATHCOMPAT
+
//
// Implementation details
//
[SecuritySafeCritical]
internal static string NormalizePath(string path, bool fullCheck)
{
- return Path.NormalizePath(
+#if FEATURE_PATHCOMPAT
+ // Appcontext switches can't currently be safely hit during AppDomain bringup
+ return Path.LegacyNormalizePath(
path: path,
fullCheck: fullCheck,
maxPathLength: PathInternal.MaxShortPath,
expandShortPaths: true);
+#else
+ return Path.NormalizePath(
+ path: path,
+ fullCheck: fullCheck,
+ expandShortPaths: true);
+#endif
}
#if FEATURE_APTCA
#endif
- // This routine is called from unmanaged code to
- // set the default fusion context.
- [System.Security.SecurityCritical] // auto-generated
+ // This routine is called from unmanaged code to
+ // set the default fusion context.
+ [System.Security.SecurityCritical] // auto-generated
private void SetupDomain(bool allowRedirects, String path, String configFile, String[] propertyNames, String[] propertyValues)
{
// It is possible that we could have multiple threads initializing
// If we add very long file name support ("\\?\") to the Path class then this is unnecesary,
// but we do not plan on doing this for now.
+
+ // Long path checks can be quirked, and as loading default quirks too early in the setup of an AppDomain is risky
+ // we'll avoid checking path lengths- we'll still fail at MAX_PATH later if we're !useAppBase when we call Path's
+ // NormalizePath.
if (!useAppBase)
- path = System.Security.Util.URLString.PreProcessForExtendedPathRemoval(path, false);
+ path = Security.Util.URLString.PreProcessForExtendedPathRemoval(
+ checkPathLength: false,
+ url: path,
+ isFileUrl: false);
+
int len = path.Length;
if (len == 0)
int count = stackDir.Count;
- if (stackDir.Count != 0)
+ if (stackDir.Count != 0
+#if FEATURE_CAS_POLICY
+ // All demands in full trust domains are no-ops, so skip
+ //
+ // The full path went through validity checks by being passed through FileIOPermissions already.
+ // As a sub string of the full path can't fail the checks if the full path passes.
+ && !CodeAccessSecurityEngine.QuickCheckForAllDemands()
+#endif
+ )
{
- String [] securityList = new String[stackDir.Count];
+ String[] securityList = new String[stackDir.Count];
stackDir.CopyTo(securityList, 0);
for (int j = 0 ; j < securityList.Length; j++)
securityList[j] += "\\."; // leaf will never have a slash at the end
// Security check for all directories not present only.
#if FEATURE_MACL
AccessControlActions control = (dirSecurity == null) ? AccessControlActions.None : AccessControlActions.Change;
- new FileIOPermission(FileIOPermissionAccess.Write, control, securityList, false, false ).Demand();
+ FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, control, securityList, false, false);
#else
#if FEATURE_CORECLR
if (checkHost)
}
}
#else
- new FileIOPermission(FileIOPermissionAccess.Write, securityList, false, false ).Demand();
+ FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, securityList, false, false);
#endif
#endif //FEATURE_MACL
}
while (stackDir.Count > 0) {
String name = stackDir[stackDir.Count - 1];
stackDir.RemoveAt(stackDir.Count - 1);
- if (name.Length >= Path.MAX_DIRECTORY_PATH)
+ if (PathInternal.IsDirectoryTooLong(name))
throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
r = Win32Native.CreateDirectory(name, secAttrs);
if (!r && (firstError == 0)) {
state.EnsureState();
}
#else
- new FileIOPermission(FileIOPermissionAccess.PathDiscovery, GetDemandDir(name, true)).Demand();
+ FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, GetDemandDir(name, true));
#endif // FEATURE_CORECLR
errorString = name;
}
return InternalGetCurrentDirectory(false);
}
+ [System.Security.SecuritySafeCritical]
+ private static string InternalGetCurrentDirectory(bool checkHost)
+ {
+ string currentDirectory = (
+#if FEATURE_PATHCOMPAT
+ AppContextSwitches.UseLegacyPathHandling ? LegacyGetCurrentDirectory() :
+#endif
+ NewGetCurrentDirectory());
+
+ string demandPath = GetDemandDir(currentDirectory, true);
+
+#if FEATURE_CORECLR
+ if (checkHost)
+ {
+ FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandPath);
+ state.EnsureState();
+ }
+#else
+ FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, demandPath, false, false);
+#endif
+ return currentDirectory;
+ }
+
+#if FEATURE_PATHCOMPAT
[System.Security.SecurityCritical]
- private static String InternalGetCurrentDirectory(bool checkHost)
+ private static String LegacyGetCurrentDirectory()
{
StringBuilder sb = StringBuilderCache.Acquire(Path.MaxPath + 1);
if (Win32Native.GetCurrentDirectory(sb.Capacity, sb) == 0)
// this will return a short file name.
if (currentDirectory.IndexOf('~') >= 0) {
int r = Win32Native.GetLongPathName(currentDirectory, sb, sb.Capacity);
- if (r == 0 || r >= Path.MaxPath) {
+ if (r == 0 || r >= Path.MaxPath) {
int errorCode = Marshal.GetLastWin32Error();
if (r >= Path.MaxPath)
errorCode = Win32Native.ERROR_FILENAME_EXCED_RANGE;
StringBuilderCache.Release(sb);
String demandPath = GetDemandDir(currentDirectory, true);
-
-#if FEATURE_CORECLR
- if (checkHost)
- {
- FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandPath);
- state.EnsureState();
- }
-#else
- new FileIOPermission( FileIOPermissionAccess.PathDiscovery, new String[] { demandPath }, false, false ).Demand();
-#endif
return currentDirectory;
}
+#endif // FEATURE_PATHCOMPAT
+
+ [System.Security.SecurityCritical]
+ private static string NewGetCurrentDirectory()
+ {
+ using (StringBuffer buffer = new StringBuffer(PathInternal.MaxShortPath))
+ {
+ uint result = 0;
+ while ((result = Win32Native.GetCurrentDirectoryW(buffer.CharCapacity, buffer.GetHandle())) > buffer.CharCapacity)
+ {
+ // Reported size is greater than the buffer size. Increase the capacity.
+ // The size returned includes the null only if more space is needed (this case).
+ buffer.EnsureCharCapacity(result);
+ }
+
+ if (result == 0)
+ __Error.WinIOError();
+ buffer.Length = result;
+
+#if !PLATFORM_UNIX
+ if (buffer.Contains('~'))
+ return LongPathHelper.GetLongPathName(buffer);
+#endif
+
+ return buffer.ToString();
+ }
+ }
#if FEATURE_CORECLR
[System.Security.SecurityCritical] // auto-generated
[System.Security.SecuritySafeCritical]
#endif
public static void SetCurrentDirectory(String path)
- {
+ {
if (path==null)
throw new ArgumentNullException("value");
if (path.Length==0)
String fullsourceDirName = Path.GetFullPathInternal(sourceDirName);
String sourcePath = GetDemandDir(fullsourceDirName, false);
-
- if (sourcePath.Length >= Path.MAX_DIRECTORY_PATH)
+
+ if (PathInternal.IsDirectoryTooLong(sourcePath))
throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
String fulldestDirName = Path.GetFullPathInternal(destDirName);
String destPath = GetDemandDir(fulldestDirName, false);
- if (destPath.Length >= Path.MAX_DIRECTORY_PATH)
+ if (PathInternal.IsDirectoryTooLong(destPath))
throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
-
+
#if FEATURE_CORECLR
if (checkHost) {
FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.Write | FileSecurityStateAccess.Read, sourceDirName, sourcePath);
FileIOPermission.QuickDemand(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, sourcePath, false, false);
FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, destPath, false, false);
#endif
-
+
if (String.Compare(sourcePath, destPath, StringComparison.OrdinalIgnoreCase) == 0)
throw new IOException(Environment.GetResourceString("IO.IO_SourceDestMustBeDifferent"));
String sourceRoot = Path.GetPathRoot(sourcePath);
String destinationRoot = Path.GetPathRoot(destPath);
if (String.Compare(sourceRoot, destinationRoot, StringComparison.OrdinalIgnoreCase) != 0)
- throw new IOException(Environment.GetResourceString("IO.IO_SourceDestMustHaveSameRoot"));
+ throw new IOException(Environment.GetResourceString("IO.IO_SourceDestMustHaveSameRoot"));
if (!Win32Native.MoveFile(sourceDirName, destDirName))
{
path = path.Trim();
#if !PLATFORM_UNIX
- if (path.Length > 2 && path.IndexOf( ':', 2 ) != -1)
- throw new NotSupportedException( Environment.GetResourceString( "Argument_PathFormatNotSupported" ) );
-#endif // !PLATFORM_UNIX
-
- System.IO.Path.CheckInvalidPathChars(path);
+ if (!PathInternal.IsDevice(path) && PathInternal.HasInvalidVolumeSeparator(path))
+ throw new ArgumentException(Environment.GetResourceString("Argument_PathFormatNotSupported"));
+#endif
-#if !PLATFORM_UNIX
- if (path.IndexOfAny( m_illegalCharacters ) != -1)
- throw new ArgumentException( Environment.GetResourceString( "Argument_InvalidPathChars" ) );
-#endif // !PLATFORM_UNIX
+ System.IO.Path.CheckInvalidPathChars(path, checkAdditional: true);
}
}
}
private const char LastAnsi = (char)255;
private const char Delete = (char)127;
- // Trim trailing white spaces, tabs etc but don't be aggressive in removing everything that has UnicodeCategory of trailing space.
- // string.WhitespaceChars will trim more aggressively than what the underlying FS does (for ex, NTFS, FAT).
- internal static readonly char[] s_trimEndChars =
- {
- (char)0x9, // Horizontal tab
- (char)0xA, // Line feed
- (char)0xB, // Vertical tab
- (char)0xC, // Form feed
- (char)0xD, // Carriage return
- (char)0x20, // Space
- (char)0x85, // Next line
- (char)0xA0 // Non breaking space
- };
-
[ThreadStatic]
private static StringBuffer t_fullPathBuffer;
GetFullPathName(path, fullPath);
// Trim whitespace off the end of the string. Win32 normalization trims only U+0020.
- fullPath.TrimEnd(s_trimEndChars);
+ fullPath.TrimEnd(Path.TrimEndChars);
if (fullPath.Length >= maxPathLength)
{
__Error.WinIOError(errorCode, path);
}
+ // It is significantly more complicated to get the long path with minimal allocations if we're injecting the extended dos path prefix. The implicit version
+ // should match up with what is in CoreFx System.Runtime.Extensions.
+#if !FEATURE_IMPLICIT_LONGPATH
[System.Security.SecuritySafeCritical]
private unsafe static string TryExpandShortFileName(StringBuffer outputBuffer, string originalPath)
{
return bufferToUse.ToString();
}
}
+#else // !FEATURE_IMPLICIT_LONGPATH
+
+ private static uint GetInputBuffer(StringBuffer content, bool isDosUnc, out StringBuffer buffer)
+ {
+ uint length = content.Length;
+
+ length += isDosUnc ? (uint)PathInternal.UncExtendedPrefixToInsert.Length : (uint)PathInternal.ExtendedPathPrefix.Length;
+ buffer = new StringBuffer(length);
+
+ if (isDosUnc)
+ {
+ buffer.CopyFrom(bufferIndex: 0, source: PathInternal.UncExtendedPathPrefix);
+ uint prefixDifference = (uint)(PathInternal.UncExtendedPathPrefix.Length - PathInternal.UncPathPrefix.Length);
+ content.CopyTo(bufferIndex: prefixDifference, destination: buffer, destinationIndex: (uint)PathInternal.ExtendedPathPrefix.Length, count: content.Length - prefixDifference);
+ return prefixDifference;
+ }
+ else
+ {
+ uint prefixSize = (uint)PathInternal.ExtendedPathPrefix.Length;
+ buffer.CopyFrom(bufferIndex: 0, source: PathInternal.ExtendedPathPrefix);
+ content.CopyTo(bufferIndex: 0, destination: buffer, destinationIndex: prefixSize, count: content.Length);
+ return prefixSize;
+ }
+ }
+
+ private static string TryExpandShortFileName(StringBuffer outputBuffer, string originalPath)
+ {
+ // We'll have one of a few cases by now (the normalized path will have already:
+ //
+ // 1. Dos path (C:\)
+ // 2. Dos UNC (\\Server\Share)
+ // 3. Dos device path (\\.\C:\, \\?\C:\)
+ //
+ // We want to put the extended syntax on the front if it doesn't already have it, which may mean switching from \\.\.
+
+ uint rootLength = PathInternal.GetRootLength(outputBuffer);
+ bool isDevice = PathInternal.IsDevice(outputBuffer);
+
+ StringBuffer inputBuffer = null;
+ bool isDosUnc = false;
+ uint rootDifference = 0;
+ bool wasDotDevice = false;
+
+ // Add the extended prefix before expanding to allow growth over MAX_PATH
+ if (isDevice)
+ {
+ // We have one of the following (\\?\ or \\.\)
+ // We will never get \??\ here as GetFullPathName() does not recognize \??\ and will return it as C:\??\ (or whatever the current drive is).
+ inputBuffer = new StringBuffer();
+ inputBuffer.Append(outputBuffer);
+
+ if (outputBuffer[2] == '.')
+ {
+ wasDotDevice = true;
+ inputBuffer[2] = '?';
+ }
+ }
+ else
+ {
+ // \\Server\Share, but not \\.\ or \\?\.
+ // We need to know this to be able to push \\?\UNC\ on if required
+ isDosUnc = outputBuffer.Length > 1 && outputBuffer[0] == '\\' && outputBuffer[1] == '\\' && !PathInternal.IsDevice(outputBuffer);
+ rootDifference = GetInputBuffer(outputBuffer, isDosUnc, out inputBuffer);
+ }
+
+ rootLength += rootDifference;
+ uint inputLength = inputBuffer.Length;
+
+ bool success = false;
+ uint foundIndex = inputBuffer.Length - 1;
+
+ while (!success)
+ {
+ uint result = Win32Native.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity);
+
+ // Replace any temporary null we added
+ if (inputBuffer[foundIndex] == '\0') inputBuffer[foundIndex] = '\\';
+
+ if (result == 0)
+ {
+ // Look to see if we couldn't find the file
+ int error = Marshal.GetLastWin32Error();
+ if (error != Win32Native.ERROR_FILE_NOT_FOUND && error != Win32Native.ERROR_PATH_NOT_FOUND)
+ {
+ // Some other failure, give up
+ break;
+ }
+
+ // We couldn't find the path at the given index, start looking further back in the string.
+ foundIndex--;
+
+ for (; foundIndex > rootLength && inputBuffer[foundIndex] != '\\'; foundIndex--) ;
+ if (foundIndex == rootLength)
+ {
+ // Can't trim the path back any further
+ break;
+ }
+ else
+ {
+ // Temporarily set a null in the string to get Windows to look further up the path
+ inputBuffer[foundIndex] = '\0';
+ }
+ }
+ else if (result > outputBuffer.CharCapacity)
+ {
+ // Not enough space. The result count for this API does not include the null terminator.
+ outputBuffer.EnsureCharCapacity(result);
+ result = Win32Native.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity);
+ }
+ else
+ {
+ // Found the path
+ success = true;
+ outputBuffer.Length = result;
+ if (foundIndex < inputLength - 1)
+ {
+ // It was a partial find, put the non-existent part of the path back
+ outputBuffer.Append(inputBuffer, foundIndex, inputBuffer.Length - foundIndex);
+ }
+ }
+ }
+
+ // Strip out the prefix and return the string
+ StringBuffer bufferToUse = success ? outputBuffer : inputBuffer;
+ if (wasDotDevice)
+ bufferToUse[2] = '.';
+
+ string returnValue = null;
+
+ int newLength = (int)(bufferToUse.Length - rootDifference);
+ if (isDosUnc)
+ {
+ // Need to go from \\?\UNC\ to \\?\UN\\
+ bufferToUse[(uint)PathInternal.UncExtendedPathPrefix.Length - 1] = '\\';
+ }
+
+ // We now need to strip out any added characters at the front of the string
+ if (bufferToUse.SubstringEquals(originalPath, rootDifference, newLength))
+ {
+ // Use the original path to avoid allocating
+ returnValue = originalPath;
+ }
+ else
+ {
+ returnValue = bufferToUse.Substring(rootDifference, newLength);
+ }
+
+ inputBuffer.Dispose();
+ return returnValue;
+ }
+#endif // FEATURE_IMPLICIT_LONGPATH
}
}
\ No newline at end of file
// Platform specific directory separator character. This is backslash
// ('\') on Windows and slash ('/') on Unix.
//
-#if !PLATFORM_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.
+
+ // 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".
public static readonly char VolumeSeparatorChar = ':';
#else
public static readonly char VolumeSeparatorChar = '/';
-#endif // !PLATFORM_UNIX
-
+#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
#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) 0x9, (char) 0xA, (char) 0xB, (char) 0xC, (char) 0xD, (char) 0x20, (char) 0x85, (char) 0xA0};
-
-#if !PLATFORM_UNIX
- private static readonly char[] RealInvalidPathChars = { '\"', '<', '>', '|', '\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 };
+ // 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
+ };
- // This is used by HasIllegalCharacters
- private static readonly char[] InvalidPathCharsWithAdditionalChecks = { '\"', '<', '>', '|', '\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, '*', '?' };
+#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' };
- // This is used by HasIllegalCharacters
- private static readonly char[] InvalidPathCharsWithAdditionalChecks = { '\0', '*', '?' };
-
private static readonly char[] InvalidFileNameChars = { '\0', '/' };
#endif // !PLATFORM_UNIX
// Make this public sometime.
// 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.
-#if !PLATFORM_UNIX
- internal static readonly int MaxPath = 260;
-#else
- internal static readonly int MaxPath = 1024;
-#endif
+ internal static readonly int MaxPath = PathInternal.MaxShortPath;
private static readonly int MaxDirectoryLength = 255;
// 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
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) {
- if (path != null) {
+ public static String GetDirectoryName(String path)
+ {
+ if (path != null)
+ {
CheckInvalidPathChars(path);
- string normalizedPath = NormalizePath(path, false);
+ // 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 (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 = Path.RemoveLongPathPrefix(path);
+ string tempPath = RemoveLongPathPrefix(path);
// FileIOPermission cannot handle paths that contain ? or *
// So we only pass to FileIOPermission the text up to them.
// 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)
- Path.GetFullPath(tempPath.Substring(0, pos));
+ GetFullPath(tempPath.Substring(0, pos));
}
- catch (SecurityException) {
+ 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);
+ normalizedPath = NormalizePath(path, fullCheck: false, expandShortPaths: false);
}
}
catch (PathTooLongException) { }
int root = GetRootLength(path);
int i = path.Length;
- if (i > root) {
+ if (i > root)
+ {
i = path.Length;
if (i == root) return null;
- while (i > root && path[--i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar);
+ while (i > root && path[--i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar);
return path.Substring(0, i);
}
}
// 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) {
+ internal static int GetRootLength(string path)
+ {
CheckInvalidPathChars(path);
-
- int i = 0;
- int length = path.Length;
-#if !PLATFORM_UNIX
- if (length >= 1 && (IsDirectorySeparator(path[0]))) {
- // handles UNC names and directories off current drive's root.
- i = 1;
- if (length >= 2 && (IsDirectorySeparator(path[1]))) {
+#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;
- int n = 2;
- while (i < length && ((path[i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar) || --n > 0)) i++;
+ if (length >= 3 && (IsDirectorySeparator(path[2]))) i++;
}
+ return i;
}
- else if (length >= 2 && path[1] == VolumeSeparatorChar) {
- // handles A:\foo.
- i = 2;
- if (length >= 3 && (IsDirectorySeparator(path[2]))) i++;
- }
- return i;
-#else
- if (length >= 1 && (IsDirectorySeparator(path[0]))) {
- i = 1;
+ else
+#endif // !PLATFORM_UNIX && FEATURE_PATHCOMPAT
+ {
+ return PathInternal.GetRootLength(path);
}
- return i;
-#endif // !PLATFORM_UNIX
}
internal static bool IsDirectorySeparator(char c) {
return (c==DirectorySeparatorChar || c == AltDirectorySeparatorChar);
}
-
public static char[] GetInvalidPathChars()
{
return (char[]) RealInvalidPathChars.Clone();
// 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) {
+ internal static string GetFullPathInternal(string path)
+ {
if (path == null)
throw new ArgumentNullException("path");
Contract.EndContractBlock();
- String newPath = NormalizePath(path, true);
-
+ 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, MaxPath);
+ 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)
+ internal unsafe static string NormalizePath(string path, bool fullCheck, bool expandShortPaths)
{
- return NormalizePath(path, fullCheck, MaxPath, expandShortPaths);
+ return NormalizePath(path, fullCheck,
+#if FEATURE_PATHCOMPAT
+ AppContextSwitches.BlockLongPaths ? PathInternal.MaxShortPath :
+#endif
+ PathInternal.MaxLongPath,
+ expandShortPaths);
}
[System.Security.SecuritySafeCritical] // auto-generated
- internal unsafe static String NormalizePath(String path, bool fullCheck, int maxPathLength) {
- return NormalizePath(path, fullCheck, maxPathLength, true);
+ 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 NormalizePath(String path, bool fullCheck, int maxPathLength, bool expandShortPaths) {
+ 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
path = path.TrimEnd(TrimEndChars);
// Look for illegal path characters.
- CheckInvalidPathChars(path);
+ if (PathInternal.AnyPathHasIllegalCharacters(path))
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
}
int index = 0;
return newBuffer.ToStringOrExisting(path);
}
- internal const int MaxLongPath = 32000;
+#endif // FEATURE_PATHCOMPAT
+
+ internal const int MaxLongPath = PathInternal.MaxLongPath;
- private const string LongPathPrefix = @"\\?\";
- private const string UNCPathPrefix = @"\\";
- private const string UNCLongPathPrefixToInsert = @"?\UNC\";
- private const string UNCLongPathPrefix = @"\\?\UNC\";
+ 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 unsafe static bool HasLongPathPrefix(String path)
+ internal static bool HasLongPathPrefix(string path)
{
- return path.StartsWith(LongPathPrefix, StringComparison.Ordinal);
+#if FEATURE_PATHCOMPAT
+ if (AppContextSwitches.UseLegacyPathHandling)
+ return path.StartsWith(LongPathPrefix, StringComparison.Ordinal);
+ else
+#endif
+ return PathInternal.IsExtended(path);
}
- internal unsafe static String AddLongPathPrefix(String path)
+ internal static string AddLongPathPrefix(string path)
{
- if (path.StartsWith(LongPathPrefix, StringComparison.Ordinal))
- return 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.
+ 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;
+ return LongPathPrefix + path;
+ }
+ else
+#endif
+ {
+ return PathInternal.EnsureExtendedPrefix(path);
+ }
}
- internal unsafe static String RemoveLongPathPrefix(String path)
+ internal static string RemoveLongPathPrefix(string path)
{
- if (!path.StartsWith(LongPathPrefix, StringComparison.Ordinal))
- return 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.
+ 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);
+ return path.Substring(4);
+ }
+ else
+#endif
+ {
+ return PathInternal.RemoveExtendedPrefix(path);
+ }
}
- internal unsafe static StringBuilder RemoveLongPathPrefix(StringBuilder pathSB)
+ internal static StringBuilder RemoveLongPathPrefix(StringBuilder pathSB)
{
- string path = pathSB.ToString();
- if (!path.StartsWith(LongPathPrefix, StringComparison.Ordinal))
- return pathSB;
+#if FEATURE_PATHCOMPAT
+ if (AppContextSwitches.UseLegacyPathHandling)
+ {
+ if (!PathInternal.StartsWithOrdinal(pathSB, LongPathPrefix))
+ return pathSB;
- if (path.StartsWith(UNCLongPathPrefix, StringComparison.OrdinalIgnoreCase))
- return pathSB.Remove(2, 6); // Given \\?\UNC\server\share we return \\server\share => @'\\' + path.SubString(UNCLongPathPrefix.Length) => The actual command simply reduces the operation cost.
+ // 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);
+ 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
[Pure]
public static String GetPathRoot(String path) {
if (path == null) return null;
- path = NormalizePath(path, false);
+
+ // 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));
}
#if !FEATURE_CORECLR
new EnvironmentPermission(PermissionState.Unrestricted).Demand();
#endif
- StringBuilder sb = new StringBuilder(MaxPath);
- uint r = Win32Native.GetTempPath(MaxPath, sb);
+ 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);
internal static bool IsRelative(string path)
{
Contract.Assert(path != null, "path can't be null");
-#if !PLATFORM_UNIX
- if ((path.Length >= 3 && path[1] == VolumeSeparatorChar && path[2] == DirectorySeparatorChar &&
- ((path[0] >= 'a' && path[0] <= 'z') || (path[0] >= 'A' && path[0] <= 'Z'))) ||
- (path.Length >= 2 && path[0] == '\\' && path[1] == '\\'))
-#else
- if(path.Length >= 1 && path[0] == VolumeSeparatorChar)
-#endif // !PLATFORM_UNIX
- return false;
- else
- return true;
-
+ return PathInternal.IsPartiallyQualified(path);
}
// Returns a cryptographically strong random 8.3 string that can be
return InternalGetTempFileName(false);
}
- [System.Security.SecurityCritical]
+ [System.Security.SecurityCritical]
private static String InternalGetTempFileName(bool checkHost)
{
String path = GetTempPath();
state.EnsureState();
}
#else
- new FileIOPermission(FileIOPermissionAccess.Write, path).Demand();
+ FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, path);
#endif
StringBuilder sb = new StringBuilder(MaxPath);
uint r = Win32Native.GetTempFileName(path, "tmp", 0, sb);
}
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 (":").
//
}
- internal static bool HasIllegalCharacters(String path, bool checkAdditional)
- {
- Contract.Requires(path != null);
-
- if (checkAdditional)
- {
- return path.IndexOfAny(InvalidPathCharsWithAdditionalChecks) >= 0;
- }
-
- return path.IndexOfAny(RealInvalidPathChars) >= 0;
- }
-
internal static void CheckInvalidPathChars(String path, bool checkAdditional = false)
{
if (path == null)
throw new ArgumentNullException("path");
- if (Path.HasIllegalCharacters(path, checkAdditional))
+ 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");
internal const string UncExtendedPathPrefix = @"\\?\UNC\";
internal const string DevicePathPrefix = @"\\.\";
internal const int DevicePrefixLength = 4;
+#if !PLATFORM_UNIX
internal const int MaxShortPath = 260;
internal const int MaxShortDirectoryPath = 248;
+#else
+ internal const int MaxShortPath = 1024;
+ internal const int MaxShortDirectoryPath = MaxShortPath;
+#endif
// Windows is limited in long paths by the max size of its internal representation of a unicode string.
// UNICODE_STRING has a max length of USHORT in _bytes_ without a trailing null.
internal const int MaxLongPath = short.MaxValue;
internal static readonly int MaxComponentLength = 255;
+#if !PLATFORM_UNIX
internal static readonly char[] InvalidPathChars =
{
'\"', '<', '>', '|', '\0',
(char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
(char)31
};
+#else
+ internal static readonly char[] InvalidPathChars = { '\0' };
+#endif
+
/// <summary>
/// Validates volume separator only occurs as C: or \\?\C:. This logic is meant to filter out Alternate Data Streams.
/// </summary>
internal static bool AnyPathHasIllegalCharacters(string path, bool checkAdditional = false)
{
- return path.IndexOfAny(InvalidPathChars) >= 0 || (checkAdditional && AnyPathHasWildCardCharacters(path));
+ return path.IndexOfAny(InvalidPathChars) >= 0
+#if !PLATFORM_UNIX
+ || (checkAdditional && AnyPathHasWildCardCharacters(path))
+#endif
+ ;
}
/// <summary>
private unsafe static uint GetRootLength(char* path, ulong pathLength)
{
uint i = 0;
+
+#if PLATFORM_UNIX
+ if (pathLength >= 1 && (IsDirectorySeparator(path[0])))
+ i = 1;
+#else
uint volumeSeparatorLength = 2; // Length to the colon "C:"
uint uncRootLength = 2; // Length to the start of the server name "\\"
i = volumeSeparatorLength;
if (pathLength >= volumeSeparatorLength + 1 && IsDirectorySeparator(path[volumeSeparatorLength])) i++;
}
+#endif // !PLATFORM_UNIX
return i;
}
/// </remarks>
internal static bool IsPartiallyQualified(string path)
{
+#if PLATFORM_UNIX
+ return !(path.Length >= 1 && path[0] == Path.DirectorySeparatorChar);
+#else
if (path.Length < 2)
{
// It isn't fixed, it must be relative. There is no way to specify a fixed
// To match old behavior we'll check the drive character for validity as the path is technically
// not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream.
&& IsValidDriveChar(path[0]));
+#endif // !PLATFORM_UNIX
}
/// <summary>
/// </remarks>
internal static bool IsPartiallyQualified(StringBuffer path)
{
+#if PLATFORM_UNIX
+ return !(path.Length >= 1 && path[0] == Path.DirectorySeparatorChar);
+#else
if (path.Length < 2)
{
// It isn't fixed, it must be relative. There is no way to specify a fixed
// To match old behavior we'll check the drive character for validity as the path is technically
// not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream.
&& IsValidDriveChar(path[0]));
+#endif // !PLATFORM_UNIX
}
/// <summary>
- /// Returns the characters to skip at the start of the path if it starts with space(s) and a drive or directory separator.
+ /// On Windows, returns the characters to skip at the start of the path if it starts with space(s) and a drive or directory separator.
/// (examples are " C:", " \")
/// This is a legacy behavior of Path.GetFullPath().
/// </summary>
/// </remarks>
internal static int PathStartSkip(string path)
{
+#if !PLATFORM_UNIX
int startIndex = 0;
while (startIndex < path.Length && path[startIndex] == ' ') startIndex++;
// Go ahead and skip spaces as we're either " C:" or " \"
return startIndex;
}
+#endif
return 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool IsDirectorySeparator(char c)
{
- return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
+ return c == Path.DirectorySeparatorChar
+#if !PLATFORM_UNIX
+ || c == Path.AltDirectorySeparatorChar
+#endif
+ ;
}
/// <summary>
current = path[i];
if (IsDirectorySeparator(current)
&& (current != Path.DirectorySeparatorChar
+#if !PLATFORM_UNIX
// Check for sequential separators past the first position (we need to keep initial two for UNC/extended)
- || (i > 0 && i + 1 < path.Length && IsDirectorySeparator(path[i + 1]))))
+ || (i > 0 && i + 1 < path.Length && IsDirectorySeparator(path[i + 1]))
+#endif
+ ))
{
- normalized = false;
+ normalized = false;
break;
}
}
StringBuilder builder = StringBuilderCache.Acquire(path.Length);
+#if !PLATFORM_UNIX
+ // On Windows we always keep the first separator, even if the next is a separator (we need to keep initial two for UNC/extended)
if (IsDirectorySeparator(path[start]))
{
start++;
builder.Append(Path.DirectorySeparatorChar);
}
+#endif
for (int i = start; i < path.Length; i++)
{
return StringBuilderCache.GetStringAndRelease(builder);
}
+
+#if PLATFORM_UNIX
+ // We rely on Windows to remove relative segments on Windows. This would need to be updated to
+ // handle the proper rooting on Windows if we for some reason need it.
+
+ /// <summary>
+ /// Try to remove relative segments from the given path (without combining with a root).
+ /// </summary>
+ /// <param name="skip">Skip the specified number of characters before evaluating.</param>
+ internal static string RemoveRelativeSegments(string path, int skip = 0)
+ {
+ bool flippedSeparator = false;
+
+ // Remove "//", "/./", and "/../" from the path by copying each character to the output,
+ // except the ones we're removing, such that the builder contains the normalized path
+ // at the end.
+ var sb = StringBuilderCache.Acquire(path.Length);
+ if (skip > 0)
+ {
+ sb.Append(path, 0, skip);
+ }
+
+ int componentCharCount = 0;
+ for (int i = skip; i < path.Length; i++)
+ {
+ char c = path[i];
+
+ if (PathInternal.IsDirectorySeparator(c) && i + 1 < path.Length)
+ {
+ componentCharCount = 0;
+
+ // Skip this character if it's a directory separator and if the next character is, too,
+ // e.g. "parent//child" => "parent/child"
+ if (PathInternal.IsDirectorySeparator(path[i + 1]))
+ {
+ continue;
+ }
+
+ // Skip this character and the next if it's referring to the current directory,
+ // e.g. "parent/./child" =? "parent/child"
+ if ((i + 2 == path.Length || PathInternal.IsDirectorySeparator(path[i + 2])) &&
+ path[i + 1] == '.')
+ {
+ i++;
+ continue;
+ }
+
+ // Skip this character and the next two if it's referring to the parent directory,
+ // e.g. "parent/child/../grandchild" => "parent/grandchild"
+ if (i + 2 < path.Length &&
+ (i + 3 == path.Length || PathInternal.IsDirectorySeparator(path[i + 3])) &&
+ path[i + 1] == '.' && path[i + 2] == '.')
+ {
+ // Unwind back to the last slash (and if there isn't one, clear out everything).
+ int s;
+ for (s = sb.Length - 1; s >= 0; s--)
+ {
+ if (PathInternal.IsDirectorySeparator(sb[s]))
+ {
+ sb.Length = s;
+ break;
+ }
+ }
+ if (s < 0)
+ {
+ sb.Length = 0;
+ }
+
+ i += 2;
+ continue;
+ }
+ }
+
+ if (++componentCharCount > PathInternal.MaxComponentLength)
+ {
+ throw new PathTooLongException();
+ }
+
+ // Normalize the directory separator if needed
+ if (c != Path.DirectorySeparatorChar && c == Path.AltDirectorySeparatorChar)
+ {
+ c = Path.DirectorySeparatorChar;
+ flippedSeparator = true;
+ }
+
+ sb.Append(c);
+ }
+
+ if (flippedSeparator || sb.Length != path.Length)
+ {
+ return StringBuilderCache.GetStringAndRelease(sb);
+ }
+ else
+ {
+ // We haven't changed the source path, return the original
+ StringBuilderCache.Release(sb);
+ return path;
+ }
+ }
+#endif // PLATFORM_UNIX
}
}
\ No newline at end of file
Array.Copy(pathListOrig, pathList, pathListOrig.Length);
}
- CheckIllegalCharacters( pathList );
ArrayList pathArrayList = StringExpressionSet.CreateListFromExpressions(pathList, needFullPath);
-
+
+ // If we need the full path the standard illegal characters will be checked in StringExpressionSet.
+ CheckIllegalCharacters(pathList, onlyCheckExtras: needFullPath);
+
+ // StringExpressionSet will do minor normalization, trimming spaces and replacing alternate
+ // directory separators. It will make an attemt to expand short file names and will check
+ // for standard colon placement.
+ //
+ // If needFullPath is true it will call NormalizePath- which performs short name expansion
+ // and does the normal validity checks.
+
if ((access & FileIOPermissionAccess.Read) != 0)
{
if (m_read == null)
}
}
- private static void CheckIllegalCharacters( String[] str )
+ private static void CheckIllegalCharacters(String[] str, bool onlyCheckExtras)
{
+#if !PLATFORM_UNIX
+ for (int i = 0; i < str.Length; ++i)
+ {
+ // FileIOPermission doesn't allow for normalizing across various volume names. This means "C:\" and
+ // "\\?\C:\" won't be considered correctly. In addition there are many other aliases for the volume
+ // besides "C:" such as (in one concrete example) "\\?\Harddisk0Partition2\", "\\?\HarddiskVolume6\",
+ // "\\?\Volume{d1655348-0000-0000-0000-f01500000000}\", etc.
+ //
+ // We'll continue to explicitly block extended syntax here by disallowing wildcards no matter where
+ // they occur in the string (e.g. \\?\ isn't ok)
+ if (CheckExtraPathCharacters(str[i]))
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
+
+ if (!onlyCheckExtras)
+ Path.CheckInvalidPathChars(str[i]);
+ }
+#else
+ // There are no "extras" on Unix
+ if (onlyCheckExtras)
+ return;
+
for (int i = 0; i < str.Length; ++i)
{
- Path.CheckInvalidPathChars(str[i], true);
}
+#endif
+ }
+
+#if !PLATFORM_UNIX
+ /// <summary>
+ /// Check for ?,* and null, ignoring extended syntax.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private unsafe static bool CheckExtraPathCharacters(string path)
+ {
+ char currentChar;
+ for (int i = 0; i < path.Length; i++)
+ {
+ currentChar = path[i];
+
+ // We also check for null here as StringExpressionSet will trim it out. (Ensuring we still throw as we always have.)
+ if (currentChar == '*' || currentChar == '?' || currentChar == '\0') return true;
+ }
+ return false;
}
+#endif
private static bool AccessIsSet( FileIOPermissionAccess access, FileIOPermissionAccess question )
{
/// FileIOPermission object would have performed.
///
/// IMPORTANT: This method should only be used after calling GetFullPath on the path to verify
- ///
/// </summary>
- /// <param name="access"></param>
- /// <param name="path"></param>
- /// <param name="checkForDuplicates"></param>
- /// <param name="needFullPath"></param>
[System.Security.SecuritySafeCritical]
internal static void QuickDemand(FileIOPermissionAccess access, string fullPath, bool checkForDuplicates, bool needFullPath)
{
new FileIOPermission(access, new string[] { fullPath }, checkForDuplicates, needFullPath).Demand();
}
else
-#endif
+#endif
{
- //Emulate FileIOPermission checks
- Path.CheckInvalidPathChars(fullPath, true);
+ EmulateFileIOPermissionChecks(fullPath);
+ }
+ }
- if (fullPath.Length > 2 && fullPath.IndexOf(':', 2) != -1)
+ /// <summary>
+ /// Call this method if you don't need a the FileIOPermission for anything other than calling Demand() once.
+ ///
+ /// This method tries to verify full access before allocating a FileIOPermission object.
+ /// If full access is there, then we still have to emulate the checks that creating the
+ /// FileIOPermission object would have performed.
+ ///
+ /// IMPORTANT: This method should only be used after calling GetFullPath on the path to verify
+ ///
+ /// </summary>
+ [System.Security.SecuritySafeCritical]
+ internal static void QuickDemand(FileIOPermissionAccess access, string[] fullPathList, bool checkForDuplicates = false, bool needFullPath = true)
+ {
+#if FEATURE_CAS_POLICY
+ if (!CodeAccessSecurityEngine.QuickCheckForAllDemands())
+ {
+ new FileIOPermission(access, fullPathList, checkForDuplicates, needFullPath).Demand();
+ }
+ else
+#endif
+ {
+ foreach (string fullPath in fullPathList)
+ {
+ EmulateFileIOPermissionChecks(fullPath);
+ }
+ }
+ }
+
+ [System.Security.SecuritySafeCritical]
+ internal static void QuickDemand(PermissionState state)
+ {
+ // Should be a no-op without CAS
+#if FEATURE_CAS_POLICY
+ if (!CodeAccessSecurityEngine.QuickCheckForAllDemands())
+ {
+ new FileIOPermission(state).Demand();
+ }
+#endif
+ }
+
+#if FEATURE_MACL
+ [System.Security.SecuritySafeCritical]
+ internal static void QuickDemand(FileIOPermissionAccess access, AccessControlActions control, string fullPath, bool checkForDuplicates = false, bool needFullPath = true)
+ {
+ if (!CodeAccessSecurityEngine.QuickCheckForAllDemands())
+ {
+ new FileIOPermission(access, control, new string[] { fullPath }, checkForDuplicates, needFullPath).Demand();
+ }
+ else
+ {
+ EmulateFileIOPermissionChecks(fullPath);
+ }
+ }
+
+ [System.Security.SecuritySafeCritical]
+ internal static void QuickDemand(FileIOPermissionAccess access, AccessControlActions control, string[] fullPathList, bool checkForDuplicates = true, bool needFullPath = true)
+ {
+ if (!CodeAccessSecurityEngine.QuickCheckForAllDemands())
+ {
+ new FileIOPermission(access, control, fullPathList, checkForDuplicates, needFullPath).Demand();
+ }
+ else
+ {
+ foreach (string fullPath in fullPathList)
+ {
+ EmulateFileIOPermissionChecks(fullPath);
+ }
+ }
+ }
+#endif
+
+ /// <summary>
+ /// Perform the additional path checks that would normally happen when creating a FileIOPermission object.
+ /// </summary>
+ /// <param name="fullPath">A path that has already gone through GetFullPath or Normalize</param>
+ internal static void EmulateFileIOPermissionChecks(string fullPath)
+ {
+ // Callers should have already made checks for invalid path format via normalization. This method will only make the
+ // additional checks needed to throw the same exceptions that would normally throw when using FileIOPermission.
+ // These checks are done via CheckIllegalCharacters() and StringExpressionSet in AddPathList() above.
+ //
+ // We have to check the beginning as some paths may be passed in as path + @"\.", which will be normalized away.
+ BCLDebug.Assert(
+ fullPath.StartsWith(Path.NormalizePath(fullPath, fullCheck: false), StringComparison.OrdinalIgnoreCase),
+ string.Format("path isn't normalized: {0}", fullPath));
+
+ // Checking for colon / invalid characters on device paths blocks legitimate access to objects such as named pipes.
+ if (
+#if FEATURE_PATHCOMPAT
+ AppContextSwitches.UseLegacyPathHandling ||
+#endif
+ !PathInternal.IsDevice(fullPath))
+ {
+ // GetFullPath already checks normal invalid path characters. We need to just check additional (wildcard) characters here.
+ // (By calling the standard helper we can allow extended paths \\?\ through when the support is enabled.)
+ if (PathInternal.HasWildCardCharacters(fullPath))
+ {
+ throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
+ }
+
+ if (PathInternal.HasInvalidVolumeSeparator(fullPath))
{
throw new NotSupportedException(Environment.GetResourceString("Argument_PathFormatNotSupported"));
}
}
}
}
-
+
[Serializable]
internal sealed class FileIOAccess
{
#if !FEATURE_CASE_SENSITIVE_FILESYSTEM
private bool m_ignoreCase = true;
-#else
+#else
private bool m_ignoreCase = false;
#endif // !FEATURE_CASE_SENSITIVE_FILESYSTEM
return true;
}
-
+
private static String GetRoot( String path )
{
-#if !PLATFORM_UNIX
+#if !PLATFORM_UNIX
String str = path.Substring( 0, 3 );
if (str.EndsWith( ":\\", StringComparison.Ordinal))
#else
String str = path.Substring( 0, 1 );
if(str == "/")
-#endif // !PLATFORM_UNIX
+#endif // !PLATFORM_UNIX
{
return str;
}
if (str[index] == null)
throw new ArgumentNullException( "str" );
+ // Replace alternate directory separators
String oneString = StaticProcessWholeString( str[index] );
if (oneString != null && oneString.Length != 0)
{
+ // Trim leading and trailing spaces
String temp = StaticProcessSingleString( oneString);
int indexOfNull = temp.IndexOf( '\0' );
if (temp != null && temp.Length != 0)
{
- if (Path.IsRelative(temp))
+ if (PathInternal.IsPartiallyQualified(temp))
{
- throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) );
+ throw new ArgumentException(Environment.GetResourceString( "Argument_AbsolutePathRequired" ) );
}
temp = CanonicalizePath( temp, needFullPath );
-
retArrayList.Add( temp );
}
}
}
[System.Security.SecurityCritical] // auto-generated
- internal static String CanonicalizePath( String path, bool needFullPath )
+ internal static string CanonicalizePath(string path, bool needFullPath)
{
-
-#if !PLATFORM_UNIX
- if (path.IndexOf( '~' ) != -1)
- {
- string longPath = null;
- GetLongPathName(path, JitHelpers.GetStringHandleOnStack(ref longPath));
- path = (longPath != null) ? longPath : path;
- }
-
- if (path.IndexOf( ':', 2 ) != -1)
- throw new NotSupportedException( Environment.GetResourceString( "Argument_PathFormatNotSupported" ) );
-#endif // !PLATFORM_UNIX
-
if (needFullPath)
{
- String newPath = System.IO.Path.GetFullPathInternal( path );
- if (path.EndsWith( m_directorySeparator + ".", StringComparison.Ordinal ))
+ string newPath = Path.GetFullPathInternal(path);
+ if (path.EndsWith(m_directorySeparator + ".", StringComparison.Ordinal))
{
- if (newPath.EndsWith( m_directorySeparator ))
+ if (newPath.EndsWith(m_directorySeparator))
{
newPath += ".";
}
{
newPath += m_directorySeparator + ".";
}
- }
- return newPath;
+ }
+ path = newPath;
}
- else
- return path;
+#if !PLATFORM_UNIX
+ else if (path.IndexOf('~') != -1)
+ {
+ // GetFullPathInternal() will expand 8.3 file names
+ string longPath = null;
+ GetLongPathName(path, JitHelpers.GetStringHandleOnStack(ref longPath));
+ path = (longPath != null) ? longPath : path;
+ }
+
+ // This blocks usage of alternate data streams and some extended syntax paths (\\?\C:\). Checking after
+ // normalization allows valid paths such as " C:\" to be considered ok (as it will become "C:\").
+ if (path.IndexOf(':', 2) != -1)
+ throw new NotSupportedException(Environment.GetResourceString("Argument_PathFormatNotSupported"));
+#endif // !PLATFORM_UNIX
+
+ return path;
}
}
}
temp = url.Substring( index );
#endif // !PLATFORM_UNIX
- }
+ }
else
{
throw new ArgumentException( Environment.GetResourceString( "Argument_InvalidUrl" ) );
temp = temp.Substring( Rindex, portIndex - Rindex ) + temp.Substring( tempIndex );
}
else
- throw new ArgumentException( Environment.GetResourceString( "Argument_InvalidUrl" ) );
+ throw new ArgumentException( Environment.GetResourceString( "Argument_InvalidUrl" ) );
}
else
throw new ArgumentException( Environment.GetResourceString( "Argument_InvalidUrl" ) );
}
return temp;
- }
+ }
// This does three things:
// 1. It makes the following modifications to the start of the string:
// 3. Throws a PathTooLongException if the length of the resulting URL is >= MAX_PATH.
// This is done to prevent security issues due to canonicalization truncations.
// Remove this method when the Path class supports "\\?\"
- internal static String PreProcessForExtendedPathRemoval(String url, bool isFileUrl)
+ internal static string PreProcessForExtendedPathRemoval(string url, bool isFileUrl)
+ {
+ return PreProcessForExtendedPathRemoval(checkPathLength: true, url: url, isFileUrl: isFileUrl);
+ }
+
+ internal static string PreProcessForExtendedPathRemoval(bool checkPathLength, string url, bool isFileUrl)
+ {
+ bool isUncShare = false;
+ return PreProcessForExtendedPathRemoval(checkPathLength: checkPathLength, url: url, isFileUrl: isFileUrl, isUncShare: ref isUncShare);
+ }
+
+ // Keeping this signature to avoid reflection breaks
+ private static string PreProcessForExtendedPathRemoval(string url, bool isFileUrl, ref bool isUncShare)
{
- bool uncShare = false;
- return PreProcessForExtendedPathRemoval(url, isFileUrl, ref uncShare);
+ return PreProcessForExtendedPathRemoval(checkPathLength: true, url: url, isFileUrl: isFileUrl, isUncShare: ref isUncShare);
}
- private static String PreProcessForExtendedPathRemoval(String url, bool isFileUrl, ref bool isUncShare)
+ private static string PreProcessForExtendedPathRemoval(bool checkPathLength, string url, bool isFileUrl, ref bool isUncShare)
{
// This is the modified URL that we will return
StringBuilder modifiedUrl = new StringBuilder(url);
}
// ITEM 3 - If the path is greater than or equal (due to terminating NULL in windows) MAX_PATH, we throw.
- if (modifiedUrl.Length >= Path.MaxPath)
+ if (checkPathLength)
{
- throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
+ // This needs to be a separate method to avoid hitting the static constructor on AppContextSwitches
+ CheckPathTooLong(modifiedUrl);
}
// Create the result string from the StringBuilder
return modifiedUrl.ToString();
}
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static void CheckPathTooLong(StringBuilder path)
+ {
+ if (path.Length >= (
+#if FEATURE_PATHCOMPAT
+ AppContextSwitches.BlockLongPaths ? PathInternal.MaxShortPath :
+#endif
+ PathInternal.MaxLongPath))
+ {
+ throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
+ }
+ }
// Do any misc massaging of data in the URL
private String PreProcessURL(String url, bool isFileURL)