FileUtil.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / ndp / fx / src / xsp / System / Web / Util / FileUtil.cs / 1590299 / FileUtil.cs

                            //------------------------------------------------------------------------------ 
// 
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//----------------------------------------------------------------------------- 

/* 
 * UrlPath class 
 *
 * Copyright (c) 1999 Microsoft Corporation 
 */

namespace System.Web.Util {
using System.Security.Permissions; 
using System.Text;
using System.Runtime.Serialization.Formatters; 
using System.Runtime.InteropServices; 
using System.Collections;
using System.Globalization; 
using System.IO;
using System.Web.Hosting;

 
internal struct FileTimeInfo {
    internal long LastWriteTime; 
    internal long Size; 

    internal static readonly FileTimeInfo MinValue = new FileTimeInfo(0, 0); 

    internal FileTimeInfo(long lastWriteTime, long size) {
        LastWriteTime = lastWriteTime;
        Size = size; 
    }
 
    public override bool Equals(object obj) { 
        FileTimeInfo fti;
 
        if (obj is FileTimeInfo) {
            fti = (FileTimeInfo) obj;
            return (LastWriteTime == fti.LastWriteTime) && (Size == fti.Size);
        } 
        else {
            return false; 
        } 
    }
 
    public static bool operator == (FileTimeInfo value1, FileTimeInfo value2)
    {
        return (value1.LastWriteTime == value2.LastWriteTime) &&
               (value1.Size == value2.Size); 
    }
 
    public unsafe static bool operator != (FileTimeInfo value1, FileTimeInfo value2) 
    {
        return !(value1 == value2); 
    }

    public override int GetHashCode(){
        return HashCodeCombiner.CombineHashCodes(LastWriteTime.GetHashCode(), Size.GetHashCode()); 
    }
 
 
}
 
/*
 * Helper methods relating to file operations
 */
internal class FileUtil { 

    private FileUtil() { 
    } 

    [FileIOPermission(SecurityAction.Assert, AllFiles = FileIOPermissionAccess.Read)] 
    internal static bool FileExists(String filename) {
        bool exists = false;

        try { 
            exists = File.Exists(filename);
        } 
        catch { 
        }
 
        return exists;
    }

    // For a given path, if its beneath the app root, return the first existing directory 
    internal static string GetFirstExistingDirectory(string appRoot, string fileName) {
        if (IsBeneathAppRoot(appRoot, fileName)) { 
            string existingDir = appRoot; 
            do {
                int nextSeparator = fileName.IndexOf(Path.DirectorySeparatorChar, existingDir.Length + 1); 
                if (nextSeparator > -1) {
                    string nextDir = fileName.Substring(0, nextSeparator);
                    if (FileUtil.DirectoryExists(nextDir, false)) {
                        existingDir = nextDir; 
                        continue;
                    } 
                } 
                break;
            } while (true); 

            return existingDir;
        }
        return null; 
    }
 
    internal static bool IsBeneathAppRoot(string appRoot, string filePath) { 
        if (filePath.Length > appRoot.Length + 1
            && filePath.IndexOf(appRoot, StringComparison.OrdinalIgnoreCase) > -1 
            && filePath[appRoot.Length] == Path.DirectorySeparatorChar) {
            return true;
        }
        return false; 
    }
 
    // Remove the final backslash from a directory path, unless it's something like c:\ 
    internal static String RemoveTrailingDirectoryBackSlash(String path) {
 
        if (path == null)
            return null;

        int length = path.Length; 
        if (length > 3 && path[length - 1] == '\\')
            path = path.Substring(0, length - 1); 
 
        return path;
    } 

    private static int _maxPathLength = 259;
    // If the path is longer than the maximum length
    // Trim the end and append the hashcode to it. 
    internal static String TruncatePathIfNeeded(string path, int reservedLength) {
        int maxPathLength = _maxPathLength - reservedLength; 
        if (path.Length > maxPathLength) { 
            //
 
            path = path.Substring(0, maxPathLength - 13) +
                path.GetHashCode().ToString(CultureInfo.InvariantCulture);
        }
 
        return path;
    } 
 
