// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
/*=============================================================================
**
** Class: PermissionListSet.cs
** 
** <OWNER>[....]</OWNER>
** <OWNER>[....]</OWNER>
**
** Purpose: Holds state about A/G/R permissionsets in a callstack or appdomain
**          (Replacement for PermissionListSet)
**
=============================================================================*/

namespace System.Security
{
    using System.Globalization;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.Security.Permissions;
    using System.Threading;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;

    [Serializable]
    sealed internal class PermissionListSet
    {
        // Only internal (public) methods are creation methods and demand evaluation methods.
        // Scroll down to the end to see them.
        private PermissionSetTriple m_firstPermSetTriple;
        private ArrayList m_permSetTriples;
#if FEATURE_COMPRESSEDSTACK
        private ArrayList m_zoneList;
        private ArrayList m_originList;
#endif // FEATURE_COMPRESSEDSTACK

        internal PermissionListSet() {}

        private void EnsureTriplesListCreated()
        {
            if (m_permSetTriples == null)
            {
                m_permSetTriples = new ArrayList();
                if (m_firstPermSetTriple != null)
                {
                    m_permSetTriples.Add(m_firstPermSetTriple);
                    m_firstPermSetTriple = null;
                }
            }
        }

#if FEATURE_PLS
        [System.Security.SecurityCritical]  // auto-generated
        internal void UpdateDomainPLS (PermissionListSet adPLS) {
            if (adPLS != null && adPLS.m_firstPermSetTriple != null)
                UpdateDomainPLS(adPLS.m_firstPermSetTriple.GrantSet, adPLS.m_firstPermSetTriple.RefusedSet);
        }

        [System.Security.SecurityCritical]  // auto-generated
        internal void UpdateDomainPLS (PermissionSet grantSet, PermissionSet deniedSet) {
            Contract.Assert(m_permSetTriples == null, "m_permSetTriples != null");
            if (m_firstPermSetTriple == null)
                m_firstPermSetTriple = new PermissionSetTriple();

            // update the grant and denied sets
            m_firstPermSetTriple.UpdateGrant(grantSet);
            m_firstPermSetTriple.UpdateRefused(deniedSet);
        }
#endif // FEATURE_PLS

        private void Terminate(PermissionSetTriple currentTriple)
        {
            UpdateTripleListAndCreateNewTriple(currentTriple, null);
        }

        [System.Security.SecurityCritical]  // auto-generated
        private void Terminate(PermissionSetTriple currentTriple, PermissionListSet pls)
        {
#if FEATURE_COMPRESSEDSTACK
            this.UpdateZoneAndOrigin(pls);
#endif // FEATURE_COMPRESSEDSTACK
            this.UpdatePermissions(currentTriple, pls);
            this.UpdateTripleListAndCreateNewTriple(currentTriple, null);
        }

        [System.Security.SecurityCritical]  // auto-generated
        private bool Update(PermissionSetTriple currentTriple, PermissionListSet pls)
        {
#if FEATURE_COMPRESSEDSTACK
            this.UpdateZoneAndOrigin(pls);
#endif // FEATURE_COMPRESSEDSTACK
            return this.UpdatePermissions(currentTriple, pls);
        }

        [System.Security.SecurityCritical]  // auto-generated
        private bool Update(PermissionSetTriple currentTriple, FrameSecurityDescriptor fsd)
        {
#if FEATURE_COMPRESSEDSTACK
           FrameSecurityDescriptorWithResolver fsdWithResolver = fsd as FrameSecurityDescriptorWithResolver;
           if (fsdWithResolver != null)
           {
               return Update2(currentTriple, fsdWithResolver);
           }
#endif // FEATURE_COMPRESSEDSTACK

           // check imperative
           bool fHalt = Update2(currentTriple, fsd, false);
           if (!fHalt)            
           {
                // then declarative
                fHalt = Update2(currentTriple, fsd, true);
           }
           return fHalt;
        }

#if FEATURE_COMPRESSEDSTACK
        [System.Security.SecurityCritical]
        private bool Update2(PermissionSetTriple currentTriple, FrameSecurityDescriptorWithResolver fsdWithResolver)
        {
            System.Reflection.Emit.DynamicResolver resolver = fsdWithResolver.Resolver;
            CompressedStack dynamicCompressedStack = resolver.GetSecurityContext();
            dynamicCompressedStack.CompleteConstruction(null);
            return this.Update(currentTriple, dynamicCompressedStack.PLS);
        }
#endif // FEATURE_COMPRESSEDSTACK

