Allow long paths
authorJeremy Kuhne <jeremy.kuhne@microsoft.com>
Tue, 10 May 2016 02:25:32 +0000 (19:25 -0700)
committerJeremy Kuhne <jeremy.kuhne@microsoft.com>
Fri, 13 May 2016 18:01:02 +0000 (11:01 -0700)
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

16 files changed:
clr.coreclr.props
clr.defines.targets
clr.desktop.props
src/mscorlib/mscorlib.shared.sources.props
src/mscorlib/src/Microsoft/Win32/Win32Native.cs
src/mscorlib/src/System/AppContext/AppContextSwitches.cs
src/mscorlib/src/System/AppDomain.cs
src/mscorlib/src/System/AppDomainSetup.cs
src/mscorlib/src/System/IO/Directory.cs
src/mscorlib/src/System/IO/FileSecurityState.cs
src/mscorlib/src/System/IO/LongPathHelper.cs
src/mscorlib/src/System/IO/Path.cs
src/mscorlib/src/System/IO/PathInternal.cs
src/mscorlib/src/System/Security/Permissions/FileIOPermission.cs
src/mscorlib/src/System/Security/Util/StringExpressionSet.cs
src/mscorlib/src/System/Security/Util/URLString.cs

index ce27318..8ed490a 100644 (file)
 
     <FeatureCoreFxGlobalization>true</FeatureCoreFxGlobalization>
   </PropertyGroup>
+
+  <PropertyGroup Condition="'$(TargetsUnix)' != 'true'">
+    <FeatureImplicitLongPath>true</FeatureImplicitLongPath>
+  </PropertyGroup>
 </Project>
index 2d8d0ce..4c668e5 100644 (file)
@@ -8,7 +8,7 @@
         <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>
index 050249a..7c94bcd 100644 (file)
@@ -67,6 +67,7 @@
     <FeatureMultiModuleAssemblies>true</FeatureMultiModuleAssemblies>
     <FeatureNativeImageGeneration>true</FeatureNativeImageGeneration>
     <FeatureNongenericCollections>true</FeatureNongenericCollections>
+    <FeaturePathCompat>true</FeaturePathCompat>
     <FeaturePerfmon>true</FeaturePerfmon>
     <FeaturePls>true</FeaturePls>
     <FeaturePrejit>true</FeaturePrejit>
index 2a073c1..d2b27fc 100644 (file)
     <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" />
index 34fbf0d..ba6030e 100644 (file)
@@ -1301,6 +1301,9 @@ namespace Microsoft.Win32 {
                   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);
 
index 5fdd2bc..3a96ec2 100644 (file)
@@ -39,6 +39,36 @@ namespace System
             }
         }
 
+#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
         //
