diff options
author | Jiyoung Yun <jy910.yun@samsung.com> | 2016-11-23 19:09:09 +0900 |
---|---|---|
committer | Jiyoung Yun <jy910.yun@samsung.com> | 2016-11-23 19:09:09 +0900 |
commit | 4b4aad7217d3292650e77eec2cf4c198ea9c3b4b (patch) | |
tree | 98110734c91668dfdbb126fcc0e15ddbd93738ca /src/mscorlib/src/System/IO/PathHelper.cs | |
parent | fa45f57ed55137c75ac870356a1b8f76c84b229c (diff) | |
download | coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.tar.gz coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.tar.bz2 coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.zip |
Imported Upstream version 1.1.0upstream/1.1.0
Diffstat (limited to 'src/mscorlib/src/System/IO/PathHelper.cs')
-rw-r--r-- | src/mscorlib/src/System/IO/PathHelper.cs | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/IO/PathHelper.cs b/src/mscorlib/src/System/IO/PathHelper.cs new file mode 100644 index 0000000000..8e39b3c537 --- /dev/null +++ b/src/mscorlib/src/System/IO/PathHelper.cs @@ -0,0 +1,448 @@ +// 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. + +#if FEATURE_PATHCOMPAT +using System; +using System.Collections; +using System.Text; +using Microsoft.Win32; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Globalization; +using System.Runtime.Versioning; +using System.Security; +using System.Security.Permissions; +using System.Diagnostics.Contracts; + +namespace System.IO { + + // ABOUT: + // Helps with path normalization; support allocating on the stack or heap + // + // PathHelper can't stackalloc the array for obvious reasons; you must pass + // in an array of chars allocated on the stack. + // + // USAGE: + // Suppose you need to represent a char array of length len. Then this is the + // suggested way to instantiate PathHelper: + // *************************************************************************** + // PathHelper pathHelper; + // if (charArrayLength less than stack alloc threshold == Path.MaxPath) + // char* arrayPtr = stackalloc char[Path.MaxPath]; + // pathHelper = new PathHelper(arrayPtr); + // else + // pathHelper = new PathHelper(capacity, maxPath); + // *************************************************************************** + // + // note in the StringBuilder ctor: + // - maxPath may be greater than Path.MaxPath (for isolated storage) + // - capacity may be greater than maxPath. This is even used for non-isolated + // storage scenarios where we want to temporarily allow strings greater + // than Path.MaxPath if they can be normalized down to Path.MaxPath. This + // can happen if the path contains escape characters "..". + // + unsafe internal struct PathHelper { // should not be serialized + + // maximum size, max be greater than max path if contains escape sequence + private int m_capacity; + // current length (next character position) + private int m_length; + // max path, may be less than capacity + private int m_maxPath; + + // ptr to stack alloc'd array of chars + [SecurityCritical] + private char* m_arrayPtr; + + // StringBuilder + private StringBuilder m_sb; + + // whether to operate on stack alloc'd or heap alloc'd array + private bool useStackAlloc; + + // Whether to skip calls to Win32Native.GetLongPathName becasue we tried before and failed: + private bool doNotTryExpandShortFileName; + + // Instantiates a PathHelper with a stack alloc'd array of chars + [System.Security.SecurityCritical] + internal PathHelper(char* charArrayPtr, int length) { + Contract.Requires(charArrayPtr != null); + // force callers to be aware of this + Contract.Requires(length == Path.MaxPath); + this.m_length = 0; + this.m_sb = null; + + this.m_arrayPtr = charArrayPtr; + this.m_capacity = length; + this.m_maxPath = Path.MaxPath; + useStackAlloc = true; + doNotTryExpandShortFileName = false; + } + + // Instantiates a PathHelper with a heap alloc'd array of ints. Will create a StringBuilder + [System.Security.SecurityCritical] + internal PathHelper(int capacity, int maxPath) + { + this.m_length = 0; + this.m_arrayPtr = null; + this.useStackAlloc = false; + + this.m_sb = new StringBuilder(capacity); + this.m_capacity = capacity; + this.m_maxPath = maxPath; + doNotTryExpandShortFileName = false; + } + + internal int Length { + get { + if (useStackAlloc) { + return m_length; + } + else { + return m_sb.Length; + } + } + set { + if (useStackAlloc) { + m_length = value; + } + else { + m_sb.Length = value; + } + } + } + + internal int Capacity { + get { + return m_capacity; + } + } + + internal char this[int index] { + [System.Security.SecurityCritical] + get { + Contract.Requires(index >= 0 && index < Length); + if (useStackAlloc) { + return m_arrayPtr[index]; + } + else { + return m_sb[index]; + } + } + [System.Security.SecurityCritical] + set { + Contract.Requires(index >= 0 && index < Length); + if (useStackAlloc) { + m_arrayPtr[index] = value; + } + else { + m_sb[index] = value; + } + } + } + + [System.Security.SecurityCritical] + internal unsafe void Append(char value) { + if (Length + 1 >= m_capacity) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + + if (useStackAlloc) { + m_arrayPtr[Length] = value; + m_length++; + } + else { + m_sb.Append(value); + } + } + + [System.Security.SecurityCritical] + internal unsafe int GetFullPathName() { + if (useStackAlloc) { + char* finalBuffer = stackalloc char[Path.MaxPath + 1]; + int result = Win32Native.GetFullPathName(m_arrayPtr, Path.MaxPath + 1, finalBuffer, IntPtr.Zero); + + // If success, the return buffer length does not account for the terminating null character. + // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character. + // If failure, the return buffer length is zero + if (result > Path.MaxPath) { + char* tempBuffer = stackalloc char[result]; + finalBuffer = tempBuffer; + result = Win32Native.GetFullPathName(m_arrayPtr, result, finalBuffer, IntPtr.Zero); + } + + // Full path is genuinely long + if (result >= Path.MaxPath) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + + Contract.Assert(result < Path.MaxPath, "did we accidently remove a PathTooLongException check?"); + if (result == 0 && m_arrayPtr[0] != '\0') { + __Error.WinIOError(); + } + + else if (result < Path.MaxPath) { + // Null terminate explicitly (may be only needed for some cases such as empty strings) + // GetFullPathName return length doesn't account for null terminating char... + finalBuffer[result] = '\0'; // Safe to write directly as result is < Path.MaxPath + } + + // We have expanded the paths and GetLongPathName may or may not behave differently from before. + // We need to call it again to see: + doNotTryExpandShortFileName = false; + + String.wstrcpy(m_arrayPtr, finalBuffer, result); + // Doesn't account for null terminating char. Think of this as the last + // valid index into the buffer but not the length of the buffer + Length = result; + return result; + } + else { + StringBuilder finalBuffer = new StringBuilder(m_capacity + 1); + int result = Win32Native.GetFullPathName(m_sb.ToString(), m_capacity + 1, finalBuffer, IntPtr.Zero); + + // If success, the return buffer length does not account for the terminating null character. + // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character. + // If failure, the return buffer length is zero + if (result > m_maxPath) { + finalBuffer.Length = result; + result = Win32Native.GetFullPathName(m_sb.ToString(), result, finalBuffer, IntPtr.Zero); + } + + // Fullpath is genuinely long + if (result >= m_maxPath) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + + Contract.Assert(result < m_maxPath, "did we accidentally remove a PathTooLongException check?"); + if (result == 0 && m_sb[0] != '\0') { + if (Length >= m_maxPath) { + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + } + __Error.WinIOError(); + } + + // We have expanded the paths and GetLongPathName may or may not behave differently from before. + // We need to call it again to see: + doNotTryExpandShortFileName = false; + + m_sb = finalBuffer; + return result; + } + } + + [System.Security.SecurityCritical] + internal unsafe bool TryExpandShortFileName() { + + if (doNotTryExpandShortFileName) + return false; + + if (useStackAlloc) { + NullTerminate(); + char* buffer = UnsafeGetArrayPtr(); + char* shortFileNameBuffer = stackalloc char[Path.MaxPath + 1]; + + int r = Win32Native.GetLongPathName(buffer, shortFileNameBuffer, Path.MaxPath); + + // If success, the return buffer length does not account for the terminating null character. + // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character. + // If failure, the return buffer length is zero + if (r >= Path.MaxPath) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + + if (r == 0) { + // Note: GetLongPathName will return ERROR_INVALID_FUNCTION on a + // path like \\.\PHYSICALDEVICE0 - some device driver doesn't + // support GetLongPathName on that string. This behavior is + // by design, according to the Core File Services team. + // We also get ERROR_NOT_ENOUGH_QUOTA in SQL_CLR_STRESS runs + // intermittently on paths like D:\DOCUME~1\user\LOCALS~1\Temp\ + + // We do not need to call GetLongPathName if we know it will fail becasue the path does not exist: + int lastErr = Marshal.GetLastWin32Error(); + if (lastErr == Win32Native.ERROR_FILE_NOT_FOUND || lastErr == Win32Native.ERROR_PATH_NOT_FOUND) + doNotTryExpandShortFileName = true; + + return false; + } + + // Safe to copy as we have already done Path.MaxPath bound checking + String.wstrcpy(buffer, shortFileNameBuffer, r); + Length = r; + // We should explicitly null terminate as in some cases the long version of the path + // might actually be shorter than what we started with because of Win32's normalization + // Safe to write directly as bufferLength is guaranteed to be < Path.MaxPath + NullTerminate(); + return true; + } + else { + StringBuilder sb = GetStringBuilder(); + + String origName = sb.ToString(); + String tempName = origName; + bool addedPrefix = false; + if (tempName.Length > Path.MaxPath) { + tempName = Path.AddLongPathPrefix(tempName); + addedPrefix = true; + } + sb.Capacity = m_capacity; + sb.Length = 0; + int r = Win32Native.GetLongPathName(tempName, sb, m_capacity); + + if (r == 0) { + // Note: GetLongPathName will return ERROR_INVALID_FUNCTION on a + // path like \\.\PHYSICALDEVICE0 - some device driver doesn't + // support GetLongPathName on that string. This behavior is + // by design, according to the Core File Services team. + // We also get ERROR_NOT_ENOUGH_QUOTA in SQL_CLR_STRESS runs + // intermittently on paths like D:\DOCUME~1\user\LOCALS~1\Temp\ + + // We do not need to call GetLongPathName if we know it will fail becasue the path does not exist: + int lastErr = Marshal.GetLastWin32Error(); + if (Win32Native.ERROR_FILE_NOT_FOUND == lastErr || Win32Native.ERROR_PATH_NOT_FOUND == lastErr) + doNotTryExpandShortFileName = true; + + sb.Length = 0; + sb.Append(origName); + return false; + } + + if (addedPrefix) + r -= 4; + + // If success, the return buffer length does not account for the terminating null character. + // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character. + // If failure, the return buffer length is zero + if (r >= m_maxPath) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + + + sb = Path.RemoveLongPathPrefix(sb); + Length = sb.Length; + return true; + + } + } + + [System.Security.SecurityCritical] + internal unsafe void Fixup(int lenSavedName, int lastSlash) { + if (useStackAlloc) { + char* savedName = stackalloc char[lenSavedName]; + String.wstrcpy(savedName, m_arrayPtr + lastSlash + 1, lenSavedName); + Length = lastSlash; + NullTerminate(); + doNotTryExpandShortFileName = false; + bool r = TryExpandShortFileName(); + // Clean up changes made to the newBuffer. + Append(Path.DirectorySeparatorChar); + if (Length + lenSavedName >= Path.MaxPath) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + String.wstrcpy(m_arrayPtr + Length, savedName, lenSavedName); + Length = Length + lenSavedName; + + } + else { + String savedName = m_sb.ToString(lastSlash + 1, lenSavedName); + Length = lastSlash; + doNotTryExpandShortFileName = false; + bool r = TryExpandShortFileName(); + // Clean up changes made to the newBuffer. + Append(Path.DirectorySeparatorChar); + if (Length + lenSavedName >= m_maxPath) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + m_sb.Append(savedName); + } + } + + [System.Security.SecurityCritical] + internal unsafe bool OrdinalStartsWith(String compareTo, bool ignoreCase) { + if (Length < compareTo.Length) + return false; + + if (useStackAlloc) { + NullTerminate(); + if (ignoreCase) { + String s = new String(m_arrayPtr, 0, compareTo.Length); + return compareTo.Equals(s, StringComparison.OrdinalIgnoreCase); + } + else { + for (int i = 0; i < compareTo.Length; i++) { + if (m_arrayPtr[i] != compareTo[i]) { + return false; + } + } + return true; + } + } + else { + if (ignoreCase) { + return m_sb.ToString().StartsWith(compareTo, StringComparison.OrdinalIgnoreCase); + } + else { + return m_sb.ToString().StartsWith(compareTo, StringComparison.Ordinal); + } + } + } + + [System.Security.SecurityCritical] + private unsafe bool OrdinalEqualsStackAlloc(String compareTo) + { + Contract.Requires(useStackAlloc, "Currently no efficient implementation for StringBuilder.OrdinalEquals(String)"); + + if (Length != compareTo.Length) { + return false; + } + + for (int i = 0; i < compareTo.Length; i++) { + if (m_arrayPtr[i] != compareTo[i]) { + return false; + } + } + + return true; + } + + [System.Security.SecuritySafeCritical] + public override String ToString() { + if (useStackAlloc) { + return new String(m_arrayPtr, 0, Length); + } + else { + return m_sb.ToString(); + } + } + + [System.Security.SecuritySafeCritical] + internal String ToStringOrExisting(String existingString) + { + if (useStackAlloc) { + return OrdinalEqualsStackAlloc(existingString) ? + existingString : + new String(m_arrayPtr, 0, Length); + } + else { + string newString = m_sb.ToString(); // currently no good StringBuilder.OrdinalEquals(string) + return String.Equals(newString, existingString, StringComparison.Ordinal) ? + existingString : + newString; + } + } + + [System.Security.SecurityCritical] + private unsafe char* UnsafeGetArrayPtr() { + Contract.Requires(useStackAlloc, "This should never be called for PathHelpers wrapping a StringBuilder"); + return m_arrayPtr; + } + + private StringBuilder GetStringBuilder() { + Contract.Requires(!useStackAlloc, "This should never be called for PathHelpers that wrap a stackalloc'd buffer"); + return m_sb; + } + + [System.Security.SecurityCritical] + private unsafe void NullTerminate() { + Contract.Requires(useStackAlloc, "This should never be called for PathHelpers wrapping a StringBuilder"); + m_arrayPtr[m_length] = '\0'; + } + + } +} +#endif // FEATURE_PATHCOMPAT
\ No newline at end of file |