        [System.Security.SecurityCritical]  // auto-generated
        private bool Update2(PermissionSetTriple currentTriple, FrameSecurityDescriptor fsd, bool fDeclarative)
        {
            // Deny
            PermissionSet deniedPset = fsd.GetDenials(fDeclarative);
            if (deniedPset != null)
            {
                currentTriple.UpdateRefused(deniedPset);
            }

            // permit only
            PermissionSet permitOnlyPset = fsd.GetPermitOnly(fDeclarative);
            if (permitOnlyPset != null)
            {
                currentTriple.UpdateGrant(permitOnlyPset);
            }

            // Assert all possible
            if (fsd.GetAssertAllPossible())
            {
                // If we have no grant set, it means that the only assembly we've seen on the stack so
                // far is mscorlib. Since mscorlib will always be fully trusted, the grant set of the
                // compressed stack is also FullTrust.
                if (currentTriple.GrantSet == null)
                    currentTriple.GrantSet = PermissionSet.s_fullTrust;

                UpdateTripleListAndCreateNewTriple(currentTriple, m_permSetTriples);
                currentTriple.GrantSet = PermissionSet.s_fullTrust;
                currentTriple.UpdateAssert(fsd.GetAssertions(fDeclarative));
                return true;
            }
                
            // Assert
            PermissionSet assertPset = fsd.GetAssertions(fDeclarative);
            if (assertPset != null)
            {
                if (assertPset.IsUnrestricted())
                {
                    // If we have no grant set, it means that the only assembly we've seen on the stack so
                    // far is mscorlib. Since mscorlib will always be fully trusted, the grant set of the
                    // compressed stack is also FullTrust.
                    if (currentTriple.GrantSet == null)
                        currentTriple.GrantSet = PermissionSet.s_fullTrust;

                    UpdateTripleListAndCreateNewTriple(currentTriple, m_permSetTriples);
                    currentTriple.GrantSet = PermissionSet.s_fullTrust;
                    currentTriple.UpdateAssert(assertPset);
                    return true;
                }

                PermissionSetTriple retTriple = currentTriple.UpdateAssert(assertPset);
                if (retTriple != null)
                {
                    EnsureTriplesListCreated();
                    m_permSetTriples.Add(retTriple);
                }
            }

            return false;
        }
        [System.Security.SecurityCritical]  // auto-generated
        private void Update(PermissionSetTriple currentTriple, PermissionSet in_g, PermissionSet in_r)
        {
#if FEATURE_COMPRESSEDSTACK
            ZoneIdentityPermission z;
            UrlIdentityPermission u;
            currentTriple.UpdateGrant(in_g, out z, out u);
            currentTriple.UpdateRefused(in_r);
            AppendZoneOrigin(z, u);
#else // !FEATURE_COMPRESEDSTACK
            currentTriple.UpdateGrant(in_g);
            currentTriple.UpdateRefused(in_r);
#endif // FEATURE_COMPRESSEDSTACK
        }

