//------------------------------------------------------------------------------
// <copyright file="DBDataPermission.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
// <owner current="true" primary="false">Microsoft</owner>
//------------------------------------------------------------------------------

namespace System.Data.Common {

    using System.Collections;
    using System.Data.Common;
    using System.Diagnostics;
    using System.Globalization;
    using System.Runtime.Serialization;
    using System.Security;
    using System.Security.Permissions;
    using System.Text;

    [SecurityPermissionAttribute(SecurityAction.InheritanceDemand, ControlEvidence=true, ControlPolicy=true)]
    [Serializable]
    public abstract class DBDataPermission :  CodeAccessPermission, IUnrestrictedPermission {

        private bool _isUnrestricted;// = false;
        private bool _allowBlankPassword;// = false;
        private NameValuePermission _keyvaluetree = NameValuePermission.Default;
        private /*DBConnectionString[]*/ArrayList _keyvalues; // = null;

        [ Obsolete("DBDataPermission() has been deprecated.  Use the DBDataPermission(PermissionState.None) constructor.  http://go.microsoft.com/fwlink/?linkid=14202", true) ] // V1.2.3300, MDAC 86034
        protected DBDataPermission() : this(PermissionState.None) { // V1.0.3300
        }

        protected DBDataPermission(PermissionState state) { // V1.0.3300
            if (state == PermissionState.Unrestricted) {
                _isUnrestricted = true;
            }
            else if (state == PermissionState.None) {
                _isUnrestricted = false;
            }
            else {
                throw ADP.InvalidPermissionState(state);
            }
        }

        [ Obsolete("DBDataPermission(PermissionState state,Boolean allowBlankPassword) has been deprecated.  Use the DBDataPermission(PermissionState.None) constructor.  http://go.microsoft.com/fwlink/?linkid=14202", true) ] // V1.2.3300, MDAC 86034
        protected DBDataPermission(PermissionState state, bool allowBlankPassword) : this(state) { // V1.0.3300,  MDAC 84281
            AllowBlankPassword = allowBlankPassword;
        }

        protected DBDataPermission(DBDataPermission permission) { // V1.0.5000,  for Copy
            if (null == permission) {
                throw ADP.ArgumentNull("permissionAttribute");
            }
            CopyFrom(permission);
        }

        protected DBDataPermission(DBDataPermissionAttribute permissionAttribute) { // V1.0.5000, for CreatePermission
            if (null == permissionAttribute) {
                throw ADP.ArgumentNull("permissionAttribute");
            }
            _isUnrestricted = permissionAttribute.Unrestricted;
            if (!_isUnrestricted) {
                _allowBlankPassword = permissionAttribute.AllowBlankPassword;
                if (permissionAttribute.ShouldSerializeConnectionString() || permissionAttribute.ShouldSerializeKeyRestrictions()) { // MDAC 86773
                    Add(permissionAttribute.ConnectionString, permissionAttribute.KeyRestrictions, permissionAttribute.KeyRestrictionBehavior);
                }
            }
        }

        // how connectionString security is used
        // parsetable (all string) is shared with connection
        internal DBDataPermission(DbConnectionOptions connectionOptions) { // v2.0
            if (null != connectionOptions) {
                _allowBlankPassword = connectionOptions.HasBlankPassword; // MDAC 84563
                AddPermissionEntry(new DBConnectionString(connectionOptions));
            }
        }

        public bool AllowBlankPassword { // V1.0.3300
            get {
                return _allowBlankPassword;
            }
            set { // MDAC 61263
                // for behavioral backward compatability with V1.1
                // set_AllowBlankPassword does not _isUnrestricted=false
                _allowBlankPassword = value;
            }
        }

        public virtual void Add(string connectionString, string restrictions, KeyRestrictionBehavior behavior) { // V1.0.5000
            DBConnectionString constr = new DBConnectionString(connectionString, restrictions, behavior, null, false);
            AddPermissionEntry(constr);
        }

        internal void AddPermissionEntry(DBConnectionString entry) {
            if (null == _keyvaluetree) {
                _keyvaluetree = new NameValuePermission();
            }
            if (null == _keyvalues) {
                _keyvalues = new ArrayList();
            }
            NameValuePermission.AddEntry(_keyvaluetree, _keyvalues, entry);
            _isUnrestricted = false; // MDAC 84639
        }

        protected void Clear() { // V1.2.3300, MDAC 83105
            _keyvaluetree = null;
            _keyvalues = null;
        }

        // IPermission interface methods
        // [ObsoleteAttribute("override Copy instead of using default implementation")] // not inherited
        override public IPermission Copy() {
            DBDataPermission copy = CreateInstance();
            copy.CopyFrom(this);
            return copy;
        }

