File.Exists 拒绝访问文件与拒绝访问目录时的行为不同

File.Exists acts differently when access is denied to the file vs denied to the dir

基于 File.ExistsMSDN documentationFile.Exists 方法应该 return false 出现任何错误,包括调用者无权读取文件。

我希望它 return false 当文件设置为 FullControl 拒绝用户访问和 FullControl 拒绝用户访问目录时文件位于。

我看到的是当用户可以访问目录,但不能访问文件时,File.Exists returns true;但是,如果用户无权访问该目录,File.Exists returns false.

我写了一个小程序来演示我在说什么:

using System;
using System.DirectoryServices;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;

namespace ConsoleApplication1
{
    internal class Program
    {
        private const string DirName = "TestDir";
        private const string FileName = "File.txt";
        private const string Password = "Password1";
        private const string UserName = "PermissionTestUser";
        private static WindowsImpersonationContext Identity = null;
        private static IntPtr LogonToken = IntPtr.Zero;

        public enum LogonProvider
        {
            LOGON32_PROVIDER_DEFAULT = 0,
            LOGON32_PROVIDER_WINNT35 = 1,
            LOGON32_PROVIDER_WINNT40 = 2,
            LOGON32_PROVIDER_WINNT50 = 3
        };

        public enum LogonType
        {
            LOGON32_LOGON_INTERACTIVE = 2,
            LOGON32_LOGON_NETWORK = 3,
            LOGON32_LOGON_BATCH = 4,
            LOGON32_LOGON_SERVICE = 5,
            LOGON32_LOGON_UNLOCK = 7,
            LOGON32_LOGON_NETWORK_CLEARTEXT = 8, // Win2K or higher
            LOGON32_LOGON_NEW_CREDENTIALS = 9 // Win2K or higher
        };

        public static void Main(string[] args)
        {
            string filePath = Path.Combine(DirName, FileName);
            try
            {
                CreateUser();
                CreateDir();
                CreateFile(filePath);

                // grant user full control to the dir
                SetAccess(DirName, AccessControlType.Allow);
                // deny user full control to the file
                SetAccess(filePath, AccessControlType.Deny);

                // impersonate user
                Impersonate();
                Console.WriteLine("File.Exists (with dir permissions): {0}", File.Exists(filePath));
                UndoImpersonate();

                // deny access to dir
                SetAccess(DirName, AccessControlType.Deny);

                // impersonate user
                Impersonate();
                Console.WriteLine("File.Exists (without dir permissions): {0}", File.Exists(filePath));
                UndoImpersonate();
            }
            finally
            {
                UndoImpersonate();
                DeleteDir();
                DeleteUser();
            }
        }

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        private static extern bool CloseHandle(IntPtr handle);

        private static void CreateDir()
        {
            Directory.CreateDirectory(DirName);
        }

        private static void CreateFile(string path)
        {
            File.Create(path).Dispose();
        }

        private static void CreateUser()
        {
            DirectoryEntry ad = new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer");
            DirectoryEntry newUser = ad.Children.Add(UserName, "user");
            newUser.Invoke("SetPassword", new object[] { Password });
            newUser.Invoke("Put", new object[] { "Description", "Test user" });
            newUser.CommitChanges();
        }

        private static void DeleteDir()
        {
            Directory.Delete(DirName, true);
        }

        private static void DeleteUser()
        {
            DirectoryEntry ad = new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer");
            DirectoryEntries users = ad.Children;
            DirectoryEntry user = users.Find(UserName, "user");

            if (user != null)
            {
                users.Remove(user);
            }
        }

        private static void Impersonate()
        {
            if (LogonUser(UserName, ".", Password, (int)LogonType.LOGON32_LOGON_INTERACTIVE, (int)LogonProvider.LOGON32_PROVIDER_DEFAULT, ref LogonToken))
            {
                Identity = WindowsIdentity.Impersonate(LogonToken);
                return;
            }
        }

        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        private static extern bool LogonUser(string lpszUserName,
            string lpszDomain,
            string lpszPassword,
            int dwLogonType,
            int dwLogonProvider,
            ref IntPtr phToken);

        private static void SetAccess(string path, AccessControlType type)
        {
            FileSecurity fs = File.GetAccessControl(path);
            FileSystemAccessRule far = new FileSystemAccessRule(UserName, FileSystemRights.FullControl, type);
            fs.AddAccessRule(far);
            File.SetAccessControl(path, fs);
        }

        private static void UndoImpersonate()
        {
            if (Identity != null)
            {
                Identity.Undo();
                Identity = null;
            }

            if (LogonToken != IntPtr.Zero)
            {
                CloseHandle(LogonToken);
                LogonToken = IntPtr.Zero;
            }
        }
    }
}

这个程序运行的结果是:

File.Exists (with dir permissions): True
File.Exists (without dir permissions): False

任何人都可以解释为什么他们不同吗?在这两种情况下,用户都没有文件的读取权限。

这是 File.Exist 的默认行为。根据 MSDN:

File.Exist

Return Value Type: System.Boolean

true if the caller has the required permissions and path contains the name of an existing file; otherwise, false. This method also returns false if path is null, an invalid path, or a zero-length string. If the caller does not have sufficient permissions to read the specified file, no exception is thrown and the method returns false regardless of the existence of path.

另外

The Exists method should not be used for path validation, this method merely checks if the file specified in path exists. Passing an invalid path to Exists returns false.

也就是说,这里的required permission,就是需要知道文件存在的权限(如方法名暗示,File.Exist)。这意味着只要用户有权访问该目录,它就可以知道文件是否存在

在给定目录权限的情况下,用户是否具有文件访问权限不会影响用户对文件存在的了解。但是没有目录权限,用户无法知道文件的存在,因此 File.Exist returns false


编辑(根据评论反馈):

可能最令人困惑的部分是最后一句话:

If the caller does not have sufficient permissions to read the specified file, no exception is thrown and the method returns false regardless of the existence of path.

读取指定文件的足够权限取决于目录的read-access而不是read-access 指定的文件。 (Rob 先生的补充评论)。 "sufficient" 这个词可能会给出一些关于它 依赖于 read-access 到 parent 目录的行为的暗示需要, read-access到指定文件。

但我承认,解释和选词听起来可能更像是counter-intuitive,因为人们可能会直觉地将"sufficient permissions to read the specified file"解释为read-access 到指定文件而不是父目录。

如果文件确实存在但是File.Exists(filePath) returns false,这意味着应用程序对目录和文件都没有读取权限,这可以是证明了。

如果你真的想测试一个文件是否存在,你可以调用File.GetAccessControl(filePath)。如果调用returns无一例外,说明文件确实存在