e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
633 lines
24 KiB
C#
633 lines
24 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="UrlPath.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
private static readonly char[] s_invalidPathChars = Path.GetInvalidPathChars();
|
|
|
|
// 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;
|
|
|
|
// DevDiv 340712: GetConfigPathData generates n^2 exceptions where n is number of incorrectly placed '/'
|
|
// Explicitly prevent frequent exception cases since this method is called a few times per url segment
|
|
if ((physicalPath != null) &&
|
|
(physicalPath.Length > _maxPathLength ||
|
|
physicalPath.IndexOfAny(s_invalidPathChars) != -1 ||
|
|
// Contains ':' at any position other than 2nd char
|
|
(physicalPath.Length > 0 && physicalPath[0] == ':') ||
|
|
(physicalPath.Length > 2 && physicalPath.IndexOf(':', 2) > 0))) {
|
|
|
|
// see comment below
|
|
pathTooLong = true;
|
|
return true;
|
|
}
|
|
|
|
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 ? "<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
|
|
|
|
}
|
|
|
|
}
|