3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 // <OWNER>Microsoft</OWNER>
10 // Representation for code groups used for the policy mechanism
13 namespace System.Security.Policy {
16 using System.Security.Util;
17 using System.Security;
18 using System.Collections;
19 using System.Reflection;
20 using System.Globalization;
21 using System.Runtime.Serialization;
22 using System.Runtime.Versioning;
23 using System.Diagnostics.Contracts;
26 //This is a simple property bag used to describe connect back access.
29 [System.Runtime.InteropServices.ComVisible(true)]
30 public class CodeConnectAccess {
31 private string _LowerCaseScheme;
32 private string _LowerCasePort;
35 private const string DefaultStr = "$default"; //must remain in lower case
36 private const string OriginStr = "$origin"; //must remain in lower case
38 internal const int NoPort = -1;
39 internal const int AnyPort = -2; // This safely excludes -1 (if we decide to support "any" port later)
42 // public helper fields to deal with special scheme and port values.
44 public static readonly int DefaultPort = -3;
45 public static readonly int OriginPort = -4;
46 public static readonly string OriginScheme = OriginStr;
47 public static readonly string AnyScheme = "*";
50 // A set of public static class factories
52 public CodeConnectAccess(string allowScheme, int allowPort)
54 if (!IsValidScheme(allowScheme))
55 throw new ArgumentOutOfRangeException("allowScheme");
57 SetCodeConnectAccess(allowScheme.ToLower(CultureInfo.InvariantCulture), allowPort);
60 public static CodeConnectAccess CreateOriginSchemeAccess(int allowPort)
62 CodeConnectAccess access = new CodeConnectAccess();
63 access.SetCodeConnectAccess(OriginScheme, allowPort);
67 public static CodeConnectAccess CreateAnySchemeAccess(int allowPort)
69 CodeConnectAccess access = new CodeConnectAccess();
70 access.SetCodeConnectAccess(AnyScheme, allowPort);
74 private CodeConnectAccess()
78 private void SetCodeConnectAccess(string lowerCaseScheme, int allowPort)
80 _LowerCaseScheme = lowerCaseScheme;
82 if (allowPort == DefaultPort)
83 _LowerCasePort = DefaultStr;
84 else if (allowPort == OriginPort)
85 _LowerCasePort = OriginStr;
88 if (allowPort < 0 || allowPort > 0xFFFF)
89 throw new ArgumentOutOfRangeException("allowPort");
91 _LowerCasePort = allowPort.ToString(CultureInfo.InvariantCulture);
97 public String Scheme {
98 get {return _LowerCaseScheme;}
102 get {return _IntPort;}
105 public override bool Equals(object o)
107 if ((object)this == (object)o)
110 CodeConnectAccess that = (o as CodeConnectAccess);
115 return this.Scheme == that.Scheme && this.Port == that.Port;
118 public override int GetHashCode()
120 return Scheme.GetHashCode() + Port.GetHashCode();
126 // The valid scheme values are: "*", "$origin", or a valid Uri scheme
127 // The valid port valies are "$origin" "$default" or a valid Uri port
129 internal CodeConnectAccess(string allowScheme, string allowPort)
131 if (allowScheme == null || allowScheme.Length == 0)
132 throw new ArgumentNullException("allowScheme");
134 if (allowPort == null || allowPort.Length == 0)
135 throw new ArgumentNullException("allowPort");
136 Contract.EndContractBlock();
138 _LowerCaseScheme = allowScheme.ToLower(CultureInfo.InvariantCulture);
140 if (_LowerCaseScheme == OriginScheme)
141 _LowerCaseScheme = OriginScheme;
142 else if (_LowerCaseScheme == AnyScheme)
143 _LowerCaseScheme = AnyScheme;
144 else if (!IsValidScheme(_LowerCaseScheme))
145 throw new ArgumentOutOfRangeException("allowScheme");
147 _LowerCasePort = allowPort.ToLower(CultureInfo.InvariantCulture);
149 if (_LowerCasePort == DefaultStr)
150 _IntPort = DefaultPort;
151 else if (_LowerCasePort == OriginStr)
152 _IntPort = OriginPort;
155 _IntPort = Int32.Parse(allowPort, CultureInfo.InvariantCulture);
157 if (_IntPort < 0 || _IntPort > 0xFFFF)
158 throw new ArgumentOutOfRangeException("allowPort");
160 _LowerCasePort = _IntPort.ToString(CultureInfo.InvariantCulture);
164 internal bool IsOriginScheme {
165 get {return (object)_LowerCaseScheme == (object)OriginScheme;}
168 internal bool IsAnyScheme {
169 get {return (object)_LowerCaseScheme == (object)AnyScheme;}
172 internal bool IsDefaultPort {
173 get {return Port == DefaultPort;}
176 internal bool IsOriginPort {
177 get {return Port == OriginPort;}
180 // More Internal stuff
182 internal string StrPort {
183 get { return _LowerCasePort;}
187 internal static bool IsValidScheme(string scheme)
189 if (((object)scheme == null) || (scheme.Length == 0) || !IsAsciiLetter(scheme[0]))
192 for (int i = scheme.Length - 1; i > 0; --i) {
193 if (!(IsAsciiLetterOrDigit(scheme[i]) || (scheme[i] == '+') || (scheme[i] == '-') || (scheme[i] == '.')))
200 private static bool IsAsciiLetterOrDigit(char character) {
201 return IsAsciiLetter(character) || (character >= '0' && character <= '9');
205 private static bool IsAsciiLetter(char character) {
206 return (character >= 'a' && character <= 'z') ||
207 (character >= 'A' && character <= 'Z');
212 [System.Runtime.InteropServices.ComVisible(true)]
213 sealed public class NetCodeGroup : CodeGroup, IUnionSemanticCodeGroup
215 [System.Security.SecurityCritical] // auto-generated
216 [System.Diagnostics.Conditional( "_DEBUG" )]
217 [ResourceExposure(ResourceScope.None)]
218 [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)]
219 private static void DEBUG_OUT( String str )
226 System.Text.StringBuilder sb = new System.Text.StringBuilder();
228 sb.Append ((char)13) ;
229 sb.Append ((char)10) ;
230 PolicyManager.DebugOut( file, sb.ToString() );
233 Console.WriteLine( str );
239 private static bool debug;
240 private static readonly bool to_file;
241 private const String file = "c:\\com99\\src\\bcl\\debug.txt";
244 [OptionalField(VersionAdded = 2)]
245 private ArrayList m_schemesList;
246 [OptionalField(VersionAdded = 2)]
247 private ArrayList m_accessList;
250 private void OnDeserializing(StreamingContext ctx)
252 m_schemesList = null;
259 private const string c_IgnoreUserInfo = ""; // don't need anymore since WebPermission will ignore userinfo anyway. was: @"([^\\/?#]*@)?";
260 // not exactly correct syntax but should work fine assuming System.Uri will not accept bogus Uri schemes
261 private const string c_AnyScheme = @"([0-9a-z+\-\.]+)://";
263 private static readonly char[] c_SomeRegexChars = new char[] {'.', '-', '+', '[', ']', /* rest are unc-only*/ '{', '$', '^', '#', ')', '(', ' '};
265 public static readonly string AnyOtherOriginScheme= CodeConnectAccess.AnyScheme;
266 public static readonly string AbsentOriginScheme = string.Empty;
268 internal NetCodeGroup()
274 public NetCodeGroup( IMembershipCondition membershipCondition )
275 : base( membershipCondition, (PolicyStatement)null )
281 // Reset the talkback access to nothing.
282 // When a new instance of NetCodeGroup is created it's populated with default talkback rules
284 public void ResetConnectAccess()
286 m_schemesList = null;
290 // Added public stuff for programmatic support of the talkback access
291 // The connectAccess can be null means an empty access (no access) is added
293 public void AddConnectAccess(string originScheme, CodeConnectAccess connectAccess)
295 if (originScheme == null)
296 throw new ArgumentNullException("originScheme");
297 Contract.EndContractBlock();
299 if (originScheme != AbsentOriginScheme && originScheme != AnyOtherOriginScheme && !CodeConnectAccess.IsValidScheme(originScheme))
300 throw new ArgumentOutOfRangeException("originScheme");
302 if (originScheme == AbsentOriginScheme && connectAccess.IsOriginScheme)
303 throw new ArgumentOutOfRangeException("connectAccess");
305 if (m_schemesList == null)
307 m_schemesList = new ArrayList();
308 m_accessList = new ArrayList();
311 originScheme = originScheme.ToLower(CultureInfo.InvariantCulture);
313 for (int i=0; i < m_schemesList.Count; ++i)
315 if ((string)m_schemesList[i] == originScheme)
317 // originScheme entry is found and we may want to add nothing to it.
318 if (connectAccess == null)
321 ArrayList list = (ArrayList)m_accessList[i];
322 for (i = 0; i < list.Count; ++i)
324 if (((CodeConnectAccess)list[i]).Equals(connectAccess))
327 list.Add(connectAccess);
332 // originScheme entry is not found, create a new one.
333 m_schemesList.Add(originScheme);
334 ArrayList newOriginSchemeList = new ArrayList();
335 m_accessList.Add(newOriginSchemeList);
337 // we may want to keep it empty.
338 if (connectAccess != null)
339 newOriginSchemeList.Add(connectAccess);
343 // Each DictionaryEntry will contain
344 // Key=originScheme and Value=CodeConnectAccess[] array
346 public DictionaryEntry[] GetConnectAccessRules()
348 if (m_schemesList == null)
351 DictionaryEntry[] result = new DictionaryEntry[m_schemesList.Count];
352 for (int i = 0; i < result.Length; ++i)
354 result[i].Key = m_schemesList[i];
355 result[i].Value = ((ArrayList)m_accessList[i]).ToArray(typeof(CodeConnectAccess));
360 [System.Security.SecuritySafeCritical] // auto-generated
361 public override PolicyStatement Resolve( Evidence evidence )
363 if (evidence == null)
364 throw new ArgumentNullException("evidence");
365 Contract.EndContractBlock();
367 object usedEvidence = null;
368 if (PolicyManager.CheckMembershipCondition(MembershipCondition,
372 PolicyStatement thisPolicy = CalculateAssemblyPolicy( evidence );
374 // If any delay-evidence was used to generate this grant set, then we need to keep track of
375 // that for potentially later forcing it to be verified.
376 IDelayEvaluatedEvidence delayEvidence = usedEvidence as IDelayEvaluatedEvidence;
377 bool delayEvidenceNeedsVerification = delayEvidence != null && !delayEvidence.IsVerified;
378 if (delayEvidenceNeedsVerification)
380 thisPolicy.AddDependentEvidence(delayEvidence);
383 bool foundExclusiveChild = false;
384 IEnumerator enumerator = this.Children.GetEnumerator();
385 while (enumerator.MoveNext() && !foundExclusiveChild)
387 PolicyStatement childPolicy = PolicyManager.ResolveCodeGroup(enumerator.Current as CodeGroup,
389 if (childPolicy != null)
391 thisPolicy.InplaceUnion(childPolicy);
393 if ((childPolicy.Attributes & PolicyStatementAttribute.Exclusive) == PolicyStatementAttribute.Exclusive)
395 foundExclusiveChild = true;
409 PolicyStatement IUnionSemanticCodeGroup.InternalResolve( Evidence evidence )
411 if (evidence == null)
412 throw new ArgumentNullException("evidence");
414 Contract.EndContractBlock();
416 if (this.MembershipCondition.Check( evidence ))
418 return CalculateAssemblyPolicy( evidence );
424 public override CodeGroup ResolveMatchingCodeGroups( Evidence evidence )
426 if (evidence == null)
427 throw new ArgumentNullException("evidence");
428 Contract.EndContractBlock();
430 if (this.MembershipCondition.Check( evidence ))
432 CodeGroup retGroup = this.Copy();
434 retGroup.Children = new ArrayList();
436 IEnumerator enumerator = this.Children.GetEnumerator();
438 while (enumerator.MoveNext())
440 CodeGroup matchingGroups = ((CodeGroup)enumerator.Current).ResolveMatchingCodeGroups( evidence );
442 // If the child has a policy, we are done.
444 if (matchingGroups != null)
446 retGroup.AddChild( matchingGroups );
459 private string EscapeStringForRegex( string str )
463 System.Text.StringBuilder sb = null;
465 while (start < str.Length && (idx = str.IndexOfAny(c_SomeRegexChars, start)) != -1)
467 if (sb == null) sb = new System.Text.StringBuilder(str.Length*2);
468 sb.Append(str, start, idx - start).Append('\\').Append(str[idx]);
474 if (start < str.Length)
475 sb.Append(str, start, str.Length - start);
477 return sb.ToString();
481 internal SecurityElement CreateWebPermission(string host,
484 string assemblyOverride)
487 scheme = string.Empty;
489 // If there is no OriginScheme host string, no talk back access is possible
490 if (host == null || host.Length == 0)
493 host = host.ToLower(CultureInfo.InvariantCulture);
494 scheme = scheme.ToLower(CultureInfo.InvariantCulture);
496 int intPort = CodeConnectAccess.NoPort;
497 if (port != null && port.Length != 0)
498 intPort = Int32.Parse(port, CultureInfo.InvariantCulture );
502 CodeConnectAccess[] access = FindAccessRulesForScheme(scheme);
503 if (access == null || access.Length == 0)
506 SecurityElement root = new SecurityElement( "IPermission" );
508 // If we were given a specific assembly to find the WebPermission type in use that, otherwise use
509 // the current version of System.dll. This enables us to build WebPermissions targeting older
510 // runtimes for ClickOnce trust decisions that need to target the older runtime.
511 string permissionAssembly = assemblyOverride == null ?
512 "System, Version=" + ThisAssembly.Version + ", Culture=neutral, PublicKeyToken=" + AssemblyRef.EcmaPublicKeyToken :
515 root.AddAttribute( "class", "System.Net.WebPermission, " + permissionAssembly);
516 root.AddAttribute( "version", "1" );
518 SecurityElement connectAccess = new SecurityElement( "ConnectAccess" );
520 host = EscapeStringForRegex(host);
521 scheme = EscapeStringForRegex(scheme);
522 string uriStr = TryPermissionAsOneString(access, scheme, host, intPort);
526 SecurityElement uri = new SecurityElement( "URI" );
527 uri.AddAttribute( "uri", uriStr );
528 connectAccess.AddChild( uri );
532 if (port.Length != 0)
535 for (int i = 0; i < access.Length; ++i)
537 uriStr = GetPermissionAccessElementString(access[i], scheme, host, port);
538 SecurityElement uri = new SecurityElement( "URI" );
539 uri.AddAttribute( "uri", uriStr );
540 connectAccess.AddChild( uri );
544 root.AddChild( connectAccess );
550 private CodeConnectAccess[] FindAccessRulesForScheme(string lowerCaseScheme)
552 if (m_schemesList == null)
555 int i = m_schemesList.IndexOf(lowerCaseScheme);
558 // Trying default rule but only if the passed string is not about "no scheme case"
559 if (lowerCaseScheme == AbsentOriginScheme || (i = m_schemesList.IndexOf(AnyOtherOriginScheme)) == -1)
563 ArrayList accessList = (ArrayList)m_accessList[i];
564 return (CodeConnectAccess[])accessList.ToArray(typeof(CodeConnectAccess));
567 // This is an attempt to optimize resulting regex if the rules can be combined into one expression string
569 private string TryPermissionAsOneString(CodeConnectAccess[] access, string escapedScheme, string escapedHost, int intPort)
572 bool originPort = true;
573 bool anyScheme = false;
574 int sameCustomPort = CodeConnectAccess.AnyPort;
577 // We can compact rules in one regex if the destination port is the same for all granted accesses.
578 // We may have three cases (order is significant)
579 // - No port (empty) in the resulting rule
580 // - Origin server port that is intPort parameter
581 // - Some custom port that is the same for all accesses
583 for (int i = 0; i < access.Length; ++i)
585 noPort &= (access[i].IsDefaultPort || (access[i].IsOriginPort && intPort == CodeConnectAccess.NoPort));
586 originPort&= (access[i].IsOriginPort || access[i].Port == intPort);
588 if (access[i].Port >= 0)
591 if (sameCustomPort == CodeConnectAccess.AnyPort)
593 sameCustomPort = access[i].Port;
595 else if (access[i].Port != sameCustomPort)
597 // found conflicting ports
598 sameCustomPort = CodeConnectAccess.NoPort;
603 // Cannot compress Regex if saw at least one "default port" access rule and another one with exact port.
604 sameCustomPort = CodeConnectAccess.NoPort;
607 if (access[i].IsAnyScheme)
611 if (!noPort && !originPort && sameCustomPort == CodeConnectAccess.NoPort)
614 // We can produce the resulting expression as one string
615 System.Text.StringBuilder sb = new System.Text.StringBuilder(c_AnyScheme.Length * access.Length + c_IgnoreUserInfo.Length*2 + escapedHost.Length);
617 sb.Append(c_AnyScheme);
622 for (; i < access.Length; ++i)
624 // This is to avoid output like (http|http|http)
628 if (access[i].Scheme == access[k].Scheme)
635 sb.Append(access[i].IsOriginScheme? escapedScheme: EscapeStringForRegex(access[i].Scheme));
641 sb.Append(c_IgnoreUserInfo).Append(escapedHost);
644 else if (originPort) sb.Append(':').Append(intPort);
645 else sb.Append(':').Append(sameCustomPort);
648 return sb.ToString();
651 // This tries to return a single element to be added into resulting WebPermission
652 // Returns Null if there is nothing to add.
654 private string GetPermissionAccessElementString(CodeConnectAccess access, string escapedScheme, string escapedHost, string strPort)
656 System.Text.StringBuilder sb = new System.Text.StringBuilder(c_AnyScheme.Length*2 + c_IgnoreUserInfo.Length + escapedHost.Length);
658 if (access.IsAnyScheme)
659 sb.Append(c_AnyScheme);
660 else if (access.IsOriginScheme)
661 sb.Append(escapedScheme).Append("://");
663 sb.Append(EscapeStringForRegex(access.Scheme)).Append("://");
665 sb.Append(c_IgnoreUserInfo).Append(escapedHost);
667 if (access.IsDefaultPort) {;}
668 else if (access.IsOriginPort)
670 else sb.Append(':').Append(access.StrPort);
673 return sb.ToString();
676 internal PolicyStatement CalculatePolicy( String host, String scheme, String port )
678 SecurityElement webPerm = CreateWebPermission( host, scheme, port, null );
680 SecurityElement root = new SecurityElement( "PolicyStatement" );
681 SecurityElement permSet = new SecurityElement( "PermissionSet" );
682 permSet.AddAttribute( "class", "System.Security.PermissionSet" );
683 permSet.AddAttribute( "version", "1" );
686 permSet.AddChild( webPerm );
688 root.AddChild( permSet );
690 PolicyStatement policy = new PolicyStatement();
691 policy.FromXml( root );
695 private PolicyStatement CalculateAssemblyPolicy( Evidence evidence )
698 PolicyStatement thisPolicy = null;
700 Url url = evidence.GetHostEvidence<Url>();
703 thisPolicy = CalculatePolicy( url.GetURLString().Host, url.GetURLString().Scheme, url.GetURLString().Port );
706 if (thisPolicy == null)
708 Site site = evidence.GetHostEvidence<Site>();
711 thisPolicy = CalculatePolicy(site.Name, null, null);
715 if (thisPolicy == null)
716 thisPolicy = new PolicyStatement( new PermissionSet( false ), PolicyStatementAttribute.Nothing );
721 public override CodeGroup Copy()
723 NetCodeGroup group = new NetCodeGroup( this.MembershipCondition );
725 group.Name = this.Name;
726 group.Description = this.Description;
727 if (m_schemesList != null)
729 group.m_schemesList = (ArrayList)this.m_schemesList.Clone();
730 group.m_accessList = new ArrayList(this.m_accessList.Count);
731 for (int i = 0; i < this.m_accessList.Count; ++i)
732 group.m_accessList.Add(((ArrayList)this.m_accessList[i]).Clone());
735 IEnumerator enumerator = this.Children.GetEnumerator();
737 while (enumerator.MoveNext())
739 group.AddChild( (CodeGroup)enumerator.Current );
746 public override String MergeLogic
750 return Environment.GetResourceString( "MergeLogic_Union" );
754 public override String PermissionSetName
758 return Environment.GetResourceString( "NetCodeGroup_PermissionSet" );
763 public override String AttributeString
771 public override bool Equals( Object o)
773 if ((object)this == (object)o)
776 NetCodeGroup that = (o as NetCodeGroup);
778 if (that == null || !base.Equals(that))
781 if ((this.m_schemesList == null) != (that.m_schemesList == null))
784 if (this.m_schemesList == null)
787 if (this.m_schemesList.Count != that.m_schemesList.Count)
791 for (int i = 0; i < this.m_schemesList.Count; ++i)
793 int idx = that.m_schemesList.IndexOf(this.m_schemesList[i]);
797 ArrayList thisList = (ArrayList)this.m_accessList[i];
798 ArrayList thatList = (ArrayList)that.m_accessList[idx];
799 if (thisList.Count != thatList.Count)
802 for (int k = 0; k < thisList.Count; ++k)
804 if (!thatList.Contains(thisList[k]))
813 public override int GetHashCode()
815 return base.GetHashCode() + GetRulesHashCode();
817 private int GetRulesHashCode()
819 if (m_schemesList == null)
823 for(int i = 0; i < m_schemesList.Count; ++i)
824 result += ((string)m_schemesList[i]).GetHashCode();
826 foreach (ArrayList accessList in m_accessList)
827 for(int i = 0; i < accessList.Count; ++i)
828 result += ((CodeConnectAccess)accessList[i]).GetHashCode();
833 protected override void CreateXml( SecurityElement element, PolicyLevel level )
835 DictionaryEntry[] rules = GetConnectAccessRules();
839 SecurityElement rulesElement = new SecurityElement("connectAccessRules");
841 foreach (DictionaryEntry rule in rules)
843 SecurityElement codeOriginElement = new SecurityElement("codeOrigin");
844 codeOriginElement.AddAttribute("scheme", (string) rule.Key);
845 foreach (CodeConnectAccess access in (CodeConnectAccess[])rule.Value)
847 SecurityElement accessElem = new SecurityElement("connectAccess");
848 accessElem.AddAttribute("scheme", access.Scheme);
849 accessElem.AddAttribute("port", access.StrPort);
850 codeOriginElement.AddChild(accessElem);
852 rulesElement.AddChild(codeOriginElement);
854 element.AddChild(rulesElement);
857 protected override void ParseXml( SecurityElement e, PolicyLevel level )
859 //Reset the exiting content
860 ResetConnectAccess();
862 SecurityElement et = e.SearchForChildByTag("connectAccessRules");
864 if (et == null || et.Children == null)
866 // Everett behavior, same as calling a default ctor.
871 foreach(SecurityElement codeOriginElem in et.Children)
873 if (codeOriginElem.Tag.Equals("codeOrigin"))
875 string originScheme = codeOriginElem.Attribute("scheme");
876 bool oneAdded = false;
878 if (codeOriginElem.Children != null)
880 foreach(SecurityElement accessElem in codeOriginElem.Children)
882 if (accessElem.Tag.Equals("connectAccess"))
884 string connectScheme = accessElem.Attribute("scheme");
885 string connectPort = accessElem.Attribute("port");
886 AddConnectAccess(originScheme, new CodeConnectAccess(connectScheme, connectPort));
890 // improper tag found, just ignore
897 //special case as to no talkback access for a given scheme
898 AddConnectAccess(originScheme, null);
903 // improper tag found, just ignore
908 internal override String GetTypeName()
910 return "System.Security.Policy.NetCodeGroup";
913 // This method is called at the ctor time to populate default accesses (V1.1 compat)
915 private void SetDefaults()
917 // No access for file://
918 AddConnectAccess("file", null);
920 // access fot http://
921 AddConnectAccess("http", new CodeConnectAccess("http", CodeConnectAccess.OriginPort));
922 AddConnectAccess("http", new CodeConnectAccess("https", CodeConnectAccess.OriginPort));
929 // access fot https://
930 AddConnectAccess("https", new CodeConnectAccess("https", CodeConnectAccess.OriginPort));
944 // access for no scheme and for any other scheme
945 AddConnectAccess(NetCodeGroup.AbsentOriginScheme, CodeConnectAccess.CreateAnySchemeAccess(CodeConnectAccess.OriginPort));
946 AddConnectAccess(NetCodeGroup.AnyOtherOriginScheme, CodeConnectAccess.CreateOriginSchemeAccess(CodeConnectAccess.OriginPort));