index d78418d..c84a7aa 100644 (file)
@@ -3816,11 +3816,19 @@ namespace System {
         [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
@@ -3933,9 +3941,9 @@ namespace System {
 
 #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
index ad99e63..f1057da 100644 (file)
@@ -340,8 +340,16 @@ namespace System {
 
             // 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)
index 95f2f37..be74538 100644 (file)
@@ -215,9 +215,17 @@ namespace System.IO {
 
             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
@@ -225,7 +233,7 @@ namespace System.IO {
                 // 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)
@@ -237,7 +245,7 @@ namespace System.IO {
                     }
                 }
 #else
-                new FileIOPermission(FileIOPermissionAccess.Write, securityList, false, false ).Demand();
+                FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, securityList, false, false);
 #endif
 #endif //FEATURE_MACL
             }
@@ -265,7 +273,7 @@ namespace System.IO {
             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)) {
@@ -293,7 +301,7 @@ namespace System.IO {
                                     state.EnsureState();
                                 }
 #else
-                                new FileIOPermission(FileIOPermissionAccess.PathDiscovery, GetDemandDir(name, true)).Demand();
+                                FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, GetDemandDir(name, true));
 #endif // FEATURE_CORECLR
                                 errorString = name;
                             }
@@ -950,8 +958,32 @@ namespace System.IO {
             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)
@@ -962,7 +994,7 @@ namespace System.IO {
             // 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;
@@ -977,19 +1009,36 @@ namespace System.IO {
             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
@@ -997,7 +1046,7 @@ namespace System.IO {
         [System.Security.SecuritySafeCritical]
         #endif
         public static void SetCurrentDirectory(String path)
-        {        
+        {
             if (path==null)
                 throw new ArgumentNullException("value");
             if (path.Length==0)
@@ -1050,16 +1099,16 @@ namespace System.IO {
 
             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);
@@ -1071,14 +1120,14 @@ namespace System.IO {
             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))
             {
index 530acfa..249848a 100644 (file)
@@ -122,16 +122,11 @@ namespace System.IO
                 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);
             }
         }
     }
index 5adfb07..41cbb15 100644 (file)
@@ -20,20 +20,6 @@ namespace System.IO
         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;
 
@@ -66,7 +52,7 @@ namespace System.IO
                 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)
                 {
@@ -287,6 +273,9 @@ namespace System.IO
             __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)
         {
@@ -363,5 +352,156 @@ namespace System.IO
                 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
index 6f247c2..10da27c 100644 (file)
@@ -38,19 +38,19 @@ namespace System.IO {
         // 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".  
@@ -59,8 +59,8 @@ namespace System.IO {
         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
@@ -74,22 +74,26 @@ namespace System.IO {
 #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
 
@@ -103,18 +107,14 @@ namespace System.IO {
         // 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
@@ -151,28 +151,47 @@ namespace System.IO {
             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.
@@ -185,14 +204,15 @@ namespace System.IO {
                         // 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) { }
@@ -205,10 +225,11 @@ namespace System.IO {
 
                 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);
                 }
             }
@@ -218,41 +239,46 @@ namespace System.IO {
         // 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();
@@ -324,34 +350,160 @@ namespace System.IO {
         // 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
@@ -362,7 +514,8 @@ namespace System.IO {
                 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;
@@ -771,52 +924,87 @@ namespace System.IO {
 
             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 
@@ -868,7 +1056,10 @@ namespace System.IO {
         [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));
         }
 
@@ -878,8 +1069,8 @@ namespace System.IO {
 #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);
@@ -893,17 +1084,7 @@ namespace System.IO {
         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 
@@ -958,7 +1139,7 @@ namespace System.IO {
             return InternalGetTempFileName(false);
         }
 
-        [System.Security.SecurityCritical]  
+        [System.Security.SecurityCritical]
         private static String InternalGetTempFileName(bool checkHost) 
         {
             String path = GetTempPath();
@@ -974,7 +1155,7 @@ namespace System.IO {
                 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);
@@ -1005,8 +1186,7 @@ namespace System.IO {
             }
             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 (":").
         //
@@ -1218,28 +1398,15 @@ namespace System.IO {
 
         }
 
-        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");
index ac1afb8..a7441b5 100644 (file)
@@ -20,8 +20,13 @@ namespace System.IO
         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.
@@ -29,6 +34,7 @@ namespace System.IO
         internal const int MaxLongPath = short.MaxValue;
         internal static readonly int MaxComponentLength = 255;
 
+#if !PLATFORM_UNIX
         internal static readonly char[] InvalidPathChars =
         {
             '\"', '<', '>', '|', '\0',
@@ -37,6 +43,10 @@ namespace System.IO
             (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.
@@ -353,7 +363,11 @@ namespace System.IO
         /// </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>
@@ -411,6 +425,11 @@ namespace System.IO
         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 "\\"
 
@@ -452,6 +471,7 @@ namespace System.IO
                 i = volumeSeparatorLength;
                 if (pathLength >= volumeSeparatorLength + 1 && IsDirectorySeparator(path[volumeSeparatorLength])) i++;
             }
+#endif // !PLATFORM_UNIX
             return i;
         }
 
@@ -480,6 +500,9 @@ namespace System.IO
         /// </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
@@ -502,6 +525,7 @@ namespace System.IO
                 // 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>
@@ -518,6 +542,9 @@ namespace System.IO
         /// </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
@@ -540,10 +567,11 @@ namespace System.IO
                 // 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>
@@ -552,6 +580,7 @@ namespace System.IO
         /// </remarks>
         internal static int PathStartSkip(string path)
         {
+#if !PLATFORM_UNIX
             int startIndex = 0;
             while (startIndex < path.Length && path[startIndex] == ' ') startIndex++;
 
@@ -561,6 +590,7 @@ namespace System.IO
                 // Go ahead and skip spaces as we're either " C:" or " \"
                 return startIndex;
             }
+#endif
 
             return 0;
         }
@@ -571,7 +601,11 @@ namespace System.IO
         [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>
@@ -616,10 +650,13 @@ namespace System.IO
                     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;
                     }
                 }
@@ -629,11 +666,14 @@ namespace System.IO
 
             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++)
             {
@@ -657,5 +697,105 @@ namespace System.IO
 
             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
index e66eb34..e43c49c 100644 (file)
@@ -220,9 +220,18 @@ namespace System.Security.Permissions {
                 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)
@@ -533,13 +542,53 @@ namespace System.Security.Permissions {
             }
         }
 
-        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 )
         {
@@ -942,12 +991,7 @@ namespace System.Security.Permissions {
         /// 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)
         {
@@ -957,25 +1001,126 @@ namespace System.Security.Permissions {
                 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
         
@@ -1235,16 +1380,16 @@ namespace System.Security.Permissions {
             
             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;
             }
index ae45546..19937f5 100644 (file)
@@ -219,10 +219,12 @@ namespace System.Security.Util {
                 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' );
@@ -232,14 +234,13 @@ namespace System.Security.Util {
 
                     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 );
                     }
                 }
@@ -737,27 +738,14 @@ namespace System.Security.Util {
         }
 
         [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 += ".";
                     }
@@ -765,11 +753,25 @@ namespace System.Security.Util {
                     {
                         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;
         }
     }
 }
index b09e1c4..8ac00ca 100644 (file)
@@ -281,7 +281,7 @@ namespace System.Security.Util {
 
                         temp = url.Substring( index );
 #endif  // !PLATFORM_UNIX
-                     }                                
+                     }
                     else
                     {
                         throw new ArgumentException( Environment.GetResourceString( "Argument_InvalidUrl" ) );
@@ -355,7 +355,7 @@ namespace System.Security.Util {
                         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" ) );
@@ -366,7 +366,7 @@ namespace System.Security.Util {
             }
                 
             return temp;
-        }                
+        }
 
         // This does three things:
         // 1. It makes the following modifications to the start of the string:
@@ -377,13 +377,24 @@ namespace System.Security.Util {
         // 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);
@@ -457,15 +468,28 @@ namespace System.Security.Util {
             }
 
             // 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)