2010-03-03 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Channels / HttpListenerManager.cs
1 //
2 // HttpListenerManager.cs
3 //
4 // Author:
5 //      Vladimir Krasnov <vladimirk@mainsoft.com>
6 //      Atsushi Enomoto  <atsushi@ximian.com>
7 //
8 // Copyright (C) 2005-2006 Mainsoft, Inc.  http://www.mainsoft.com
9 // Copyright (C) 2009-2010 Novell, Inc.  http://www.novell.com
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30 using System;
31 using System.Collections.Generic;
32 using System.Collections.Specialized;
33 using System.IdentityModel.Selectors;
34 using System.IdentityModel.Tokens;
35 using System.ServiceModel.Description;
36 using System.ServiceModel.Security;
37 using System.Text;
38 using System.Threading;
39 using System.Net;
40 using System.Web;
41
42 namespace System.ServiceModel.Channels
43 {
44         abstract class HttpContextInfo
45         {
46                 public abstract NameValueCollection QueryString { get; }
47                 public abstract Uri RequestUrl { get; }
48                 public abstract string HttpMethod { get; }
49                 public abstract void Abort ();
50
51                 public abstract string User { get; }
52                 public abstract string Password { get; }
53                 public abstract void ReturnUnauthorized ();
54         }
55
56         class HttpListenerContextInfo : HttpContextInfo
57         {
58                 public HttpListenerContextInfo (HttpListenerContext ctx)
59                 {
60                         this.ctx = ctx;
61                 }
62                 
63                 HttpListenerContext ctx;
64
65                 public HttpListenerContext Source {
66                         get { return ctx; }
67                 }
68
69                 public override NameValueCollection QueryString {
70                         get { return ctx.Request.QueryString; }
71                 }
72                 public override Uri RequestUrl {
73                         get { return ctx.Request.Url; }
74                 }
75                 public override string HttpMethod {
76                         get { return ctx.Request.HttpMethod; }
77                 }
78                 public override void Abort ()
79                 {
80                         ctx.Response.Abort ();
81                 }
82
83                 public override string User {
84                         get { return ctx.User != null ? ((HttpListenerBasicIdentity) ctx.User.Identity).Name : null; }
85                 }
86
87                 public override string Password {
88                         get { return ctx.User != null ? ((HttpListenerBasicIdentity) ctx.User.Identity).Password : null; }
89                 }
90
91                 public override void ReturnUnauthorized ()
92                 {
93                         ctx.Response.StatusCode = 401;
94                 }
95         }
96
97         class AspNetHttpContextInfo : HttpContextInfo
98         {
99                 public AspNetHttpContextInfo (HttpContext ctx)
100                 {
101                         this.ctx = ctx;
102                 }
103                 
104                 HttpContext ctx;
105
106                 public HttpContext Source {
107                         get { return ctx; }
108                 }
109
110                 public override NameValueCollection QueryString {
111                         get { return ctx.Request.QueryString; }
112                 }
113                 public override Uri RequestUrl {
114                         get { return ctx.Request.Url; }
115                 }
116                 public override string HttpMethod {
117                         get { return ctx.Request.HttpMethod; }
118                 }
119
120                 public override void Abort ()
121                 {
122                         ctx.Response.Close ();
123                 }
124
125                 public override string User {
126                         get { return ctx.User != null ? ((HttpListenerBasicIdentity) ctx.User.Identity).Name : null; }
127                 }
128
129                 // FIXME: how to acquire this?
130                 public override string Password {
131                         get { throw new NotImplementedException (); }
132                 }
133
134                 public override void ReturnUnauthorized ()
135                 {
136                         ctx.Response.StatusCode = 401;
137                 }
138         }
139
140         internal class HttpSimpleListenerManager : HttpListenerManager
141         {
142                 static Dictionary<Uri, HttpListener> opened_listeners;
143                 HttpListener http_listener;
144
145                 static HttpSimpleListenerManager ()
146                 {
147                         opened_listeners = new Dictionary<Uri, HttpListener> ();
148                 }
149
150                 public HttpSimpleListenerManager (IChannelListener channelListener, HttpTransportBindingElement source, ServiceCredentialsSecurityTokenManager securityTokenManager)
151                         : base (channelListener, source, securityTokenManager)
152                 {
153                 }
154
155                 protected override void OnRegister (IChannelListener channelListener, TimeSpan timeout)
156                 {
157                         lock (opened_listeners) {
158                                 if (!opened_listeners.ContainsKey (channelListener.Uri)) {
159                                         HttpListener listener = new HttpListener ();
160                                         listener.AuthenticationSchemeSelectorDelegate = delegate (HttpListenerRequest req) {
161                                                 return Source.AuthenticationScheme;
162                                         };
163                                         listener.Realm = Source.Realm;
164                                         listener.UnsafeConnectionNtlmAuthentication = Source.UnsafeConnectionNtlmAuthentication;
165
166                                         string uriString = channelListener.Uri.ToString ();
167                                         if (!uriString.EndsWith ("/", StringComparison.Ordinal))
168                                                 uriString += "/";
169                                         listener.Prefixes.Add (uriString);
170                                         listener.Start ();
171
172                                         opened_listeners [channelListener.Uri] = listener;
173                                 }
174
175                                 http_listener = opened_listeners [channelListener.Uri];
176                         }
177                 }
178
179                 protected override void OnUnregister (IChannelListener listener, bool abort)
180                 {
181                         lock (opened_listeners) {
182                                 if (http_listener == null)
183                                         return;
184                                 if (http_listener.IsListening) {
185                                         if (abort)
186                                                 http_listener.Abort ();
187                                         else
188                                                 http_listener.Close ();
189                                 }
190                                 ((IDisposable) http_listener).Dispose ();
191
192                                 opened_listeners.Remove (listener.Uri);
193                         }
194
195                         http_listener = null;
196                 }
197
198                 protected override void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceivedCallback)
199                 {
200                         http_listener.BeginGetContext (delegate (IAsyncResult result) {
201                                 var hctx = http_listener.EndGetContext (result);
202                                 contextReceivedCallback (new HttpListenerContextInfo (hctx));
203                         }, null);
204                 }
205         }
206
207         internal class AspNetListenerManager : HttpListenerManager
208         {
209                 SvcHttpHandler http_handler;
210
211                 public AspNetListenerManager (IChannelListener channelListener, HttpTransportBindingElement source, ServiceCredentialsSecurityTokenManager securityTokenManager)
212                         : base (channelListener, source, securityTokenManager)
213                 {
214                         http_handler = SvcHttpHandlerFactory.GetHandlerForListener (channelListener);
215                 }
216
217                 public SvcHttpHandler Source {
218                         get { return http_handler; }
219                 }
220
221                 protected override void OnRegister (IChannelListener channelListener, TimeSpan timeout)
222                 {
223                         http_handler.RegisterListener (channelListener);
224                 }
225
226                 protected override void OnUnregister (IChannelListener listener, bool abort)
227                 {
228                         http_handler.UnregisterListener (listener);
229                 }
230
231                 Func<IChannelListener,HttpContext> wait_delegate;
232
233                 protected override void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceivedCallback)
234                 {
235                         if (wait_delegate == null)
236                                 wait_delegate = new Func<IChannelListener,HttpContext> (http_handler.WaitForRequest);
237                         wait_delegate.BeginInvoke (listener, delegate (IAsyncResult result) {
238                                 var ctx = wait_delegate.EndInvoke (result);
239                                 contextReceivedCallback (ctx != null ? new AspNetHttpContextInfo (ctx) : null);
240                                 }, null);
241                 }
242         }
243
244         internal abstract class HttpListenerManager
245         {
246                 static Dictionary<Uri, List<IChannelListener>> registered_channels;
247                 IChannelListener channel_listener;
248                 MetadataPublishingInfo mex_info;
249                 HttpGetWsdl wsdl_instance;
250                 AutoResetEvent wait_http_ctx = new AutoResetEvent (false);
251                 List<HttpContextInfo> pending = new List<HttpContextInfo> ();
252
253                 public MetadataPublishingInfo MexInfo { get { return mex_info; } }
254                 public HttpTransportBindingElement Source { get; private set; }
255
256                 SecurityTokenAuthenticator security_token_authenticator;
257                 SecurityTokenResolver security_token_resolver;
258
259                 static HttpListenerManager ()
260                 {
261                         registered_channels = new Dictionary<Uri, List<IChannelListener>> ();
262                 }
263
264                 protected HttpListenerManager (IChannelListener channelListener, HttpTransportBindingElement source, ServiceCredentialsSecurityTokenManager securityTokenManager)
265                 {
266                         this.channel_listener = channelListener;
267                         // FIXME: this cast should not be required, but current JIT somehow causes an internal error.
268                         mex_info = ((IChannelListener) channelListener).GetProperty<MetadataPublishingInfo> ();
269                         wsdl_instance = mex_info != null ? mex_info.Instance : null;
270                         Source = source;
271
272                         if (securityTokenManager != null) {
273                                 var str = new SecurityTokenRequirement () { TokenType = SecurityTokenTypes.UserName };
274                                 security_token_authenticator = securityTokenManager.CreateSecurityTokenAuthenticator (str, out security_token_resolver);
275                         }
276                 }
277
278                 public void Open (TimeSpan timeout)
279                 {
280                         if (!registered_channels.ContainsKey (channel_listener.Uri))
281                                 registered_channels [channel_listener.Uri] = new List<IChannelListener> ();
282                         OnRegister (channel_listener, timeout);
283                         registered_channels [channel_listener.Uri].Add (channel_listener);
284
285                         // make sure to fill wsdl_instance among other 
286                         // listeners. It is somewhat hacky way, but 
287                         // otherwise there is no assured way to do it.
288                         var wsdl = wsdl_instance;
289                         if (wsdl == null)
290                                 foreach (var l in registered_channels [channel_listener.Uri])
291                                         if ((wsdl = l.GetProperty<HttpListenerManager> ().wsdl_instance) != null)
292                                                 break;
293                         if (wsdl != null) {
294                                 foreach (var l in registered_channels [channel_listener.Uri])
295                                         l.GetProperty<HttpListenerManager> ().wsdl_instance = wsdl;
296                         }
297                 }
298
299                 public void Stop (bool abort)
300                 {
301                         List<IChannelListener> channelsList = registered_channels [channel_listener.Uri];
302                         channelsList.Remove (channel_listener);
303
304                         try {
305                                 foreach (var ctx in pending)
306                                         ctx.Abort ();
307                         } catch (Exception ex) {
308                                 // FIXME: log it
309                                 Console.WriteLine ("error during HTTP channel listener shutdown: " + ex);
310                         }
311
312                         if (channelsList.Count == 0)
313                                 OnUnregister (channel_listener, abort);
314                 }
315
316                 protected abstract void OnRegister (IChannelListener listener, TimeSpan timeout);
317                 protected abstract void OnUnregister (IChannelListener listener, bool abort);
318
319                 public void CancelGetHttpContextAsync ()
320                 {
321                         wait_http_ctx.Set ();
322                 }
323
324                 // Do not directly handle retrieved HttpListenerContexts when
325                 // the listener received ones.
326                 // Instead, iterate every listeners to find the most-likely-
327                 // matching one and immediately handle the listener context.
328                 // If the listener is not requesting a context right now, then
329                 // store it in *each* listener's queue.
330
331                 public void GetHttpContextAsync (TimeSpan timeout, Action<HttpContextInfo> callback)
332                 {
333                         lock (pending) {
334                                 foreach (var pctx in pending) {
335                                         if (FilterHttpContext (pctx)) {
336                                                 callback (pctx);
337                                                 return;
338                                         }
339                                 }
340                         }
341                         KickContextReceiver (channel_listener, DispatchHttpListenerContext);
342                         wait_http_ctx.WaitOne (timeout);
343                         lock (pending) {
344                                 HttpContextInfo ctx = pending.Count > 0 ? pending [0] : null;
345                                 if (ctx != null)
346                                         pending.Remove (ctx);
347                                 callback (ctx);
348                         }
349                 }
350
351                 protected abstract void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceiverCallback);
352
353                 void DispatchHttpListenerContext (HttpContextInfo ctx)
354                 {
355                         if (wsdl_instance == null) {
356                                 AddListenerContext (ctx);
357                                 return;
358                         }
359                         foreach (var l in registered_channels [channel_listener.Uri]) {
360                                 var lm = l.GetProperty<HttpListenerManager> ();
361                                 if (lm.FilterHttpContext (ctx)) {
362                                         lm.AddListenerContext (ctx);
363                                         return;
364                                 }
365                         }
366                         AddListenerContext (ctx);
367                 }
368
369                 void AddListenerContext (HttpContextInfo ctx)
370                 {
371                         if (Source.AuthenticationScheme != AuthenticationSchemes.Anonymous) {
372                                 if (security_token_authenticator != null)
373                                         // FIXME: use return value?
374                                         try {
375                                                 security_token_authenticator.ValidateToken (new UserNameSecurityToken (ctx.User, ctx.Password));
376                                         } catch (Exception) {
377                                                 ctx.ReturnUnauthorized ();
378                                         }
379                                 else {
380                                         ctx.ReturnUnauthorized ();
381                                 }
382                         }
383
384                         lock (registered_channels) {
385                                 pending.Add (ctx);
386                                 // FIXME: this should not be required, but it somehow saves some failures wrt concurrent calls.
387                                 Thread.Sleep (100);
388                                 wait_http_ctx.Set ();
389                         }
390                 }
391
392                 const UriComponents cmpflag = UriComponents.HttpRequestUrl ^ UriComponents.Query;
393                 const UriFormat fmtflag = UriFormat.SafeUnescaped;
394
395                 internal bool FilterHttpContext (HttpContextInfo ctx)
396                 {
397                         if (ctx.HttpMethod.ToUpper () != "GET")
398                                 return mex_info == null;
399
400                         if (wsdl_instance == null)
401                                 return true;
402                         if (channel_listener.State != CommunicationState.Opened)
403                                 return true;
404
405                         if (wsdl_instance.WsdlUrl != null && Uri.Compare (ctx.RequestUrl, wsdl_instance.WsdlUrl, cmpflag, fmtflag, StringComparison.Ordinal) == 0) {
406                                 if (mex_info == null)
407                                         return false; // Do not handle this at normal dispatcher.
408                                 if (ctx.QueryString [null] == "wsdl")
409                                         return mex_info.SupportsMex; // wsdl dispatcher should handle this.
410                                 if (!wsdl_instance.HelpUrl.Equals (wsdl_instance.WsdlUrl))
411                                         return true; // in case help URL is not equivalent to WSDL URL, it anyways returns WSDL regardless of ?wsdl existence.
412                         }
413                         if (wsdl_instance.HelpUrl != null && Uri.Compare (ctx.RequestUrl, wsdl_instance.HelpUrl, cmpflag, fmtflag, StringComparison.Ordinal) == 0) {
414                                 // Do not handle this at normal dispatcher.
415                                 // Do return true otherwise, even if it is with "?wsdl".
416                                 // (It must be handled above if applicable.)
417                                 return mex_info != null;
418                         }
419
420                         return mex_info == null;
421                 }
422         }
423 }