        // Called from the VM for HG CS construction        
        [System.Security.SecurityCritical]  // auto-generated
        private void Update(PermissionSet in_g)
        {
            if (m_firstPermSetTriple == null)
                m_firstPermSetTriple = new PermissionSetTriple();
            Update(m_firstPermSetTriple, in_g, null);
        }
        
#if FEATURE_COMPRESSEDSTACK
        private void UpdateZoneAndOrigin(PermissionListSet pls)
        {
            if (pls != null)
            {
                if (this.m_zoneList == null && pls.m_zoneList != null && pls.m_zoneList.Count > 0)
                    this.m_zoneList = new ArrayList();
                UpdateArrayList(this.m_zoneList, pls.m_zoneList);
                if (this.m_originList == null && pls.m_originList != null && pls.m_originList.Count > 0)
                    this.m_originList = new ArrayList();                                
                UpdateArrayList(this.m_originList, pls.m_originList);
            }
        }
#endif // FEATURE_COMPRESSEDSTACK

        [System.Security.SecurityCritical]  // auto-generated
        private bool UpdatePermissions(PermissionSetTriple currentTriple, PermissionListSet pls)
        {
            if (pls != null)
            {
                if (pls.m_permSetTriples != null)
                {
                    // DCS has an AGR List. So we need to add the AGR List
                    UpdateTripleListAndCreateNewTriple(currentTriple,pls.m_permSetTriples);
                }
                else
                {
                    // Common case: One AGR set
                    
                    PermissionSetTriple tmp_psTriple = pls.m_firstPermSetTriple;
                    PermissionSetTriple retTriple;
                    // First try and update currentTriple. Return value indicates if we can stop construction
                    if (currentTriple.Update(tmp_psTriple, out retTriple))
                        return true;
                    // If we got a non-null retTriple, what it means is that compression failed,
                    // and we now have 2 triples to deal with: retTriple and currentTriple.
                    // retTriple has to be appended first. then currentTriple.
                    if (retTriple != null)
                    {
                        EnsureTriplesListCreated();
                        // we just created a new triple...add the previous one (returned) to the list
                        m_permSetTriples.Add(retTriple);
                    }
                }
            }
            else
            {
                // pls can be null only outside the loop in CreateCompressedState
                UpdateTripleListAndCreateNewTriple(currentTriple, null);
            }
            

            return false;
            
        }


        private void UpdateTripleListAndCreateNewTriple(PermissionSetTriple currentTriple, ArrayList tripleList)
        {
            if (!currentTriple.IsEmpty())
            {
                if (m_firstPermSetTriple == null && m_permSetTriples == null)
                {
                    m_firstPermSetTriple = new PermissionSetTriple(currentTriple);
                }
                else
                {
                    EnsureTriplesListCreated();
                    m_permSetTriples.Add(new PermissionSetTriple(currentTriple));
                }
                currentTriple.Reset();
            }
            if (tripleList != null)
            {
                EnsureTriplesListCreated();
                m_permSetTriples.AddRange(tripleList);
            }
        }

        private static void UpdateArrayList(ArrayList current, ArrayList newList)
        {
            if (newList == null)
                return;

            for(int i=0;i < newList.Count; i++)
            {
                if (!current.Contains(newList[i]))
                    current.Add(newList[i]);
            }

        }

#if FEATURE_COMPRESSEDSTACK
        private void AppendZoneOrigin(ZoneIdentityPermission z, UrlIdentityPermission u)
        {

            if (z != null)
            {
                if (m_zoneList == null)
                    m_zoneList = new ArrayList();
                z.AppendZones(m_zoneList);
            }

            if (u != null)
            {
                if (m_originList == null)
                    m_originList = new ArrayList();
                u.AppendOrigin(m_originList);
            }
        }

[System.Security.SecurityCritical]  // auto-generated
[System.Runtime.InteropServices.ComVisible(true)]
        // public(internal) interface begins...
        // Creation functions
        static internal PermissionListSet CreateCompressedState(CompressedStack cs, CompressedStack innerCS)
        {
            // function that completes the construction of the compressed stack if not done so already (bottom half for demand evaluation)
            
            bool bHaltConstruction = false;
            if (cs.CompressedStackHandle == null)
                return null; //  FT case or Security off
   
            PermissionListSet pls = new PermissionListSet();
            PermissionSetTriple currentTriple = new PermissionSetTriple();
            int numDomains = CompressedStack.GetDCSCount(cs.CompressedStackHandle);
            for (int i=numDomains-1; (i >= 0 && !bHaltConstruction) ; i--)
            {
                DomainCompressedStack dcs = CompressedStack.GetDomainCompressedStack(cs.CompressedStackHandle, i);
                if (dcs == null)
                    continue; // we hit a FT Domain
                if (dcs.PLS == null)
                {
                    // We failed on some DCS
                    throw new SecurityException(String.Format(CultureInfo.InvariantCulture, Environment.GetResourceString("Security_Generic")));
                }
                pls.UpdateZoneAndOrigin(dcs.PLS);
                pls.Update(currentTriple, dcs.PLS); 
                bHaltConstruction = dcs.ConstructionHalted;
            }
            if (!bHaltConstruction)
            {
                PermissionListSet tmp_pls = null;
                // Construction did not halt. 
                if (innerCS != null)
                {
                    innerCS.CompleteConstruction(null);
                    tmp_pls = innerCS.PLS;
                }
                pls.Terminate(currentTriple, tmp_pls);
            }
            else
            {
                pls.Terminate(currentTriple);
            }

            return pls;
        }

