File: system\security\util\stringexpressionset.cs
Project: ndp\clr\src\bcl\mscorlib.csproj (mscorlib)
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
// StringExpressionSet
//
// <OWNER>Microsoft</OWNER>
//
 
namespace System.Security.Util {    
    using System.Text;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    using System.Globalization;
    using System.Runtime.Versioning;
    using System.IO;
    using System.Diagnostics.Contracts;
 
    [Serializable]
    internal class StringExpressionSet
    {
        // This field, as well as the expressions fields below are critical since they may contain
        // canonicalized full path data potentially built out of relative data passed as input to the
        // StringExpressionSet.  Full trust code using the string expression set needs to ensure that before
        // exposing this data out to partial trust, they protect against this.  Possibilities include:
        //
        //  1. Using the throwOnRelative flag
        //  2. Ensuring that the partial trust code has permission to see full path data
        //  3. Not using this set for paths (eg EnvironmentStringExpressionSet)
        //
        [SecurityCritical]
        protected ArrayList m_list;
        protected bool m_ignoreCase;
        [SecurityCritical]
        protected String m_expressions;
        [SecurityCritical]
        protected String[] m_expressionsArray;
 
        protected bool m_throwOnRelative;
        
        protected static readonly char[] m_separators = { ';' };
        protected static readonly char[] m_trimChars = { ' ' };
 
        protected static readonly char m_directorySeparator = '\\';
        protected static readonly char m_alternateDirectorySeparator = '/';
        
        public StringExpressionSet()
            : this( true, null, false )
        {
        }
        
        public StringExpressionSet( String str )
            : this( true, str, false )
        {
        }
        
        public StringExpressionSet( bool ignoreCase, bool throwOnRelative )
            : this( ignoreCase, null, throwOnRelative )
        {
        }
        
        [System.Security.SecuritySafeCritical]  // auto-generated
        public StringExpressionSet( bool ignoreCase, String str, bool throwOnRelative )
        {
            m_list = null;
            m_ignoreCase = ignoreCase;
            m_throwOnRelative = throwOnRelative;
            if (str == null)
                m_expressions = null;
            else
            AddExpressions( str );
        }
 
        protected virtual StringExpressionSet CreateNewEmpty()
        {
            return new StringExpressionSet();
        }
        
        [SecuritySafeCritical]
        public virtual StringExpressionSet Copy()
        {
            // SafeCritical: just copying this value around, not leaking it
 
            StringExpressionSet copy = CreateNewEmpty();
            if (this.m_list != null)
                copy.m_list = new ArrayList(this.m_list);
 
            copy.m_expressions = this.m_expressions;
            copy.m_ignoreCase = this.m_ignoreCase;
            copy.m_throwOnRelative = this.m_throwOnRelative;
            return copy;
        }
        
        public void SetThrowOnRelative( bool throwOnRelative )
        {
            this.m_throwOnRelative = throwOnRelative;
        }
 
        private static String StaticProcessWholeString( String str )
        {
            return str.Replace( m_alternateDirectorySeparator, m_directorySeparator );
        }
 
        private static String StaticProcessSingleString( String str )
        {
            return str.Trim( m_trimChars );
        }
 
        protected virtual String ProcessWholeString( String str )
        {
            return StaticProcessWholeString(str);
        }
 
        protected virtual String ProcessSingleString( String str )
        {
            return StaticProcessSingleString(str);
        }
        
        [System.Security.SecurityCritical]  // auto-generated
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        public void AddExpressions( String str )
        {
            if (str == null)
                throw new ArgumentNullException( "str" );
            Contract.EndContractBlock();
            if (str.Length == 0)
                return;
 
            str = ProcessWholeString( str );
 
            if (m_expressions == null)
                m_expressions = str;
            else
                m_expressions = m_expressions + m_separators[0] + str;
 
            m_expressionsArray = null;
 
            // We have to parse the string and compute the list here.
            // The logic in this class tries to delay this parsing but
            // since operations like IsSubsetOf are called during 
            // demand evaluation, it is not safe to delay this step
            // as that would cause concurring threads to update the object
            // at the same time. The CheckList operation should ideally be
            // removed from this class, but for the sake of keeping the 
            // changes to a minimum here, we simply make sure m_list 
            // cannot be null by parsing m_expressions eagerly.
 
            String[] arystr = Split( str );
 
            if (m_list == null)
                m_list = new ArrayList();
 
            for (int index = 0; index < arystr.Length; ++index)
            {
                if (arystr[index] != null && !arystr[index].Equals( "" ))
                {
                    String temp = ProcessSingleString( arystr[index] );
                    int indexOfNull = temp.IndexOf( '\0' );
 
                    if (indexOfNull != -1)
                        temp = temp.Substring( 0, indexOfNull );
 
                    if (temp != null && !temp.Equals( "" ))
                    {
                        if (m_throwOnRelative)
                        {
                            if (Path.IsRelative(temp))
                            {
                                throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) );
                            }
 
                            temp = CanonicalizePath( temp );
                        }
 
                        m_list.Add( temp );
                    }
                }
            }
 