    /*
     * Canonicalize the directory, and makes sure it ends with a '\' 
     */
    internal static string FixUpPhysicalDirectory(string dir) {
        if (dir == null)
            return null; 

        dir = Path.GetFullPath(dir); 
 
        // Append '\' to the directory if necessary.
        if (!StringUtil.StringEndsWith(dir, @"\")) 
            dir = dir + @"\";

        return dir;
    } 

    // Fail if the physical path is not canonical 
    static internal void CheckSuspiciousPhysicalPath(string physicalPath) { 
        if (IsSuspiciousPhysicalPath(physicalPath)) {
            throw new HttpException(404, String.Empty); 
        }
    }

    // Check whether the physical path is not canonical 
    // NOTE: this API throws if we don't have permission to the file.
    // NOTE: The compare needs to be case insensitive (VSWhidbey 444513) 
    static internal bool IsSuspiciousPhysicalPath(string physicalPath) { 
        bool pathTooLong;
 
        if (!IsSuspiciousPhysicalPath(physicalPath, out pathTooLong)) {
            return false;
        }
 
        if (!pathTooLong) {
            return true; 
        } 

        // physical path too long -> not good because we still need to make 
        // it work for virtual path provider scenarios

        // first a few simple checks:
        if (physicalPath.IndexOf('/') >= 0) { 
            return true;
        } 
 
        string slashDots = "\\..";
        int idxSlashDots = physicalPath.IndexOf(slashDots, StringComparison.Ordinal); 
        if (idxSlashDots >= 0
            && (physicalPath.Length == idxSlashDots + slashDots.Length
                || physicalPath[idxSlashDots + slashDots.Length] == '\\')) {
            return true; 
        }
 
        // the real check is to go right to left until there is no longer path-too-long 
        // and see if the canonicalization check fails then
 
        int pos = physicalPath.LastIndexOf('\\');

        while (pos >= 0) {
            string path = physicalPath.Substring(0, pos); 

            if (!IsSuspiciousPhysicalPath(path, out pathTooLong)) { 
                // reached a non-suspicious path that is not too long 
                return false;
            } 

            if (!pathTooLong) {
                // reached a suspicious path that is not too long
                return true; 
            }
 
            // trim the path some more 
            pos = physicalPath.LastIndexOf('\\', pos-1);
        } 

        // backtracted to the end without reaching a non-suspicious path
        // this is suspicious (should happen because app root at least should be ok)
        return true; 
    }
 
    // VSWhidbey 609102 - Medium trust apps may hit this method, and if the physical path exists, 
    // Path.GetFullPath will seek PathDiscovery permissions and throw an exception.
    [FileIOPermissionAttribute(SecurityAction.Assert, AllFiles=FileIOPermissionAccess.PathDiscovery)] 
    static internal bool IsSuspiciousPhysicalPath(string physicalPath, out bool pathTooLong) {
        bool isSuspicious;

        try { 
            isSuspicious = !String.IsNullOrEmpty(physicalPath) &&
                String.Compare(physicalPath, Path.GetFullPath(physicalPath), 
                    StringComparison.OrdinalIgnoreCase) != 0; 
            pathTooLong = false;
        } 
        catch (PathTooLongException) {
            isSuspicious = true;
            pathTooLong = true;
        } 
        catch (NotSupportedException) {
            // see comment below -- we do the same for ':' 
            isSuspicious = true; 
            pathTooLong = true;
        } 
        catch (ArgumentException) {
            // DevDiv Bugs 152256:  Illegal characters {",|} in path prevent configuration system from working.
            // We need to catch this exception and conservatively assume that the path is suspicious in
            // such a case. 
            // We also set pathTooLong to true because at this point we do not know if the path is too long
            // or not. If we assume that pathTooLong is false, it means that our path length enforcement 
            // is bypassed by using URLs with illegal characters. We do not want that. Moreover, returning 
            // pathTooLong = true causes the current logic to peel of URL fragments, which can also find a
            // path without illegal characters to retrieve the config. 
            isSuspicious = true;
            pathTooLong = true;
        }
 
        return isSuspicious;
    } 
 
    static bool HasInvalidLastChar(string physicalPath) {
        // see VSWhidbey #108945 
        // We need to filter out directory names which end
        // in " " or ".".  We want to treat path names that
        // end in these characters as files - however, Windows
        // will strip these characters off the end of the name, 
        // which may result in the name being treated as a
        // directory instead. 
 
        if (String.IsNullOrEmpty(physicalPath)) {
            return false; 
        }

        char lastChar = physicalPath[physicalPath.Length - 1];
        return lastChar == ' ' || lastChar == '.'; 
    }
 
    internal static bool DirectoryExists(String dirname) { 
        bool exists = false;
        dirname = RemoveTrailingDirectoryBackSlash(dirname); 
        if (HasInvalidLastChar(dirname))
            return false;

        try { 
            exists = Directory.Exists(dirname);
        } 
        catch { 
        }
 
        return exists;
    }

    internal static bool DirectoryAccessible(String dirname) { 
        bool accessible = false;
        dirname = RemoveTrailingDirectoryBackSlash(dirname); 
        if (HasInvalidLastChar(dirname)) 
            return false;
 
        try {
            accessible = (new DirectoryInfo(dirname)).Exists;
        }
        catch { 
        }
 
        return accessible; 
    }
 
    private static Char[] _invalidFileNameChars = Path.GetInvalidFileNameChars();
    internal static bool IsValidDirectoryName(String name) {
        if (String.IsNullOrEmpty(name)) {
            return false; 
        }
 
        if (name.IndexOfAny(_invalidFileNameChars, 0) != -1) { 
            return false;
        } 

        if (name.Equals(".") || name.Equals("..")) {
            return false;
        } 

        return true; 
    } 

    // 
    // Given a physical path, determine if it exists, and whether it is a directory or file.
    //
    // If directoryExistsOnError is set, set exists=true and isDirectory=true if we cannot confirm that the path does not exist.
    // If fileExistsOnError is set, set exists=true and isDirectory=false if we cannot confirm that the path does not exist. 
    //
 
    // this code is called by config that doesn't have AspNetHostingPermission 
    internal static void PhysicalPathStatus(string physicalPath, bool directoryExistsOnError, bool fileExistsOnError, out bool exists, out bool isDirectory) {
        exists = false; 
        isDirectory = true;

        Debug.Assert(!(directoryExistsOnError && fileExistsOnError), "!(directoryExistsOnError && fileExistsOnError)");
 
        if (String.IsNullOrEmpty(physicalPath))
            return; 
 
        using (new ApplicationImpersonationContext()) {
            UnsafeNativeMethods.WIN32_FILE_ATTRIBUTE_DATA data; 
            bool ok = UnsafeNativeMethods.GetFileAttributesEx(physicalPath, UnsafeNativeMethods.GetFileExInfoStandard, out data);
            if (ok) {
                exists = true;
                isDirectory = ((data.fileAttributes & (int) FileAttributes.Directory) == (int) FileAttributes.Directory); 
                if (isDirectory && HasInvalidLastChar(physicalPath)) {
                    exists = false; 
                } 
            }
            else { 
                if (directoryExistsOnError || fileExistsOnError) {
                    // Set exists to true if we cannot confirm that the path does NOT exist.
                    int hr = Marshal.GetHRForLastWin32Error();
                    if (!(hr == HResults.E_FILENOTFOUND || hr == HResults.E_PATHNOTFOUND)) { 
                        exists = true;
                        isDirectory = directoryExistsOnError; 
                    } 
                }
            } 
        }
    }

    // 
    // Use to avoid the perf hit of a Demand when the Demand is not necessary for security.
    // 
    // If trueOnError is set, then return true if we cannot confirm that the file does NOT exist. 
    //
    internal static bool DirectoryExists(string filename, bool trueOnError) { 
        filename = RemoveTrailingDirectoryBackSlash(filename);
        if (HasInvalidLastChar(filename)) {
            return false;
        } 

        UnsafeNativeMethods.WIN32_FILE_ATTRIBUTE_DATA data; 
        bool ok = UnsafeNativeMethods.GetFileAttributesEx(filename, UnsafeNativeMethods.GetFileExInfoStandard, out data); 
        if (ok) {
            // The path exists. Return true if it is a directory, false if a file. 
            return (data.fileAttributes & (int) FileAttributes.Directory) == (int) FileAttributes.Directory;
        }
        else {
            if (!trueOnError) { 
                return false;
            } 
            else { 
                // Return true if we cannot confirm that the file does NOT exist.
                int hr = Marshal.GetHRForLastWin32Error(); 
                if (hr == HResults.E_FILENOTFOUND || hr == HResults.E_PATHNOTFOUND) {
                    return false;
                }
                else { 
                    return true;
                } 
            } 
        }
    } 
}


// 
// Wraps the Win32 API FindFirstFile
// 
sealed class FindFileData { 

    private FileAttributesData _fileAttributesData; 
    private string _fileNameLong;
    private string _fileNameShort;

    internal string FileNameLong { get { return _fileNameLong; } } 
    internal string FileNameShort { get { return _fileNameShort; } }
    internal FileAttributesData FileAttributesData { get { return _fileAttributesData; } } 
 
    // FindFile - given a file name, gets the file attributes and short form (8.3 format) of a file name.
    static internal int FindFile(string path, out FindFileData data) { 
        IntPtr hFindFile;
        UnsafeNativeMethods.WIN32_FIND_DATA wfd;

        data = null; 

        // Remove trailing slash if any, otherwise FindFirstFile won't work correctly 
        path = FileUtil.RemoveTrailingDirectoryBackSlash(path); 
#if DBG
        Debug.Assert(Path.GetDirectoryName(path) != null, "Path.GetDirectoryName(path) != null"); 
        Debug.Assert(Path.GetFileName(path) != null, "Path.GetFileName(path) != null");
#endif

        hFindFile = UnsafeNativeMethods.FindFirstFile(path, out wfd); 
        int lastError = Marshal.GetLastWin32Error(); // FXCOP demands that this preceed the ==
        if (hFindFile == UnsafeNativeMethods.INVALID_HANDLE_VALUE) { 
            return HttpException.HResultFromLastError(lastError); 
        }
 
        UnsafeNativeMethods.FindClose(hFindFile);

#if DBG
        string file = Path.GetFileName(path); 
        file = file.TrimEnd(' ', '.');
        Debug.Assert(StringUtil.EqualsIgnoreCase(file, wfd.cFileName) || 
                     StringUtil.EqualsIgnoreCase(file, wfd.cAlternateFileName), 
                     "Path to FindFile is not for a single file: " + path);
#endif 

        data = new FindFileData(ref wfd);
        return HResults.S_OK;
    } 

    // FindFile - takes a full-path and a root-directory-path, and is used to get the 
    // short form (8.3 format) of the relative-path.  A FindFileData structure is returned 
    // with FileNameLong and FileNameShort relative to the specified root-directory-path.
    // 
    // For example, if full-path is "c:\vdir\subdirectory\t.aspx" and root-directory-path
    // is "c:\vdir", then the relative-path will be "subdirectory\t.aspx" and it's short
    // form will be something like "subdir~1\t~1.ASP".
    // 
    // This is used by FileChangesMonitor to support the ability to monitor all files and
    // directories at any depth beneath the application root directory. 
    internal static int FindFile(string fullPath, string rootDirectoryPath, out FindFileData data) { 

        int hr = FindFileData.FindFile(fullPath, out data); 
        if (hr != HResults.S_OK || String.IsNullOrEmpty(rootDirectoryPath)) {
            return hr;
        }
 
#if DBG
        // The trailing slash should have been removed already, unless the root is "c:\" 
        Debug.Assert(rootDirectoryPath.Length < 4 || rootDirectoryPath[rootDirectoryPath.Length-1] != '\\', "Trailing slash unexpected: " + rootDirectoryPath); 
#endif
 
        // remove it just in case
        rootDirectoryPath = FileUtil.RemoveTrailingDirectoryBackSlash(rootDirectoryPath);

#if DBG 
        Debug.Assert(fullPath.IndexOf(rootDirectoryPath, StringComparison.OrdinalIgnoreCase) == 0,
                     "fullPath (" + fullPath + ") is not within rootDirectoryPath (" + rootDirectoryPath + ")"); 
#endif 

        // crawl backwards along the subdirectories of fullPath until we get to the specified rootDirectoryPath 
        string relativePathLong = String.Empty;
        string relativePathShort = String.Empty;
        string currentParentDir = Path.GetDirectoryName(fullPath);
        while (currentParentDir != null 
               && currentParentDir.Length > rootDirectoryPath.Length+1
               && currentParentDir.IndexOf(rootDirectoryPath, StringComparison.OrdinalIgnoreCase) == 0) { 
 
            UnsafeNativeMethods.WIN32_FIND_DATA fd;
            IntPtr hFindFile = UnsafeNativeMethods.FindFirstFile(currentParentDir, out fd); 
            int lastError = Marshal.GetLastWin32Error(); // FXCOP demands that this preceed the ==
            if (hFindFile == UnsafeNativeMethods.INVALID_HANDLE_VALUE) {
                return HttpException.HResultFromLastError(lastError);
            } 
            UnsafeNativeMethods.FindClose(hFindFile);
 
#if DBG 
            Debug.Assert(!String.IsNullOrEmpty(fd.cFileName), "!String.IsNullOrEmpty(fd.cFileName)");
#endif 

            // build the long and short versions of the relative path
            relativePathLong = fd.cFileName + Path.DirectorySeparatorChar + relativePathLong;
            if (!String.IsNullOrEmpty(fd.cAlternateFileName)) { 
                relativePathShort = fd.cAlternateFileName + Path.DirectorySeparatorChar + relativePathShort;
            } 
            else { 
                relativePathShort = fd.cFileName + Path.DirectorySeparatorChar + relativePathShort;
            } 

            currentParentDir = Path.GetDirectoryName(currentParentDir);
        }
 
        if (!String.IsNullOrEmpty(relativePathLong)) {
            data.PrependRelativePath(relativePathLong, relativePathShort); 
        } 

#if DBG 
        Debug.Trace("FindFile", "fullPath=" + fullPath + ", rootDirectoryPath=" + rootDirectoryPath);
        Debug.Trace("FindFile", "relativePathLong=" + relativePathLong + ", relativePathShort=" + relativePathShort);
        string fileNameShort = data.FileNameShort == null ? "" : data.FileNameShort;
        Debug.Trace("FindFile", "FileNameLong=" + data.FileNameLong + ", FileNameShrot=" + fileNameShort); 
#endif
 
        return hr; 
    }
 
    internal FindFileData(ref UnsafeNativeMethods.WIN32_FIND_DATA wfd) {
        _fileAttributesData = new FileAttributesData(ref wfd);
        _fileNameLong = wfd.cFileName;
        if (wfd.cAlternateFileName != null 
            && wfd.cAlternateFileName.Length > 0
            && !StringUtil.EqualsIgnoreCase(wfd.cFileName, wfd.cAlternateFileName)) { 
            _fileNameShort = wfd.cAlternateFileName; 
        }
    } 

    private void PrependRelativePath(string relativePathLong, string relativePathShort) {
        _fileNameLong = relativePathLong + _fileNameLong;
 
        // if the short form is null or empty, prepend the short relative path to the long form
        string fileName = String.IsNullOrEmpty(_fileNameShort) ? _fileNameLong : _fileNameShort; 
        _fileNameShort = relativePathShort + fileName; 

        // if the short form is the same as the long form, set the short form to null 
        if (StringUtil.EqualsIgnoreCase(_fileNameShort, _fileNameLong)) {
            _fileNameShort = null;
        }
    } 
}
 
// 
// Wraps the Win32 API GetFileAttributesEx
// We use this api in addition to FindFirstFile because FindFirstFile 
// does not work for volumes (e.g. "c:\")
//
sealed class FileAttributesData {
    internal readonly FileAttributes    FileAttributes; 
    internal readonly DateTime          UtcCreationTime;
    internal readonly DateTime          UtcLastAccessTime; 
    internal readonly DateTime          UtcLastWriteTime; 
    internal readonly long              FileSize;
 
    static internal FileAttributesData NonExistantAttributesData {
        get {
            return new FileAttributesData();
        } 
    }
 
    static internal int GetFileAttributes(string path, out FileAttributesData fad) { 
        fad = null;
 
        UnsafeNativeMethods.WIN32_FILE_ATTRIBUTE_DATA  data;
        if (!UnsafeNativeMethods.GetFileAttributesEx(path, UnsafeNativeMethods.GetFileExInfoStandard, out data)) {
            return HttpException.HResultFromLastError(Marshal.GetLastWin32Error());
        } 

        fad = new FileAttributesData(ref data); 
        return HResults.S_OK; 
    }
 
    FileAttributesData() {
        FileSize = -1;
    }
 
    FileAttributesData(ref UnsafeNativeMethods.WIN32_FILE_ATTRIBUTE_DATA data) {
        FileAttributes    = (FileAttributes) data.fileAttributes; 
        UtcCreationTime   = DateTimeUtil.FromFileTimeToUtc(((long)data.ftCreationTimeHigh)   << 32 | (long)data.ftCreationTimeLow); 
        UtcLastAccessTime = DateTimeUtil.FromFileTimeToUtc(((long)data.ftLastAccessTimeHigh) << 32 | (long)data.ftLastAccessTimeLow);
        UtcLastWriteTime  = DateTimeUtil.FromFileTimeToUtc(((long)data.ftLastWriteTimeHigh)  << 32 | (long)data.ftLastWriteTimeLow); 
        FileSize          = (long)(uint)data.fileSizeHigh << 32 | (long)(uint)data.fileSizeLow;
    }

    internal FileAttributesData(ref UnsafeNativeMethods.WIN32_FIND_DATA wfd) { 
        FileAttributes    = (FileAttributes) wfd.dwFileAttributes;
        UtcCreationTime   = DateTimeUtil.FromFileTimeToUtc(((long)wfd.ftCreationTime_dwHighDateTime)   << 32 | (long)wfd.ftCreationTime_dwLowDateTime); 
        UtcLastAccessTime = DateTimeUtil.FromFileTimeToUtc(((long)wfd.ftLastAccessTime_dwHighDateTime) << 32 | (long)wfd.ftLastAccessTime_dwLowDateTime); 
        UtcLastWriteTime  = DateTimeUtil.FromFileTimeToUtc(((long)wfd.ftLastWriteTime_dwHighDateTime)  << 32 | (long)wfd.ftLastWriteTime_dwLowDateTime);
        FileSize          = (long)wfd.nFileSizeHigh << 32 | (long)wfd.nFileSizeLow; 
    }

#if DBG
    internal string DebugDescription(string indent) { 
        StringBuilder   sb = new StringBuilder(200);
        string          i2 = indent + "    "; 
 
        sb.Append(indent + "FileAttributesData\n");
        sb.Append(i2 + "FileAttributes: " + FileAttributes + "\n"); 
        sb.Append(i2 + "  CreationTime: " + Debug.FormatUtcDate(UtcCreationTime) + "\n");
        sb.Append(i2 + "LastAccessTime: " + Debug.FormatUtcDate(UtcLastAccessTime) + "\n");
        sb.Append(i2 + " LastWriteTime: " + Debug.FormatUtcDate(UtcLastWriteTime) + "\n");
        sb.Append(i2 + "      FileSize: " + FileSize.ToString("n0", NumberFormatInfo.InvariantInfo) + "\n"); 

        return sb.ToString(); 
    } 
#endif
 
}

}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//------------------------------------------------------------------------------ 
// 
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//----------------------------------------------------------------------------- 

/* 
 * UrlPath class 
 *
 * Copyright (c) 1999 Microsoft Corporation 
 */

namespace System.Web.Util {
using System.Security.Permissions; 
using System.Text;
using System.Runtime.Serialization.Formatters; 
using System.Runtime.InteropServices; 
using System.Collections;
using System.Globalization; 
using System.IO;
using System.Web.Hosting;

 
internal struct FileTimeInfo {
    internal long LastWriteTime; 
    internal long Size; 

