This change ports the support files from NetFxDev1. It doesn't change behavior yet...
authorJeremy Kuhne <jeremy.kuhne@microsoft.com>
Fri, 6 May 2016 00:20:27 +0000 (17:20 -0700)
committerJeremy Kuhne <jeremy.kuhne@microsoft.com>
Fri, 6 May 2016 00:20:27 +0000 (17:20 -0700)
[tfs-changeset: 1602284]

src/mscorlib/mscorlib.shared.sources.props
src/mscorlib/src/Microsoft/Win32/Win32Native.cs
src/mscorlib/src/System/AppContext/AppContextDefaultValues.Defaults.cs
src/mscorlib/src/System/AppDomain.cs
src/mscorlib/src/System/AppDomainSetup.cs
src/mscorlib/src/System/IO/LongPathHelper.cs [new file with mode: 0644]
src/mscorlib/src/System/IO/PathInternal.cs [new file with mode: 0644]
src/mscorlib/src/System/Runtime/InteropServices/NativeBuffer.cs [new file with mode: 0644]
src/mscorlib/src/System/Runtime/InteropServices/SafeHeapHandle.cs [new file with mode: 0644]
src/mscorlib/src/System/Runtime/InteropServices/StringBuffer.cs [new file with mode: 0644]

index 7fef3f5..2a073c1 100644 (file)
     <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SEHException.cs" />
     <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeBuffer.cs" />
     <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeHandle.cs" />
+    <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeHeapHandle.cs" />
     <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\BStrWrapper.cs" />
     <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\CurrencyWrapper.cs" />
     <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ErrorWrapper.cs" />
     <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\InvalidComObjectException.cs" />
     <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeArrayRankMismatchException.cs" />
     <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeArrayTypeMismatchException.cs" />
+    <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NativeBuffer.cs" />
+    <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\StringBuffer.cs" />
     <InteropSources Condition="'$(FeatureCoreClr)'=='true'"  Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NativeCallableAttribute.cs" />
     <InteropSources Condition="'$(FeatureCominterop)' != 'true'" Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NonPortable.cs" />
     <InteropSources Condition="'$(FeatureCominterop)' == 'true'" Include="$(BclSourcesRoot)\System\Runtime\InteropServices\DispatchWrapper.cs" />
     <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 Include="$(BclSourcesRoot)\System\IO\PathInternal.cs" />
     <IoSources Include="$(BclSourcesRoot)\System\IO\PathTooLongException.cs" />
     <IoSources Include="$(BclSourcesRoot)\System\IO\PinnedBufferMemoryStream.cs" />
     <IoSources Include="$(BclSourcesRoot)\System\IO\ReadLinesIterator.cs" />
index 84fdf8a..34fbf0d 100644 (file)
@@ -897,9 +897,18 @@ namespace Microsoft.Win32 {
         [DllImport(KERNEL32, SetLastError = true, CharSet = CharSet.Auto, BestFitMapping = false)]
         internal unsafe static extern int GetLongPathName(char* path, char* longPathBuffer, int bufferLength);
 
+        [DllImport(KERNEL32, SetLastError = true, ExactSpelling = true)]
+        internal unsafe static extern uint GetFullPathNameW(char* path, uint numBufferChars, SafeHandle buffer, IntPtr mustBeZero);
+
         [DllImport(KERNEL32, SetLastError = true, CharSet = CharSet.Auto, BestFitMapping = false)]
         internal static extern int GetLongPathName(String path, [Out]StringBuilder longPathBuffer, int bufferLength);
 
+        [DllImport(KERNEL32, SetLastError = true, ExactSpelling = true)]
+        internal static extern uint GetLongPathNameW(SafeHandle lpszShortPath, SafeHandle lpszLongPath, uint cchBuffer);
+
+        [DllImport(KERNEL32, SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
+        internal static extern uint GetLongPathNameW(string lpszShortPath, SafeHandle lpszLongPath, uint cchBuffer);
+
         // Disallow access to all non-file devices from methods that take
         // a String.  This disallows DOS devices like "con:", "com1:", 
         // "lpt1:", etc.  Use this to avoid security problems, like allowing
index 50b275e..c80913e 100644 (file)
@@ -12,8 +12,11 @@ namespace System
         internal static readonly string SwitchNoAsyncCurrentCulture = "Switch.System.Globalization.NoAsyncCurrentCulture";
         internal static readonly string SwitchThrowExceptionIfDisposedCancellationTokenSource = "Switch.System.Threading.ThrowExceptionIfDisposedCancellationTokenSource";
         internal static readonly string SwitchPreserveEventListnerObjectIdentity = "Switch.System.Diagnostics.EventSource.PreserveEventListnerObjectIdentity";
+#if FEATURE_PATHCOMPAT
+        internal static readonly string SwitchUseLegacyPathHandling = "Switch.System.IO.UseLegacyPathHandling";
+        internal static readonly string SwitchBlockLongPaths = "Switch.System.IO.BlockLongPaths";
+#endif
 
-        
         // This is a partial method. Platforms can provide an implementation of it that will set override values
         // from whatever mechanism is available on that platform. If no implementation is provided, the compiler is going to remove the calls
         // to it from the code
@@ -40,6 +43,13 @@ namespace System
                             AppContext.DefineSwitchDefault(SwitchNoAsyncCurrentCulture, true);
                             AppContext.DefineSwitchDefault(SwitchThrowExceptionIfDisposedCancellationTokenSource, true);
                         }
+#if FEATURE_PATHCOMPAT
+                        if (version <= 40601)
+                        {
+                            AppContext.DefineSwitchDefault(SwitchUseLegacyPathHandling, true);
+                            AppContext.DefineSwitchDefault(SwitchBlockLongPaths, true);
+                        }
+#endif
                         break;
                     }
                 case "WindowsPhone":
index 855564b..d78418d 100644 (file)
@@ -3628,7 +3628,7 @@ namespace System {
                         if (Path.IsRelative(propertyValues[i]))
                             throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) );
 
-                        newSetup.ApplicationBase=Path.NormalizePath(propertyValues[i],true);
+                        newSetup.ApplicationBase = NormalizePath(propertyValues[i], fullCheck: true);
 
                     }
 #if FEATURE_CAS_POLICY
@@ -3636,10 +3636,10 @@ namespace System {
                     {
                         providedSecurityInfo=new Evidence();
                         providedSecurityInfo.AddHostEvidence(new Url(propertyValues[i]));
-                        ad.SetDataHelper(propertyNames[i],propertyValues[i],null);                        
+                        ad.SetDataHelper(propertyNames[i],propertyValues[i],null);
                     }
 #endif // FEATURE_CAS_POLICY
-#if FEATURE_LOADER_OPTIMIZATION                    
+#if FEATURE_LOADER_OPTIMIZATION
                     else
                     if(propertyNames[i]=="LOADER_OPTIMIZATION")
                     {
@@ -3655,8 +3655,8 @@ namespace System {
                             default: throw new ArgumentException(Environment.GetResourceString("Argument_UnrecognizedLoaderOptimization"), "LOADER_OPTIMIZATION");
                         }
                     }
-#endif // FEATURE_LOADER_OPTIMIZATION                    
-#if FEATURE_CORECLR      
+#endif // FEATURE_LOADER_OPTIMIZATION
+#if FEATURE_CORECLR
                     else
                     if(propertyNames[i]=="NATIVE_DLL_SEARCH_DIRECTORIES")
                     {
@@ -3710,7 +3710,7 @@ namespace System {
                             if (Path.IsRelative(path))
                                 throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) );
 
-                            string appPath=Path.NormalizePath(path,true);
+                            string appPath = NormalizePath(path, fullCheck: true);
                             normalisedAppPathList.Append(appPath);
                             normalisedAppPathList.Append(Path.PathSeparator);
                         }
@@ -3719,12 +3719,12 @@ namespace System {
                         {
                             normalisedAppPathList.Remove(normalisedAppPathList.Length - 1, 1);
                         }
-                        ad.SetDataHelper(propertyNames[i],normalisedAppPathList.ToString(),null);        // not supported by fusion, so set explicitly                
+                        ad.SetDataHelper(propertyNames[i],normalisedAppPathList.ToString(),null);        // not supported by fusion, so set explicitly
                     }
                     else
                     if(propertyNames[i]!= null)
                     {
-                        ad.SetDataHelper(propertyNames[i],propertyValues[i],null);     // just propagate                   
+                        ad.SetDataHelper(propertyNames[i],propertyValues[i],null);     // just propagate
                     }
 #endif
 
@@ -3813,6 +3813,15 @@ namespace System {
 #endif // FEATURE_CLICKONCE
         }
 
