2 // ClientAccessPolicy.cs
5 // Atsushi Enomoto <atsushi@ximian.com>
6 // Moonlight List (moonlight-list@lists.ximian.com)
8 // Copyright (C) 2009-2010 Novell, Inc. http://www.novell.com
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.Collections.Generic;
37 namespace System.Net.Policy {
39 partial class ClientAccessPolicy : BaseDomainPolicy {
41 public class AccessPolicy {
43 public const short MinPort = 4502;
44 public const short MaxPort = 4534;
46 public List<AllowFrom> AllowedServices { get; private set; }
47 public List<GrantTo> GrantedResources { get; private set; }
48 public long PortMask { get; set; }
50 public AccessPolicy ()
52 AllowedServices = new List<AllowFrom> ();
53 GrantedResources = new List<GrantTo> ();
56 public bool PortAllowed (int port)
58 if ((port < MinPort) || (port > MaxPort))
61 return (((PortMask >> (port - MinPort)) & 1) == 1);
65 public ClientAccessPolicy ()
67 AccessPolicyList = new List<AccessPolicy> ();
70 public List<AccessPolicy> AccessPolicyList { get; private set; }
72 public bool IsAllowed (IPEndPoint endpoint)
74 foreach (AccessPolicy policy in AccessPolicyList) {
75 // does something allow our URI in this policy ?
76 foreach (AllowFrom af in policy.AllowedServices) {
77 // fake "GET" as method as this does not apply to sockets
78 if (af.IsAllowed (ApplicationUri, "GET")) {
79 // if so, is our request port allowed ?
80 if (policy.PortAllowed (endpoint.Port))
85 // no policy allows this socket connection
89 // note: tests show that it only applies to Silverlight policy (seems to work with Flash)
90 // and only if we're not granting full access (i.e. '/' with all subpaths)
91 // https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=466043
92 private bool CheckOriginalPath (Uri uri)
94 // Path Restriction for cross-domain requests
95 // http://msdn.microsoft.com/en-us/library/cc838250(VS.95).aspx
96 string original = uri.OriginalString;
97 // applies to the *path* only (not the query part)
98 int query = original.IndexOf ('?');
100 original = original.Substring (0, query);
102 if (original.Contains ('%') || original.Contains ("./") || original.Contains ("..")) {
103 // special case when no path restriction applies - i.e. the above characters are accepted by SL
104 if (AccessPolicyList.Count != 1)
106 AccessPolicy policy = AccessPolicyList [0];
107 if (policy.GrantedResources.Count != 1)
109 GrantTo gt = policy.GrantedResources [0];
110 if (gt.Resources.Count != 1)
112 Resource r = gt.Resources [0];
113 return (r.IncludeSubpaths && (r.Path == "/"));
118 public override bool IsAllowed (WebRequest request)
120 return IsAllowed (request.RequestUri, request.Method, request.Headers.AllKeys);
123 public bool IsAllowed (Uri uri, string method, params string [] headerKeys)
125 foreach (AccessPolicy policy in AccessPolicyList) {
126 // does something allow our URI in this policy ?
127 foreach (AllowFrom af in policy.AllowedServices) {
128 // is the application (XAP) URI allowed by the policy ?
130 if (!af.HttpRequestHeaders.IsAllowed (headerKeys)) {
134 if (af.IsAllowed (ApplicationUri, method)) {
135 foreach (GrantTo gt in policy.GrantedResources) {
136 // is the requested access to the Uri granted under this policy ?
137 if (gt.IsGranted (uri)) {
138 // at this stage the URI has removed the "offending" characters so
139 // we need to look at the original
140 return CheckOriginalPath (uri);
146 // no policy allows this web connection
150 public class AllowFrom {
154 Domains = new List<string> ();
155 HttpRequestHeaders = new Headers ();
158 public bool AllowAnyDomain { get; set; }
160 public List<string> Domains { get; private set; }
162 public Headers HttpRequestHeaders { get; private set; }
164 public bool AllowAnyMethod { get; set; }
166 public bool IsAllowed (Uri uri, string method)
169 if (!AllowAnyMethod) {
170 // if not all methods are allowed (*) then only GET and POST request are possible
171 // further restriction exists in the Client http stack
172 if ((String.Compare (method, "GET", StringComparison.OrdinalIgnoreCase) != 0) &&
173 (String.Compare (method, "POST", StringComparison.OrdinalIgnoreCase) != 0)) {
182 if (Domains.All (domain => !CheckDomainUri (uri, domain)))
187 const string AllHttpScheme = "http://*";
188 const string AllHttpsScheme = "https://*";
189 const string AllFileScheme = "file:///";
191 static bool CheckDomainUri (Uri applicationUri, string policy)
194 if (Uri.TryCreate (policy, UriKind.Absolute, out uri)) {
195 // if no local path is part of the policy domain then we compare to the root
196 if (uri.LocalPath == "/")
197 return (uri.ToString () == ApplicationRoot);
198 // otherwise the path must match
199 if (uri.LocalPath != ApplicationUri.LocalPath)
201 return (CrossDomainPolicyManager.GetRoot (uri) == ApplicationRoot);
204 // SL policies supports a * wildcard at the start of their host name (but not elsewhere)
206 // check for matching protocol
207 if (!policy.StartsWith (ApplicationUri.Scheme))
210 switch (ApplicationUri.Scheme) {
212 if (policy == AllHttpScheme)
213 return (applicationUri.Port == 80);
216 if (policy == AllHttpsScheme)
217 return (applicationUri.Port == 443);
220 if (policy == AllFileScheme)
225 if (policy.IndexOf ("://*.", ApplicationUri.Scheme.Length) != ApplicationUri.Scheme.Length)
227 // remove *. from uri
228 policy = policy.Remove (ApplicationUri.Scheme.Length + 3, 2);
229 // create Uri - without the *. it should be a valid one
230 if (!Uri.TryCreate (policy, UriKind.Absolute, out uri))
232 // path must be "empty" and query and fragment (really) empty
233 if ((uri.LocalPath != "/") || !String.IsNullOrEmpty (uri.Query) || !String.IsNullOrEmpty (uri.Fragment))
236 if (ApplicationUri.Port != uri.Port)
238 // the application uri host must end with the policy host name
239 return ApplicationUri.DnsSafeHost.EndsWith (uri.DnsSafeHost);
247 Resources = new List<Resource> ();
250 public List<Resource> Resources { get; private set; }
252 public bool IsGranted (Uri uri)
254 foreach (var gr in Resources) {
255 if (gr.IncludeSubpaths) {
256 string granted = gr.Path;
257 string local = uri.LocalPath;
258 if (local.StartsWith (granted, StringComparison.Ordinal)) {
259 // "/test" equals "/test" and "test/xyx" but not "/test2"
260 // "/test/" equals "test/xyx" but not "/test" or "/test2"
261 if (local.Length == granted.Length)
263 else if (granted [granted.Length - 1] == '/')
265 else if (local [granted.Length] == '/')
269 if (uri.LocalPath == gr.Path)
277 public class Resource {
283 // an empty Path Ressource makes the *whole* policy file invalid
284 if (String.IsNullOrEmpty (value))
285 throw new NotSupportedException ();
290 public bool IncludeSubpaths { get; set; }