2010-06-23 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.Dispatcher;
37 using System.ServiceModel.Security;
38 using System.Security.Principal;
39 using System.Text;
40 using System.Threading;
41 using System.Net;
42 using System.Web;
43
44 namespace System.ServiceModel.Channels
45 {
46         abstract class HttpContextInfo
47         {
48                 public abstract NameValueCollection QueryString { get; }
49                 public abstract Uri RequestUrl { get; }
50                 public abstract string HttpMethod { get; }
51                 public abstract void Abort ();
52
53                 public abstract string User { get; }
54                 public abstract string Password { get; }
55                 public abstract void ReturnUnauthorized ();
56         }
57
58         class HttpListenerContextInfo : HttpContextInfo
59         {
60                 public HttpListenerContextInfo (HttpListenerContext ctx)
61                 {
62                         this.ctx = ctx;
63                 }
64                 
65                 HttpListenerContext ctx;
66
67                 public HttpListenerContext Source {
68                         get { return ctx; }
69                 }
70
71                 public override NameValueCollection QueryString {
72                         get { return ctx.Request.QueryString; }
73                 }
74                 public override Uri RequestUrl {
75                         get { return ctx.Request.Url; }
76                 }
77                 public override string HttpMethod {
78                         get { return ctx.Request.HttpMethod; }
79                 }
80                 public override void Abort ()
81                 {
82                         ctx.Response.Abort ();
83                 }
84
85                 public override string User {
86                         get { return ctx.User != null ? ((HttpListenerBasicIdentity) ctx.User.Identity).Name : null; }
87                 }
88
89                 public override string Password {
90                         get { return ctx.User != null ? ((HttpListenerBasicIdentity) ctx.User.Identity).Password : null; }
91                 }
92
93                 public override void ReturnUnauthorized ()
94                 {
95                         ctx.Response.StatusCode = 401;
96                 }
97         }
98
99         class AspNetHttpContextInfo : HttpContextInfo
100         {
101                 public AspNetHttpContextInfo (HttpContext ctx)
102                 {
103                         this.ctx = ctx;
104                 }
105                 
106                 HttpContext ctx;
107
108                 public HttpContext Source {
109                         get { return ctx; }
110                 }
111
112                 public override NameValueCollection QueryString {
113                         get { return ctx.Request.QueryString; }
114                 }
115                 public override Uri RequestUrl {
116                         get { return ctx.Request.Url; }
117                 }
118                 public override string HttpMethod {
119                         get { return ctx.Request.HttpMethod; }
120                 }
121
122                 public override void Abort ()
123                 {
124                         ctx.Response.Close ();
125                 }
126
127                 public override string User {
128                         get { return ctx.User != null ? ((GenericIdentity) ctx.User.Identity).Name : null; }
129                 }
130
131                 // FIXME: how to acquire this?
132                 public override string Password {
133                         get { return null; }
134                 }
135
136                 public override void ReturnUnauthorized ()
137                 {
138                         ctx.Response.StatusCode = 401;
139                 }
140         }
141
142         internal class HttpSimpleListenerManager : HttpListenerManager
143         {
144                 static Dictionary<object,Dictionary<Uri,HttpListener>> http_listeners_table = new Dictionary<object,Dictionary<Uri,HttpListener>> ();
145
146                 Dictionary<Uri, HttpListener> opened_listeners;
147                 HttpListener http_listener;
148
149                 public HttpSimpleListenerManager (IChannelListener channelListener, HttpTransportBindingElement source, ServiceCredentialsSecurityTokenManager securityTokenManager, ChannelDispatcher dispatcher)
150                         : base (channelListener, source, securityTokenManager, dispatcher)
151                 {
152                         object key = dispatcher != null ? dispatcher.Host : new object (); // so that HttpChannelListener without ServiceHost is always assigned a new table.
153                         if (!http_listeners_table.TryGetValue (key, out opened_listeners)) {
154                                 opened_listeners = new Dictionary<Uri, HttpListener> ();
155                                 http_listeners_table [key] = opened_listeners;
156                         }
157                 }
158
159                 protected override void OnRegister (IChannelListener channelListener, TimeSpan timeout)
160                 {
161                         lock (opened_listeners) {
162                                 if (!opened_listeners.ContainsKey (channelListener.Uri)) {
163                                         HttpListener listener = new HttpListener ();
164                                         listener.AuthenticationSchemeSelectorDelegate = delegate (HttpListenerRequest req) {
165                                                 return Source.AuthenticationScheme;
166                                         };
167                                         listener.Realm = Source.Realm;
168                                         listener.UnsafeConnectionNtlmAuthentication = Source.UnsafeConnectionNtlmAuthentication;
169
170                                         string uriString = channelListener.Uri.ToString ();
171                                         if (!uriString.EndsWith ("/", StringComparison.Ordinal))
172                                                 uriString += "/";
173                                         listener.Prefixes.Add (uriString);
174                                         listener.Start ();
175
176                                         opened_listeners [channelListener.Uri] = listener;
177                                 }
178
179                                 http_listener = opened_listeners [channelListener.Uri];
180                         }
181                 }
182
183                 protected override void OnUnregister (IChannelListener listener, bool abort)
184                 {
185                         lock (opened_listeners) {
186                                 if (http_listener == null)
187                                         return;
188                                 if (http_listener.IsListening) {
189                                         if (abort)
190                                                 http_listener.Abort ();
191                                         else
192                                                 http_listener.Close ();
193                                 }
194                                 ((IDisposable) http_listener).Dispose ();
195
196                                 opened_listeners.Remove (listener.Uri);
197                         }
198
199                         http_listener = null;
200                 }
201
202                 protected override void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceivedCallback)
203                 {
204                         http_listener.BeginGetContext (delegate (IAsyncResult result) {
205                                 var hctx = http_listener.EndGetContext (result);
206                                 contextReceivedCallback (new HttpListenerContextInfo (hctx));
207                         }, null);
208                 }
209         }
210
211         internal class AspNetListenerManager : HttpListenerManager
212         {
213                 SvcHttpHandler http_handler;
214
215                 public AspNetListenerManager (IChannelListener channelListener, HttpTransportBindingElement source, ServiceCredentialsSecurityTokenManager securityTokenManager, ChannelDispatcher dispatcher)
216                         : base (channelListener, source, securityTokenManager, dispatcher)
217                 {
218                         http_handler = SvcHttpHandler.Current;
219                 }
220
221                 internal SvcHttpHandler HttpHandler { get { return http_handler; } }
222
223                 protected override void OnRegister (IChannelListener channelListener, TimeSpan timeout)
224                 {
225                         http_handler.RegisterListener (channelListener);
226                 }
227
228                 protected override void OnUnregister (IChannelListener listener, bool abort)
229                 {
230                         http_handler.UnregisterListener (listener);
231                 }
232
233                 Func<IChannelListener,HttpContext> wait_delegate;
234
235                 protected override void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceivedCallback)
236                 {
237                         if (wait_delegate == null)
238                                 wait_delegate = new Func<IChannelListener,HttpContext> (http_handler.WaitForRequest);
239 // FIXME: Remove this workaround. This Func<>.BeginInvoke() invocation
240 // somehow fails and does not kick this method asynchronously.
241 #if false
242                         wait_delegate.BeginInvoke (listener, delegate (IAsyncResult result) {
243                                 var ctx = wait_delegate.EndInvoke (result);
244                                 contextReceivedCallback (ctx != null ? new AspNetHttpContextInfo (ctx) : null);
245                                 }, null);
246 #else
247                         var ctx = wait_delegate.Invoke (listener);
248                                 contextReceivedCallback (ctx != null ? new AspNetHttpContextInfo (ctx) : null);
249 #endif
250                 }
251         }
252
253         internal abstract class HttpListenerManager
254         {
255                 static Dictionary<Uri, List<IChannelListener>> registered_channels;
256                 IChannelListener channel_listener;
257                 MetadataPublishingInfo mex_info;
258                 HttpGetWsdl wsdl_instance;
259                 ManualResetEvent wait_http_ctx = new ManualResetEvent (false);
260                 List<HttpContextInfo> pending = new List<HttpContextInfo> ();
261
262                 public MetadataPublishingInfo MexInfo { get { return mex_info; } }
263                 public HttpTransportBindingElement Source { get; private set; }
264                 public ChannelDispatcher Dispatcher { get; private set; }
265
266                 SecurityTokenAuthenticator security_token_authenticator;
267                 SecurityTokenResolver security_token_resolver;
268
269                 static HttpListenerManager ()
270                 {
271                         registered_channels = new Dictionary<Uri, List<IChannelListener>> ();
272                 }
273
274                 protected HttpListenerManager (IChannelListener channelListener, HttpTransportBindingElement source, ServiceCredentialsSecurityTokenManager securityTokenManager, ChannelDispatcher dispatcher)
275                 {
276                         this.Dispatcher = dispatcher;
277                         this.channel_listener = channelListener;
278                         mex_info = Dispatcher != null ? Dispatcher.Listener.GetProperty<MetadataPublishingInfo> () : null;
279                         wsdl_instance = mex_info != null ? mex_info.Instance : null;
280                         Source = source;
281
282                         if (securityTokenManager != null) {
283                                 var str = new SecurityTokenRequirement () { TokenType = SecurityTokenTypes.UserName };
284                                 security_token_authenticator = securityTokenManager.CreateSecurityTokenAuthenticator (str, out security_token_resolver);
285                         }
286                 }
287
288                 public void Open (TimeSpan timeout)
289                 {
290                         if (!registered_channels.ContainsKey (channel_listener.Uri))
291                                 registered_channels [channel_listener.Uri] = new List<IChannelListener> ();
292                         OnRegister (channel_listener, timeout);
293                         registered_channels [channel_listener.Uri].Add (channel_listener);
294
295                         // make sure to fill wsdl_instance among other 
296                         // listeners. It is somewhat hacky way, but 
297                         // otherwise there is no assured way to do it.
298                         var wsdl = wsdl_instance;
299                         if (wsdl == null)
300                                 foreach (var l in registered_channels [channel_listener.Uri])
301                                         if ((wsdl = l.GetProperty<HttpListenerManager> ().wsdl_instance) != null)
302                                                 break;
303                         if (wsdl != null) {
304                                 foreach (var l in registered_channels [channel_listener.Uri])
305                                         l.GetProperty<HttpListenerManager> ().wsdl_instance = wsdl;
306                         }
307                 }
308
309                 public void Stop (bool abort)
310                 {
311                         List<IChannelListener> channelsList = registered_channels [channel_listener.Uri];
312                         channelsList.Remove (channel_listener);
313
314                         try {
315                                 foreach (var ctx in pending)
316                                         ctx.Abort ();
317                         } catch (Exception ex) {
318                                 // FIXME: log it
319                                 Console.WriteLine ("error during HTTP channel listener shutdown: " + ex);
320                         }
321
322                         if (channelsList.Count == 0)
323                                 OnUnregister (channel_listener, abort);
324                 }
325
326                 protected abstract void OnRegister (IChannelListener listener, TimeSpan timeout);
327                 protected abstract void OnUnregister (IChannelListener listener, bool abort);
328
329                 public void CancelGetHttpContextAsync ()
330                 {
331                         wait_http_ctx.Set ();
332                 }
333
334                 // Do not directly handle retrieved HttpListenerContexts when
335                 // the listener received ones.
336                 // Instead, iterate every listeners to find the most-likely-
337                 // matching one and immediately handle the listener context.
338                 // If the listener is not requesting a context right now, then
339                 // store it in *each* listener's queue.
340
341                 public void GetHttpContextAsync (TimeSpan timeout, Action<HttpContextInfo> callback)
342                 {
343                         lock (pending) {
344                                 foreach (var pctx in pending) {
345                                         if (FilterHttpContext (pctx)) {
346                                                 pending.Remove (pctx);
347                                                 callback (pctx);
348                                                 return;
349                                         }
350                                 }
351                         }
352                         KickContextReceiver (channel_listener, DispatchHttpListenerContext);
353                         wait_http_ctx.WaitOne (timeout);
354                         wait_http_ctx.Reset ();
355                         lock (pending) {
356                                 HttpContextInfo ctx = pending.Count > 0 ? pending [0] : null;
357                                 if (ctx != null)
358                                         pending.Remove (ctx);
359                                 callback (ctx);
360                         }
361                 }
362
363                 protected abstract void KickContextReceiver (IChannelListener listener, Action<HttpContextInfo> contextReceiverCallback);
364
365                 void DispatchHttpListenerContext (HttpContextInfo ctx)
366                 {
367                         foreach (var l in registered_channels [channel_listener.Uri]) {
368                                 var lm = l.GetProperty<HttpListenerManager> ();
369                                 if (lm.FilterHttpContext (ctx)) {
370                                         lm.AddListenerContext (ctx);
371                                         return;
372                                 }
373                         }
374                         AddListenerContext (ctx);
375                 }
376
377                 void AddListenerContext (HttpContextInfo ctx)
378                 {
379                         if (Source.AuthenticationScheme != AuthenticationSchemes.Anonymous) {
380                                 if (security_token_authenticator != null)
381                                         // FIXME: use return value?
382                                         try {
383                                                 security_token_authenticator.ValidateToken (new UserNameSecurityToken (ctx.User, ctx.Password));
384                                         } catch (Exception) {
385                                                 ctx.ReturnUnauthorized ();
386                                         }
387                                 else {
388                                         ctx.ReturnUnauthorized ();
389                                 }
390                         }
391
392                         lock (pending) {
393                                 pending.Add (ctx);
394                                 // FIXME: this should not be required, but it somehow saves some failures wrt concurrent calls.
395                                 Thread.Sleep (100);
396                                 wait_http_ctx.Set ();
397                         }
398                 }
399
400                 const UriComponents cmpflag = UriComponents.HttpRequestUrl ^ UriComponents.Query;
401                 const UriFormat fmtflag = UriFormat.SafeUnescaped;
402
403                 internal bool FilterHttpContext (HttpContextInfo ctx)
404                 {
405                                 if (Dispatcher == null)
406                                         return true; // no mex can be involved.
407                                 if (ctx.HttpMethod.ToUpper () != "GET")
408                                         return !Dispatcher.IsMex; // non-GET request never matches mex channel dispatcher.
409                                 var sme = Dispatcher.Host.Extensions.Find<ServiceMetadataExtension> ();
410                                 if (sme == null)
411                                         return true; // no mex can be involved.
412
413                                 var listener = Dispatcher.Listener;
414                                 var mex = sme.Instance;
415
416                                 // now the request is GET, and we have to return true or false based on the matrix below:
417                                 // matches wsdl or help| yes      |  no      |
418                                 // mex                 | yes | no | yes | no |
419                                 // --------------------+-----+----+-----+----+
420                                 //                     |  T  | F  |  F  |  T |
421
422                                 bool match =
423                                         (mex.WsdlUrl != null && Uri.Compare (ctx.RequestUrl, mex.WsdlUrl, cmpflag, fmtflag, StringComparison.Ordinal) == 0) ||
424                                         (mex.HelpUrl != null && Uri.Compare (ctx.RequestUrl, mex.HelpUrl, cmpflag, fmtflag, StringComparison.Ordinal) == 0);
425
426                                 return !(match ^ Dispatcher.IsMex);
427                 }
428         }
429 }