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