+        [SecuritySafeCritical]
+        internal static string NormalizePath(string path, bool fullCheck)
+        {
+            return Path.NormalizePath(
+                path: path,
+                fullCheck: fullCheck,
+                maxPathLength: PathInternal.MaxShortPath,
+                expandShortPaths: true);
+        }
 
 #if FEATURE_APTCA
         // Called from DomainAssembly in Conditional APTCA cases
@@ -3924,9 +3933,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 9deae2a..ad99e63 100644 (file)
@@ -471,7 +471,9 @@ namespace System {
 
                     bool slash = false;
                     if ((path[0] == '/') || (path[0] == '\\')) {
-                        String pathRoot = Path.GetPathRoot(appBase);
+                        string pathRoot = AppDomain.NormalizePath(appBase, fullCheck: false);
+                        pathRoot = pathRoot.Substring(0, IO.PathInternal.GetRootLength(pathRoot));
+
                         if (pathRoot.Length == 0) { // URL
                             int index = appBase.IndexOf(":/", StringComparison.Ordinal);
                             if (index == -1)
@@ -516,7 +518,7 @@ namespace System {
                     path = StringBuilderCache.GetStringAndRelease(result);
                 }
                 else
-                    path = Path.GetFullPathInternal(path);
+                    path = AppDomain.NormalizePath(path, fullCheck: true);
             }
 
             return path;
diff --git a/src/mscorlib/src/System/IO/LongPathHelper.cs b/src/mscorlib/src/System/IO/LongPathHelper.cs
new file mode 100644 (file)
index 0000000..5adfb07
--- /dev/null
@@ -0,0 +1,367 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics.Contracts;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using Microsoft.Win32;
+
+namespace System.IO
+{
+    /// <summary>
+    /// Wrapper to help with path normalization.
+    /// </summary>
+    internal class LongPathHelper
+    {
+        // Can't be over 8.3 and be a short name
+        private const int MaxShortName = 12;
+
+        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;
+
+        /// <summary>
+        /// Normalize the given path.
+        /// </summary>
+        /// <remarks>
+        /// Normalizes via Win32 GetFullPathName(). It will also trim all "typical" whitespace at the end of the path (see s_trimEndChars). Will also trim initial
+        /// spaces if the path is determined to be rooted.
+        /// 
+        /// Note that invalid characters will be checked after the path is normalized, which could remove bad characters. (C:\|\..\a.txt -- C:\a.txt)
+        /// </remarks>
+        /// <param name="path">Path to normalize</param>
+        /// <param name="checkInvalidCharacters">True to check for invalid characters</param>
+        /// <param name="expandShortPaths">Attempt to expand short paths if true</param>
+        /// <exception cref="ArgumentException">Thrown if the path is an illegal UNC (does not contain a full server/share) or contains illegal characters.</exception>
+        /// <exception cref="PathTooLongException">Thrown if the path or a path segment exceeds the filesystem limits.</exception>
+        /// <exception cref="FileNotFoundException">Thrown if Windows returns ERROR_FILE_NOT_FOUND. (See Win32Marshal.GetExceptionForWin32Error)</exception>
+        /// <exception cref="DirectoryNotFoundException">Thrown if Windows returns ERROR_PATH_NOT_FOUND. (See Win32Marshal.GetExceptionForWin32Error)</exception>
+        /// <exception cref="UnauthorizedAccessException">Thrown if Windows returns ERROR_ACCESS_DENIED. (See Win32Marshal.GetExceptionForWin32Error)</exception>
+        /// <exception cref="IOException">Thrown if Windows returns an error that doesn't map to the above. (See Win32Marshal.GetExceptionForWin32Error)</exception>
+        /// <returns>Normalized path</returns>
+        [System.Security.SecurityCritical]
+        unsafe internal static string Normalize(string path, uint maxPathLength, bool checkInvalidCharacters, bool expandShortPaths)
+        {
+            // Get the full path
+            StringBuffer fullPath = t_fullPathBuffer ?? (t_fullPathBuffer = new StringBuffer(PathInternal.MaxShortPath));
+            try
+            {
+                GetFullPathName(path, fullPath);
+
+                // Trim whitespace off the end of the string. Win32 normalization trims only U+0020.
+                fullPath.TrimEnd(s_trimEndChars);
+
+                if (fullPath.Length >= maxPathLength)
+                {
+                    // Fullpath is genuinely too long
+                    throw new PathTooLongException();
+                }
+
+                // Checking path validity used to happen before getting the full path name. To avoid additional input allocation
+                // (to trim trailing whitespace) we now do it after the Win32 call. This will allow legitimate paths through that
+                // used to get kicked back (notably segments with invalid characters might get removed via "..").
+                //
+                // There is no way that GetLongPath can invalidate the path so we'll do this (cheaper) check before we attempt to
+                // expand short file names.
+
+                // Scan the path for:
+                //
+                //  - Illegal path characters.
+                //  - Invalid UNC paths like \\, \\server, \\server\.
+                //  - Segments that are too long (over MaxComponentLength)
+
+                // As the path could be > 60K, we'll combine the validity scan. None of these checks are performed by the Win32
+                // GetFullPathName() API.
+
+                bool possibleShortPath = false;
+                bool foundTilde = false;
+
+                // We can get UNCs as device paths through this code (e.g. \\.\UNC\), we won't validate them as there isn't
+                // an easy way to normalize without extensive cost (we'd have to hunt down the canonical name for any device
+                // path that contains UNC or  to see if the path was doing something like \\.\GLOBALROOT\Device\Mup\,
+                // \\.\GLOBAL\UNC\, \\.\GLOBALROOT\GLOBAL??\UNC\, etc.
+                bool specialPath = fullPath.Length > 1 && fullPath[0] == '\\' && fullPath[1] == '\\';
+                bool isDevice = PathInternal.IsDevice(fullPath);
+                bool possibleBadUnc = specialPath && !isDevice;
+                uint index = specialPath ? 2u : 0;
+                uint lastSeparator = specialPath ? 1u : 0;
+                uint segmentLength;
+                char* start = fullPath.CharPointer;
+                char current;
+
+                while (index < fullPath.Length)
+                {
+                    current = start[index];
+
+                    // Try to skip deeper analysis. '?' and higher are valid/ignorable except for '\', '|', and '~'
+                    if (current < '?' || current == '\\' || current == '|' || current == '~')
+                    {
+                        switch (current)
+                        {
+                            case '|':
+                            case '>':
+                            case '<':
+                            case '\"':
+                                if (checkInvalidCharacters) throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
+                                // No point in expanding a bad path
+                                foundTilde = false;
+                                break;
+                            case '~':
+                                foundTilde = true;
+                                break;
+                            case '\\':
+                                segmentLength = index - lastSeparator - 1;
+                                if (segmentLength > (uint)PathInternal.MaxComponentLength)
+                                    throw new PathTooLongException();
+                                lastSeparator = index;
+
+                                if (foundTilde)
+                                {
+                                    if (segmentLength <= MaxShortName)
+                                    {
+                                        // Possibly a short path.
+                                        possibleShortPath = true;
+                                    }
+
+                                    foundTilde = false;
+                                }
+
+                                if (possibleBadUnc)
+                                {
+                                    // If we're at the end of the path and this is the first separator, we're missing the share.
+                                    // Otherwise we're good, so ignore UNC tracking from here.
+                                    if (index == fullPath.Length - 1)
+                                        throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC"));
+                                    else
+                                        possibleBadUnc = false;
+                                }
+
+                                break;
+
+                            default:
+                                if (checkInvalidCharacters && current < ' ') throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
+                                break;
+                        }
+                    }
+
+                    index++;
+                }
+
+                if (possibleBadUnc)
+                    throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC"));
+
+                segmentLength = fullPath.Length - lastSeparator - 1;
+                if (segmentLength > (uint)PathInternal.MaxComponentLength)
+                    throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
+
+                if (foundTilde && segmentLength <= MaxShortName)
+                    possibleShortPath = true;
+
+                // Check for a short filename path and try and expand it. Technically you don't need to have a tilde for a short name, but
+                // this is how we've always done this. This expansion is costly so we'll continue to let other short paths slide.
+                if (expandShortPaths && possibleShortPath)
+                {
+                    return TryExpandShortFileName(fullPath, originalPath: path);
+                }
+                else
+                {
+                    if (fullPath.Length == (uint)path.Length && fullPath.StartsWith(path))
+                    {
+                        // If we have the exact same string we were passed in, don't bother to allocate another string from the StringBuilder.
+                        return path;
+                    }
+                    else
+                    {
+                        return fullPath.ToString();
+                    }
+                }
+            }
+            finally
+            {
+                // Clear the buffer
+                fullPath.Free();
+            }
+        }
+
+        [System.Security.SecurityCritical]
+        unsafe private static void GetFullPathName(string path, StringBuffer fullPath)
+        {
+            // If the string starts with an extended prefix we would need to remove it from the path before we call GetFullPathName as
+            // it doesn't root extended paths correctly. We don't currently resolve extended paths, so we'll just assert here.
+            Contract.Assert(PathInternal.IsPartiallyQualified(path) || !PathInternal.IsExtended(path));
+
+            // Historically we would skip leading spaces *only* if the path started with a drive " C:" or a UNC " \\"
+            int startIndex = PathInternal.PathStartSkip(path);
+
+            fixed (char* pathStart = path)
+            {
+                uint result = 0;
+                while ((result = Win32Native.GetFullPathNameW(pathStart + startIndex, fullPath.CharCapacity, fullPath.GetHandle(), IntPtr.Zero)) > fullPath.CharCapacity)
+                {
+                    // Reported size (which does not include the null) is greater than the buffer size. Increase the capacity.
+                    fullPath.EnsureCharCapacity(result);
+                }
+
+                if (result == 0)
+                {
+                    // Failure, get the error and throw
+                    int errorCode = Marshal.GetLastWin32Error();
+                    if (errorCode == 0)
+                        errorCode = Win32Native.ERROR_BAD_PATHNAME;
+                    __Error.WinIOError(errorCode, path);
+                }
+
+                fullPath.Length = result;
+            }
+        }
+
+        [System.Security.SecurityCritical]
+        unsafe internal static string GetLongPathName(StringBuffer path)
+        {
+            using (StringBuffer outputBuffer = new StringBuffer(path.Length))
+            {
+                uint result = 0;
+                while ((result = Win32Native.GetLongPathNameW(path.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity)) > outputBuffer.CharCapacity)
+                {
+                    // Reported size (which does not include the null) is greater than the buffer size. Increase the capacity.
+                    outputBuffer.EnsureCharCapacity(result);
+                }
+
+                if (result == 0)
+                {
+                    // Failure, get the error and throw
+                    GetErrorAndThrow(path.ToString());
+                }
+
+                outputBuffer.Length = result;
+                return outputBuffer.ToString();
+            }
+        }
+
+        [System.Security.SecurityCritical]
+        unsafe internal static string GetLongPathName(string path)
+        {
+            using (StringBuffer outputBuffer = new StringBuffer((uint)path.Length))
+            {
+                uint result = 0;
+                while ((result = Win32Native.GetLongPathNameW(path, outputBuffer.GetHandle(), outputBuffer.CharCapacity)) > outputBuffer.CharCapacity)
+                {
+                    // Reported size (which does not include the null) is greater than the buffer size. Increase the capacity.
+                    outputBuffer.EnsureCharCapacity(result);
+                }
+
+                if (result == 0)
+                {
+                    // Failure, get the error and throw
+                    GetErrorAndThrow(path);
+                }
+
+                outputBuffer.Length = result;
+                return outputBuffer.ToString();
+            }
+        }
+
+        [System.Security.SecurityCritical]
+        private static void GetErrorAndThrow(string path)
+        {
+            int errorCode = Marshal.GetLastWin32Error();
+            if (errorCode == 0)
+                errorCode = Win32Native.ERROR_BAD_PATHNAME;
+            __Error.WinIOError(errorCode, path);
+        }
+
+        [System.Security.SecuritySafeCritical]
+        private unsafe static string TryExpandShortFileName(StringBuffer outputBuffer, string originalPath)
+        {
+            // We guarantee we'll expand short names for paths that only partially exist. As such, we need to find the part of the path that actually does exist. To
+            // avoid allocating like crazy we'll create only one input array and modify the contents with embedded nulls.
+
+            Contract.Assert(!PathInternal.IsPartiallyQualified(outputBuffer), "should have resolved by now");
+
+            using (StringBuffer inputBuffer = new StringBuffer(outputBuffer))
+            {
+                bool success = false;
+                uint lastIndex = outputBuffer.Length - 1;
+                uint foundIndex = lastIndex;
+                uint rootLength = PathInternal.GetRootLength(outputBuffer);
+
+                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);
+                    }
+                    else
+                    {
+                        // Found the path
+                        success = true;
+                        outputBuffer.Length = result;
+                        if (foundIndex < lastIndex)
+                        {
+                            // It was a partial find, put the non-existant part of the path back
+                            outputBuffer.Append(inputBuffer, foundIndex, inputBuffer.Length - foundIndex);
+                        }
+                    }
+                }
+
+                StringBuffer bufferToUse = success ? outputBuffer : inputBuffer;
+
+                if (bufferToUse.SubstringEquals(originalPath))
+                {
+                    // Use the original path to avoid allocating
+                    return originalPath;
+                }
+
+                return bufferToUse.ToString();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/mscorlib/src/System/IO/PathInternal.cs b/src/mscorlib/src/System/IO/PathInternal.cs
new file mode 100644 (file)
index 0000000..ac1afb8
--- /dev/null
@@ -0,0 +1,661 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using Microsoft.Win32;
+using System;
+using System.Diagnostics.Contracts;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Security;
+using System.Text;
+
+namespace System.IO
+{
+    /// <summary>Contains internal path helpers that are shared between many projects.</summary>
+    internal static class PathInternal
+    {
+        internal const string ExtendedPathPrefix = @"\\?\";
+        internal const string UncPathPrefix = @"\\";
+        internal const string UncExtendedPrefixToInsert = @"?\UNC\";
+        internal const string UncExtendedPathPrefix = @"\\?\UNC\";
+        internal const string DevicePathPrefix = @"\\.\";
+        internal const int DevicePrefixLength = 4;
+        internal const int MaxShortPath = 260;
+        internal const int MaxShortDirectoryPath = 248;
+
+        // 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.
+        // https://msdn.microsoft.com/en-us/library/windows/hardware/ff564879.aspx
+        internal const int MaxLongPath = short.MaxValue;
+        internal static readonly int MaxComponentLength = 255;
+
+        internal static readonly char[] InvalidPathChars =
+        {
+            '\"', '<', '>', '|', '\0',
+            (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
+            (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
+            (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
+            (char)31
+        };
+
+        /// <summary>
+        /// Validates volume separator only occurs as C: or \\?\C:. This logic is meant to filter out Alternate Data Streams.
+        /// </summary>
+        /// <returns>True if the path has an invalid volume separator.</returns>
+        internal static bool HasInvalidVolumeSeparator(string path)
+        {
+            // Toss out paths with colons that aren't a valid drive specifier.
+            // Cannot start with a colon and can only be of the form "C:" or "\\?\C:".
+            // (Note that we used to explicitly check "http:" and "file:"- these are caught by this check now.)
+
+            // We don't care about skipping starting space for extended paths. Assume no knowledge of extended paths if we're forcing old path behavior.
+            bool isExtended = 
+#if FEATURE_PATHCOMPAT
+                !AppContextSwitches.UseLegacyPathHandling &&
+#endif
+                IsExtended(path);
+            int startIndex = isExtended ? ExtendedPathPrefix.Length : PathStartSkip(path);
+
+            // If we start with a colon
+            if ((path.Length > startIndex && path[startIndex] == Path.VolumeSeparatorChar)
+                // Or have an invalid drive letter and colon
+                || (path.Length >= startIndex + 2 && path[startIndex + 1] == Path.VolumeSeparatorChar && !IsValidDriveChar(path[startIndex]))
+                // Or have any colons beyond the drive colon
+                || (path.Length > startIndex + 2 && path.IndexOf(Path.VolumeSeparatorChar, startIndex + 2) != -1))
+            {
+                return true;
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Returns true if the given StringBuilder starts with the given value.
+        /// </summary>
+        /// <param name="value">The string to compare against the start of the StringBuilder.</param>
+        internal static bool StartsWithOrdinal(StringBuilder builder, string value, bool ignoreCase = false)
+        {
+            if (value == null || builder.Length < value.Length)
+                return false;
+
+            if (ignoreCase)
+            {
+                for (int i = 0; i < value.Length; i++)
+                    if (char.ToUpperInvariant(builder[i]) != char.ToUpperInvariant(value[i])) return false;
+            }
+            else
+            {
+                for (int i = 0; i < value.Length; i++)
+                    if (builder[i] != value[i]) return false;
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// Returns true if the given character is a valid drive letter
+        /// </summary>
+        internal static bool IsValidDriveChar(char value)
+        {
+            return ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z'));
+        }
+
+        /// <summary>
+        /// Returns true if the path is too long
+        /// </summary>
+        internal static bool IsPathTooLong(string fullPath)
+        {
+            // We'll never know precisely what will fail as paths get changed internally in Windows and
+            // may grow beyond / shrink below exceed MaxLongPath.
+#if FEATURE_PATHCOMPAT
+            if (AppContextSwitches.BlockLongPaths)
+            {
+                // We allow paths of any length if extended (and not in compat mode)
+                if (AppContextSwitches.UseLegacyPathHandling || !IsExtended(fullPath))
+                    return fullPath.Length >= MaxShortPath;
+            }
+#endif
+
+            return fullPath.Length >= MaxLongPath;
+        }
+
+        /// <summary>
+        /// Return true if any path segments are too long
+        /// </summary>
+        internal static bool AreSegmentsTooLong(string fullPath)
+        {
+            int length = fullPath.Length;
+            int lastSeparator = 0;
+
+            for (int i = 0; i < length; i++)
+            {
+                if (IsDirectorySeparator(fullPath[i]))
+                {
+                    if (i - lastSeparator > MaxComponentLength)
+                        return true;
+                    lastSeparator = i;
+                }
+            }
+
+            if (length - 1 - lastSeparator > MaxComponentLength)
+                return true;
+
+            return false;
+        }
+
+        /// <summary>
+        /// Returns true if the directory is too long
+        /// </summary>
+        internal static bool IsDirectoryTooLong(string fullPath)
+        {
+#if FEATURE_PATHCOMPAT
+            if (AppContextSwitches.BlockLongPaths)
+            {
+                // We allow paths of any length if extended (and not in compat mode)
+                if (AppContextSwitches.UseLegacyPathHandling || !IsExtended(fullPath))
+                    return (fullPath.Length >= MaxShortDirectoryPath);
+            }
+#endif
+
+            return IsPathTooLong(fullPath);
+        }
+
+        /// <summary>
+        /// Adds the extended path prefix (\\?\) if not relative or already a device path.
+        /// </summary>
+        internal static string EnsureExtendedPrefix(string path)
+        {
+            // Putting the extended prefix on the path changes the processing of the path. It won't get normalized, which
+            // means adding to relative paths will prevent them from getting the appropriate current directory inserted.
+
+            // If it already has some variant of a device path (\??\, \\?\, \\.\, //./, etc.) we don't need to change it
+            // as it is either correct or we will be changing the behavior. When/if Windows supports long paths implicitly
+            // in the future we wouldn't want normalization to come back and break existing code.
+
+            // In any case, all internal usages should be hitting normalize path (Path.GetFullPath) before they hit this
+            // shimming method. (Or making a change that doesn't impact normalization, such as adding a filename to a
+            // normalized base path.)
+            if (IsPartiallyQualified(path) || IsDevice(path))
+                return path;
+
+            // Given \\server\share in longpath becomes \\?\UNC\server\share
+            if (path.StartsWith(UncPathPrefix, StringComparison.OrdinalIgnoreCase))
+                return path.Insert(2, UncExtendedPrefixToInsert);
+
+            return ExtendedPathPrefix + path;
+        }
+
+        /// <summary>
+        /// Removes the extended path prefix (\\?\) if present.
+        /// </summary>
+        internal static string RemoveExtendedPrefix(string path)
+        {
+            if (!IsExtended(path))
+                return path;
+
+            // Given \\?\UNC\server\share we return \\server\share
+            if (IsExtendedUnc(path))
+                return path.Remove(2, 6);
+
+            return path.Substring(DevicePrefixLength);
+        }
+
+        /// <summary>
+        /// Removes the extended path prefix (\\?\) if present.
+        /// </summary>
+        internal static StringBuilder RemoveExtendedPrefix(StringBuilder path)
+        {
+            if (!IsExtended(path))
+                return path;
+
+            // Given \\?\UNC\server\share we return \\server\share
+            if (IsExtendedUnc(path))
+                return path.Remove(2, 6);
+
+            return path.Remove(0, DevicePrefixLength);
+        }
+
+        /// <summary>
+        /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\")
+        /// </summary>
+        internal static bool IsDevice(string path)
+        {
+            // If the path begins with any two separators it will be recognized and normalized and prepped with
+            // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not.
+            return IsExtended(path)
+                ||
+                (
+                    path.Length >= DevicePrefixLength
+                    && IsDirectorySeparator(path[0])
+                    && IsDirectorySeparator(path[1])
+                    && (path[2] == '.' || path[2] == '?')
+                    && IsDirectorySeparator(path[3])
+                );
+        }
+
+        /// <summary>
+        /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\")
+        /// </summary>
+        internal static bool IsDevice(StringBuffer path)
+        {
+            // If the path begins with any two separators it will be recognized and normalized and prepped with
+            // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not.
+            return IsExtended(path)
+                ||
+                (
+                    path.Length >= DevicePrefixLength
+                    && IsDirectorySeparator(path[0])
+                    && IsDirectorySeparator(path[1])
+                    && (path[2] == '.' || path[2] == '?')
+                    && IsDirectorySeparator(path[3])
+                );
+        }
+
+        /// <summary>
+        /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the
+        /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization
+        /// and path length checks.
+        /// </summary>
+        internal static bool IsExtended(string path)
+        {
+            // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths.
+            // Skipping of normalization will *only* occur if back slashes ('\') are used.
+            return path.Length >= DevicePrefixLength
+                && path[0] == '\\'
+                && (path[1] == '\\' || path[1] == '?')
+                && path[2] == '?'
+                && path[3] == '\\';
+        }
+
+        /// <summary>
+        /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the
+        /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization
+        /// and path length checks.
+        /// </summary>
+        internal static bool IsExtended(StringBuilder path)
+        {
+            // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths.
+            // Skipping of normalization will *only* occur if back slashes ('\') are used.
+            return path.Length >= DevicePrefixLength
+                && path[0] == '\\'
+                && (path[1] == '\\' || path[1] == '?')
+                && path[2] == '?'
+                && path[3] == '\\';
+        }
+
+        /// <summary>
+        /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the
+        /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization
+        /// and path length checks.
+        /// </summary>
+        internal static bool IsExtended(StringBuffer path)
+        {
+            // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths.
+            // Skipping of normalization will *only* occur if back slashes ('\') are used.
+            return path.Length >= DevicePrefixLength
+                && path[0] == '\\'
+                && (path[1] == '\\' || path[1] == '?')
+                && path[2] == '?'
+                && path[3] == '\\';
+        }
+
+        /// <summary>
+        /// Returns true if the path uses the extended UNC syntax (\\?\UNC\ or \??\UNC\)
+        /// </summary>
+        internal static bool IsExtendedUnc(string path)
+        {
+            return path.Length >= UncExtendedPathPrefix.Length
+                && IsExtended(path)
+                && char.ToUpper(path[4]) == 'U'
+                && char.ToUpper(path[5]) == 'N'
+                && char.ToUpper(path[6]) == 'C'
+                && path[7] == '\\';
+        }
+
+        /// <summary>
+        /// Returns true if the path uses the extended UNC syntax (\\?\UNC\ or \??\UNC\)
+        /// </summary>
+        internal static bool IsExtendedUnc(StringBuilder path)
+        {
+            return path.Length >= UncExtendedPathPrefix.Length
+                && IsExtended(path)
+                && char.ToUpper(path[4]) == 'U'
+                && char.ToUpper(path[5]) == 'N'
+                && char.ToUpper(path[6]) == 'C'
+                && path[7] == '\\';
+        }
+
+        /// <summary>
+        /// Returns a value indicating if the given path contains invalid characters (", &lt;, &gt;, | 
+        /// NUL, or any ASCII char whose integer representation is in the range of 1 through 31).
+        /// Does not check for wild card characters ? and *.
+        ///
+        /// Will not check if the path is a device path and not in Legacy mode as many of these
+        /// characters are valid for devices (pipes for example).
+        /// </summary>
+        internal static bool HasIllegalCharacters(string path, bool checkAdditional = false)
+        {
+            if (
+#if FEATURE_PATHCOMPAT
+            !AppContextSwitches.UseLegacyPathHandling &&
+#endif
+                IsDevice(path))
+            {
+                return false;
+            }
+
+            return AnyPathHasIllegalCharacters(path, checkAdditional: checkAdditional);
+        }
+
+        /// <summary>
+        /// Version of HasIllegalCharacters that checks no AppContextSwitches. Only use if you know you need to skip switches and don't care
+        /// about proper device path handling.
+        /// </summary>
+        internal static bool AnyPathHasIllegalCharacters(string path, bool checkAdditional = false)
+        {
+            return path.IndexOfAny(InvalidPathChars) >= 0 || (checkAdditional && AnyPathHasWildCardCharacters(path));
+        }
+
+        /// <summary>
+        /// Check for ? and *.
+        /// </summary>
+        internal static bool HasWildCardCharacters(string path)
+        {
+            // Question mark is part of some device paths
+            int startIndex =
+#if FEATURE_PATHCOMPAT
+            AppContextSwitches.UseLegacyPathHandling ? 0 : 
+#endif
+            IsDevice(path) ? ExtendedPathPrefix.Length : 0;
+            return AnyPathHasWildCardCharacters(path, startIndex: startIndex);
+        }
+
+        /// <summary>
+        /// Version of HasWildCardCharacters that checks no AppContextSwitches. Only use if you know you need to skip switches and don't care
+        /// about proper device path handling.
+        /// </summary>
+        internal static bool AnyPathHasWildCardCharacters(string path, int startIndex = 0)
+        {
+            char currentChar;
+            for (int i = startIndex; i < path.Length; i++)
+            {
+                currentChar = path[i];
+                if (currentChar == '*' || currentChar == '?') return true;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Gets the length of the root of the path (drive, share, etc.).
+        /// </summary>
+        [System.Security.SecuritySafeCritical]
+        internal unsafe static int GetRootLength(string path)
+        {
+            fixed (char* value = path)
+            {
+                return (int)GetRootLength(value, (ulong)path.Length);
+            }
+        }
+
+        /// <summary>
+        /// Gets the length of the root of the path (drive, share, etc.).
+        /// </summary>
+        [System.Security.SecuritySafeCritical]
+        internal unsafe static uint GetRootLength(StringBuffer path)
+        {
+            if (path.Length == 0) return 0;
+            return GetRootLength(path.CharPointer, path.Length);
+        }
+
+        [System.Security.SecurityCritical]
+        private unsafe static uint GetRootLength(char* path, ulong pathLength)
+        {
+            uint i = 0;
+            uint volumeSeparatorLength = 2;  // Length to the colon "C:"
+            uint uncRootLength = 2;          // Length to the start of the server name "\\"
+
+            bool extendedSyntax = StartsWithOrdinal(path, pathLength, ExtendedPathPrefix);
+            bool extendedUncSyntax = StartsWithOrdinal(path, pathLength, UncExtendedPathPrefix);
+            if (extendedSyntax)
+            {
+                // Shift the position we look for the root from to account for the extended prefix
+                if (extendedUncSyntax)
+                {
+                    // "\\" -> "\\?\UNC\"
+                    uncRootLength = (uint)UncExtendedPathPrefix.Length;
+                }
+                else
+                {
+                    // "C:" -> "\\?\C:"
+                    volumeSeparatorLength += (uint)ExtendedPathPrefix.Length;
+                }
+            }
+
+            if ((!extendedSyntax || extendedUncSyntax) && pathLength > 0 && IsDirectorySeparator(path[0]))
+            {
+                // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo")
+
+                i = 1; //  Drive rooted (\foo) is one character
+                if (extendedUncSyntax || (pathLength > 1 && IsDirectorySeparator(path[1])))
+                {
+                    // UNC (\\?\UNC\ or \\), scan past the next two directory separators at most
+                    // (e.g. to \\?\UNC\Server\Share or \\Server\Share\)
+                    i = uncRootLength;
+                    int n = 2; // Maximum separators to skip
+                    while (i < pathLength && (!IsDirectorySeparator(path[i]) || --n > 0)) i++;
+                }
+            }
+            else if (pathLength >= volumeSeparatorLength && path[volumeSeparatorLength - 1] == Path.VolumeSeparatorChar)
+            {
+                // Path is at least longer than where we expect a colon, and has a colon (\\?\A:, A:)
+                // If the colon is followed by a directory separator, move past it
+                i = volumeSeparatorLength;
+                if (pathLength >= volumeSeparatorLength + 1 && IsDirectorySeparator(path[volumeSeparatorLength])) i++;
+            }
+            return i;
+        }
+
+        [System.Security.SecurityCritical]
+        private unsafe static bool StartsWithOrdinal(char* source, ulong sourceLength, string value)
+        {
+            if (sourceLength < (ulong)value.Length) return false;
+            for (int i = 0; i < value.Length; i++)
+            {
+                if (value[i] != source[i]) return false;
+            }
+            return true;
+        }
+
+        /// <summary>
+        /// Returns true if the path specified is relative to the current drive or working directory.
+        /// Returns false if the path is fixed to a specific drive or UNC path.  This method does no
+        /// validation of the path (URIs will be returned as relative as a result).
+        /// </summary>
+        /// <remarks>
+        /// Handles paths that use the alternate directory separator.  It is a frequent mistake to
+        /// assume that rooted paths (Path.IsPathRooted) are not relative.  This isn't the case.
+        /// "C:a" is drive relative- meaning that it will be resolved against the current directory
+        /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory
+        /// will not be used to modify the path).
+        /// </remarks>
+        internal static bool IsPartiallyQualified(string path)
+        {
+            if (path.Length < 2)
+            {
+                // It isn't fixed, it must be relative.  There is no way to specify a fixed
+                // path with one character (or less).
+                return true;
+            }
+
+            if (IsDirectorySeparator(path[0]))
+            {
+                // There is no valid way to specify a relative path with two initial slashes or
+                // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\
+                return !(path[1] == '?' || IsDirectorySeparator(path[1]));
+            }
+
+            // The only way to specify a fixed path that doesn't begin with two slashes
+            // is the drive, colon, slash format- i.e. C:\
+            return !((path.Length >= 3)
+                && (path[1] == Path.VolumeSeparatorChar)
+                && IsDirectorySeparator(path[2])
+                // 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]));
+        }
+
+        /// <summary>
+        /// Returns true if the path specified is relative to the current drive or working directory.
+        /// Returns false if the path is fixed to a specific drive or UNC path.  This method does no
+        /// validation of the path (URIs will be returned as relative as a result).
+        /// </summary>
+        /// <remarks>
+        /// Handles paths that use the alternate directory separator.  It is a frequent mistake to
+        /// assume that rooted paths (Path.IsPathRooted) are not relative.  This isn't the case.
+        /// "C:a" is drive relative- meaning that it will be resolved against the current directory
+        /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory
+        /// will not be used to modify the path).
+        /// </remarks>
+        internal static bool IsPartiallyQualified(StringBuffer path)
+        {
+            if (path.Length < 2)
+            {
+                // It isn't fixed, it must be relative.  There is no way to specify a fixed
+                // path with one character (or less).
+                return true;
+            }
+
+            if (IsDirectorySeparator(path[0]))
+            {
+                // There is no valid way to specify a relative path with two initial slashes or
+                // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\
+                return !(path[1] == '?' || IsDirectorySeparator(path[1]));
+            }
+
+            // The only way to specify a fixed path that doesn't begin with two slashes
+            // is the drive, colon, slash format- i.e. C:\
+            return !((path.Length >= 3)
+                && (path[1] == Path.VolumeSeparatorChar)
+                && IsDirectorySeparator(path[2])
+                // 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]));
+        }
+
+        /// <summary>
+        /// 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>
+        /// Note that this conflicts with IsPathRooted() which doesn't (and never did) such a skip.
+        /// </remarks>
+        internal static int PathStartSkip(string path)
+        {
+            int startIndex = 0;
+            while (startIndex < path.Length && path[startIndex] == ' ') startIndex++;
+
+            if (startIndex > 0 && (startIndex < path.Length && IsDirectorySeparator(path[startIndex]))
+                || (startIndex + 1 < path.Length && path[startIndex + 1] == Path.VolumeSeparatorChar && IsValidDriveChar(path[startIndex])))
+            {
+                // Go ahead and skip spaces as we're either " C:" or " \"
+                return startIndex;
+            }
+
+            return 0;
+        }
+
+        /// <summary>
+        /// True if the given character is a directory separator.
+        /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static bool IsDirectorySeparator(char c)
+        {
+            return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
+        }
+
+        /// <summary>
+        /// Normalize separators in the given path. Converts forward slashes into back slashes and compresses slash runs, keeping initial 2 if present.
+        /// Also trims initial whitespace in front of "rooted" paths (see PathStartSkip).
+        /// 
+        /// This effectively replicates the behavior of the legacy NormalizePath when it was called with fullCheck=false and expandShortpaths=false.
+        /// The current NormalizePath gets directory separator normalization from Win32's GetFullPathName(), which will resolve relative paths and as
+        /// such can't be used here (and is overkill for our uses).
+        /// 
+        /// Like the current NormalizePath this will not try and analyze periods/spaces within directory segments.
+        /// </summary>
+        /// <remarks>
+        /// The only callers that used to use Path.Normalize(fullCheck=false) were Path.GetDirectoryName() and Path.GetPathRoot(). Both usages do
+        /// not need trimming of trailing whitespace here.
+        /// 
+        /// GetPathRoot() could technically skip normalizing separators after the second segment- consider as a future optimization.
+        /// 
+        /// For legacy desktop behavior with ExpandShortPaths:
+        ///  - It has no impact on GetPathRoot() so doesn't need consideration.
+        ///  - It could impact GetDirectoryName(), but only if the path isn't relative (C:\ or \\Server\Share).
+        /// 
+        /// In the case of GetDirectoryName() the ExpandShortPaths behavior was undocumented and provided inconsistent results if the path was
+        /// fixed/relative. For example: "C:\PROGRA~1\A.TXT" would return "C:\Program Files" while ".\PROGRA~1\A.TXT" would return ".\PROGRA~1". If you
+        /// ultimately call GetFullPath() this doesn't matter, but if you don't or have any intermediate string handling could easily be tripped up by
+        /// this undocumented behavior.
+        /// </remarks>
+        internal static string NormalizeDirectorySeparators(string path)
+        {
+            if (string.IsNullOrEmpty(path)) return path;
+
+            char current;
+            int start = PathStartSkip(path);
+
+            if (start == 0)
+            {
+                // Make a pass to see if we need to normalize so we can potentially skip allocating
+                bool normalized = true;
+
+                for (int i = 0; i < path.Length; i++)
+                {
+                    current = path[i];
+                    if (IsDirectorySeparator(current)
+                        && (current != Path.DirectorySeparatorChar
+                            // 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]))))
+                    {
+                        normalized = false;
+                        break;
+                    }
+                }
+
+                if (normalized) return path;
+            }
+
+            StringBuilder builder = StringBuilderCache.Acquire(path.Length);
+
+            if (IsDirectorySeparator(path[start]))
+            {
+                start++;
+                builder.Append(Path.DirectorySeparatorChar);
+            }
+
+            for (int i = start; i < path.Length; i++)
+            {
+                current = path[i];
+
+                // If we have a separator
+                if (IsDirectorySeparator(current))
+                {
+                    // If the next is a separator, skip adding this
+                    if (i + 1 < path.Length && IsDirectorySeparator(path[i + 1]))
+                    {
+                        continue;
+                    }
+
+                    // Ensure it is the primary separator
+                    current = Path.DirectorySeparatorChar;
+                }
+
+                builder.Append(current);
+            }
+
+            return StringBuilderCache.GetStringAndRelease(builder);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/mscorlib/src/System/Runtime/InteropServices/NativeBuffer.cs b/src/mscorlib/src/System/Runtime/InteropServices/NativeBuffer.cs
new file mode 100644 (file)
index 0000000..ebe5b7b
--- /dev/null
@@ -0,0 +1,194 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+
+namespace System.Runtime.InteropServices
+{
+    /// <summary>
+    /// Wrapper for access to the native heap. Dispose to free the memory. Try to use with using statements.
+    /// Does not allocate zero size buffers, and will free the existing native buffer if capacity is dropped to zero.
+    /// 
+#if !FEATURE_CORECLR
+    /// NativeBuffer utilizes a cache of heap buffers.
+#endif
+    /// </summary>
+    /// <remarks>
+    /// Suggested use through P/Invoke: define DllImport arguments that take a byte buffer as SafeHandle.
+    /// 
+    /// Using SafeHandle will ensure that the buffer will not get collected during a P/Invoke.
+    /// (Notably AddRef and ReleaseRef will be called by the interop layer.)
+    /// 
+    /// This class is not threadsafe, changing the capacity or disposing on multiple threads risks duplicate heap
+    /// handles or worse.
+    /// </remarks>
+    internal class NativeBuffer : IDisposable
+    {
+#if !FEATURE_CORECLR
+        // The need for caching the heap handles isn't as great in CoreCLR as most current usages of this class'
+        // consumers are wrapped by CoreFx. (As opposed to NetFX 4.6 where there is no wrapping)
+        private readonly static SafeHeapHandleCache s_handleCache;
+#endif
+        [System.Security.SecurityCritical]
+        private readonly static SafeHandle s_emptyHandle;
+        [System.Security.SecurityCritical]
+        private SafeHeapHandle _handle;
+        private ulong _capacity;
+
+        [System.Security.SecuritySafeCritical]
+        static NativeBuffer()
+        {
+            s_emptyHandle = new EmptySafeHandle();
+#if !FEATURE_CORECLR
+            s_handleCache = new SafeHeapHandleCache();
+#endif
+        }
+
+        /// <summary>
+        /// Create a buffer with at least the specified initial capacity in bytes.
+        /// </summary>
+        public NativeBuffer(ulong initialMinCapacity = 0)
+        {
+            EnsureByteCapacity(initialMinCapacity);
+        }
+
+        protected unsafe void* VoidPointer
+        {
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            [System.Security.SecurityCritical]
+            get
+            {
+                return _handle == null ? null : _handle.DangerousGetHandle().ToPointer();
+            }
+        }
+
+        protected unsafe byte* BytePointer
+        {
+            [System.Security.SecurityCritical]
+            get
+            {
+                return (byte*)VoidPointer;
+            }
+        }
+
+        /// <summary>
+        /// Get the handle for the buffer.
+        /// </summary>
+        [System.Security.SecuritySafeCritical]
+        public SafeHandle GetHandle()
+        {
+            // Marshalling code will throw on null for SafeHandle
+            return _handle ?? s_emptyHandle;
+        }
+
+        /// <summary>
+        /// The capacity of the buffer in bytes.
+        /// </summary>
+        public ulong ByteCapacity
+        {
+            get { return _capacity; }
+        }
+
+        /// <summary>
+        /// Ensure capacity in bytes is at least the given minimum.
+        /// </summary>
+        /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception>
+        /// <exception cref="ArgumentOutOfRangeException">Thrown if attempting to set <paramref name="nameof(minCapacity)"/> to a value that is larger than the maximum addressable memory.</exception>
+        [System.Security.SecuritySafeCritical]
+        public void EnsureByteCapacity(ulong minCapacity)
+        {
+            if (_capacity < minCapacity)
+            {
+                Resize(minCapacity);
+                _capacity = minCapacity;
+            }
+        }
+
+        public unsafe byte this[ulong index]
+        {
+            [System.Security.SecuritySafeCritical]
+            get
+            {
+                if (index >= _capacity) throw new ArgumentOutOfRangeException();
+                return BytePointer[index];
+            }
+            [System.Security.SecuritySafeCritical]
+            set
+            {
+                if (index >= _capacity) throw new ArgumentOutOfRangeException();
+                BytePointer[index] = value;
+            }
+        }
+
+        [System.Security.SecuritySafeCritical]
+        private unsafe void Resize(ulong byteLength)
+        {
+            if (byteLength == 0)
+            {
+                ReleaseHandle();
+                return;
+            }
+
+            if (_handle == null)
+            {
+#if FEATURE_CORECLR
+                _handle = new SafeHeapHandle(byteLength);
+#else
+                _handle = s_handleCache.Acquire(byteLength);
+#endif
+            }
+            else
+            {
+                _handle.Resize(byteLength);
+            }
+        }
+
+        [System.Security.SecuritySafeCritical]
+        private void ReleaseHandle()
+        {
+            if (_handle != null)
+            {
+#if !FEATURE_CORECLR
+                s_handleCache.Release(_handle);
+#endif
+                _capacity = 0;
+                _handle = null;
+            }
+        }
+
+        /// <summary>
+        /// Release the backing buffer
+        /// </summary>
+        [System.Security.SecuritySafeCritical]
+        public virtual void Free()
+        {
+            ReleaseHandle();
+        }
+
+        [System.Security.SecuritySafeCritical]
+        public void Dispose()
+        {
+            Free();
+        }
+
+        [System.Security.SecurityCritical]
+        private sealed class EmptySafeHandle : SafeHandle
+        {
+            public EmptySafeHandle() : base(IntPtr.Zero, true) { }
+
+            public override bool IsInvalid
+            {
+                [System.Security.SecurityCritical]
+                get
+                { return true; }
+            }
+
+            [System.Security.SecurityCritical]
+            protected override bool ReleaseHandle()
+            {
+                return true;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/mscorlib/src/System/Runtime/InteropServices/SafeHeapHandle.cs b/src/mscorlib/src/System/Runtime/InteropServices/SafeHeapHandle.cs
new file mode 100644 (file)
index 0000000..b0c422d
--- /dev/null
@@ -0,0 +1,115 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+    /// <summary>
+    /// Handle for heap memory that allows tracking of capacity and reallocating.
+    /// </summary>
+    [System.Security.SecurityCritical]
+    internal sealed class SafeHeapHandle : SafeBuffer
+    {
+        /// <summary>
+        /// Allocate a buffer of the given size if requested.
+        /// </summary>
+        /// <param name="byteLength">Required size in bytes. Must be less than UInt32.MaxValue for 32 bit or UInt64.MaxValue for 64 bit.</param>
+        /// <exception cref="OutOfMemoryException">Thrown if the requested memory size cannot be allocated.</exception>
+        /// <exception cref="ArgumentOutOfRangeException">Thrown if size is greater than the maximum memory size.</exception>
+        public SafeHeapHandle(ulong byteLength) : base(ownsHandle: true)
+        {
+            Resize(byteLength);
+        }
+
+        public override bool IsInvalid
+        {
+            [System.Security.SecurityCritical]
+            get
+            { return handle == IntPtr.Zero; }
+        }
+
+        /// <summary>
+        /// Resize the buffer to the given size if requested.
+        /// </summary>
+        /// <param name="byteLength">Required size in bytes. Must be less than UInt32.MaxValue for 32 bit or UInt64.MaxValue for 64 bit.</param>
+        /// <exception cref="OutOfMemoryException">Thrown if the requested memory size cannot be allocated.</exception>
+        /// <exception cref="ArgumentOutOfRangeException">Thrown if size is greater than the maximum memory size.</exception>
+        public void Resize(ulong byteLength)
+        {
+            if (IsClosed) throw new ObjectDisposedException("SafeHeapHandle");
+
+            ulong originalLength = 0;
+            if (handle == IntPtr.Zero)
+            {
+                handle = Marshal.AllocHGlobal((IntPtr)byteLength);
+            }
+            else
+            {
+                originalLength = ByteLength;
+
+                // This may or may not be the same handle, may realloc in place. If the
+                // handle changes Windows will deal with the old handle, trying to free it will
+                // cause an error.
+                handle = Marshal.ReAllocHGlobal(pv: handle, cb: (IntPtr)byteLength);
+            }
+
+            if (handle == IntPtr.Zero)
+            {
+                // Only real plausible answer
+                throw new OutOfMemoryException();
+            }
+
+            if (byteLength > originalLength)
+            {
+                // Add pressure
+                ulong addedBytes = byteLength - originalLength;
+                if (addedBytes > long.MaxValue)
+                {
+                    GC.AddMemoryPressure(long.MaxValue);
+                    GC.AddMemoryPressure((long)(addedBytes - long.MaxValue));
+                }
+                else
+                {
+                    GC.AddMemoryPressure((long)addedBytes);
+                }
+            }
+            else
+            {
+                // Shrank or did nothing, release pressure if needed
+                RemoveMemoryPressure(originalLength - byteLength);
+            }
+
+            Initialize(byteLength);
+        }
+
+        private void RemoveMemoryPressure(ulong removedBytes)
+        {
+            if (removedBytes == 0) return;
+
+            if (removedBytes > long.MaxValue)
+            {
+                GC.RemoveMemoryPressure(long.MaxValue);
+                GC.RemoveMemoryPressure((long)(removedBytes - long.MaxValue));
+            }
+            else
+            {
+                GC.RemoveMemoryPressure((long)removedBytes);
+            }
+        }
+
+        [System.Security.SecurityCritical]
+        protected override bool ReleaseHandle()
+        {
+            IntPtr handle = this.handle;
+            this.handle = IntPtr.Zero;
+
+            if (handle != IntPtr.Zero)
+            {
+                RemoveMemoryPressure(ByteLength);
+                Marshal.FreeHGlobal(handle);
+            }
+
+            return true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/mscorlib/src/System/Runtime/InteropServices/StringBuffer.cs b/src/mscorlib/src/System/Runtime/InteropServices/StringBuffer.cs
new file mode 100644 (file)
index 0000000..15b1b6a
--- /dev/null
@@ -0,0 +1,402 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+    /// <summary>
+    /// Native buffer that deals in char size increments. Dispose to free memory. Allows buffers larger
+    /// than a maximum size string to enable working with very large string arrays.  Always makes ordinal
+    /// comparisons.
+    /// 
+    /// A more performant replacement for StringBuilder when performing native interop.
+    /// </summary>
+    /// <remarks>
+    /// Suggested use through P/Invoke: define DllImport arguments that take a character buffer as IntPtr.
+    /// NativeStringBuffer has an implicit conversion to IntPtr.
+    /// </remarks>
+    internal class StringBuffer : NativeBuffer
+    {
+        private uint _length;
+
+        /// <summary>
+        /// Instantiate the buffer with capacity for at least the specified number of characters. Capacity
+        /// includes the trailing null character.
+        /// </summary>
+        public StringBuffer(uint initialCapacity = 0)
+            : base(initialCapacity * (ulong)sizeof(char))
+        {
+        }
+
+        /// <summary>
+        /// Instantiate the buffer with a copy of the specified string.
+        /// </summary>
+        public StringBuffer(string initialContents)
+            : base(0)
+        {
+            // We don't pass the count of bytes to the base constructor, appending will
+            // initialize to the correct size for the specified initial contents.
+            if (initialContents != null)
+            {
+                Append(initialContents);
+            }
+        }
+
+        /// <summary>
+        /// Instantiate the buffer with a copy of the specified StringBuffer.
+        /// </summary>
+        public StringBuffer(StringBuffer initialContents)
+            : base(0)
+        {
+            // We don't pass the count of bytes to the base constructor, appending will
+            // initialize to the correct size for the specified initial contents.
+            if (initialContents != null)
+            {
+                Append(initialContents);
+            }
+        }
+
+        /// <summary>
+        /// Get/set the character at the given index.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">Thrown if attempting to index outside of the buffer length.</exception>
+        public unsafe char this[uint index]
+        {
+            [System.Security.SecuritySafeCritical]
+            get
+            {
+                if (index >= _length) throw new ArgumentOutOfRangeException("index");
+                return CharPointer[index];
+            }
+            [System.Security.SecuritySafeCritical]
+            set
+            {
+                if (index >= _length) throw new ArgumentOutOfRangeException("index");
+                CharPointer[index] = value;
+            }
+        }
+
+        /// <summary>
+        /// Character capacity of the buffer. Includes the count for the trailing null character.
+        /// </summary>
+        public uint CharCapacity
+        {
+            [System.Security.SecuritySafeCritical]
+            get
+            {
+                ulong byteCapacity = ByteCapacity;
+                ulong charCapacity = byteCapacity == 0 ? 0 : byteCapacity / sizeof(char);
+                return charCapacity > uint.MaxValue ? uint.MaxValue : (uint)charCapacity;
+            }
+        }
+
+        /// <summary>
+        /// Ensure capacity in characters is at least the given minimum. Capacity includes space for the trailing
+        /// null, which is not part of the Length.
+        /// </summary>
+        /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception>
+        [System.Security.SecuritySafeCritical]
+        public void EnsureCharCapacity(uint minCapacity)
+        {
+            EnsureByteCapacity(minCapacity * (ulong)sizeof(char));
+        }
+
+        /// <summary>
+        /// The logical length of the buffer in characters. (Does not include the final null, which is auto appended.) Will automatically attempt to increase capacity.
+        /// This is where the usable data ends.
+        /// </summary>
+        /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception>
+        /// <exception cref="ArgumentOutOfRangeException">Thrown if the set size in bytes is uint.MaxValue (as space is implicitly reservced for the trailing null).</exception>
+        public unsafe uint Length
+        {
+            get { return _length; }
+            [System.Security.SecuritySafeCritical]
+            set
+            {
+                if (value == uint.MaxValue) throw new ArgumentOutOfRangeException("Length");
+
+                // Null terminate
+                EnsureCharCapacity(value + 1);
+                CharPointer[value] = '\0';
+
+                _length = value;
+            }
+        }
+
+        /// <summary>
+        /// For use when the native api null terminates but doesn't return a length.
+        /// If no null is found, the length will not be changed.
+        /// </summary>
+        [System.Security.SecuritySafeCritical]
+        public unsafe void SetLengthToFirstNull()
+        {
+            char* buffer = CharPointer;
+            uint capacity = CharCapacity;
+            for (uint i = 0; i < capacity; i++)
+            {
+                if (buffer[i] == '\0')
+                {
+                    _length = i;
+                    break;
+                }
+            }
+        }
+
+        internal unsafe char* CharPointer
+        {
+            [System.Security.SecurityCritical]
+            get
+            {
+                return (char*)VoidPointer;
+            }
+        }
+
+        /// <summary>
+        /// True if the buffer contains the given character.
+        /// </summary>
+        [System.Security.SecurityCritical]
+        public unsafe bool Contains(char value)
+        {
+            char* start = CharPointer;
+            uint length = _length;
+
+            for (uint i = 0; i < length; i++)
+            {
+                if (*start++ == value) return true;
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Returns true if the buffer starts with the given string.
+        /// </summary>
+        [System.Security.SecuritySafeCritical]
+        public bool StartsWith(string value)
+        {
+            if (value == null) throw new ArgumentNullException("value");
+            if (_length < (uint)value.Length) return false;
+            return SubstringEquals(value, startIndex: 0, count: value.Length);
+        }
+
+        /// <summary>
+        /// Returns true if the specified StringBuffer substring equals the given value.
+        /// </summary>
+        /// <param name="value">The value to compare against the specified substring.</param>
+        /// <param name="startIndex">Start index of the sub string.</param>
+        /// <param name="count">Length of the substring, or -1 to check all remaining.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range
+        /// of the buffer's length.
+        /// </exception>
+        [System.Security.SecuritySafeCritical]
+        public unsafe bool SubstringEquals(string value, uint startIndex = 0, int count = -1)
+        {
+            if (value == null) return false;
+            if (count < -1) throw new ArgumentOutOfRangeException("count");
+            if (startIndex > _length) throw new ArgumentOutOfRangeException("startIndex");
+
+            uint realCount = count == -1 ? _length - startIndex : (uint)count;
+            if (checked(startIndex + realCount) > _length) throw new ArgumentOutOfRangeException("count");
+
+            int length = value.Length;
+
+            // Check the substring length against the input length
+            if (realCount != (uint)length) return false;
+
+            fixed (char* valueStart = value)
+            {
+                char* bufferStart = CharPointer + startIndex;
+                for (int i = 0; i < length; i++)
+                {
+                    // Note that indexing in this case generates faster code than trying to copy the pointer and increment it
+                    if (*bufferStart++ != valueStart[i]) return false;
+                }
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// Append the given string.
+        /// </summary>
+        /// <param name="value">The string to append.</param>
+        /// <param name="startIndex">The index in the input string to start appending from.</param>
+        /// <param name="count">The count of characters to copy from the input string, or -1 for all remaining.</param>
+        /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range
+        /// of <paramref name="value"/> characters.
+        /// </exception>
+        [System.Security.SecuritySafeCritical]
+        public void Append(string value, int startIndex = 0, int count = -1)
+        {
+            CopyFrom(
+                bufferIndex: _length,
+                source: value,
+                sourceIndex: startIndex,
+                count: count);
+        }
+
+        /// <summary>
+        /// Append the given buffer.
+        /// </summary>
+        /// <param name="value">The buffer to append.</param>
+        /// <param name="startIndex">The index in the input buffer to start appending from.</param>
+        /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown if <paramref name="startIndex"/> is outside the range of <paramref name="value"/> characters.
+        /// </exception>
+        public void Append(StringBuffer value, uint startIndex = 0)
+        {
+            if (value == null) throw new ArgumentNullException("value");
+            if (value.Length == 0) return;
+            value.CopyTo(
+                bufferIndex: startIndex,
+                destination: this,
+                destinationIndex: _length,
+                count: value.Length);
+        }
+
+        /// <summary>
+        /// Append the given buffer.
+        /// </summary>
+        /// <param name="value">The buffer to append.</param>
+        /// <param name="startIndex">The index in the input buffer to start appending from.</param>
+        /// <param name="count">The count of characters to copy from the buffer string.</param>
+        /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range
+        /// of <paramref name="value"/> characters.
+        /// </exception>
+        public void Append(StringBuffer value, uint startIndex, uint count)
+        {
+            if (value == null) throw new ArgumentNullException("value");
+            if (count == 0) return;
+            value.CopyTo(
+                bufferIndex: startIndex,
+                destination: this,
+                destinationIndex: _length,
+                count: count);
+        }
+
+        /// <summary>
+        /// Copy contents to the specified buffer. Destination index must be within current destination length.
+        /// Will grow the destination buffer if needed.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown if <paramref name="bufferIndex"/> or <paramref name="destinationIndex"/> or <paramref name="count"/> are outside the range
+        /// of <paramref name="value"/> characters.
+        /// </exception>
+        /// <exception cref="ArgumentNullException">Thrown if <paramref name="destination"/> is null.</exception>
+        [System.Security.SecuritySafeCritical]
+        public unsafe void CopyTo(uint bufferIndex, StringBuffer destination, uint destinationIndex, uint count)
+        {
+            if (destination == null) throw new ArgumentNullException("destination");
+            if (destinationIndex > destination._length) throw new ArgumentOutOfRangeException("destinationIndex");
+            if (bufferIndex >= _length) throw new ArgumentOutOfRangeException("bufferIndex");
+            if (_length < checked(bufferIndex + count)) throw new ArgumentOutOfRangeException("count");
+
+            if (count == 0) return;
+            uint lastIndex = checked(destinationIndex + count);
+            if (destination._length < lastIndex) destination.Length = lastIndex;
+
+            Buffer.MemoryCopy(
+                source: CharPointer + bufferIndex,
+                destination: destination.CharPointer + destinationIndex,
+                destinationSizeInBytes: checked((long)(destination.ByteCapacity - (destinationIndex * sizeof(char)))),
+                sourceBytesToCopy: checked((long)count * sizeof(char)));
+        }
+
+        /// <summary>
+        /// Copy contents from the specified string into the buffer at the given index. Start index must be within the current length of
+        /// the buffer, will grow as necessary.
+        /// </summary>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown if <paramref name="bufferIndex"/> or <paramref name="sourceIndex"/> or <paramref name="count"/> are outside the range
+        /// of <paramref name="value"/> characters.
+        /// </exception>
+        /// <exception cref="ArgumentNullException">Thrown if <paramref name="source"/> is null.</exception>
+        [System.Security.SecuritySafeCritical]
+        public unsafe void CopyFrom(uint bufferIndex, string source, int sourceIndex = 0, int count = -1)
+        {
+            if (source == null) throw new ArgumentNullException("source");
+            if (bufferIndex > _length) throw new ArgumentOutOfRangeException("bufferIndex");
+            if (sourceIndex < 0 || sourceIndex >= source.Length) throw new ArgumentOutOfRangeException("sourceIndex");
+            if (count == -1) count = source.Length - sourceIndex;
+            if (count < 0 || source.Length - count < sourceIndex) throw new ArgumentOutOfRangeException("count");
+
+            if (count == 0) return;
+            uint lastIndex = bufferIndex + (uint)count;
+            if (_length < lastIndex) Length = lastIndex;
+
+            fixed (char* content = source)
+            {
+                Buffer.MemoryCopy(
+                    source: content + sourceIndex,
+                    destination: CharPointer + bufferIndex,
+                    destinationSizeInBytes: checked((long)(ByteCapacity - (bufferIndex * sizeof(char)))),
+                    sourceBytesToCopy: (long)count * sizeof(char));
+            }
+        }
+
+        /// <summary>
+        /// Trim the specified values from the end of the buffer. If nothing is specified, nothing is trimmed.
+        /// </summary>
+        [System.Security.SecuritySafeCritical]
+        public unsafe void TrimEnd(char[] values)
+        {
+            if (values == null || values.Length == 0 || _length == 0) return;
+
+            char* end = CharPointer + _length - 1;
+
+            while (_length > 0 && Array.IndexOf(values, *end) >= 0)
+            {
+                Length = _length - 1;
+                end--;
+            }
+        }
+
+        /// <summary>
+        /// String representation of the entire buffer. If the buffer is larger than the maximum size string (int.MaxValue) this will throw.
+        /// </summary>
+        /// <exception cref="InvalidOperationException">Thrown if the buffer is too big to fit into a string.</exception>
+        [System.Security.SecuritySafeCritical]
+        public unsafe override string ToString()
+        {
+            if (_length == 0) return string.Empty;
+            if (_length > int.MaxValue) throw new InvalidOperationException();
+            return new string(CharPointer, startIndex: 0, length: (int)_length);
+        }
+
+        /// <summary>
+        /// Get the given substring in the buffer.
+        /// </summary>
+        /// <param name="count">Count of characters to take, or remaining characters from <paramref name="startIndex"/> if -1.</param>
+        /// <exception cref="ArgumentOutOfRangeException">
+        /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range of the buffer's length
+        /// or count is greater than the maximum string size (int.MaxValue).
+        /// </exception>
+        [System.Security.SecuritySafeCritical]
+        public unsafe string Substring(uint startIndex, int count = -1)
+        {
+            if (startIndex > (_length == 0 ? 0 : _length - 1)) throw new ArgumentOutOfRangeException("startIndex");
+            if (count < -1) throw new ArgumentOutOfRangeException("count");
+
+            uint realCount = count == -1 ? _length - startIndex : (uint)count;
+            if (realCount > int.MaxValue || checked(startIndex + realCount) > _length) throw new ArgumentOutOfRangeException("count");
+            if (realCount == 0) return string.Empty;
+
+            // The buffer could be bigger than will fit into a string, but the substring might fit. As the starting
+            // index might be bigger than int we need to index ourselves.
+            return new string(value: CharPointer + startIndex, startIndex: 0, length: (int)realCount);
+        }
+
+        [System.Security.SecuritySafeCritical]
+        public override void Free()
+        {
+            base.Free();
+            _length = 0;
+        }
+    }
+}
\ No newline at end of file