Fix access policy when more than one scheme is present #659791
[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")) {
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                         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 ?
129                                         // check headers
130                                         if (!af.HttpRequestHeaders.IsAllowed (headerKeys)) {
131                                                 return false;
132                                         }
133
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);
141                                                         }
142                                                 }
143                                         }
144                                 }
145                         }
146                         // no policy allows this web connection
147                         return false;
148                 }
149
150                 public class AllowFrom {
151
152                         public AllowFrom ()
153                         {
154                                 Domains = new List<string> ();
155                                 HttpRequestHeaders = new Headers ();
156                         }
157
158                         public bool AllowAnyDomain { get; set; }
159
160                         public List<string> Domains { get; private set; }
161
162                         public Headers HttpRequestHeaders { get; private set; }
163
164                         public bool AllowAnyMethod { get; set; }
165
166                         public bool IsAllowed (Uri uri, string method)
167                         {
168                                 // check methods
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)) {
174                                                 return false;
175                                         }
176                                 }
177
178                                 // check domains
179                                 if (AllowAnyDomain)
180                                         return true;
181
182                                 if (Domains.All (domain => !CheckDomainUri (uri, domain)))
183                                         return false;
184                                 return true;
185                         }
186
187                         const string AllHttpScheme = "http://*";
188                         const string AllHttpsScheme = "https://*";
189                         const string AllFileScheme = "file:///";
190
191                         static bool CheckDomainUri (Uri applicationUri, string policy)
192                         {
193                                 Uri uri;
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)
200                                                 return false;
201                                         return (CrossDomainPolicyManager.GetRoot (uri) == ApplicationRoot);
202                                 }
203
204                                 // SL policies supports a * wildcard at the start of their host name (but not elsewhere)
205
206                                 // check for matching protocol
207                                 if (!policy.StartsWith (ApplicationUri.Scheme))
208                                         return false;
209
210                                 switch (ApplicationUri.Scheme) {
211                                 case "http":
212                                         if (policy == AllHttpScheme)
213                                                 return (applicationUri.Port == 80);
214                                         break;
215                                 case "https":
216                                         if (policy == AllHttpsScheme)
217                                                 return (applicationUri.Port == 443);
218                                         break;
219                                 case "file":
220                                         if (policy == AllFileScheme)
221                                                 return true;
222                                         break;
223                                 }
224
225                                 if (policy.IndexOf ("://*.", ApplicationUri.Scheme.Length) != ApplicationUri.Scheme.Length)
226                                         return false;
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))
231                                         return false;
232                                 // path must be "empty" and query and fragment (really) empty
233                                 if ((uri.LocalPath != "/") || !String.IsNullOrEmpty (uri.Query) || !String.IsNullOrEmpty (uri.Fragment))
234                                         return false;
235                                 // port must match
236                                 if (ApplicationUri.Port != uri.Port)
237                                         return false;
238                                 // the application uri host must end with the policy host name
239                                 return ApplicationUri.DnsSafeHost.EndsWith (uri.DnsSafeHost);
240                         }
241                 }
242
243                 public class GrantTo
244                 {
245                         public GrantTo ()
246                         {
247                                 Resources = new List<Resource> ();
248                         }
249
250                         public List<Resource> Resources { get; private set; }
251
252                         public bool IsGranted (Uri uri)
253                         {
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)
262                                                                 return true;
263                                                         else if (granted [granted.Length - 1] == '/')
264                                                                 return true;
265                                                         else if (local [granted.Length] == '/')
266                                                                 return true;
267                                                 }
268                                         } else {
269                                                 if (uri.LocalPath == gr.Path)
270                                                         return true;
271                                         }
272                                 }
273                                 return false;
274                         }
275                 }
276
277                 public class Resource {
278                         private string path;
279
280                         public string Path { 
281                                 get { return path; }
282                                 set {
283                                         // an empty Path Ressource makes the *whole* policy file invalid
284                                         if (String.IsNullOrEmpty (value))
285                                                 throw new NotSupportedException ();
286                                         path = value;
287                                 }
288                         }
289
290                         public bool IncludeSubpaths { get; set; }
291                 }
292         }
293 }
294
295 #endif
296
297