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