2 // CrossDomainPolicyManager.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;
35 using System.Net.Sockets;
36 using System.Reflection;
37 using System.Security;
39 using System.Threading;
41 namespace System.Net.Policy {
43 internal static class CrossDomainPolicyManager {
45 public static string GetRoot (Uri uri)
47 if ((uri.Scheme == "http" && uri.Port == 80) || (uri.Scheme == "https" && uri.Port == 443) || (uri.Port == -1))
48 return String.Format ("{0}://{1}/", uri.Scheme, uri.DnsSafeHost);
50 return String.Format ("{0}://{1}:{2}/", uri.Scheme, uri.DnsSafeHost, uri.Port);
53 public const string ClientAccessPolicyFile = "/clientaccesspolicy.xml";
54 public const string CrossDomainFile = "/crossdomain.xml";
56 const int Timeout = 10000;
60 static Dictionary<string,ICrossDomainPolicy> policies = new Dictionary<string,ICrossDomainPolicy> ();
62 static internal ICrossDomainPolicy PolicyDownloadPolicy = new PolicyDownloadPolicy ();
63 static ICrossDomainPolicy site_of_origin_policy = new SiteOfOriginPolicy ();
64 static ICrossDomainPolicy no_access_policy = new NoAccessPolicy ();
66 static Uri GetRootUri (Uri uri)
68 return new Uri (GetRoot (uri));
71 public static Uri GetSilverlightPolicyUri (Uri uri)
73 return new Uri (GetRootUri (uri), CrossDomainPolicyManager.ClientAccessPolicyFile);
76 public static Uri GetFlashPolicyUri (Uri uri)
78 return new Uri (GetRootUri (uri), CrossDomainPolicyManager.CrossDomainFile);
81 public static ICrossDomainPolicy GetCachedWebPolicy (Uri uri)
83 // if we request an Uri from the same site then we return an "always positive" policy
84 if (SiteOfOriginPolicy.HasSameOrigin (uri, BaseDomainPolicy.ApplicationUri))
85 return site_of_origin_policy;
87 // otherwise we search for an already downloaded policy for the web site
88 string root = GetRoot (uri);
89 ICrossDomainPolicy policy = null;
90 policies.TryGetValue (root, out policy);
91 // and we return it (if we have it) or null (if we dont)
95 private static void AddPolicy (Uri responseUri, ICrossDomainPolicy policy)
97 string root = GetRoot (responseUri);
98 policies [root] = policy;
101 // see moon/test/2.0/WebPolicies/Pages.xaml.cs for all test cases
102 private static bool CheckContentType (string contentType)
104 const string application_xml = "application/xml";
106 // most common case: all text/* are accepted
107 if (contentType.StartsWith ("text/"))
110 // special case (e.g. used in nbcolympics)
111 if (contentType.StartsWith (application_xml)) {
112 if (application_xml.Length == contentType.Length)
113 return true; // exact match
115 // e.g. "application/xml; charset=x" - we do not care what comes after ';'
116 if (contentType.Length > application_xml.Length)
117 return contentType [application_xml.Length] == ';';
122 public static ICrossDomainPolicy BuildSilverlightPolicy (HttpWebResponse response)
124 // return null if no Silverlight policy was found, since we offer a second chance with a flash policy
125 if ((response.StatusCode != HttpStatusCode.OK) || !CheckContentType (response.ContentType))
128 ICrossDomainPolicy policy = null;
130 policy = ClientAccessPolicy.FromStream (response.GetResponseStream ());
132 AddPolicy (response.ResponseUri, policy);
133 } catch (Exception ex) {
134 Console.WriteLine (String.Format ("CrossDomainAccessManager caught an exception while reading {0}: {1}",
135 response.ResponseUri, ex));
141 public static ICrossDomainPolicy BuildFlashPolicy (HttpWebResponse response)
143 ICrossDomainPolicy policy = null;
144 if ((response.StatusCode == HttpStatusCode.OK) && CheckContentType (response.ContentType)) {
146 policy = FlashCrossDomainPolicy.FromStream (response.GetResponseStream ());
147 } catch (Exception ex) {
148 Console.WriteLine (String.Format ("CrossDomainAccessManager caught an exception while reading {0}: {1}",
149 response.ResponseUri, ex));
152 if (policy != null) {
153 // see DRT# 864 and 865
154 string site_control = response.InternalHeaders ["X-Permitted-Cross-Domain-Policies"];
155 if (!String.IsNullOrEmpty (site_control))
156 (policy as FlashCrossDomainPolicy).SiteControl = site_control;
160 // the flash policy was the last chance, keep a NoAccess into the cache
162 policy = no_access_policy;
164 AddPolicy (response.ResponseUri, policy);
170 // - we connect once to a site for the entire application life time
171 // - this returns us a policy file (silverlight format only) or else no access is granted
172 // - this policy file
173 // - can contain multiple policies
174 // - can apply to multiple domains
175 // - can grant access to several resources
177 static Dictionary<string,ClientAccessPolicy> socket_policies = new Dictionary<string,ClientAccessPolicy> ();
178 static byte [] socket_policy_file_request = Encoding.UTF8.GetBytes ("<policy-file-request/>");
179 const int PolicyPort = 943;
181 static Stream GetPolicyStream (IPEndPoint endpoint)
183 MemoryStream ms = new MemoryStream ();
184 ManualResetEvent mre = new ManualResetEvent (false);
185 // Silverlight only support TCP
186 Socket socket = new Socket (endpoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
188 // Application code can't connect to port 943, so we need a special/internal API/ctor to allow this
189 SocketAsyncEventArgs saea = new SocketAsyncEventArgs (true);
190 saea.RemoteEndPoint = new IPEndPoint (endpoint.Address, PolicyPort);
191 saea.Completed += delegate (object sender, SocketAsyncEventArgs e) {
192 if (e.SocketError != SocketError.Success) {
197 switch (e.LastOperation) {
198 case SocketAsyncOperation.Connect:
199 e.SetBuffer (socket_policy_file_request, 0, socket_policy_file_request.Length);
200 socket.SendAsync (e);
202 case SocketAsyncOperation.Send:
203 byte [] buffer = new byte [256];
204 e.SetBuffer (buffer, 0, buffer.Length);
205 socket.ReceiveAsync (e);
207 case SocketAsyncOperation.Receive:
208 int transfer = e.BytesTransferred;
210 ms.Write (e.Buffer, 0, transfer);
211 // Console.Write (Encoding.UTF8.GetString (e.Buffer, 0, transfer));
214 if ((transfer == 0) || (transfer < e.Buffer.Length)) {
218 socket.ReceiveAsync (e);
224 socket.ConnectAsync (saea);
226 // behave like there's no policy (no socket access) if we timeout
227 if (!mre.WaitOne (Timeout))
233 static Stream GetPolicyStream (Uri uri)
236 throw new NotSupportedException ("Fetching socket policy from " + uri.ToString () + " is not yet available in moonlight");
239 public static ClientAccessPolicy CreateForEndPoint (IPEndPoint endpoint, SocketClientAccessPolicyProtocol protocol)
244 case SocketClientAccessPolicyProtocol.Tcp:
245 s = GetPolicyStream (endpoint);
247 case SocketClientAccessPolicyProtocol.Http:
248 // <quote>It will NOT attempt to download the policy via the custom TCP protocol if the
249 // policy check fails.</quote>
250 // http://blogs.msdn.com/ncl/archive/2010/04/15/silverlight-4-socket-policy-changes.aspx
251 string url = String.Format ("http://{0}:80{1}", endpoint.Address.ToString (),
252 CrossDomainPolicyManager.ClientAccessPolicyFile);
253 s = GetPolicyStream (new Uri (url));
257 if ((s == null) || (s.Length == 0))
260 ClientAccessPolicy policy = null;
262 policy = (ClientAccessPolicy) ClientAccessPolicy.FromStream (s);
263 } catch (Exception ex) {
264 Console.WriteLine (String.Format ("CrossDomainAccessManager caught an exception while reading {0}: {1}",
265 endpoint, ex.Message));
272 static public bool CheckEndPoint (EndPoint endpoint, SocketClientAccessPolicyProtocol protocol)
274 // if needed transform the DnsEndPoint into a usable IPEndPoint
275 IPEndPoint ip = (endpoint as IPEndPoint);
277 throw new ArgumentException ("endpoint");
279 // find the policy (cached or to be downloaded) associated with the endpoint
280 string address = ip.Address.ToString ();
281 ClientAccessPolicy policy = null;
282 if (!socket_policies.TryGetValue (address, out policy)) {
283 policy = CreateForEndPoint (ip, protocol);
284 socket_policies.Add (address, policy);
287 // no access granted if no policy is available
291 // does the policy allows access ?
292 return policy.IsAllowed (ip);