Merge branch 'master' of github.com:mono/mono
[mono.git] / mcs / class / System / System.Net / HttpListener.cs
1 //
2 // System.Net.HttpListener
3 //
4 // Author:
5 //      Gonzalo Paniagua Javier (gonzalo@novell.com)
6 //
7 // Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 #if NET_2_0 && SECURITY_DEP
30
31 using System.Collections;
32 using System.Threading;
33 //TODO: logging
34 namespace System.Net {
35         public sealed class HttpListener : IDisposable {
36                 AuthenticationSchemes auth_schemes;
37                 HttpListenerPrefixCollection prefixes;
38                 AuthenticationSchemeSelector auth_selector; 
39                 string realm;
40                 bool ignore_write_exceptions;
41                 bool unsafe_ntlm_auth;
42                 bool listening;
43                 bool disposed;
44
45                 Hashtable registry;   // Dictionary<HttpListenerContext,HttpListenerContext> 
46                 ArrayList ctx_queue;  // List<HttpListenerContext> ctx_queue;
47                 ArrayList wait_queue; // List<ListenerAsyncResult> wait_queue;
48                 Hashtable connections;
49
50                 public HttpListener ()
51                 {
52                         prefixes = new HttpListenerPrefixCollection (this);
53                         registry = new Hashtable ();
54                         connections = Hashtable.Synchronized (new Hashtable ());
55                         ctx_queue = new ArrayList ();
56                         wait_queue = new ArrayList ();
57                         auth_schemes = AuthenticationSchemes.Anonymous;
58                 }
59
60                 // TODO: Digest, NTLM and Negotiate require ControlPrincipal
61                 public AuthenticationSchemes AuthenticationSchemes {
62                         get { return auth_schemes; }
63                         set {
64                                 CheckDisposed ();
65                                 auth_schemes = value;
66                         }
67                 }
68
69                 public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate {
70                         get { return auth_selector; }
71                         set {
72                                 CheckDisposed ();
73                                 auth_selector = value;
74                         }
75                 }
76
77                 public bool IgnoreWriteExceptions {
78                         get { return ignore_write_exceptions; }
79                         set {
80                                 CheckDisposed ();
81                                 ignore_write_exceptions = value;
82                         }
83                 }
84
85                 public bool IsListening {
86                         get { return listening; }
87                 }
88
89                 public static bool IsSupported {
90                         get { return true; }
91                 }
92
93                 public HttpListenerPrefixCollection Prefixes {
94                         get {
95                                 CheckDisposed ();
96                                 return prefixes;
97                         }
98                 }
99
100                 // TODO: use this
101                 public string Realm {
102                         get { return realm; }
103                         set {
104                                 CheckDisposed ();
105                                 realm = value;
106                         }
107                 }
108
109                 [MonoTODO ("Support for NTLM needs some loving.")]
110                 public bool UnsafeConnectionNtlmAuthentication {
111                         get { return unsafe_ntlm_auth; }
112                         set {
113                                 CheckDisposed ();
114                                 unsafe_ntlm_auth = value;
115                         }
116                 }
117
118                 public void Abort ()
119                 {
120                         if (disposed)
121                                 return;
122
123                         if (!listening) {
124                                 return;
125                         }
126
127                         Close (true);
128                 }
129
130                 public void Close ()
131                 {
132                         if (disposed)
133                                 return;
134
135                         if (!listening) {
136                                 disposed = true;
137                                 return;
138                         }
139
140                         Close (true);
141                         disposed = true;
142                 }
143
144                 void Close (bool force)
145                 {
146                         CheckDisposed ();
147                         EndPointManager.RemoveListener (this);
148                         Cleanup (force);
149                 }
150
151                 void Cleanup (bool close_existing)
152                 {
153                         lock (registry) {
154                                 if (close_existing) {
155                                         // Need to copy this since closing will call UnregisterContext
156                                         ICollection keys = registry.Keys;
157                                         var all = new HttpListenerContext [keys.Count];
158                                         keys.CopyTo (all, 0);
159                                         registry.Clear ();
160                                         for (int i = all.Length - 1; i >= 0; i--)
161                                                 all [i].Connection.Close (true);
162                                 }
163
164                                 lock (connections) {
165                                         ICollection keys = connections.Keys;
166                                         var conns = new HttpConnection [keys.Count];
167                                         keys.CopyTo (conns, 0);
168                                         connections.Clear ();
169                                         for (int i = conns.Length - 1; i >= 0; i--)
170                                                 conns [i].Close (true);
171                                 }
172                                 lock (ctx_queue) {
173                                         ICollection keys = connections.Keys;
174                                         var ctxs = new HttpListenerContext [keys.Count];
175                                         keys.CopyTo (ctxs, 0);
176                                         ctx_queue.Clear ();
177                                         for (int i = ctxs.Length - 1; i >= 0; i--)
178                                                 ctxs [i].Connection.Close (true);
179                                 }
180
181                                 lock (wait_queue) {
182                                         foreach (ListenerAsyncResult ares in wait_queue) {
183                                                 ares.Complete ("Listener was closed.");
184                                         }
185                                         wait_queue.Clear ();
186                                 }
187                         }
188                 }
189
190                 public IAsyncResult BeginGetContext (AsyncCallback callback, Object state)
191                 {
192                         CheckDisposed ();
193                         if (!listening)
194                                 throw new InvalidOperationException ("Please, call Start before using this method.");
195
196                         ListenerAsyncResult ares = new ListenerAsyncResult (callback, state);
197
198                         // lock wait_queue early to avoid race conditions
199                         lock (wait_queue) {
200                                 lock (ctx_queue) {
201                                         HttpListenerContext ctx = GetContextFromQueue ();
202                                         if (ctx != null) {
203                                                 ares.Complete (ctx, true);
204                                                 return ares;
205                                         }
206                                 }
207
208                                 wait_queue.Add (ares);
209                         }
210
211                         return ares;
212                 }
213
214                 public HttpListenerContext EndGetContext (IAsyncResult asyncResult)
215                 {
216                         CheckDisposed ();
217                         if (asyncResult == null)
218                                 throw new ArgumentNullException ("asyncResult");
219
220                         ListenerAsyncResult ares = asyncResult as ListenerAsyncResult;
221                         if (ares == null)
222                                 throw new ArgumentException ("Wrong IAsyncResult.", "asyncResult");
223
224                         if (!ares.IsCompleted)
225                                 ares.AsyncWaitHandle.WaitOne ();
226
227                         lock (wait_queue) {
228                                 int idx = wait_queue.IndexOf (ares);
229                                 if (idx >= 0)
230                                         wait_queue.RemoveAt (idx);
231                         }
232
233                         HttpListenerContext context = ares.GetContext ();
234                         context.ParseAuthentication (SelectAuthenticationScheme (context));
235                         return context; // This will throw on error.
236                 }
237
238                 internal AuthenticationSchemes SelectAuthenticationScheme (HttpListenerContext context)
239                 {
240                         if (AuthenticationSchemeSelectorDelegate != null)
241                                 return AuthenticationSchemeSelectorDelegate (context.Request);
242                         else
243                                 return auth_schemes;
244                 }
245
246                 public HttpListenerContext GetContext ()
247                 {
248                         // The prefixes are not checked when using the async interface!?
249                         if (prefixes.Count == 0)
250                                 throw new InvalidOperationException ("Please, call AddPrefix before using this method.");
251
252                         IAsyncResult ares = BeginGetContext (null, null);
253                         return EndGetContext (ares);
254                 }
255
256                 public void Start ()
257                 {
258                         CheckDisposed ();
259                         if (listening)
260                                 return;
261
262                         EndPointManager.AddListener (this);
263                         listening = true;
264                 }
265
266                 public void Stop ()
267                 {
268                         CheckDisposed ();
269                         listening = false;
270                         Close (false);
271                 }
272
273                 void IDisposable.Dispose ()
274                 {
275                         if (disposed)
276                                 return;
277
278                         Close (true); //TODO: Should we force here or not?
279                         disposed = true;
280                 }
281
282                 internal void CheckDisposed ()
283                 {
284                         if (disposed)
285                                 throw new ObjectDisposedException (GetType ().ToString ());
286                 }
287
288                 // Must be called with a lock on ctx_queue
289                 HttpListenerContext GetContextFromQueue ()
290                 {
291                         if (ctx_queue.Count == 0)
292                                 return null;
293
294                         HttpListenerContext context = (HttpListenerContext) ctx_queue [0];
295                         ctx_queue.RemoveAt (0);
296                         return context;
297                 }
298
299                 internal void RegisterContext (HttpListenerContext context)
300                 {
301                         lock (registry)
302                                 registry [context] = context;
303
304                         ListenerAsyncResult ares = null;
305                         lock (wait_queue) {
306                                 if (wait_queue.Count == 0) {
307                                         lock (ctx_queue)
308                                                 ctx_queue.Add (context);
309                                 } else {
310                                         ares = (ListenerAsyncResult) wait_queue [0];
311                                         wait_queue.RemoveAt (0);
312                                 }
313                         }
314                         if (ares != null)
315                                 ares.Complete (context);
316                 }
317
318                 internal void UnregisterContext (HttpListenerContext context)
319                 {
320                         lock (registry)
321                                 registry.Remove (context);
322                         lock (ctx_queue) {
323                                 int idx = ctx_queue.IndexOf (context);
324                                 if (idx >= 0)
325                                         ctx_queue.RemoveAt (idx);
326                         }
327                 }
328
329                 internal void AddConnection (HttpConnection cnc)
330                 {
331                         connections [cnc] = cnc;
332                 }
333
334                 internal void RemoveConnection (HttpConnection cnc)
335                 {
336                         connections.Remove (cnc);
337                 }
338         }
339 }
340 #endif
341