3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 // <OWNER>[....]</OWNER>
11 namespace System.Security.Util {
14 using System.Collections;
15 using System.Collections.Generic;
16 using System.Runtime.CompilerServices;
17 using System.Runtime.InteropServices;
18 using System.Globalization;
19 using System.Runtime.Versioning;
21 using System.Diagnostics.Contracts;
24 internal class StringExpressionSet
26 // This field, as well as the expressions fields below are critical since they may contain
27 // canonicalized full path data potentially built out of relative data passed as input to the
28 // StringExpressionSet. Full trust code using the string expression set needs to ensure that before
29 // exposing this data out to partial trust, they protect against this. Possibilities include:
31 // 1. Using the throwOnRelative flag
32 // 2. Ensuring that the partial trust code has permission to see full path data
33 // 3. Not using this set for paths (eg EnvironmentStringExpressionSet)
36 protected ArrayList m_list;
37 protected bool m_ignoreCase;
39 protected String m_expressions;
41 protected String[] m_expressionsArray;
43 protected bool m_throwOnRelative;
45 protected static readonly char[] m_separators = { ';' };
46 protected static readonly char[] m_trimChars = { ' ' };
48 protected static readonly char m_directorySeparator = '\\';
49 protected static readonly char m_alternateDirectorySeparator = '/';
51 public StringExpressionSet()
52 : this( true, null, false )
56 public StringExpressionSet( String str )
57 : this( true, str, false )
61 public StringExpressionSet( bool ignoreCase, bool throwOnRelative )
62 : this( ignoreCase, null, throwOnRelative )
66 [System.Security.SecuritySafeCritical] // auto-generated
67 public StringExpressionSet( bool ignoreCase, String str, bool throwOnRelative )
70 m_ignoreCase = ignoreCase;
71 m_throwOnRelative = throwOnRelative;
75 AddExpressions( str );
78 protected virtual StringExpressionSet CreateNewEmpty()
80 return new StringExpressionSet();
83 [SecuritySafeCritical]
84 public virtual StringExpressionSet Copy()
86 // SafeCritical: just copying this value around, not leaking it
88 StringExpressionSet copy = CreateNewEmpty();
89 if (this.m_list != null)
90 copy.m_list = new ArrayList(this.m_list);
92 copy.m_expressions = this.m_expressions;
93 copy.m_ignoreCase = this.m_ignoreCase;
94 copy.m_throwOnRelative = this.m_throwOnRelative;
98 public void SetThrowOnRelative( bool throwOnRelative )
100 this.m_throwOnRelative = throwOnRelative;
103 private static String StaticProcessWholeString( String str )
105 return str.Replace( m_alternateDirectorySeparator, m_directorySeparator );
108 private static String StaticProcessSingleString( String str )
110 return str.Trim( m_trimChars );
113 protected virtual String ProcessWholeString( String str )
115 return StaticProcessWholeString(str);
118 protected virtual String ProcessSingleString( String str )
120 return StaticProcessSingleString(str);
123 [System.Security.SecurityCritical] // auto-generated
124 [ResourceExposure(ResourceScope.None)]
125 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
126 public void AddExpressions( String str )
129 throw new ArgumentNullException( "str" );
130 Contract.EndContractBlock();
134 str = ProcessWholeString( str );
136 if (m_expressions == null)
139 m_expressions = m_expressions + m_separators[0] + str;
141 m_expressionsArray = null;
143 // We have to parse the string and compute the list here.
144 // The logic in this class tries to delay this parsing but
145 // since operations like IsSubsetOf are called during
146 // demand evaluation, it is not safe to delay this step
147 // as that would cause concurring threads to update the object
148 // at the same time. The CheckList operation should ideally be
149 // removed from this class, but for the sake of keeping the
150 // changes to a minimum here, we simply make sure m_list
151 // cannot be null by parsing m_expressions eagerly.
153 String[] arystr = Split( str );
156 m_list = new ArrayList();
158 for (int index = 0; index < arystr.Length; ++index)
160 if (arystr[index] != null && !arystr[index].Equals( "" ))
162 String temp = ProcessSingleString( arystr[index] );
163 int indexOfNull = temp.IndexOf( '\0' );
165 if (indexOfNull != -1)
166 temp = temp.Substring( 0, indexOfNull );
168 if (temp != null && !temp.Equals( "" ))
170 if (m_throwOnRelative)
172 if (Path.IsRelative(temp))
174 throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) );
177 temp = CanonicalizePath( temp );
188 [System.Security.SecurityCritical] // auto-generated
189 public void AddExpressions( String[] str, bool checkForDuplicates, bool needFullPath )
191 AddExpressions(CreateListFromExpressions(str, needFullPath), checkForDuplicates);
194 [System.Security.SecurityCritical] // auto-generated
195 public void AddExpressions( ArrayList exprArrayList, bool checkForDuplicates)
197 Contract.Assert( m_throwOnRelative, "This should only be called when throw on relative is set" );
199 m_expressionsArray = null;
200 m_expressions = null;
203 m_list.AddRange(exprArrayList);
205 m_list = new ArrayList(exprArrayList);
207 if (checkForDuplicates)
212 [System.Security.SecurityCritical] // auto-generated
213 [ResourceExposure(ResourceScope.None)]
214 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
215 internal static ArrayList CreateListFromExpressions(String[] str, bool needFullPath)
219 throw new ArgumentNullException( "str" );
221 Contract.EndContractBlock();
222 ArrayList retArrayList = new ArrayList();
223 for (int index = 0; index < str.Length; ++index)
225 if (str[index] == null)
226 throw new ArgumentNullException( "str" );
228 String oneString = StaticProcessWholeString( str[index] );
230 if (oneString != null && oneString.Length != 0)
232 String temp = StaticProcessSingleString( oneString);
234 int indexOfNull = temp.IndexOf( '\0' );
236 if (indexOfNull != -1)
237 temp = temp.Substring( 0, indexOfNull );
239 if (temp != null && temp.Length != 0)
241 if (Path.IsRelative(temp))
243 throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) );
246 temp = CanonicalizePath( temp, needFullPath );
249 retArrayList.Add( temp );
257 [System.Security.SecurityCritical] // auto-generated
258 protected void CheckList()
260 if (m_list == null && m_expressions != null)
266 protected String[] Split( String expressions )
268 if (m_throwOnRelative)
270 List<String> tempList = new List<String>();
272 String[] quoteSplit = expressions.Split( '\"' );
274 for (int i = 0; i < quoteSplit.Length; ++i)
278 String[] semiSplit = quoteSplit[i].Split( ';' );
280 for (int j = 0; j < semiSplit.Length; ++j)
282 if (semiSplit[j] != null && !semiSplit[j].Equals( "" ))
283 tempList.Add( semiSplit[j] );
288 tempList.Add( quoteSplit[i] );
292 String[] finalArray = new String[tempList.Count];
294 IEnumerator enumerator = tempList.GetEnumerator();
297 while (enumerator.MoveNext())
299 finalArray[index++] = (String)enumerator.Current;
306 return expressions.Split( m_separators );
311 [System.Security.SecurityCritical] // auto-generated
312 [ResourceExposure(ResourceScope.None)]
313 [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
314 protected void CreateList()
316 String[] expressionsArray = Split( m_expressions );
318 m_list = new ArrayList();
320 for (int index = 0; index < expressionsArray.Length; ++index)
322 if (expressionsArray[index] != null && !expressionsArray[index].Equals( "" ))
324 String temp = ProcessSingleString( expressionsArray[index] );
326 int indexOfNull = temp.IndexOf( '\0' );
328 if (indexOfNull != -1)
329 temp = temp.Substring( 0, indexOfNull );
331 if (temp != null && !temp.Equals( "" ))
333 if (m_throwOnRelative)
335 if (Path.IsRelative(temp))
337 throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) );
340 temp = CanonicalizePath( temp );
349 [SecuritySafeCritical]
350 public bool IsEmpty()
352 // SafeCritical: we're just showing that the expressions are empty, the sensitive portion is their
353 // contents - not the existence of the contents
356 return m_expressions == null;
360 return m_list.Count == 0;
364 [System.Security.SecurityCritical] // auto-generated
365 public bool IsSubsetOf( StringExpressionSet ses )
370 if (ses == null || ses.IsEmpty())
376 for (int index = 0; index < this.m_list.Count; ++index)
378 if (!StringSubsetStringExpression( (String)this.m_list[index], ses, m_ignoreCase ))
386 [System.Security.SecurityCritical] // auto-generated
387 public bool IsSubsetOfPathDiscovery( StringExpressionSet ses )
392 if (ses == null || ses.IsEmpty())
398 for (int index = 0; index < this.m_list.Count; ++index)
400 if (!StringSubsetStringExpressionPathDiscovery( (String)this.m_list[index], ses, m_ignoreCase ))
409 [System.Security.SecurityCritical] // auto-generated
410 public StringExpressionSet Union( StringExpressionSet ses )
412 // If either set is empty, the union represents a copy of the other.
414 if (ses == null || ses.IsEmpty())
424 // note: insert smaller set into bigger set to reduce needed comparisons
426 StringExpressionSet bigger = ses.m_list.Count > this.m_list.Count ? ses : this;
427 StringExpressionSet smaller = ses.m_list.Count <= this.m_list.Count ? ses : this;
429 StringExpressionSet unionSet = bigger.Copy();
433 for (int index = 0; index < smaller.m_list.Count; ++index)
435 unionSet.AddSingleExpressionNoDuplicates( (String)smaller.m_list[index] );
438 unionSet.GenerateString();
444 [System.Security.SecurityCritical] // auto-generated
445 public StringExpressionSet Intersect( StringExpressionSet ses )
447 // If either set is empty, the intersection is empty
449 if (this.IsEmpty() || ses == null || ses.IsEmpty())
450 return CreateNewEmpty();
455 // Do the intersection for real
457 StringExpressionSet intersectSet = CreateNewEmpty();
459 for (int this_index = 0; this_index < this.m_list.Count; ++this_index)
461 for (int ses_index = 0; ses_index < ses.m_list.Count; ++ses_index)
463 if (StringSubsetString( (String)this.m_list[this_index], (String)ses.m_list[ses_index], m_ignoreCase ))
465 if (intersectSet.m_list == null)
467 intersectSet.m_list = new ArrayList();
469 intersectSet.AddSingleExpressionNoDuplicates( (String)this.m_list[this_index] );
471 else if (StringSubsetString( (String)ses.m_list[ses_index], (String)this.m_list[this_index], m_ignoreCase ))
473 if (intersectSet.m_list == null)
475 intersectSet.m_list = new ArrayList();
477 intersectSet.AddSingleExpressionNoDuplicates( (String)ses.m_list[ses_index] );
482 intersectSet.GenerateString();
487 [SecuritySafeCritical]
488 protected void GenerateString()
490 // SafeCritical - moves critical data around, but doesn't expose it out
493 StringBuilder sb = new StringBuilder();
495 IEnumerator enumerator = this.m_list.GetEnumerator();
498 while (enumerator.MoveNext())
501 sb.Append( m_separators[0] );
505 String currentString = (String)enumerator.Current;
506 if (currentString != null)
508 int indexOfSeparator = currentString.IndexOf( m_separators[0] );
510 if (indexOfSeparator != -1)
513 sb.Append( currentString );
515 if (indexOfSeparator != -1)
520 m_expressions = sb.ToString();
524 m_expressions = null;
528 // We don't override ToString since that API must be either transparent or safe citical. If the
529 // expressions contain paths that were canonicalized and expanded from the input that would cause
530 // information disclosure, so we instead only expose this out to trusted code that can ensure they
531 // either don't leak the information or required full path information.
533 public string UnsafeToString()
541 return m_expressions;
545 public String[] UnsafeToStringArray()
547 if (m_expressionsArray == null && m_list != null)
549 m_expressionsArray = (String[])m_list.ToArray(typeof(String));
552 return m_expressionsArray;
556 //-------------------------------
557 // protected static helper functions
558 //-------------------------------
561 private bool StringSubsetStringExpression( String left, StringExpressionSet right, bool ignoreCase )
563 for (int index = 0; index < right.m_list.Count; ++index)
565 if (StringSubsetString( left, (String)right.m_list[index], ignoreCase ))
574 private static bool StringSubsetStringExpressionPathDiscovery( String left, StringExpressionSet right, bool ignoreCase )
576 for (int index = 0; index < right.m_list.Count; ++index)
578 if (StringSubsetStringPathDiscovery( left, (String)right.m_list[index], ignoreCase ))
587 protected virtual bool StringSubsetString( String left, String right, bool ignoreCase )
589 StringComparison strComp = (ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
590 if (right == null || left == null || right.Length == 0 || left.Length == 0 ||
591 right.Length > left.Length)
595 else if (right.Length == left.Length)
597 // if they are equal in length, just do a normal compare
598 return String.Compare( right, left, strComp) == 0;
600 else if (left.Length - right.Length == 1 && left[left.Length-1] == m_directorySeparator)
602 return String.Compare( left, 0, right, 0, right.Length, strComp) == 0;
604 else if (right[right.Length-1] == m_directorySeparator)
606 // right is definitely a directory, just do a substring compare
607 return String.Compare( right, 0, left, 0, right.Length, strComp) == 0;
609 else if (left[right.Length] == m_directorySeparator)
611 // left is hinting at being a subdirectory on right, do substring compare to make find out
612 return String.Compare( right, 0, left, 0, right.Length, strComp) == 0;
620 protected static bool StringSubsetStringPathDiscovery( String left, String right, bool ignoreCase )
622 StringComparison strComp = (ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
623 if (right == null || left == null || right.Length == 0 || left.Length == 0)
627 else if (right.Length == left.Length)
629 // if they are equal in length, just do a normal compare
630 return String.Compare( right, left, strComp) == 0;
634 String shortString, longString;
636 if (right.Length < left.Length)
647 if (String.Compare( shortString, 0, longString, 0, shortString.Length, strComp) != 0)
652 if (shortString.Length == 3 &&
653 shortString.EndsWith( ":\\", StringComparison.Ordinal ) &&
654 ((shortString[0] >= 'A' && shortString[0] <= 'Z') ||
655 (shortString[0] >= 'a' && shortString[0] <= 'z')))
658 return longString[shortString.Length] == m_directorySeparator;
663 //-------------------------------
664 // protected helper functions
665 //-------------------------------
667 [SecuritySafeCritical]
668 protected void AddSingleExpressionNoDuplicates( String expression )
670 // SafeCritical: We're not exposing out the string sets, just allowing modification of them
673 m_expressionsArray = null;
674 m_expressions = null;
676 if (this.m_list == null)
677 this.m_list = new ArrayList();
679 while (index < this.m_list.Count)
681 if (StringSubsetString( (String)this.m_list[index], expression, m_ignoreCase ))
683 this.m_list.RemoveAt( index );
685 else if (StringSubsetString( expression, (String)this.m_list[index], m_ignoreCase ))
694 this.m_list.Add( expression );
697 [System.Security.SecurityCritical] // auto-generated
698 protected void Reduce()
702 if (this.m_list == null)
707 for (int i = 0; i < this.m_list.Count - 1; i++)
711 while (j < this.m_list.Count)
713 if (StringSubsetString( (String)this.m_list[j], (String)this.m_list[i], m_ignoreCase ))
715 this.m_list.RemoveAt( j );
717 else if (StringSubsetString( (String)this.m_list[i], (String)this.m_list[j], m_ignoreCase ))
719 // write the value at j into position i, delete the value at position j and keep going.
720 this.m_list[i] = this.m_list[j];
721 this.m_list.RemoveAt( j );
732 [System.Security.SecurityCritical] // auto-generated
733 [ResourceExposure(ResourceScope.Machine)]
734 [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
735 [SuppressUnmanagedCodeSecurity]
736 internal static extern void GetLongPathName( String path, StringHandleOnStack retLongPath );
738 [System.Security.SecurityCritical] // auto-generated
739 [ResourceExposure(ResourceScope.Machine)]
740 [ResourceConsumption(ResourceScope.Machine)]
741 internal static String CanonicalizePath( String path )
743 return CanonicalizePath( path, true );
746 [System.Security.SecurityCritical] // auto-generated
747 [ResourceExposure(ResourceScope.Machine)]
748 [ResourceConsumption(ResourceScope.Machine)]
749 internal static String CanonicalizePath( String path, bool needFullPath )
751 if (path.IndexOf( '~' ) != -1)
753 string longPath = null;
754 GetLongPathName(path, JitHelpers.GetStringHandleOnStack(ref longPath));
755 path = (longPath != null) ? longPath : path;
758 if (path.IndexOf( ':', 2 ) != -1)
759 throw new NotSupportedException( Environment.GetResourceString( "Argument_PathFormatNotSupported" ) );
763 String newPath = System.IO.Path.GetFullPathInternal( path );
764 if (path.EndsWith( m_directorySeparator + ".", StringComparison.Ordinal ))
766 if (newPath.EndsWith( m_directorySeparator ))
772 newPath += m_directorySeparator + ".";