            Reduce();
        }
 
        [System.Security.SecurityCritical]  // auto-generated
        public void AddExpressions( String[] str, bool checkForDuplicates, bool needFullPath )
        {
            AddExpressions(CreateListFromExpressions(str, needFullPath), checkForDuplicates);
        }
 
        [System.Security.SecurityCritical]  // auto-generated
        public void AddExpressions( ArrayList exprArrayList, bool checkForDuplicates)
        {
            Contract.Assert( m_throwOnRelative, "This should only be called when throw on relative is set" );
 
            m_expressionsArray = null;
            m_expressions = null;
 
            if (m_list != null)
                m_list.AddRange(exprArrayList);
            else
                m_list = new ArrayList(exprArrayList);
 
            if (checkForDuplicates)
                Reduce();
        }
 
 
        [System.Security.SecurityCritical]  // auto-generated
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        internal static ArrayList CreateListFromExpressions(String[] str, bool needFullPath)
        {
            if (str == null)
            {
                throw new ArgumentNullException( "str" );
            }
            Contract.EndContractBlock();
 
            ArrayList retArrayList = new ArrayList();
            for (int index = 0; index < str.Length; ++index)
            {
                if (str[index] == null)
                    throw new ArgumentNullException( "str" );
 
                // Replace alternate directory separators
                String oneString = StaticProcessWholeString( str[index] );
 
                if (oneString != null && oneString.Length != 0)
                {
                    // Trim leading and trailing spaces
                    String temp = StaticProcessSingleString(oneString);
 
                    int indexOfNull = temp.IndexOf('\0');
 
                    if (indexOfNull != -1)
                        temp = temp.Substring(0, indexOfNull);
 
                    if (temp != null && temp.Length != 0)
                    {
                        if (PathInternal.IsPartiallyQualified(temp))
                        {
                            throw new ArgumentException(Environment.GetResourceString("Argument_AbsolutePathRequired"));
                        }
 
                        temp = CanonicalizePath( temp, needFullPath );
 
                        retArrayList.Add( temp );
                    }
                }
            }
 
            return retArrayList;
        }
        
        [System.Security.SecurityCritical]  // auto-generated
        protected void CheckList()
        {
            if (m_list == null && m_expressions != null)
            {
                CreateList();
            }
        }
        
        protected String[] Split( String expressions )
        {
            if (m_throwOnRelative)
            {
                List<String> tempList = new List<String>();
 
                String[] quoteSplit = expressions.Split( '\"' );
 
                for (int i = 0; i < quoteSplit.Length; ++i)
                {
                    if (i % 2 == 0)
                    {
                        String[] semiSplit = quoteSplit[i].Split( ';' );
 
                        for (int j = 0; j < semiSplit.Length; ++j)
                        {
                            if (semiSplit[j] != null && !semiSplit[j].Equals( "" ))
                                tempList.Add( semiSplit[j] );
                        }
                    }
                    else
                    {
                        tempList.Add( quoteSplit[i] );
                    }
                }
 
                String[] finalArray = new String[tempList.Count];
 
                IEnumerator enumerator = tempList.GetEnumerator();
 
                int index = 0;
                while (enumerator.MoveNext())
                {
                    finalArray[index++] = (String)enumerator.Current;
                }
 
                return finalArray;
            }
            else
            {
                return expressions.Split( m_separators );
            }
        }
 
        
        [System.Security.SecurityCritical]  // auto-generated
        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]        
        protected void CreateList()
        {
            String[] expressionsArray = Split( m_expressions );
 
            m_list = new ArrayList();
            
            for (int index = 0; index < expressionsArray.Length; ++index)
            {
                if (expressionsArray[index] != null && !expressionsArray[index].Equals( "" ))
                {
                    String temp = ProcessSingleString( expressionsArray[index] );
 
                    int indexOfNull = temp.IndexOf( '\0' );
 
                    if (indexOfNull != -1)
                        temp = temp.Substring( 0, indexOfNull );
 
                    if (temp != null && !temp.Equals( "" ))
                    {
                        if (m_throwOnRelative)
                        {
                            if (Path.IsRelative(temp))
                            {
                                throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) );
                            }
 
                            temp = CanonicalizePath( temp );
                        }
                        
                        m_list.Add( temp );
                    }
                }
            }
        }
        
        [SecuritySafeCritical]
        public bool IsEmpty()
        {
            // SafeCritical: we're just showing that the expressions are empty, the sensitive portion is their
            // contents - not the existence of the contents
            if (m_list == null)
            {
                return m_expressions == null;
            }
            else
            {
                return m_list.Count == 0;
            }
        }
        
        [System.Security.SecurityCritical]  // auto-generated
        public bool IsSubsetOf( StringExpressionSet ses )
        {
            if (this.IsEmpty())
                return true;
            
            if (ses == null || ses.IsEmpty())
                return false;
            
            CheckList();
            ses.CheckList();
            
            for (int index = 0; index < this.m_list.Count; ++index)
            {
                if (!StringSubsetStringExpression( (String)this.m_list[index], ses, m_ignoreCase ))
                {
                    return false;
                }
            }
            return true;
        }
        
        [System.Security.SecurityCritical]  // auto-generated
        public bool IsSubsetOfPathDiscovery( StringExpressionSet ses )
        {
            if (this.IsEmpty())
                return true;
            
            if (ses == null || ses.IsEmpty())
                return false;
            
            CheckList();
            ses.CheckList();
            
            for (int index = 0; index < this.m_list.Count; ++index)
            {
                if (!StringSubsetStringExpressionPathDiscovery( (String)this.m_list[index], ses, m_ignoreCase ))
                {
                    return false;
                }
            }
            return true;
        }
 
        
        [System.Security.SecurityCritical]  // auto-generated
        public StringExpressionSet Union( StringExpressionSet ses )
        {
            // If either set is empty, the union represents a copy of the other.
            
            if (ses == null || ses.IsEmpty())
                return this.Copy();
    
            if (this.IsEmpty())
                return ses.Copy();
            
            CheckList();
            ses.CheckList();
            
            // Perform the union
            // note: insert smaller set into bigger set to reduce needed comparisons
            
            StringExpressionSet bigger = ses.m_list.Count > this.m_list.Count ? ses : this;
            StringExpressionSet smaller = ses.m_list.Count <= this.m_list.Count ? ses : this;
    
            StringExpressionSet unionSet = bigger.Copy();
            
            unionSet.Reduce();
            
            for (int index = 0; index < smaller.m_list.Count; ++index)
            {
                unionSet.AddSingleExpressionNoDuplicates( (String)smaller.m_list[index] );
            }
            
            unionSet.GenerateString();
            
            return unionSet;
        }
            
        
        [System.Security.SecurityCritical]  // auto-generated
        public StringExpressionSet Intersect( StringExpressionSet ses )
        {
            // If either set is empty, the intersection is empty
            
            if (this.IsEmpty() || ses == null || ses.IsEmpty())
                return CreateNewEmpty();
            
            CheckList();
            ses.CheckList();
            
            // Do the intersection for real
            
            StringExpressionSet intersectSet = CreateNewEmpty();
            
            for (int this_index = 0; this_index < this.m_list.Count; ++this_index)
            {
                for (int ses_index = 0; ses_index < ses.m_list.Count; ++ses_index)
                {
                    if (StringSubsetString( (String)this.m_list[this_index], (String)ses.m_list[ses_index], m_ignoreCase ))
                    {
                        if (intersectSet.m_list == null)
                        {
                            intersectSet.m_list = new ArrayList();
                        }
                        intersectSet.AddSingleExpressionNoDuplicates( (String)this.m_list[this_index] );
                    }
                    else if (StringSubsetString( (String)ses.m_list[ses_index], (String)this.m_list[this_index], m_ignoreCase ))
                    {
                        if (intersectSet.m_list == null)
                        {
                            intersectSet.m_list = new ArrayList();
                        }
                        intersectSet.AddSingleExpressionNoDuplicates( (String)ses.m_list[ses_index] );
                    }
                }
            }
            
            intersectSet.GenerateString();
            
            return intersectSet;
        }
        
        [SecuritySafeCritical]
        protected void GenerateString()
        {
            // SafeCritical - moves critical data around, but doesn't expose it out
            if (m_list != null)
            {
                StringBuilder sb = new StringBuilder();
            
                IEnumerator enumerator = this.m_list.GetEnumerator();
                bool first = true;
            
                while (enumerator.MoveNext())
                {
                    if (!first)
                        sb.Append( m_separators[0] );
                    else
                        first = false;
                            
                    String currentString = (String)enumerator.Current;
                    if (currentString != null)
                    {
                        int indexOfSeparator = currentString.IndexOf( m_separators[0] );
 
                        if (indexOfSeparator != -1)
                            sb.Append( '\"' );
 
                        sb.Append( currentString );
 
                        if (indexOfSeparator != -1)
                            sb.Append( '\"' );
                    }
                }
            
                m_expressions = sb.ToString();
            }
            else
            {
                m_expressions = null;
            }
        }            
        
        // We don't override ToString since that API must be either transparent or safe citical.  If the
        // expressions contain paths that were canonicalized and expanded from the input that would cause
        // information disclosure, so we instead only expose this out to trusted code that can ensure they
        // either don't leak the information or required full path information.
        [SecurityCritical]
        public string UnsafeToString()
        {
            CheckList();
        
            Reduce();
        
            GenerateString();
                            
            return m_expressions;
        }
 
        [SecurityCritical]
        public String[] UnsafeToStringArray()
        {
            if (m_expressionsArray == null && m_list != null)
            {
                m_expressionsArray = (String[])m_list.ToArray(typeof(String));
            }
 
            return m_expressionsArray;
        }
                
        
        //-------------------------------
        // protected static helper functions
        //-------------------------------
        
        [SecurityCritical]
        private bool StringSubsetStringExpression( String left, StringExpressionSet right, bool ignoreCase )
        {
            for (int index = 0; index < right.m_list.Count; ++index)
            {
                if (StringSubsetString( left, (String)right.m_list[index], ignoreCase ))
                {
                    return true;
                }
            }
            return false;
        }
        
        [SecurityCritical]
        private static bool StringSubsetStringExpressionPathDiscovery( String left, StringExpressionSet right, bool ignoreCase )
        {
            for (int index = 0; index < right.m_list.Count; ++index)
            {
                if (StringSubsetStringPathDiscovery( left, (String)right.m_list[index], ignoreCase ))
                {
                    return true;
                }
            }
            return false;
        }
 
        
        protected virtual bool StringSubsetString( String left, String right, bool ignoreCase )
        {
            StringComparison strComp = (ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
            if (right == null || left == null || right.Length == 0 || left.Length == 0 ||
                right.Length > left.Length)
            {
                return false;
            }
            else if (right.Length == left.Length)
            {
                // if they are equal in length, just do a normal compare
                return String.Compare( right, left, strComp) == 0;
            }
            else if (left.Length - right.Length == 1 && left[left.Length-1] == m_directorySeparator)
            {
                return String.Compare( left, 0, right, 0, right.Length, strComp) == 0;
            }
            else if (right[right.Length-1] == m_directorySeparator)
            {
                // right is definitely a directory, just do a substring compare
                return String.Compare( right, 0, left, 0, right.Length, strComp) == 0;
            }
            else if (left[right.Length] == m_directorySeparator)
            {
                // left is hinting at being a subdirectory on right, do substring compare to make find out
                return String.Compare( right, 0, left, 0, right.Length, strComp) == 0;
            }
            else
            {
                return false;
            }
        }
 
        protected static bool StringSubsetStringPathDiscovery( String left, String right, bool ignoreCase )
        {
            StringComparison strComp = (ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
            if (right == null || left == null || right.Length == 0 || left.Length == 0)
            {
                return false;
            }
            else if (right.Length == left.Length)
            {
                // if they are equal in length, just do a normal compare
                return String.Compare( right, left, strComp) == 0;
            }
            else
            {
                String shortString, longString;
 
                if (right.Length < left.Length)
                {
                    shortString = right;
                    longString = left;
                }
                else
                {
                    shortString = left;
                    longString = right;
                }
 
                if (String.Compare( shortString, 0, longString, 0, shortString.Length, strComp) != 0)
                {
                    return false;
                }
 
                if (shortString.Length == 3 &&
                    shortString.EndsWith( ":\\", StringComparison.Ordinal ) &&
                    ((shortString[0] >= 'A' && shortString[0] <= 'Z') ||
                    (shortString[0] >= 'a' && shortString[0] <= 'z')))
                     return true;
 
                return longString[shortString.Length] == m_directorySeparator;
            }
        }
 
        
        //-------------------------------
        // protected helper functions
        //-------------------------------
        
        [SecuritySafeCritical]
        protected void AddSingleExpressionNoDuplicates( String expression )
        {
            // SafeCritical: We're not exposing out the string sets, just allowing modification of them
            int index = 0;
            
            m_expressionsArray = null;
            m_expressions = null;
 
            if (this.m_list == null)
                this.m_list = new ArrayList();
 
            while (index < this.m_list.Count)
            {
                if (StringSubsetString( (String)this.m_list[index], expression, m_ignoreCase ))
                {
                    this.m_list.RemoveAt( index );
                }
                else if (StringSubsetString( expression, (String)this.m_list[index], m_ignoreCase ))
                {
                    return;
                }
                else
                {
                    index++;
                }
            }
            this.m_list.Add( expression );
        }
    
        [System.Security.SecurityCritical]  // auto-generated
        protected void Reduce()
        {
            CheckList();
            
            if (this.m_list == null)
                return;
            
            int j;
 
            for (int i = 0; i < this.m_list.Count - 1; i++)
            {
                j = i + 1;
                
                while (j < this.m_list.Count)
                {
                    if (StringSubsetString( (String)this.m_list[j], (String)this.m_list[i], m_ignoreCase ))
                    {
                        this.m_list.RemoveAt( j );
                    }
                    else if (StringSubsetString( (String)this.m_list[i], (String)this.m_list[j], m_ignoreCase ))
                    {
                        // write the value at j into position i, delete the value at position j and keep going.
                        this.m_list[i] = this.m_list[j];
                        this.m_list.RemoveAt( j );
                        j = i + 1;
                    }
                    else
                    {
                        j++;
                    }
                }
            }
        }
 
        [System.Security.SecurityCritical]  // auto-generated
        [ResourceExposure(ResourceScope.Machine)]
        [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
        [SuppressUnmanagedCodeSecurity]
        internal static extern void GetLongPathName( String path, StringHandleOnStack retLongPath );
 
        [System.Security.SecurityCritical]  // auto-generated
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        internal static String CanonicalizePath( String path )
        {
            return CanonicalizePath( path, true );
        }
 
        [System.Security.SecurityCritical]  // auto-generated
        [ResourceExposure(ResourceScope.Machine)]
        [ResourceConsumption(ResourceScope.Machine)]
        internal static string CanonicalizePath(string path, bool needFullPath)
        {
            if (needFullPath)
            {
                string newPath = Path.GetFullPathInternal(path);
                if (path.EndsWith(m_directorySeparator + ".", StringComparison.Ordinal))
                {
                    if (newPath.EndsWith(m_directorySeparator))
                    {
                        newPath += ".";
                    }
                    else
                    {
                        newPath += m_directorySeparator + ".";
                    }
                }
                path = newPath;
            }
            else if (path.IndexOf('~') != -1)
            {
                // GetFullPathInternal() will expand 8.3 file names
                string longPath = null;
                GetLongPathName(path, JitHelpers.GetStringHandleOnStack(ref longPath));
                path = (longPath != null) ? longPath : path;
            }
 
            // This blocks usage of alternate data streams and some extended syntax paths (\\?\C:\). Checking after
            // normalization allows valid paths such as " C:\" to be considered ok (as it will become "C:\").
            if (path.IndexOf(':', 2) != -1)
                throw new NotSupportedException(Environment.GetResourceString("Argument_PathFormatNotSupported"));
 
            return path;
        }
    }
}