        private void CopyFrom(DBDataPermission permission) {
            _isUnrestricted = permission.IsUnrestricted();
            if (!_isUnrestricted) {
                _allowBlankPassword = permission.AllowBlankPassword;

                if (null != permission._keyvalues) {
                    _keyvalues = (ArrayList) permission._keyvalues.Clone();

                    if (null != permission._keyvaluetree) {
                        _keyvaluetree = permission._keyvaluetree.CopyNameValue();
                    }
                }
            }
        }

        // [ Obsolete("use DBDataPermission(DBDataPermission) ctor") ]
        [System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.Demand, Name="FullTrust")] // V1.0.5000, MDAC 82936
        virtual protected DBDataPermission CreateInstance() {
            // derived class should override with a different implementation avoiding reflection to allow semi-trusted scenarios
            return (Activator.CreateInstance(GetType(), System.Reflection.BindingFlags.Public|System.Reflection.BindingFlags.Instance, null, null, CultureInfo.InvariantCulture, null) as DBDataPermission);
        }

        override public IPermission Intersect(IPermission target) { // used during Deny actions
            if (null == target) {
                return null;
            }
            if (target.GetType() != this.GetType()) {
                throw ADP.PermissionTypeMismatch();
            }
            if (this.IsUnrestricted()) { // MDAC 84803, NDPWhidbey 29121
                return target.Copy();
            }

            DBDataPermission operand = (DBDataPermission) target;
            if (operand.IsUnrestricted()) { // NDPWhidbey 29121
                return this.Copy();
            }

            DBDataPermission newPermission = (DBDataPermission) operand.Copy();
            newPermission._allowBlankPassword &= AllowBlankPassword;

            if ((null != _keyvalues) && (null != newPermission._keyvalues)) {
                newPermission._keyvalues.Clear();

                newPermission._keyvaluetree.Intersect(newPermission._keyvalues, _keyvaluetree);
            }
            else {
                // either target.Add or this.Add have not been called
                // return a non-null object so IsSubset calls will fail
                newPermission._keyvalues = null;
                newPermission._keyvaluetree = null;
            }

            if (newPermission.IsEmpty()) { // no intersection, MDAC 86773
                newPermission = null;
            }
            return newPermission;
        }

        private bool IsEmpty() { // MDAC 84804
            ArrayList keyvalues = _keyvalues;
            bool flag = (!IsUnrestricted() && !AllowBlankPassword && ((null == keyvalues) || (0 == keyvalues.Count)));
            return flag;
        }

        override public bool IsSubsetOf(IPermission target) {
            if (null == target) {
                return IsEmpty();
            }
            if (target.GetType() != this.GetType()) {
                throw ADP.PermissionTypeMismatch();
            }

            DBDataPermission superset = (target as DBDataPermission);

            bool subset = superset.IsUnrestricted();
            if (!subset) {
                if (!IsUnrestricted() &&
                    (!AllowBlankPassword || superset.AllowBlankPassword) &&
                    ((null == _keyvalues) || (null != superset._keyvaluetree))) {

                    subset = true;
                    if (null != _keyvalues) {
                        foreach(DBConnectionString kventry in _keyvalues) {
                            if(!superset._keyvaluetree.CheckValueForKeyPermit(kventry)) {
                                subset = false;
                                break;
                            }
                        }
                    }
                }
            }
            return subset;
        }

        // IUnrestrictedPermission interface methods
        public bool IsUnrestricted() {
            return _isUnrestricted;
        }

        override public IPermission Union(IPermission target) {
            if (null == target) {
                return this.Copy();
            }
            if (target.GetType() != this.GetType()) {
                throw ADP.PermissionTypeMismatch();
            }
            if (IsUnrestricted()) { // MDAC 84803
                return this.Copy();
            }

            DBDataPermission newPermission = (DBDataPermission) target.Copy();
            if (!newPermission.IsUnrestricted()) {
                newPermission._allowBlankPassword |= AllowBlankPassword;

                if (null != _keyvalues) {
                    foreach(DBConnectionString entry in _keyvalues) {
                        newPermission.AddPermissionEntry(entry);
                    }
                }
            }
            return (newPermission.IsEmpty() ? null : newPermission);
        }

        private string DecodeXmlValue(string value) {
            if ((null != value) && (0 < value.Length)) {
                value = value.Replace(""", "\"");
                value = value.Replace("'", "\'");
                value = value.Replace("<",   "<");
                value = value.Replace(">",   ">");
                value = value.Replace("&",  "&");
            }
            return value;
        }

        private string EncodeXmlValue(string value) {
            if ((null != value) && (0 < value.Length)) {
                value = value.Replace('\0', ' '); // assumption that '\0' will only be at end of string
                value = value.Trim();
                value = value.Replace("&",  "&");
                value = value.Replace(">",  ">");
                value = value.Replace("<",  "<");
                value = value.Replace("\'", "'");
                value = value.Replace("\"", """);
            }
            return value;
        }