        [System.Security.SecurityCritical]  // auto-generated
        static internal PermissionListSet CreateCompressedState(IntPtr unmanagedDCS, out bool bHaltConstruction)
        {
            PermissionListSet pls = new PermissionListSet();
            PermissionSetTriple currentTriple = new PermissionSetTriple();

            PermissionSet tmp_g, tmp_r;
            // Construct the descriptor list
            int descCount = DomainCompressedStack.GetDescCount(unmanagedDCS);
            bHaltConstruction = false;
            for(int i=0; (i < descCount && !bHaltConstruction); i++)
            {
                FrameSecurityDescriptor fsd;
                Assembly assembly;
                if (DomainCompressedStack.GetDescriptorInfo(unmanagedDCS, i, out tmp_g, out tmp_r, out assembly, out fsd))
                {
                    // Got an FSD
                    bHaltConstruction = pls.Update(currentTriple, fsd);
                }
                else
                {
                    pls.Update(currentTriple, tmp_g, tmp_r);
                }
                
            }
            if (!bHaltConstruction)
            {
                // domain
                if (!DomainCompressedStack.IgnoreDomain(unmanagedDCS))
                {
                    DomainCompressedStack.GetDomainPermissionSets(unmanagedDCS, out tmp_g, out tmp_r);
                    pls.Update(currentTriple, tmp_g, tmp_r);
                }
            }
            pls.Terminate(currentTriple);


            // return the created object
            return pls;
            
        }
        [System.Security.SecurityCritical]  // auto-generated
        static internal PermissionListSet CreateCompressedState_HG()
        {
            PermissionListSet pls = new PermissionListSet();
            CompressedStack.GetHomogeneousPLS(pls);
            return pls;
        }
#endif // #if FEATURE_COMPRESSEDSTACK
        // Private Demand evaluation functions - only called from the VM
        [System.Security.SecurityCritical]  // auto-generated
        internal bool CheckDemandNoThrow(CodeAccessPermission demand)
        {
            // AppDomain permissions - no asserts. So there should only be one triple to work with
            Contract.Assert(m_permSetTriples == null && m_firstPermSetTriple != null, "More than one PermissionSetTriple encountered in AD PermissionListSet");
            

            
            PermissionToken permToken = null;
            if (demand != null)
                permToken = PermissionToken.GetToken(demand);

            return m_firstPermSetTriple.CheckDemandNoThrow(demand, permToken);
                

        }
        [System.Security.SecurityCritical]  // auto-generated
        internal bool CheckSetDemandNoThrow(PermissionSet pSet)
        {
            // AppDomain permissions - no asserts. So there should only be one triple to work with
            Contract.Assert(m_permSetTriples == null && m_firstPermSetTriple != null, "More than one PermissionSetTriple encountered in AD PermissionListSet");

            
            return m_firstPermSetTriple.CheckSetDemandNoThrow(pSet);
        }