    internal static readonly FileTimeInfo MinValue = new FileTimeInfo(0, 0); 

    internal FileTimeInfo(long lastWriteTime, long size) {
        LastWriteTime = lastWriteTime;
        Size = size; 
    }
 
    public override bool Equals(object obj) { 
        FileTimeInfo fti;
 
        if (obj is FileTimeInfo) {
            fti = (FileTimeInfo) obj;
            return (LastWriteTime == fti.LastWriteTime) && (Size == fti.Size);
        } 
        else {
            return false; 
        } 
    }
 
    public static bool operator == (FileTimeInfo value1, FileTimeInfo value2)
    {
        return (value1.LastWriteTime == value2.LastWriteTime) &&
               (value1.Size == value2.Size); 
    }
 
    public unsafe static bool operator != (FileTimeInfo value1, FileTimeInfo value2) 
    {
        return !(value1 == value2); 
    }

    public override int GetHashCode(){
        return HashCodeCombiner.CombineHashCodes(LastWriteTime.GetHashCode(), Size.GetHashCode()); 
    }
 
 
}
 
/*
 * Helper methods relating to file operations
 */
internal class FileUtil { 

    private FileUtil() { 
    } 

    [FileIOPermission(SecurityAction.Assert, AllFiles = FileIOPermissionAccess.Read)] 
    internal static bool FileExists(String filename) {
        bool exists = false;

        try { 
            exists = File.Exists(filename);
        } 
        catch { 
        }
 
        return exists;
    }

