// 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.

/*============================================================
**
**
**
** Purpose: Managed ACL wrapper for files & directories.
**
**
===========================================================*/

using System.IO;
using System.Runtime.InteropServices;
using System.Security.Principal;
using Microsoft.Win32.SafeHandles;

namespace System.Security.AccessControl
{
    public abstract class FileSystemSecurity : NativeObjectSecurity
    {
        #region Member variables

        private const ResourceType s_ResourceType = ResourceType.FileObject;

        #endregion

        internal FileSystemSecurity(bool isContainer)
            : base(isContainer, s_ResourceType, _HandleErrorCode, isContainer)
        {
        }

        internal FileSystemSecurity(bool isContainer, string name, AccessControlSections includeSections, bool isDirectory)
            : base(isContainer, s_ResourceType, name, includeSections, _HandleErrorCode, isDirectory)
        {
        }

        internal FileSystemSecurity(bool isContainer, SafeFileHandle handle, AccessControlSections includeSections, bool isDirectory)
            : base(isContainer, s_ResourceType, handle, includeSections, _HandleErrorCode, isDirectory)
        {
        }

        private static Exception _HandleErrorCode(int errorCode, string name, SafeHandle handle, object context)
        {
            System.Exception exception = null;

            switch (errorCode)
            {
                case Interop.Errors.ERROR_INVALID_NAME:
                    exception = new ArgumentException(SR.Argument_InvalidName, nameof(name));
                    break;

                case Interop.Errors.ERROR_INVALID_HANDLE:
                    exception = new ArgumentException(SR.AccessControl_InvalidHandle);
                    break;

                case Interop.Errors.ERROR_FILE_NOT_FOUND:
                    if ((context != null) && (context is bool) && ((bool)context))
                    { // DirectorySecurity
                        if ((name != null) && (name.Length != 0))
                            exception = new DirectoryNotFoundException(name);
                        else
                            exception = new DirectoryNotFoundException();
                    }
                    else
                    {
                        if ((name != null) && (name.Length != 0))
                            exception = new FileNotFoundException(name);
                        else
                            exception = new FileNotFoundException();
                    }
                    break;

                default:
                    break;
            }

            return exception;
        }

        #region Factories

        public sealed override AccessRule AccessRuleFactory(
            IdentityReference identityReference,
            int accessMask,
            bool isInherited,
            InheritanceFlags inheritanceFlags,
            PropagationFlags propagationFlags,
            AccessControlType type)
        {
            return new FileSystemAccessRule(
                identityReference,
                accessMask,
                isInherited,
                inheritanceFlags,
                propagationFlags,
                type);
        }

        public sealed override AuditRule AuditRuleFactory(
            IdentityReference identityReference,
            int accessMask,
            bool isInherited,
            InheritanceFlags inheritanceFlags,
            PropagationFlags propagationFlags,
            AuditFlags flags)
        {
            return new FileSystemAuditRule(
                identityReference,
                accessMask,
                isInherited,
                inheritanceFlags,
                propagationFlags,
                flags);
        }

        #endregion

        #region Internal Methods

        internal AccessControlSections GetAccessControlSectionsFromChanges()
        {
            AccessControlSections persistRules = AccessControlSections.None;
            if (AccessRulesModified)
                persistRules = AccessControlSections.Access;
            if (AuditRulesModified)
                persistRules |= AccessControlSections.Audit;
            if (OwnerModified)
                persistRules |= AccessControlSections.Owner;
            if (GroupModified)
                persistRules |= AccessControlSections.Group;
            return persistRules;
        }

        internal void Persist(string fullPath)
        {
            WriteLock();

            try
            {
                AccessControlSections persistRules = GetAccessControlSectionsFromChanges();
                base.Persist(fullPath, persistRules);
                OwnerModified = GroupModified = AuditRulesModified = AccessRulesModified = false;
            }
            finally
            {
                WriteUnlock();
            }
        }

        internal void Persist(SafeFileHandle handle, string fullPath)
        {
            WriteLock();

            try
            {
                AccessControlSections persistRules = GetAccessControlSectionsFromChanges();
                base.Persist(handle, persistRules);
                OwnerModified = GroupModified = AuditRulesModified = AccessRulesModified = false;
            }
            finally
            {
                WriteUnlock();
            }
        }

        #endregion

        #region Public Methods

        public void AddAccessRule(FileSystemAccessRule rule)
        {
            base.AddAccessRule(rule);
            //PersistIfPossible();
        }

        public void SetAccessRule(FileSystemAccessRule rule)
        {
            base.SetAccessRule(rule);
        }

