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