Merge pull request #2377 from joelmartinez/docs-multiassembly-extension-fix
[mono.git] / mcs / class / referencesource / mscorlib / system / security / util / stringexpressionset.cs
1 // ==++==
2 // 
3 //   Copyright (c) Microsoft Corporation.  All rights reserved.
4 // 
5 // ==--==
6 // StringExpressionSet
7 //
8 // <OWNER>[....]</OWNER>
9 //
10  
11 namespace System.Security.Util {    
12     using System.Text;
13     using System;
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;
20     using System.IO;
21     using System.Diagnostics.Contracts;
22
23     [Serializable]
24     internal class StringExpressionSet
25     {
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:
30         //
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)
34         //
35         [SecurityCritical]
36         protected ArrayList m_list;
37         protected bool m_ignoreCase;
38         [SecurityCritical]
39         protected String m_expressions;
40         [SecurityCritical]
41         protected String[] m_expressionsArray;
42
43         protected bool m_throwOnRelative;
44         
45         protected static readonly char[] m_separators = { ';' };
46         protected static readonly char[] m_trimChars = { ' ' };
47
48         protected static readonly char m_directorySeparator = '\\';
49         protected static readonly char m_alternateDirectorySeparator = '/';
50         
51         public StringExpressionSet()
52             : this( true, null, false )
53         {
54         }
55         
56         public StringExpressionSet( String str )
57             : this( true, str, false )
58         {
59         }
60         
61         public StringExpressionSet( bool ignoreCase, bool throwOnRelative )
62             : this( ignoreCase, null, throwOnRelative )
63         {
64         }
65         
66         [System.Security.SecuritySafeCritical]  // auto-generated
67         public StringExpressionSet( bool ignoreCase, String str, bool throwOnRelative )
68         {
69             m_list = null;
70             m_ignoreCase = ignoreCase;
71             m_throwOnRelative = throwOnRelative;
72             if (str == null)
73                 m_expressions = null;
74             else
75             AddExpressions( str );
76         }
77
78         protected virtual StringExpressionSet CreateNewEmpty()
79         {
80             return new StringExpressionSet();
81         }
82         
83         [SecuritySafeCritical]
84         public virtual StringExpressionSet Copy()
85         {
86             // SafeCritical: just copying this value around, not leaking it
87
88             StringExpressionSet copy = CreateNewEmpty();
89             if (this.m_list != null)
90                 copy.m_list = new ArrayList(this.m_list);
91
92             copy.m_expressions = this.m_expressions;
93             copy.m_ignoreCase = this.m_ignoreCase;
94             copy.m_throwOnRelative = this.m_throwOnRelative;
95             return copy;
96         }
97         
98         public void SetThrowOnRelative( bool throwOnRelative )
99         {
100             this.m_throwOnRelative = throwOnRelative;
101         }
102
103         private static String StaticProcessWholeString( String str )
104         {
105             return str.Replace( m_alternateDirectorySeparator, m_directorySeparator );
106         }
107
108         private static String StaticProcessSingleString( String str )
109         {
110             return str.Trim( m_trimChars );
111         }
112
113         protected virtual String ProcessWholeString( String str )
114         {
115             return StaticProcessWholeString(str);
116         }
117
118         protected virtual String ProcessSingleString( String str )
119         {
120             return StaticProcessSingleString(str);
121         }
122         
123         [System.Security.SecurityCritical]  // auto-generated
124         [ResourceExposure(ResourceScope.None)]
125         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
126         public void AddExpressions( String str )
127         {
128             if (str == null)
129                 throw new ArgumentNullException( "str" );
130             Contract.EndContractBlock();
131             if (str.Length == 0)
132                 return;
133
134             str = ProcessWholeString( str );
135
136             if (m_expressions == null)
137                 m_expressions = str;
138             else
139                 m_expressions = m_expressions + m_separators[0] + str;
140
141             m_expressionsArray = null;
142
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.
152
153             String[] arystr = Split( str );
154
155             if (m_list == null)
156                 m_list = new ArrayList();
157
158             for (int index = 0; index < arystr.Length; ++index)
159             {
160                 if (arystr[index] != null && !arystr[index].Equals( "" ))
161                 {
162                     String temp = ProcessSingleString( arystr[index] );
163                     int indexOfNull = temp.IndexOf( '\0' );
164
165                     if (indexOfNull != -1)
166                         temp = temp.Substring( 0, indexOfNull );
167
168                     if (temp != null && !temp.Equals( "" ))
169                     {
170                         if (m_throwOnRelative)
171                         {
172                             if (Path.IsRelative(temp))
173                             {
174                                 throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) );
175                             }
176
177                             temp = CanonicalizePath( temp );
178                         }
179
180                         m_list.Add( temp );
181                     }
182                 }
183             }
184
185             Reduce();
186         }
187
188         [System.Security.SecurityCritical]  // auto-generated
189         public void AddExpressions( String[] str, bool checkForDuplicates, bool needFullPath )
190         {
191             AddExpressions(CreateListFromExpressions(str, needFullPath), checkForDuplicates);
192         }
193
194         [System.Security.SecurityCritical]  // auto-generated
195         public void AddExpressions( ArrayList exprArrayList, bool checkForDuplicates)
196         {
197             Contract.Assert( m_throwOnRelative, "This should only be called when throw on relative is set" );
198
199             m_expressionsArray = null;
200             m_expressions = null;
201
202             if (m_list != null)
203                 m_list.AddRange(exprArrayList);
204             else
205                 m_list = new ArrayList(exprArrayList);
206
207             if (checkForDuplicates)
208                 Reduce();
209         }
210
211
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)
216         {
217             if (str == null)
218             {
219                 throw new ArgumentNullException( "str" );
220             }
221             Contract.EndContractBlock();
222             ArrayList retArrayList = new ArrayList();
223             for (int index = 0; index < str.Length; ++index)
224             {
225                 if (str[index] == null)
226                     throw new ArgumentNullException( "str" );
227
228                 String oneString = StaticProcessWholeString( str[index] );
229
230                 if (oneString != null && oneString.Length != 0)
231                 {
232                     String temp = StaticProcessSingleString( oneString);
233
234                     int indexOfNull = temp.IndexOf( '\0' );
235
236                     if (indexOfNull != -1)
237                         temp = temp.Substring( 0, indexOfNull );
238
239                     if (temp != null && temp.Length != 0)
240                     {
241                         if (Path.IsRelative(temp))
242                         {
243                             throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) );
244                         }
245
246                         temp = CanonicalizePath( temp, needFullPath );
247
248
249                         retArrayList.Add( temp );
250                     }
251                 }
252             }
253
254             return retArrayList;
255         }
256         
257         [System.Security.SecurityCritical]  // auto-generated
258         protected void CheckList()
259         {
260             if (m_list == null && m_expressions != null)
261             {
262                 CreateList();
263             }
264         }
265         
266         protected String[] Split( String expressions )
267         {
268             if (m_throwOnRelative)
269             {
270                 List<String> tempList = new List<String>();
271
272                 String[] quoteSplit = expressions.Split( '\"' );
273
274                 for (int i = 0; i < quoteSplit.Length; ++i)
275                 {
276                     if (i % 2 == 0)
277                     {
278                         String[] semiSplit = quoteSplit[i].Split( ';' );
279
280                         for (int j = 0; j < semiSplit.Length; ++j)
281                         {
282                             if (semiSplit[j] != null && !semiSplit[j].Equals( "" ))
283                                 tempList.Add( semiSplit[j] );
284                         }
285                     }
286                     else
287                     {
288                         tempList.Add( quoteSplit[i] );
289                     }
290                 }
291
292                 String[] finalArray = new String[tempList.Count];
293
294                 IEnumerator enumerator = tempList.GetEnumerator();
295
296                 int index = 0;
297                 while (enumerator.MoveNext())
298                 {
299                     finalArray[index++] = (String)enumerator.Current;
300                 }
301
302                 return finalArray;
303             }
304             else
305             {
306                 return expressions.Split( m_separators );
307             }
308         }
309
310         
311         [System.Security.SecurityCritical]  // auto-generated
312         [ResourceExposure(ResourceScope.None)]
313         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]        
314         protected void CreateList()
315         {
316             String[] expressionsArray = Split( m_expressions );
317
318             m_list = new ArrayList();
319             
320             for (int index = 0; index < expressionsArray.Length; ++index)
321             {
322                 if (expressionsArray[index] != null && !expressionsArray[index].Equals( "" ))
323                 {
324                     String temp = ProcessSingleString( expressionsArray[index] );
325
326                     int indexOfNull = temp.IndexOf( '\0' );
327
328                     if (indexOfNull != -1)
329                         temp = temp.Substring( 0, indexOfNull );
330
331                     if (temp != null && !temp.Equals( "" ))
332                     {
333                         if (m_throwOnRelative)
334                         {
335                             if (Path.IsRelative(temp))
336                             {
337                                 throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) );
338                             }
339
340                             temp = CanonicalizePath( temp );
341                         }
342                         
343                         m_list.Add( temp );
344                     }
345                 }
346             }
347         }
348         
349         [SecuritySafeCritical]
350         public bool IsEmpty()
351         {
352             // SafeCritical: we're just showing that the expressions are empty, the sensitive portion is their
353             // contents - not the existence of the contents
354             if (m_list == null)
355             {
356                 return m_expressions == null;
357             }
358             else
359             {
360                 return m_list.Count == 0;
361             }
362         }
363         
364         [System.Security.SecurityCritical]  // auto-generated
365         public bool IsSubsetOf( StringExpressionSet ses )
366         {
367             if (this.IsEmpty())
368                 return true;
369             
370             if (ses == null || ses.IsEmpty())
371                 return false;
372             
373             CheckList();
374             ses.CheckList();
375             
376             for (int index = 0; index < this.m_list.Count; ++index)
377             {
378                 if (!StringSubsetStringExpression( (String)this.m_list[index], ses, m_ignoreCase ))
379                 {
380                     return false;
381                 }
382             }
383             return true;
384         }
385         
386         [System.Security.SecurityCritical]  // auto-generated
387         public bool IsSubsetOfPathDiscovery( StringExpressionSet ses )
388         {
389             if (this.IsEmpty())
390                 return true;
391             
392             if (ses == null || ses.IsEmpty())
393                 return false;
394             
395             CheckList();
396             ses.CheckList();
397             
398             for (int index = 0; index < this.m_list.Count; ++index)
399             {
400                 if (!StringSubsetStringExpressionPathDiscovery( (String)this.m_list[index], ses, m_ignoreCase ))
401                 {
402                     return false;
403                 }
404             }
405             return true;
406         }
407
408         
409         [System.Security.SecurityCritical]  // auto-generated
410         public StringExpressionSet Union( StringExpressionSet ses )
411         {
412             // If either set is empty, the union represents a copy of the other.
413             
414             if (ses == null || ses.IsEmpty())
415                 return this.Copy();
416     
417             if (this.IsEmpty())
418                 return ses.Copy();
419             
420             CheckList();
421             ses.CheckList();
422             
423             // Perform the union
424             // note: insert smaller set into bigger set to reduce needed comparisons
425             
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;
428     
429             StringExpressionSet unionSet = bigger.Copy();
430             
431             unionSet.Reduce();
432             
433             for (int index = 0; index < smaller.m_list.Count; ++index)
434             {
435                 unionSet.AddSingleExpressionNoDuplicates( (String)smaller.m_list[index] );
436             }
437             
438             unionSet.GenerateString();
439             
440             return unionSet;
441         }
442             
443         
444         [System.Security.SecurityCritical]  // auto-generated
445         public StringExpressionSet Intersect( StringExpressionSet ses )
446         {
447             // If either set is empty, the intersection is empty
448             
449             if (this.IsEmpty() || ses == null || ses.IsEmpty())
450                 return CreateNewEmpty();
451             
452             CheckList();
453             ses.CheckList();
454             
455             // Do the intersection for real
456             
457             StringExpressionSet intersectSet = CreateNewEmpty();
458             
459             for (int this_index = 0; this_index < this.m_list.Count; ++this_index)
460             {
461                 for (int ses_index = 0; ses_index < ses.m_list.Count; ++ses_index)
462                 {
463                     if (StringSubsetString( (String)this.m_list[this_index], (String)ses.m_list[ses_index], m_ignoreCase ))
464                     {
465                         if (intersectSet.m_list == null)
466                         {
467                             intersectSet.m_list = new ArrayList();
468                         }
469                         intersectSet.AddSingleExpressionNoDuplicates( (String)this.m_list[this_index] );
470                     }
471                     else if (StringSubsetString( (String)ses.m_list[ses_index], (String)this.m_list[this_index], m_ignoreCase ))
472                     {
473                         if (intersectSet.m_list == null)
474                         {
475                             intersectSet.m_list = new ArrayList();
476                         }
477                         intersectSet.AddSingleExpressionNoDuplicates( (String)ses.m_list[ses_index] );
478                     }
479                 }
480             }
481             
482             intersectSet.GenerateString();
483             
484             return intersectSet;
485         }
486         
487         [SecuritySafeCritical]
488         protected void GenerateString()
489         {
490             // SafeCritical - moves critical data around, but doesn't expose it out
491             if (m_list != null)
492             {
493                 StringBuilder sb = new StringBuilder();
494             
495                 IEnumerator enumerator = this.m_list.GetEnumerator();
496                 bool first = true;
497             
498                 while (enumerator.MoveNext())
499                 {
500                     if (!first)
501                         sb.Append( m_separators[0] );
502                     else
503                         first = false;
504                             
505                     String currentString = (String)enumerator.Current;
506                     if (currentString != null)
507                     {
508                         int indexOfSeparator = currentString.IndexOf( m_separators[0] );
509
510                         if (indexOfSeparator != -1)
511                             sb.Append( '\"' );
512
513                         sb.Append( currentString );
514
515                         if (indexOfSeparator != -1)
516                             sb.Append( '\"' );
517                     }
518                 }
519             
520                 m_expressions = sb.ToString();
521             }
522             else
523             {
524                 m_expressions = null;
525             }
526         }            
527         
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.
532         [SecurityCritical]
533         public string UnsafeToString()
534         {
535             CheckList();
536         
537             Reduce();
538         
539             GenerateString();
540                             
541             return m_expressions;
542         }
543
544         [SecurityCritical]
545         public String[] UnsafeToStringArray()
546         {
547             if (m_expressionsArray == null && m_list != null)
548             {
549                 m_expressionsArray = (String[])m_list.ToArray(typeof(String));
550             }
551
552             return m_expressionsArray;
553         }
554                 
555         
556         //-------------------------------
557         // protected static helper functions
558         //-------------------------------
559         
560         [SecurityCritical]
561         private bool StringSubsetStringExpression( String left, StringExpressionSet right, bool ignoreCase )
562         {
563             for (int index = 0; index < right.m_list.Count; ++index)
564             {
565                 if (StringSubsetString( left, (String)right.m_list[index], ignoreCase ))
566                 {
567                     return true;
568                 }
569             }
570             return false;
571         }
572         
573         [SecurityCritical]
574         private static bool StringSubsetStringExpressionPathDiscovery( String left, StringExpressionSet right, bool ignoreCase )
575         {
576             for (int index = 0; index < right.m_list.Count; ++index)
577             {
578                 if (StringSubsetStringPathDiscovery( left, (String)right.m_list[index], ignoreCase ))
579                 {
580                     return true;
581                 }
582             }
583             return false;
584         }
585
586         
587         protected virtual bool StringSubsetString( String left, String right, bool ignoreCase )
588         {
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)
592             {
593                 return false;
594             }
595             else if (right.Length == left.Length)
596             {
597                 // if they are equal in length, just do a normal compare
598                 return String.Compare( right, left, strComp) == 0;
599             }
600             else if (left.Length - right.Length == 1 && left[left.Length-1] == m_directorySeparator)
601             {
602                 return String.Compare( left, 0, right, 0, right.Length, strComp) == 0;
603             }
604             else if (right[right.Length-1] == m_directorySeparator)
605             {
606                 // right is definitely a directory, just do a substring compare
607                 return String.Compare( right, 0, left, 0, right.Length, strComp) == 0;
608             }
609             else if (left[right.Length] == m_directorySeparator)
610             {
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;
613             }
614             else
615             {
616                 return false;
617             }
618         }
619
620         protected static bool StringSubsetStringPathDiscovery( String left, String right, bool ignoreCase )
621         {
622             StringComparison strComp = (ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
623             if (right == null || left == null || right.Length == 0 || left.Length == 0)
624             {
625                 return false;
626             }
627             else if (right.Length == left.Length)
628             {
629                 // if they are equal in length, just do a normal compare
630                 return String.Compare( right, left, strComp) == 0;
631             }
632             else
633             {
634                 String shortString, longString;
635
636                 if (right.Length < left.Length)
637                 {
638                     shortString = right;
639                     longString = left;
640                 }
641                 else
642                 {
643                     shortString = left;
644                     longString = right;
645                 }
646
647                 if (String.Compare( shortString, 0, longString, 0, shortString.Length, strComp) != 0)
648                 {
649                     return false;
650                 }
651
652                 if (shortString.Length == 3 &&
653                     shortString.EndsWith( ":\\", StringComparison.Ordinal ) &&
654                     ((shortString[0] >= 'A' && shortString[0] <= 'Z') ||
655                     (shortString[0] >= 'a' && shortString[0] <= 'z')))
656                      return true;
657
658                 return longString[shortString.Length] == m_directorySeparator;
659             }
660         }
661
662         
663         //-------------------------------
664         // protected helper functions
665         //-------------------------------
666         
667         [SecuritySafeCritical]
668         protected void AddSingleExpressionNoDuplicates( String expression )
669         {
670             // SafeCritical: We're not exposing out the string sets, just allowing modification of them
671             int index = 0;
672             
673             m_expressionsArray = null;
674             m_expressions = null;
675
676             if (this.m_list == null)
677                 this.m_list = new ArrayList();
678
679             while (index < this.m_list.Count)
680             {
681                 if (StringSubsetString( (String)this.m_list[index], expression, m_ignoreCase ))
682                 {
683                     this.m_list.RemoveAt( index );
684                 }
685                 else if (StringSubsetString( expression, (String)this.m_list[index], m_ignoreCase ))
686                 {
687                     return;
688                 }
689                 else
690                 {
691                     index++;
692                 }
693             }
694             this.m_list.Add( expression );
695         }
696     
697         [System.Security.SecurityCritical]  // auto-generated
698         protected void Reduce()
699         {
700             CheckList();
701             
702             if (this.m_list == null)
703                 return;
704             
705             int j;
706
707             for (int i = 0; i < this.m_list.Count - 1; i++)
708             {
709                 j = i + 1;
710                 
711                 while (j < this.m_list.Count)
712                 {
713                     if (StringSubsetString( (String)this.m_list[j], (String)this.m_list[i], m_ignoreCase ))
714                     {
715                         this.m_list.RemoveAt( j );
716                     }
717                     else if (StringSubsetString( (String)this.m_list[i], (String)this.m_list[j], m_ignoreCase ))
718                     {
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 );
722                         j = i + 1;
723                     }
724                     else
725                     {
726                         j++;
727                     }
728                 }
729             }
730         }
731
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 );
737
738         [System.Security.SecurityCritical]  // auto-generated
739         [ResourceExposure(ResourceScope.Machine)]
740         [ResourceConsumption(ResourceScope.Machine)]
741         internal static String CanonicalizePath( String path )
742         {
743             return CanonicalizePath( path, true );
744         }
745
746         [System.Security.SecurityCritical]  // auto-generated
747         [ResourceExposure(ResourceScope.Machine)]
748         [ResourceConsumption(ResourceScope.Machine)]
749         internal static String CanonicalizePath( String path, bool needFullPath )
750         {
751             if (path.IndexOf( '~' ) != -1)
752             {
753                 string longPath = null;
754                 GetLongPathName(path, JitHelpers.GetStringHandleOnStack(ref longPath));
755                 path = (longPath != null) ? longPath : path;
756             }
757
758             if (path.IndexOf( ':', 2 ) != -1)
759                 throw new NotSupportedException( Environment.GetResourceString( "Argument_PathFormatNotSupported" ) );
760
761             if (needFullPath)
762             {
763                 String newPath = System.IO.Path.GetFullPathInternal( path );
764                 if (path.EndsWith( m_directorySeparator + ".", StringComparison.Ordinal ))
765                 {
766                     if (newPath.EndsWith( m_directorySeparator ))
767                     {
768                         newPath += ".";
769                     }
770                     else
771                     {
772                         newPath += m_directorySeparator + ".";
773                     }
774                 }                
775                 return newPath;
776             }
777             else
778                 return path;
779         }
780     }
781 }