    // For a given path, if its beneath the app root, return the first existing directory 
    internal static string GetFirstExistingDirectory(string appRoot, string fileName) {
        if (IsBeneathAppRoot(appRoot, fileName)) { 
            string existingDir = appRoot; 
            do {
                int nextSeparator = fileName.IndexOf(Path.DirectorySeparatorChar, existingDir.Length + 1); 
                if (nextSeparator > -1) {
                    string nextDir = fileName.Substring(0, nextSeparator);
                    if (FileUtil.DirectoryExists(nextDir, false)) {
                        existingDir = nextDir; 
                        continue;
                    } 
                } 
                break;
            } while (true); 

            return existingDir;
        }
        return null; 
    }
 
    internal static bool IsBeneathAppRoot(string appRoot, string filePath) { 
        if (filePath.Length > appRoot.Length + 1
            && filePath.IndexOf(appRoot, StringComparison.OrdinalIgnoreCase) > -1 
            && filePath[appRoot.Length] == Path.DirectorySeparatorChar) {
            return true;
        }
        return false; 
    }
 
    // Remove the final backslash from a directory path, unless it's something like c:\ 
    internal static String RemoveTrailingDirectoryBackSlash(String path) {
 
        if (path == null)
            return null;

        int length = path.Length; 
        if (length > 3 && path[length - 1] == '\\')
            path = path.Substring(0, length - 1); 
 
        return path;
    } 