        public void ResetAccessRule(FileSystemAccessRule rule)
        {
            base.ResetAccessRule(rule);
        }

        public bool RemoveAccessRule(FileSystemAccessRule rule)
        {
            if (rule == null)
                throw new ArgumentNullException(nameof(rule));

            // If the rule to be removed matches what is there currently then
            // remove it unaltered. That is, don't mask off the Synchronize bit.
            // This is to avoid dangling synchronize bit

            AuthorizationRuleCollection rules = GetAccessRules(true, true, rule.IdentityReference.GetType());

            for (int i = 0; i < rules.Count; i++)
            {
                FileSystemAccessRule fsrule = rules[i] as FileSystemAccessRule;

                if ((fsrule != null) && (fsrule.FileSystemRights == rule.FileSystemRights)
                    && (fsrule.IdentityReference == rule.IdentityReference)
                    && (fsrule.AccessControlType == rule.AccessControlType))
                {
                    return base.RemoveAccessRule(rule);
                }
            }

            // Mask off the synchronize bit (that is automatically added for Allow)
            // before removing the ACL. The logic here should be same as Deny and hence
            // fake a call to AccessMaskFromRights as though the ACL is for Deny

            FileSystemAccessRule ruleNew = new FileSystemAccessRule(
                                                    rule.IdentityReference,
                                                    FileSystemAccessRule.AccessMaskFromRights(rule.FileSystemRights, AccessControlType.Deny),
                                                    rule.IsInherited,
                                                    rule.InheritanceFlags,
                                                    rule.PropagationFlags,
                                                    rule.AccessControlType);

            return base.RemoveAccessRule(ruleNew);
        }

        public void RemoveAccessRuleAll(FileSystemAccessRule rule)
        {
            // We don't need to worry about the synchronize bit here
            // AccessMask is ignored anyways in a RemoveAll call

            base.RemoveAccessRuleAll(rule);
        }

        public void RemoveAccessRuleSpecific(FileSystemAccessRule rule)
        {
            if (rule == null)
                throw new ArgumentNullException(nameof(rule));

            // If the rule to be removed matches what is there currently then
            // remove it unaltered. That is, don't mask off the Synchronize bit
            // This is to avoid dangling synchronize bit

            AuthorizationRuleCollection rules = GetAccessRules(true, true, rule.IdentityReference.GetType());

            for (int i = 0; i < rules.Count; i++)
            {
                FileSystemAccessRule fsrule = rules[i] as FileSystemAccessRule;

                if ((fsrule != null) && (fsrule.FileSystemRights == rule.FileSystemRights)
                    && (fsrule.IdentityReference == rule.IdentityReference)
                    && (fsrule.AccessControlType == rule.AccessControlType))
                {
                    base.RemoveAccessRuleSpecific(rule);
                    return;
                }
            }

            // Mask off the synchronize bit (that is automatically added for Allow)
            // before removing the ACL. The logic here should be same as Deny and hence
            // fake a call to AccessMaskFromRights as though the ACL is for Deny

            FileSystemAccessRule ruleNew = new FileSystemAccessRule(
                                                    rule.IdentityReference,
                                                    FileSystemAccessRule.AccessMaskFromRights(rule.FileSystemRights, AccessControlType.Deny),
                                                    rule.IsInherited,
                                                    rule.InheritanceFlags,
                                                    rule.PropagationFlags,
                                                    rule.AccessControlType);

            base.RemoveAccessRuleSpecific(ruleNew);
        }

        public void AddAuditRule(FileSystemAuditRule rule)
        {
            base.AddAuditRule(rule);
        }

        public void SetAuditRule(FileSystemAuditRule rule)
        {
            base.SetAuditRule(rule);
        }

        public bool RemoveAuditRule(FileSystemAuditRule rule)
        {
            return base.RemoveAuditRule(rule);
        }

        public void RemoveAuditRuleAll(FileSystemAuditRule rule)
        {
            base.RemoveAuditRuleAll(rule);
        }

        public void RemoveAuditRuleSpecific(FileSystemAuditRule rule)
        {
            base.RemoveAuditRuleSpecific(rule);
        }
        #endregion

        #region some overrides
        public override Type AccessRightType
        {
            get { return typeof(System.Security.AccessControl.FileSystemRights); }
        }

        public override Type AccessRuleType
        {
            get { return typeof(System.Security.AccessControl.FileSystemAccessRule); }
        }

        public override Type AuditRuleType
        {
            get { return typeof(System.Security.AccessControl.FileSystemAuditRule); }
        }
        #endregion
    }
}