        // <IPermission class="...Permission" version="1" AllowBlankPassword=false>
        //     <add ConnectionString="provider=x;data source=y;" KeyRestrictions="address=;server=" KeyRestrictionBehavior=PreventUsage/>
        // </IPermission>
        override public void FromXml(SecurityElement securityElement) {
            // code derived from CodeAccessPermission.ValidateElement
            if (null == securityElement) {
                throw ADP.ArgumentNull("securityElement");
            }
            string tag = securityElement.Tag;
            if (!tag.Equals(XmlStr._Permission) && !tag.Equals(XmlStr._IPermission)) {
                throw ADP.NotAPermissionElement();
            }
            String version = securityElement.Attribute(XmlStr._Version);
            if ((null != version) && !version.Equals(XmlStr._VersionNumber)) {
                throw ADP.InvalidXMLBadVersion();
            }

            string unrestrictedValue = securityElement.Attribute(XmlStr._Unrestricted);
            _isUnrestricted = (null != unrestrictedValue) && Boolean.Parse(unrestrictedValue);

            Clear(); // MDAC 83105
            if (!_isUnrestricted) {
                string allowNull = securityElement.Attribute(XmlStr._AllowBlankPassword);
                _allowBlankPassword = (null != allowNull) && Boolean.Parse(allowNull);

                ArrayList children = securityElement.Children;
                if (null != children) {
                    foreach(SecurityElement keyElement in children) {
                        tag = keyElement.Tag;
                        if ((XmlStr._add == tag) || ((null != tag) && (XmlStr._add == tag.ToLower(CultureInfo.InvariantCulture)))) {
                            string constr = keyElement.Attribute(XmlStr._ConnectionString);
                            string restrt = keyElement.Attribute(XmlStr._KeyRestrictions);
                            string behavr = keyElement.Attribute(XmlStr._KeyRestrictionBehavior);

                            KeyRestrictionBehavior behavior = KeyRestrictionBehavior.AllowOnly;
                            if (null != behavr) {
                                behavior = (KeyRestrictionBehavior) Enum.Parse(typeof(KeyRestrictionBehavior), behavr, true);
                            }
                            constr = DecodeXmlValue(constr);
                            restrt = DecodeXmlValue(restrt);
                            Add(constr, restrt, behavior);
                        }
                    }
                }
            }
            else {
                _allowBlankPassword = false;
            }
        }

        // <IPermission class="...Permission" version="1" AllowBlankPassword=false>
        //     <add ConnectionString="provider=x;data source=y;"/>
        //     <add ConnectionString="provider=x;data source=y;" KeyRestrictions="user id=;password=;" KeyRestrictionBehavior=AllowOnly/>
        //     <add ConnectionString="provider=x;data source=y;" KeyRestrictions="address=;server=" KeyRestrictionBehavior=PreventUsage/>
        // </IPermission>
        override public SecurityElement ToXml() {
            Type type = this.GetType();
            SecurityElement root = new SecurityElement(XmlStr._IPermission);
            root.AddAttribute(XmlStr._class, type.AssemblyQualifiedName.Replace('\"', '\''));
            root.AddAttribute(XmlStr._Version, XmlStr._VersionNumber);

            if (IsUnrestricted()) {
                root.AddAttribute(XmlStr._Unrestricted, XmlStr._true);
            }
            else {
                root.AddAttribute(XmlStr._AllowBlankPassword, _allowBlankPassword.ToString(CultureInfo.InvariantCulture));

                if (null != _keyvalues) {
                    foreach(DBConnectionString value in _keyvalues) {
                        SecurityElement valueElement = new SecurityElement(XmlStr._add);
                        string tmp;

                        tmp = value.ConnectionString; // WebData 97375
                        tmp = EncodeXmlValue(tmp);
                        if (!ADP.IsEmpty(tmp)) {
                            valueElement.AddAttribute(XmlStr._ConnectionString, tmp);
                        }
                        tmp = value.Restrictions;
                        tmp = EncodeXmlValue(tmp);
                        if (null == tmp) { tmp = ADP.StrEmpty; }
                        valueElement.AddAttribute(XmlStr._KeyRestrictions, tmp);

                        tmp = value.Behavior.ToString();
                        valueElement.AddAttribute(XmlStr._KeyRestrictionBehavior, tmp);

                        root.AddChild(valueElement);
                    }
                }
            }
            return root;
        }

        private static class XmlStr {
            internal const string _class = "class";
            internal const string _IPermission = "IPermission";
            internal const string _Permission = "Permission";
            internal const string _Unrestricted = "Unrestricted";
            internal const string _AllowBlankPassword = "AllowBlankPassword";
            internal const string _true = "true";
            internal const string _Version = "version";
            internal const string _VersionNumber = "1";

            internal const string _add = "add";

            internal const string _ConnectionString = "ConnectionString";
            internal const string _KeyRestrictions = "KeyRestrictions";
            internal const string _KeyRestrictionBehavior = "KeyRestrictionBehavior";
        }
    }
}