    private static int _maxPathLength = 259;
    // If the path is longer than the maximum length
    // Trim the end and append the hashcode to it. 
    internal static String TruncatePathIfNeeded(string path, int reservedLength) {
        int maxPathLength = _maxPathLength - reservedLength; 
        if (path.Length > maxPathLength) { 
            //
 
            path = path.Substring(0, maxPathLength - 13) +
                path.GetHashCode().ToString(CultureInfo.InvariantCulture);
        }
 
        return path;
    } 
 
    /*
     * Canonicalize the directory, and makes sure it ends with a '\' 
     */
    internal static string FixUpPhysicalDirectory(string dir) {
        if (dir == null)
            return null; 

        dir = Path.GetFullPath(dir); 
 
        // Append '\' to the directory if necessary.
        if (!StringUtil.StringEndsWith(dir, @"\")) 
            dir = dir + @"\";

        return dir;
    } 

    // Fail if the physical path is not canonical 
    static internal void CheckSuspiciousPhysicalPath(string physicalPath) { 
        if (IsSuspiciousPhysicalPath(physicalPath)) {
            throw new HttpException(404, String.Empty); 
        }
    }

    // Check whether the physical path is not canonical 
    // NOTE: this API throws if we don't have permission to the file.
    // NOTE: The compare needs to be case insensitive (VSWhidbey 444513) 
    static internal bool IsSuspiciousPhysicalPath(string physicalPath) { 
        bool pathTooLong;
 
        if (!IsSuspiciousPhysicalPath(physicalPath, out pathTooLong)) {
            return false;
        }
 
        if (!pathTooLong) {
            return true; 
        } 

        // physical path too long -> not good because we still need to make 
        // it work for virtual path provider scenarios

        // first a few simple checks:
        if (physicalPath.IndexOf('/') >= 0) { 
            return true;
        } 
 
        string slashDots = "\\..";
        int idxSlashDots = physicalPath.IndexOf(slashDots, StringComparison.Ordinal); 
        if (idxSlashDots >= 0
            && (physicalPath.Length == idxSlashDots + slashDots.Length
                || physicalPath[idxSlashDots + slashDots.Length] == '\\')) {
            return true; 
        }
 
        // the real check is to go right to left until there is no longer path-too-long 
        // and see if the canonicalization check fails then
 
        int pos = physicalPath.LastIndexOf('\\');

        while (pos >= 0) {
            string path = physicalPath.Substring(0, pos); 

            if (!IsSuspiciousPhysicalPath(path, out pathTooLong)) { 
                // reached a non-suspicious path that is not too long 
                return false;
            } 

            if (!pathTooLong) {
                // reached a suspicious path that is not too long
                return true; 
            }
 
            // trim the path some more 
            pos = physicalPath.LastIndexOf('\\', pos-1);
        } 

        // backtracted to the end without reaching a non-suspicious path
        // this is suspicious (should happen because app root at least should be ok)
        return true; 
    }
 
    // VSWhidbey 609102 - Medium trust apps may hit this method, and if the physical path exists, 
    // Path.GetFullPath will seek PathDiscovery permissions and throw an exception.
    [FileIOPermissionAttribute(SecurityAction.Assert, AllFiles=FileIOPermissionAccess.PathDiscovery)] 
    static internal bool IsSuspiciousPhysicalPath(string physicalPath, out bool pathTooLong) {
        bool isSuspicious;

        try { 
            isSuspicious = !String.IsNullOrEmpty(physicalPath) &&
                String.Compare(physicalPath, Path.GetFullPath(physicalPath), 
                    StringComparison.OrdinalIgnoreCase) != 0; 
            pathTooLong = false;
        } 
        catch (PathTooLongException) {
            isSuspicious = true;
            pathTooLong = true;
        } 
        catch (NotSupportedException) {
            // see comment below -- we do the same for ':' 
            isSuspicious = true; 
            pathTooLong = true;
        } 
        catch (ArgumentException) {
            // DevDiv Bugs 152256:  Illegal characters {",|} in path prevent configuration system from working.
            // We need to catch this exception and conservatively assume that the path is suspicious in
            // such a case. 
            // We also set pathTooLong to true because at this point we do not know if the path is too long
            // or not. If we assume that pathTooLong is false, it means that our path length enforcement 
            // is bypassed by using URLs with illegal characters. We do not want that. Moreover, returning 
            // pathTooLong = true causes the current logic to peel of URL fragments, which can also find a
            // path without illegal characters to retrieve the config. 
            isSuspicious = true;
            pathTooLong = true;
        }
 
        return isSuspicious;
    } 
 
    static bool HasInvalidLastChar(string physicalPath) {
        // see VSWhidbey #108945 
        // We need to filter out directory names which end
        // in " " or ".".  We want to treat path names that
        // end in these characters as files - however, Windows
        // will strip these characters off the end of the name, 
        // which may result in the name being treated as a
        // directory instead. 
 
        if (String.IsNullOrEmpty(physicalPath)) {
            return false; 
        }

        char lastChar = physicalPath[physicalPath.Length - 1];
        return lastChar == ' ' || lastChar == '.'; 
    }
 
    internal static bool DirectoryExists(String dirname) { 
        bool exists = false;
        dirname = RemoveTrailingDirectoryBackSlash(dirname); 
        if (HasInvalidLastChar(dirname))
            return false;

        try { 
            exists = Directory.Exists(dirname);
        } 
        catch { 
        }
 
        return exists;
    }

    internal static bool DirectoryAccessible(String dirname) { 
        bool accessible = false;
        dirname = RemoveTrailingDirectoryBackSlash(dirname); 
        if (HasInvalidLastChar(dirname)) 
            return false;
 
        try {
            accessible = (new DirectoryInfo(dirname)).Exists;
        }
        catch { 
        }
 
        return accessible; 
    }
 
    private static Char[] _invalidFileNameChars = Path.GetInvalidFileNameChars();
    internal static bool IsValidDirectoryName(String name) {
        if (String.IsNullOrEmpty(name)) {
            return false; 
        }
 
        if (name.IndexOfAny(_invalidFileNameChars, 0) != -1) { 
            return false;
        } 

        if (name.Equals(".") || name.Equals("..")) {
            return false;
        } 

        return true; 
    } 

    // 
    // Given a physical path, determine if it exists, and whether it is a directory or file.
    //
    // If directoryExistsOnError is set, set exists=true and isDirectory=true if we cannot confirm that the path does not exist.
    // If fileExistsOnError is set, set exists=true and isDirectory=false if we cannot confirm that the path does not exist. 
    //
 
    // this code is called by config that doesn't have AspNetHostingPermission 
    internal static void PhysicalPathStatus(string physicalPath, bool directoryExistsOnError, bool fileExistsOnError, out bool exists, out bool isDirectory) {
        exists = false; 
        isDirectory = true;

        Debug.Assert(!(directoryExistsOnError && fileExistsOnError), "!(directoryExistsOnError && fileExistsOnError)");
 
        if (String.IsNullOrEmpty(physicalPath))
            return; 
 
        using (new ApplicationImpersonationContext()) {
            UnsafeNativeMethods.WIN32_FILE_ATTRIBUTE_DATA data; 
            bool ok = UnsafeNativeMethods.GetFileAttributesEx(physicalPath, UnsafeNativeMethods.GetFileExInfoStandard, out data);
            if (ok) {
                exists = true;
                isDirectory = ((data.fileAttributes & (int) FileAttributes.Directory) == (int) FileAttributes.Directory); 
                if (isDirectory && HasInvalidLastChar(physicalPath)) {
                    exists = false; 
                } 
            }
            else { 
                if (directoryExistsOnError || fileExistsOnError) {
                    // Set exists to true if we cannot confirm that the path does NOT exist.
                    int hr = Marshal.GetHRForLastWin32Error();
                    if (!(hr == HResults.E_FILENOTFOUND || hr == HResults.E_PATHNOTFOUND)) { 
                        exists = true;
                        isDirectory = directoryExistsOnError; 
                    } 
                }
            } 
        }
    }

    // 
    // Use to avoid the perf hit of a Demand when the Demand is not necessary for security.
    // 
    // If trueOnError is set, then return true if we cannot confirm that the file does NOT exist. 
    //
    internal static bool DirectoryExists(string filename, bool trueOnError) { 
        filename = RemoveTrailingDirectoryBackSlash(filename);
        if (HasInvalidLastChar(filename)) {
            return false;
        } 

        UnsafeNativeMethods.WIN32_FILE_ATTRIBUTE_DATA data; 
        bool ok = UnsafeNativeMethods.GetFileAttributesEx(filename, UnsafeNativeMethods.GetFileExInfoStandard, out data); 
        if (ok) {
            // The path exists. Return true if it is a directory, false if a file. 
            return (data.fileAttributes & (int) FileAttributes.Directory) == (int) FileAttributes.Directory;
        }
        else {
            if (!trueOnError) { 
                return false;
            } 
            else { 
                // Return true if we cannot confirm that the file does NOT exist.
                int hr = Marshal.GetHRForLastWin32Error(); 
                if (hr == HResults.E_FILENOTFOUND || hr == HResults.E_PATHNOTFOUND) {
                    return false;
                }
                else { 
                    return true;
                } 
            } 
        }
    } 
}


// 
// Wraps the Win32 API FindFirstFile
// 
sealed class FindFileData { 

    private FileAttributesData _fileAttributesData; 
    private string _fileNameLong;
    private string _fileNameShort;

    internal string FileNameLong { get { return _fileNameLong; } } 
    internal string FileNameShort { get { return _fileNameShort; } }
    internal FileAttributesData FileAttributesData { get { return _fileAttributesData; } } 
 
    // FindFile - given a file name, gets the file attributes and short form (8.3 format) of a file name.
    static internal int FindFile(string path, out FindFileData data) { 
        IntPtr hFindFile;
        UnsafeNativeMethods.WIN32_FIND_DATA wfd;

        data = null; 

        // Remove trailing slash if any, otherwise FindFirstFile won't work correctly 
        path = FileUtil.RemoveTrailingDirectoryBackSlash(path); 
#if DBG
        Debug.Assert(Path.GetDirectoryName(path) != null, "Path.GetDirectoryName(path) != null"); 
        Debug.Assert(Path.GetFileName(path) != null, "Path.GetFileName(path) != null");
#endif

        hFindFile = UnsafeNativeMethods.FindFirstFile(path, out wfd); 
        int lastError = Marshal.GetLastWin32Error(); // FXCOP demands that this preceed the ==
        if (hFindFile == UnsafeNativeMethods.INVALID_HANDLE_VALUE) { 
            return HttpException.HResultFromLastError(lastError); 
        }
 
        UnsafeNativeMethods.FindClose(hFindFile);

#if DBG
        string file = Path.GetFileName(path); 
        file = file.TrimEnd(' ', '.');
        Debug.Assert(StringUtil.EqualsIgnoreCase(file, wfd.cFileName) || 
                     StringUtil.EqualsIgnoreCase(file, wfd.cAlternateFileName), 
                     "Path to FindFile is not for a single file: " + path);
#endif 

        data = new FindFileData(ref wfd);
        return HResults.S_OK;
    } 

    // FindFile - takes a full-path and a root-directory-path, and is used to get the 
    // short form (8.3 format) of the relative-path.  A FindFileData structure is returned 
    // with FileNameLong and FileNameShort relative to the specified root-directory-path.
    // 
    // For example, if full-path is "c:\vdir\subdirectory\t.aspx" and root-directory-path
    // is "c:\vdir", then the relative-path will be "subdirectory\t.aspx" and it's short
    // form will be something like "subdir~1\t~1.ASP".
    // 
    // This is used by FileChangesMonitor to support the ability to monitor all files and
    // directories at any depth beneath the application root directory. 
    internal static int FindFile(string fullPath, string rootDirectoryPath, out FindFileData data) { 

        int hr = FindFileData.FindFile(fullPath, out data); 
        if (hr != HResults.S_OK || String.IsNullOrEmpty(rootDirectoryPath)) {
            return hr;
        }
 
#if DBG
        // The trailing slash should have been removed already, unless the root is "c:\" 
        Debug.Assert(rootDirectoryPath.Length < 4 || rootDirectoryPath[rootDirectoryPath.Length-1] != '\\', "Trailing slash unexpected: " + rootDirectoryPath); 
#endif
 
        // remove it just in case
        rootDirectoryPath = FileUtil.RemoveTrailingDirectoryBackSlash(rootDirectoryPath);

#if DBG 
        Debug.Assert(fullPath.IndexOf(rootDirectoryPath, StringComparison.OrdinalIgnoreCase) == 0,
                     "fullPath (" + fullPath + ") is not within rootDirectoryPath (" + rootDirectoryPath + ")"); 
#endif 

        // crawl backwards along the subdirectories of fullPath until we get to the specified rootDirectoryPath 
        string relativePathLong = String.Empty;
        string relativePathShort = String.Empty;
        string currentParentDir = Path.GetDirectoryName(fullPath);
        while (currentParentDir != null 
               && currentParentDir.Length > rootDirectoryPath.Length+1
               && currentParentDir.IndexOf(rootDirectoryPath, StringComparison.OrdinalIgnoreCase) == 0) { 
 
            UnsafeNativeMethods.WIN32_FIND_DATA fd;
            IntPtr hFindFile = UnsafeNativeMethods.FindFirstFile(currentParentDir, out fd); 
            int lastError = Marshal.GetLastWin32Error(); // FXCOP demands that this preceed the ==
            if (hFindFile == UnsafeNativeMethods.INVALID_HANDLE_VALUE) {
                return HttpException.HResultFromLastError(lastError);
            } 
            UnsafeNativeMethods.FindClose(hFindFile);
 
#if DBG 
            Debug.Assert(!String.IsNullOrEmpty(fd.cFileName), "!String.IsNullOrEmpty(fd.cFileName)");
#endif 

            // build the long and short versions of the relative path
            relativePathLong = fd.cFileName + Path.DirectorySeparatorChar + relativePathLong;
            if (!String.IsNullOrEmpty(fd.cAlternateFileName)) { 
                relativePathShort = fd.cAlternateFileName + Path.DirectorySeparatorChar + relativePathShort;
            } 
            else { 
                relativePathShort = fd.cFileName + Path.DirectorySeparatorChar + relativePathShort;
            } 

            currentParentDir = Path.GetDirectoryName(currentParentDir);
        }
 
        if (!String.IsNullOrEmpty(relativePathLong)) {
            data.PrependRelativePath(relativePathLong, relativePathShort); 
        } 

#if DBG 
        Debug.Trace("FindFile", "fullPath=" + fullPath + ", rootDirectoryPath=" + rootDirectoryPath);
        Debug.Trace("FindFile", "relativePathLong=" + relativePathLong + ", relativePathShort=" + relativePathShort);
        string fileNameShort = data.FileNameShort == null ? "" : data.FileNameShort;
        Debug.Trace("FindFile", "FileNameLong=" + data.FileNameLong + ", FileNameShrot=" + fileNameShort); 
#endif
 
        return hr; 
    }
 
    internal FindFileData(ref UnsafeNativeMethods.WIN32_FIND_DATA wfd) {
        _fileAttributesData = new FileAttributesData(ref wfd);
        _fileNameLong = wfd.cFileName;
        if (wfd.cAlternateFileName != null 
            && wfd.cAlternateFileName.Length > 0
            && !StringUtil.EqualsIgnoreCase(wfd.cFileName, wfd.cAlternateFileName)) { 
            _fileNameShort = wfd.cAlternateFileName; 
        }
    } 

    private void PrependRelativePath(string relativePathLong, string relativePathShort) {
        _fileNameLong = relativePathLong + _fileNameLong;
 
        // if the short form is null or empty, prepend the short relative path to the long form
        string fileName = String.IsNullOrEmpty(_fileNameShort) ? _fileNameLong : _fileNameShort; 
        _fileNameShort = relativePathShort + fileName; 

        // if the short form is the same as the long form, set the short form to null 
        if (StringUtil.EqualsIgnoreCase(_fileNameShort, _fileNameLong)) {
            _fileNameShort = null;
        }
    } 
}
 
// 
// Wraps the Win32 API GetFileAttributesEx
// We use this api in addition to FindFirstFile because FindFirstFile 
// does not work for volumes (e.g. "c:\")
//
sealed class FileAttributesData {
    internal readonly FileAttributes    FileAttributes; 
    internal readonly DateTime          UtcCreationTime;
    internal readonly DateTime          UtcLastAccessTime; 
    internal readonly DateTime          UtcLastWriteTime; 
    internal readonly long              FileSize;
 
    static internal FileAttributesData NonExistantAttributesData {
        get {
            return new FileAttributesData();
        } 
    }
 
    static internal int GetFileAttributes(string path, out FileAttributesData fad) { 
        fad = null;
 
        UnsafeNativeMethods.WIN32_FILE_ATTRIBUTE_DATA  data;
        if (!UnsafeNativeMethods.GetFileAttributesEx(path, UnsafeNativeMethods.GetFileExInfoStandard, out data)) {
            return HttpException.HResultFromLastError(Marshal.GetLastWin32Error());
        } 

        fad = new FileAttributesData(ref data); 
        return HResults.S_OK; 
    }
 
    FileAttributesData() {
        FileSize = -1;
    }
 
    FileAttributesData(ref UnsafeNativeMethods.WIN32_FILE_ATTRIBUTE_DATA data) {
        FileAttributes    = (FileAttributes) data.fileAttributes; 
        UtcCreationTime   = DateTimeUtil.FromFileTimeToUtc(((long)data.ftCreationTimeHigh)   << 32 | (long)data.ftCreationTimeLow); 
        UtcLastAccessTime = DateTimeUtil.FromFileTimeToUtc(((long)data.ftLastAccessTimeHigh) << 32 | (long)data.ftLastAccessTimeLow);
        UtcLastWriteTime  = DateTimeUtil.FromFileTimeToUtc(((long)data.ftLastWriteTimeHigh)  << 32 | (long)data.ftLastWriteTimeLow); 
        FileSize          = (long)(uint)data.fileSizeHigh << 32 | (long)(uint)data.fileSizeLow;
    }

    internal FileAttributesData(ref UnsafeNativeMethods.WIN32_FIND_DATA wfd) { 
        FileAttributes    = (FileAttributes) wfd.dwFileAttributes;
        UtcCreationTime   = DateTimeUtil.FromFileTimeToUtc(((long)wfd.ftCreationTime_dwHighDateTime)   << 32 | (long)wfd.ftCreationTime_dwLowDateTime); 
        UtcLastAccessTime = DateTimeUtil.FromFileTimeToUtc(((long)wfd.ftLastAccessTime_dwHighDateTime) << 32 | (long)wfd.ftLastAccessTime_dwLowDateTime); 
        UtcLastWriteTime  = DateTimeUtil.FromFileTimeToUtc(((long)wfd.ftLastWriteTime_dwHighDateTime)  << 32 | (long)wfd.ftLastWriteTime_dwLowDateTime);
        FileSize          = (long)wfd.nFileSizeHigh << 32 | (long)wfd.nFileSizeLow; 
    }

#if DBG
    internal string DebugDescription(string indent) { 
        StringBuilder   sb = new StringBuilder(200);
        string          i2 = indent + "    "; 
 
        sb.Append(indent + "FileAttributesData\n");
        sb.Append(i2 + "FileAttributes: " + FileAttributes + "\n"); 
        sb.Append(i2 + "  CreationTime: " + Debug.FormatUtcDate(UtcCreationTime) + "\n");
        sb.Append(i2 + "LastAccessTime: " + Debug.FormatUtcDate(UtcLastAccessTime) + "\n");
        sb.Append(i2 + " LastWriteTime: " + Debug.FormatUtcDate(UtcLastWriteTime) + "\n");
        sb.Append(i2 + "      FileSize: " + FileSize.ToString("n0", NumberFormatInfo.InvariantInfo) + "\n"); 

        return sb.ToString(); 
    } 
#endif
 
}

}

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK