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", null)) {
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 // at this stage the URI has removed the "offending" characters so we need to look at the original
126 if (!CheckOriginalPath (uri))
129 foreach (AccessPolicy policy in AccessPolicyList) {
130 // does something allow our URI in this policy ?
131 foreach (AllowFrom af in policy.AllowedServices) {
132 // is the application (XAP) URI allowed by the policy ?
133 if (af.IsAllowed (ApplicationUri, method, headerKeys)) {
134 foreach (GrantTo gt in policy.GrantedResources) {
135 // is the requested access to the Uri granted under this policy ?
136 if (gt.IsGranted (uri))
142 // no policy allows this web connection
146 public class AllowFrom {
150 Domains = new List<string> ();
151 HttpRequestHeaders = new Headers ();
152 Scheme = String.Empty;
155 public bool AllowAnyDomain { get; set; }
157 public List<string> Domains { get; private set; }
159 public Headers HttpRequestHeaders { get; private set; }
161 public bool AllowAnyMethod { get; set; }
163 public string Scheme { get; internal set; }
165 public bool IsAllowed (Uri uri, string method, string [] headerKeys)
168 if (!HttpRequestHeaders.IsAllowed (headerKeys))
172 if ((Scheme.Length > 0) && (Scheme == uri.Scheme)) {
175 return (uri.Port == 80);
177 return (uri.Port == 443);
185 if (!AllowAnyMethod) {
186 // if not all methods are allowed (*) then only GET and POST request are possible
187 // further restriction exists in the Client http stack
188 if ((String.Compare (method, "GET", StringComparison.OrdinalIgnoreCase) != 0) &&
189 (String.Compare (method, "POST", StringComparison.OrdinalIgnoreCase) != 0)) {
198 if (Domains.All (domain => !CheckDomainUri (domain)))
203 static bool CheckDomainUri (string policy)
206 if (Uri.TryCreate (policy, UriKind.Absolute, out uri)) {
207 // if no local path is part of the policy domain then we compare to the root
208 if (uri.LocalPath == "/")
209 return (uri.ToString () == ApplicationRoot);
210 // otherwise the path must match
211 if (uri.LocalPath != ApplicationUri.LocalPath)
213 return (CrossDomainPolicyManager.GetRoot (uri) == ApplicationRoot);
216 // SL policies supports a * wildcard at the start of their host name (but not elsewhere)
218 // check for matching protocol
219 if (!policy.StartsWith (ApplicationUri.Scheme))
221 // check for the wirld card immediately after the scheme
222 if (policy.IndexOf ("://*.", ApplicationUri.Scheme.Length) != ApplicationUri.Scheme.Length)
224 // remove *. from uri
225 policy = policy.Remove (ApplicationUri.Scheme.Length + 3, 2);
226 // create Uri - without the *. it should be a valid one
227 if (!Uri.TryCreate (policy, UriKind.Absolute, out uri))
229 // path must be "empty" and query and fragment (really) empty
230 if ((uri.LocalPath != "/") || !String.IsNullOrEmpty (uri.Query) || !String.IsNullOrEmpty (uri.Fragment))
233 if (ApplicationUri.Port != uri.Port)
235 // the application uri host must end with the policy host name
236 return ApplicationUri.DnsSafeHost.EndsWith (uri.DnsSafeHost);
244 Resources = new List<Resource> ();
247 public List<Resource> Resources { get; private set; }
249 public bool IsGranted (Uri uri)
251 foreach (var gr in Resources) {
252 if (gr.IncludeSubpaths) {
253 string granted = gr.Path;
254 string local = uri.LocalPath;
255 if (local.StartsWith (granted, StringComparison.Ordinal)) {
256 // "/test" equals "/test" and "test/xyx" but not "/test2"
257 // "/test/" equals "test/xyx" but not "/test" or "/test2"
258 if (local.Length == granted.Length)
260 else if (granted [granted.Length - 1] == '/')
262 else if (local [granted.Length] == '/')
266 if (uri.LocalPath == gr.Path)
274 public class Resource {
280 // an empty Path Ressource makes the *whole* policy file invalid
281 if (String.IsNullOrEmpty (value))
282 throw new NotSupportedException ();
287 public bool IncludeSubpaths { get; set; }