        // Demand evauation functions
        [System.Security.SecurityCritical]  // auto-generated
        internal bool CheckDemand(CodeAccessPermission demand, PermissionToken permToken, RuntimeMethodHandleInternal rmh)
        {
            bool bRet = SecurityRuntime.StackContinue;
            if (m_permSetTriples != null)
            {
                for (int i=0; (i < m_permSetTriples.Count && bRet != SecurityRuntime.StackHalt) ; i++)
                {
                    PermissionSetTriple psTriple = (PermissionSetTriple)m_permSetTriples[i];
                    bRet = psTriple.CheckDemand(demand, permToken, rmh);
                }
            }
            else if (m_firstPermSetTriple != null)
            {
                bRet = m_firstPermSetTriple.CheckDemand(demand, permToken, rmh);
            }

            return bRet;
        }

        [System.Security.SecurityCritical]  // auto-generated
        internal bool CheckSetDemand(PermissionSet pset , RuntimeMethodHandleInternal rmh)
        {
            PermissionSet unused;
            CheckSetDemandWithModification(pset, out unused, rmh);
            return SecurityRuntime.StackHalt; //  CS demand check always terminates the stackwalk    
        }

        [System.Security.SecurityCritical]
        internal bool CheckSetDemandWithModification(PermissionSet pset, out PermissionSet alteredDemandSet, RuntimeMethodHandleInternal rmh)
        {
            bool bRet = SecurityRuntime.StackContinue;
            PermissionSet demandSet = pset;
            alteredDemandSet = null;
            if (m_permSetTriples != null)
            {
                for (int i=0; (i < m_permSetTriples.Count && bRet != SecurityRuntime.StackHalt) ; i++)
                {
                    PermissionSetTriple psTriple = (PermissionSetTriple)m_permSetTriples[i];
                    bRet = psTriple.CheckSetDemand(demandSet, out alteredDemandSet, rmh);
                    if (alteredDemandSet != null)
                        demandSet = alteredDemandSet;
                }
            }
            else if (m_firstPermSetTriple != null)
            {
                bRet = m_firstPermSetTriple.CheckSetDemand(demandSet, out alteredDemandSet, rmh);
            }

            return bRet;
        }

        /// <summary>
        ///     Check to see if the PLS satisfies a demand for the special permissions encoded in flags
        /// </summary>
        /// <param name="flags">set of flags to check (See PermissionType)</param>
        [System.Security.SecurityCritical]  // auto-generated
        private bool CheckFlags(int flags)
        {
            Contract.Assert(flags != 0, "Invalid permission flag demand");

            bool check = true;

            if (m_permSetTriples != null)
            {
                for (int i = 0; i < m_permSetTriples.Count && check && flags != 0; i++)
                {
                    check &= ((PermissionSetTriple)m_permSetTriples[i]).CheckFlags(ref flags);
                }
            }
            else if (m_firstPermSetTriple != null)
            {
                check = m_firstPermSetTriple.CheckFlags(ref flags);
            }
            
            return check;
        }

        /// <summary>
        ///     Demand which succeeds if either a set of special permissions or a permission set is granted
        ///     to the call stack
        /// </summary>
        /// <param name="flags">set of flags to check (See PermissionType)</param>
        /// <param name="grantSet">alternate permission set to check</param>
        [System.Security.SecurityCritical]  // auto-generated
        internal void DemandFlagsOrGrantSet(int flags, PermissionSet grantSet)
        {
            if (CheckFlags(flags))
                return;

            CheckSetDemand(grantSet, RuntimeMethodHandleInternal.EmptyHandle);
        }

#if FEATURE_COMPRESSEDSTACK
        internal void GetZoneAndOrigin(ArrayList zoneList, ArrayList originList, PermissionToken zoneToken, PermissionToken originToken)
        {
            if (m_zoneList != null)
                zoneList.AddRange(m_zoneList);
            if (m_originList != null)
                originList.AddRange(m_originList);
        }
#endif
    }

}
