663b92a1819dbd9cc6dc44667f2af8da2128d980
[mono.git] / mcs / class / System / System.Net / HttpListener.cs
1 //
2 // System.Net.HttpListener
3 //
4 // Authors:
5 //      Gonzalo Paniagua Javier (gonzalo@novell.com)
6 //      Marek Safar (marek.safar@gmail.com)
7 //
8 // Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
9 // Copyright 2011 Xamarin Inc.
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
31 #if SECURITY_DEP
32 using System.IO;
33 using System.Collections;
34 using System.Threading;
35 using System.Threading.Tasks;
36 using System.Net.Security;
37 using System.Security.Authentication.ExtendedProtection;
38 using System.Security.Cryptography;
39 using System.Security.Cryptography.X509Certificates;
40
41 //TODO: logging
42 namespace System.Net {
43         public sealed partial class HttpListener : IDisposable {
44                 AuthenticationSchemes auth_schemes;
45                 HttpListenerPrefixCollection prefixes;
46                 AuthenticationSchemeSelector auth_selector; 
47                 string realm;
48                 bool ignore_write_exceptions;
49                 bool unsafe_ntlm_auth;
50                 bool listening;
51                 bool disposed;
52
53                 readonly object _internalLock; // don't rename to match CoreFx
54
55                 Hashtable registry;   // Dictionary<HttpListenerContext,HttpListenerContext> 
56                 ArrayList ctx_queue;  // List<HttpListenerContext> ctx_queue;
57                 ArrayList wait_queue; // List<ListenerAsyncResult> wait_queue;
58                 Hashtable connections;
59
60                 ServiceNameStore defaultServiceNames;
61                 ExtendedProtectionPolicy extendedProtectionPolicy;
62                 ExtendedProtectionSelector extendedProtectionSelectorDelegate;
63
64                 public delegate ExtendedProtectionPolicy ExtendedProtectionSelector (HttpListenerRequest request);
65
66                 public HttpListener ()
67                 {
68                         _internalLock = new object ();
69                         prefixes = new HttpListenerPrefixCollection (this);
70                         registry = new Hashtable ();
71                         connections = Hashtable.Synchronized (new Hashtable ());
72                         ctx_queue = new ArrayList ();
73                         wait_queue = new ArrayList ();
74                         auth_schemes = AuthenticationSchemes.Anonymous;
75                         defaultServiceNames = new ServiceNameStore ();
76                         extendedProtectionPolicy = new ExtendedProtectionPolicy (PolicyEnforcement.Never);
77                 }
78
79
80                 // TODO: Digest, NTLM and Negotiate require ControlPrincipal
81                 public AuthenticationSchemes AuthenticationSchemes {
82                         get { return auth_schemes; }
83                         set {
84                                 CheckDisposed ();
85                                 auth_schemes = value;
86                         }
87                 }
88
89                 public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate {
90                         get { return auth_selector; }
91                         set {
92                                 CheckDisposed ();
93                                 auth_selector = value;
94                         }
95                 }
96
97                 public ExtendedProtectionSelector ExtendedProtectionSelectorDelegate
98                 {
99                         get { return extendedProtectionSelectorDelegate; }
100                         set {
101                                 CheckDisposed();
102                                 if (value == null)
103                                         throw new ArgumentNullException ();
104
105                                 if (!AuthenticationManager.OSSupportsExtendedProtection)
106                                         throw new PlatformNotSupportedException (SR.GetString (SR.security_ExtendedProtection_NoOSSupport));
107
108                                 extendedProtectionSelectorDelegate = value;
109                         }
110                 }
111
112                 public bool IgnoreWriteExceptions {
113                         get { return ignore_write_exceptions; }
114                         set {
115                                 CheckDisposed ();
116                                 ignore_write_exceptions = value;
117                         }
118                 }
119
120                 public bool IsListening {
121                         get { return listening; }
122                 }
123
124                 public static bool IsSupported {
125                         get { return true; }
126                 }
127
128                 public HttpListenerPrefixCollection Prefixes {
129                         get {
130                                 CheckDisposed ();
131                                 return prefixes;
132                         }
133                 }
134
135                 [MonoTODO]
136                 public HttpListenerTimeoutManager TimeoutManager {
137                         get {
138                                 throw new NotImplementedException ();
139                         }
140                 }
141
142                 [MonoTODO ("not used anywhere in the implementation")]
143                 public ExtendedProtectionPolicy ExtendedProtectionPolicy
144                 {
145                         get {
146                                 return extendedProtectionPolicy;
147                         }
148                         set {
149                                 CheckDisposed ();
150
151                                 if (value == null)
152                                         throw new ArgumentNullException ("value");
153
154                                 if (!AuthenticationManager.OSSupportsExtendedProtection && value.PolicyEnforcement == PolicyEnforcement.Always)
155                                         throw new PlatformNotSupportedException (SR.GetString(SR.security_ExtendedProtection_NoOSSupport));
156
157                                 if (value.CustomChannelBinding != null)
158                                         throw new ArgumentException (SR.GetString (SR.net_listener_cannot_set_custom_cbt), "CustomChannelBinding");
159  
160                                 extendedProtectionPolicy = value;
161                         }
162                 }
163
164                 public ServiceNameCollection DefaultServiceNames
165                 {
166                         get {
167                                 return defaultServiceNames.ServiceNames;
168                         }
169                 }
170
171                 // TODO: use this
172                 public string Realm {
173                         get { return realm; }
174                         set {
175                                 CheckDisposed ();
176                                 realm = value;
177                         }
178                 }
179
180                 [MonoTODO ("Support for NTLM needs some loving.")]
181                 public bool UnsafeConnectionNtlmAuthentication {
182                         get { return unsafe_ntlm_auth; }
183                         set {
184                                 CheckDisposed ();
185                                 unsafe_ntlm_auth = value;
186                         }
187                 }
188
189                 public void Abort ()
190                 {
191                         if (disposed)
192                                 return;
193
194                         if (!listening) {
195                                 return;
196                         }
197
198                         Close (true);
199                 }
200
201                 public void Close ()
202                 {
203                         if (disposed)
204                                 return;
205
206                         if (!listening) {
207                                 disposed = true;
208                                 return;
209                         }
210
211                         Close (true);
212                         disposed = true;
213                 }
214
215                 void Close (bool force)
216                 {
217                         CheckDisposed ();
218                         EndPointManager.RemoveListener (this);
219                         Cleanup (force);
220                 }
221
222                 void Cleanup (bool close_existing)
223                 {
224                         lock (_internalLock) {
225                                 if (close_existing) {
226                                         // Need to copy this since closing will call UnregisterContext
227                                         ICollection keys = registry.Keys;
228                                         var all = new HttpListenerContext [keys.Count];
229                                         keys.CopyTo (all, 0);
230                                         registry.Clear ();
231                                         for (int i = all.Length - 1; i >= 0; i--)
232                                                 all [i].Connection.Close (true);
233                                 }
234
235                                 lock (connections.SyncRoot) {
236                                         ICollection keys = connections.Keys;
237                                         var conns = new HttpConnection [keys.Count];
238                                         keys.CopyTo (conns, 0);
239                                         connections.Clear ();
240                                         for (int i = conns.Length - 1; i >= 0; i--)
241                                                 conns [i].Close (true);
242                                 }
243                                 lock (ctx_queue) {
244                                         var ctxs = (HttpListenerContext []) ctx_queue.ToArray (typeof (HttpListenerContext));
245                                         ctx_queue.Clear ();
246                                         for (int i = ctxs.Length - 1; i >= 0; i--)
247                                                 ctxs [i].Connection.Close (true);
248                                 }
249
250                                 lock (wait_queue) {
251                                         Exception exc = new ObjectDisposedException ("listener");
252                                         foreach (ListenerAsyncResult ares in wait_queue) {
253                                                 ares.Complete (exc);
254                                         }
255                                         wait_queue.Clear ();
256                                 }
257                         }
258                 }
259
260                 public IAsyncResult BeginGetContext (AsyncCallback callback, Object state)
261                 {
262                         CheckDisposed ();
263                         if (!listening)
264                                 throw new InvalidOperationException ("Please, call Start before using this method.");
265
266                         ListenerAsyncResult ares = new ListenerAsyncResult (callback, state);
267
268                         // lock wait_queue early to avoid race conditions
269                         lock (wait_queue) {
270                                 lock (ctx_queue) {
271                                         HttpListenerContext ctx = GetContextFromQueue ();
272                                         if (ctx != null) {
273                                                 ares.Complete (ctx, true);
274                                                 return ares;
275                                         }
276                                 }
277
278                                 wait_queue.Add (ares);
279                         }
280
281                         return ares;
282                 }
283
284                 public HttpListenerContext EndGetContext (IAsyncResult asyncResult)
285                 {
286                         CheckDisposed ();
287                         if (asyncResult == null)
288                                 throw new ArgumentNullException ("asyncResult");
289
290                         ListenerAsyncResult ares = asyncResult as ListenerAsyncResult;
291                         if (ares == null)
292                                 throw new ArgumentException ("Wrong IAsyncResult.", "asyncResult");
293                         if (ares.EndCalled)
294                                 throw new ArgumentException ("Cannot reuse this IAsyncResult");
295                         ares.EndCalled = true;
296
297                         if (!ares.IsCompleted)
298                                 ares.AsyncWaitHandle.WaitOne ();
299
300                         lock (wait_queue) {
301                                 int idx = wait_queue.IndexOf (ares);
302                                 if (idx >= 0)
303                                         wait_queue.RemoveAt (idx);
304                         }
305
306                         HttpListenerContext context = ares.GetContext ();
307                         context.ParseAuthentication (SelectAuthenticationScheme (context));
308                         return context; // This will throw on error.
309                 }
310
311                 internal AuthenticationSchemes SelectAuthenticationScheme (HttpListenerContext context)
312                 {
313                         if (AuthenticationSchemeSelectorDelegate != null)
314                                 return AuthenticationSchemeSelectorDelegate (context.Request);
315                         else
316                                 return auth_schemes;
317                 }
318
319                 public HttpListenerContext GetContext ()
320                 {
321                         // The prefixes are not checked when using the async interface!?
322                         if (prefixes.Count == 0)
323                                 throw new InvalidOperationException ("Please, call AddPrefix before using this method.");
324
325                         ListenerAsyncResult ares = (ListenerAsyncResult) BeginGetContext (null, null);
326                         ares.InGet = true;
327                         return EndGetContext (ares);
328                 }
329
330                 public void Start ()
331                 {
332                         CheckDisposed ();
333                         if (listening)
334                                 return;
335
336                         EndPointManager.AddListener (this);
337                         listening = true;
338                 }
339
340                 public void Stop ()
341                 {
342                         CheckDisposed ();
343                         listening = false;
344                         Close (false);
345                 }
346
347                 void IDisposable.Dispose ()
348                 {
349                         if (disposed)
350                                 return;
351
352                         Close (true); //TODO: Should we force here or not?
353                         disposed = true;
354                 }
355
356                 public Task<HttpListenerContext> GetContextAsync ()
357                 {
358                         return Task<HttpListenerContext>.Factory.FromAsync (BeginGetContext, EndGetContext, null);
359                 }
360
361                 internal void CheckDisposed ()
362                 {
363                         if (disposed)
364                                 throw new ObjectDisposedException (GetType ().ToString ());
365                 }
366
367                 // Must be called with a lock on ctx_queue
368                 HttpListenerContext GetContextFromQueue ()
369                 {
370                         if (ctx_queue.Count == 0)
371                                 return null;
372
373                         HttpListenerContext context = (HttpListenerContext) ctx_queue [0];
374                         ctx_queue.RemoveAt (0);
375                         return context;
376                 }
377
378                 internal void RegisterContext (HttpListenerContext context)
379                 {
380                         lock (_internalLock)
381                                 registry [context] = context;
382
383                         ListenerAsyncResult ares = null;
384                         lock (wait_queue) {
385                                 if (wait_queue.Count == 0) {
386                                         lock (ctx_queue)
387                                                 ctx_queue.Add (context);
388                                 } else {
389                                         ares = (ListenerAsyncResult) wait_queue [0];
390                                         wait_queue.RemoveAt (0);
391                                 }
392                         }
393                         if (ares != null)
394                                 ares.Complete (context);
395                 }
396
397                 internal void UnregisterContext (HttpListenerContext context)
398                 {
399                         lock (_internalLock)
400                                 registry.Remove (context);
401                         lock (ctx_queue) {
402                                 int idx = ctx_queue.IndexOf (context);
403                                 if (idx >= 0)
404                                         ctx_queue.RemoveAt (idx);
405                         }
406                 }
407
408                 internal void AddConnection (HttpConnection cnc)
409                 {
410                         connections [cnc] = cnc;
411                 }
412
413                 internal void RemoveConnection (HttpConnection cnc)
414                 {
415                         connections.Remove (cnc);
416                 }
417         }
418 }
419 #else // SECURITY_DEP
420 namespace System.Net
421 {
422         public sealed class HttpListener
423         {
424         }
425 }
426 #endif