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
.Headers
["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 // make sure this work in a IPv6-only environment
182 static AddressFamily
GetBestFamily ()
184 if (Socket
.OSSupportsIPv4
)
185 return AddressFamily
.InterNetwork
;
186 else if (Socket
.OSSupportsIPv6
)
187 return AddressFamily
.InterNetworkV6
;
189 return AddressFamily
.Unspecified
;
192 static Stream
GetPolicyStream (IPEndPoint endpoint
)
194 MemoryStream ms
= new MemoryStream ();
195 ManualResetEvent mre
= new ManualResetEvent (false);
196 // Silverlight only support TCP
197 Socket socket
= new Socket (GetBestFamily (), SocketType
.Stream
, ProtocolType
.Tcp
);
199 // Application code can't connect to port 943, so we need a special/internal API/ctor to allow this
200 SocketAsyncEventArgs saea
= new SocketAsyncEventArgs (true);
201 saea
.RemoteEndPoint
= new IPEndPoint (endpoint
.Address
, PolicyPort
);
202 saea
.Completed
+= delegate (object sender
, SocketAsyncEventArgs e
) {
203 if (e
.SocketError
!= SocketError
.Success
) {
208 switch (e
.LastOperation
) {
209 case SocketAsyncOperation
.Connect
:
210 e
.SetBuffer (socket_policy_file_request
, 0, socket_policy_file_request
.Length
);
211 socket
.SendAsync (e
);
213 case SocketAsyncOperation
.Send
:
214 byte [] buffer
= new byte [256];
215 e
.SetBuffer (buffer
, 0, buffer
.Length
);
216 socket
.ReceiveAsync (e
);
218 case SocketAsyncOperation
.Receive
:
219 int transfer
= e
.BytesTransferred
;
221 ms
.Write (e
.Buffer
, 0, transfer
);
222 // Console.Write (Encoding.UTF8.GetString (e.Buffer, 0, transfer));
225 if ((transfer
== 0) || (transfer
< e
.Buffer
.Length
)) {
229 socket
.ReceiveAsync (e
);
235 socket
.ConnectAsync (saea
);
237 // behave like there's no policy (no socket access) if we timeout
238 if (!mre
.WaitOne (Timeout
))
244 public static ClientAccessPolicy
CreateForEndPoint (IPEndPoint endpoint
)
246 Stream s
= GetPolicyStream (endpoint
);
250 ClientAccessPolicy policy
= null;
252 policy
= (ClientAccessPolicy
) ClientAccessPolicy
.FromStream (s
);
253 } catch (Exception ex
) {
254 Console
.WriteLine (String
.Format ("CrossDomainAccessManager caught an exception while reading {0}: {1}",
255 endpoint
, ex
.Message
));
262 static public bool CheckEndPoint (EndPoint endpoint
)
264 // if needed transform the DnsEndPoint into a usable IPEndPoint
265 IPEndPoint ip
= (endpoint
as IPEndPoint
);
267 throw new ArgumentException ("endpoint");
269 // find the policy (cached or to be downloaded) associated with the endpoint
270 string address
= ip
.Address
.ToString ();
271 ClientAccessPolicy policy
= null;
272 if (!socket_policies
.TryGetValue (address
, out policy
)) {
273 policy
= CreateForEndPoint (ip
);
274 socket_policies
.Add (address
, policy
);
277 // no access granted if no policy is available
281 // does the policy allows access ?
282 return policy
.IsAllowed (ip
);