2010-04-07 Sebastien Pouliot <sebastien@ximian.com>
[mono.git] / mcs / class / System.Net / System.Net.Policy / ClientAccessPolicy.cs
1 //
2 // ClientAccessPolicy.cs
3 //
4 // Authors:
5 //      Atsushi Enomoto <atsushi@ximian.com>
6 //      Moonlight List (moonlight-list@lists.ximian.com)
7 //
8 // Copyright (C) 2009-2010 Novell, Inc.  http://www.novell.com
9 //
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:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
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.
28 //
29
30 #if NET_2_1
31
32 using System;
33 using System.Collections.Generic;
34 using System.IO;
35 using System.Linq;
36
37 namespace System.Net.Policy {
38
39         partial class ClientAccessPolicy : BaseDomainPolicy {
40
41                 public class AccessPolicy {
42
43                         public const short MinPort = 4502;
44                         public const short MaxPort = 4534;
45
46                         public List<AllowFrom> AllowedServices { get; private set; }
47                         public List<GrantTo> GrantedResources { get; private set; }
48                         public long PortMask { get; set; }
49
50                         public AccessPolicy ()
51                         {
52                                 AllowedServices = new List<AllowFrom> ();
53                                 GrantedResources = new List<GrantTo> ();
54                         }
55
56                         public bool PortAllowed (int port)
57                         {
58                                 if ((port < MinPort) || (port > MaxPort))
59                                         return false;
60
61                                 return (((PortMask >> (port - MinPort)) & 1) == 1);
62                         }
63                 }
64
65                 public ClientAccessPolicy ()
66                 {
67                         AccessPolicyList = new List<AccessPolicy> ();
68                 }
69
70                 public List<AccessPolicy> AccessPolicyList { get; private set; }
71
72                 public bool IsAllowed (IPEndPoint endpoint)
73                 {
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))
81                                                         return true;
82                                         }
83                                 }
84                         }
85                         // no policy allows this socket connection
86                         return false;
87                 }
88
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)
93                 {
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 ('?');
99                         if (query != -1)
100                                 original = original.Substring (0, query);
101
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)
105                                         return false;
106                                 AccessPolicy policy = AccessPolicyList [0];
107                                 if (policy.GrantedResources.Count != 1)
108                                         return false;
109                                 GrantTo gt = policy.GrantedResources [0];
110                                 if (gt.Resources.Count != 1)
111                                         return false;
112                                 Resource r = gt.Resources [0];
113                                 return (r.IncludeSubpaths && (r.Path == "/"));
114                         }
115                         return true;
116                 }
117
118                 public override bool IsAllowed (WebRequest request)
119                 {
120                         return IsAllowed (request.RequestUri, request.Method, request.Headers.AllKeys);
121                 }
122
123                 public bool IsAllowed (Uri uri, string method, params string [] headerKeys)
124                 {
125                         // at this stage the URI has removed the "offending" characters so we need to look at the original
126                         if (!CheckOriginalPath (uri)) 
127                                 return false;
128
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))
137                                                                 return true;
138                                                 }
139                                         }
140                                 }
141                         }
142                         // no policy allows this web connection
143                         return false;
144                 }
145
146                 public class AllowFrom {
147
148                         public AllowFrom ()
149                         {
150                                 Domains = new List<string> ();
151                                 HttpRequestHeaders = new Headers ();
152                                 Scheme = String.Empty;
153                         }
154
155                         public bool AllowAnyDomain { get; set; }
156
157                         public List<string> Domains { get; private set; }
158
159                         public Headers HttpRequestHeaders { get; private set; }
160
161                         public bool AllowAnyMethod { get; set; }
162
163                         public string Scheme { get; internal set; }
164
165                         public bool IsAllowed (Uri uri, string method, string [] headerKeys)
166                         {
167                                 // check headers
168                                 if (!HttpRequestHeaders.IsAllowed (headerKeys))
169                                         return false;
170
171                                 // check scheme
172                                 if ((Scheme.Length > 0) && (Scheme == uri.Scheme)) {
173                                         switch (Scheme) {
174                                         case "http":
175                                                 return (uri.Port == 80);
176                                         case "https":
177                                                 return (uri.Port == 443);
178                                         case "file":
179                                                 return true;
180                                         default:
181                                                 return false;
182                                         }
183                                 }
184                                 // check methods
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)) {
190                                                 return false;
191                                         }
192                                 }
193
194                                 // check domains
195                                 if (AllowAnyDomain)
196                                         return true;
197
198                                 if (Domains.All (domain => !CheckDomainUri (domain)))
199                                         return false;
200                                 return true;
201                         }
202
203                         static bool CheckDomainUri (string policy)
204                         {
205                                 Uri uri;
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)
212                                                 return false;
213                                         return (CrossDomainPolicyManager.GetRoot (uri) == ApplicationRoot);
214                                 }
215
216                                 // SL policies supports a * wildcard at the start of their host name (but not elsewhere)
217
218                                 // check for matching protocol
219                                 if (!policy.StartsWith (ApplicationUri.Scheme))
220                                         return false;
221                                 // check for the wirld card immediately after the scheme
222                                 if (policy.IndexOf ("://*.", ApplicationUri.Scheme.Length) != ApplicationUri.Scheme.Length)
223                                         return false;
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))
228                                         return false;
229                                 // path must be "empty" and query and fragment (really) empty
230                                 if ((uri.LocalPath != "/") || !String.IsNullOrEmpty (uri.Query) || !String.IsNullOrEmpty (uri.Fragment))
231                                         return false;
232                                 // port must match
233                                 if (ApplicationUri.Port != uri.Port)
234                                         return false;
235                                 // the application uri host must end with the policy host name
236                                 return ApplicationUri.DnsSafeHost.EndsWith (uri.DnsSafeHost);
237                         }
238                 }
239
240                 public class GrantTo
241                 {
242                         public GrantTo ()
243                         {
244                                 Resources = new List<Resource> ();
245                         }
246
247                         public List<Resource> Resources { get; private set; }
248
249                         public bool IsGranted (Uri uri)
250                         {
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)
259                                                                 return true;
260                                                         else if (granted [granted.Length - 1] == '/')
261                                                                 return true;
262                                                         else if (local [granted.Length] == '/')
263                                                                 return true;
264                                                 }
265                                         } else {
266                                                 if (uri.LocalPath == gr.Path)
267                                                         return true;
268                                         }
269                                 }
270                                 return false;
271                         }
272                 }
273
274                 public class Resource {
275                         private string path;
276
277                         public string Path { 
278                                 get { return path; }
279                                 set {
280                                         // an empty Path Ressource makes the *whole* policy file invalid
281                                         if (String.IsNullOrEmpty (value))
282                                                 throw new NotSupportedException ();
283                                         path = value;
284                                 }
285                         }
286
287                         public bool IncludeSubpaths { get; set; }
288                 }
289         }
290 }
291
292 #endif
293
294