2007-10-27 Miguel de Icaza <miguel@novell.com>
[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.Generic;
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                 Dictionary<HttpListenerContext,HttpListenerContext> registry;
46                 List<HttpListenerContext> ctx_queue;
47                 List<ListenerAsyncResult> wait_queue;
48
49                 public HttpListener ()
50                 {
51                         prefixes = new HttpListenerPrefixCollection (this);
52                         registry = new Dictionary<HttpListenerContext,HttpListenerContext> ();
53                         ctx_queue = new List<HttpListenerContext> ();
54                         wait_queue = new List<ListenerAsyncResult> ();
55                         auth_schemes = AuthenticationSchemes.Anonymous;
56                 }
57
58                 // TODO: Digest, NTLM and Negotiate require ControlPrincipal
59                 public AuthenticationSchemes AuthenticationSchemes {
60                         get { return auth_schemes; }
61                         set {
62                                 CheckDisposed ();
63                                 auth_schemes = value;
64                         }
65                 }
66
67                 //TODO: when is this called?
68                 public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate {
69                         get { return auth_selector; }
70                         set {
71                                 CheckDisposed ();
72                                 auth_selector = value;
73                         }
74                 }
75
76                 public bool IgnoreWriteExceptions {
77                         get { return ignore_write_exceptions; }
78                         set {
79                                 CheckDisposed ();
80                                 ignore_write_exceptions = value;
81                         }
82                 }
83
84                 public bool IsListening {
85                         get { return listening; }
86                 }
87
88                 public static bool IsSupported {
89                         get { return true; }
90                 }
91
92                 public HttpListenerPrefixCollection Prefixes {
93                         get {
94                                 CheckDisposed ();
95                                 return prefixes;
96                         }
97                 }
98
99                 // TODO: use this
100                 public string Realm {
101                         get { return realm; }
102                         set {
103                                 CheckDisposed ();
104                                 realm = value;
105                         }
106                 }
107
108                 [MonoTODO ("Support for NTLM needs some loving.")]
109                 public bool UnsafeConnectionNtlmAuthentication {
110                         get { return unsafe_ntlm_auth; }
111                         set {
112                                 CheckDisposed ();
113                                 unsafe_ntlm_auth = value;
114                         }
115                 }
116
117                 public void Abort ()
118                 {
119                         if (disposed)
120                                 return;
121
122                         if (!listening) {
123                                 return;
124                         }
125
126                         Close (true);
127                 }
128
129                 public void Close ()
130                 {
131                         if (disposed)
132                                 return;
133
134                         if (!listening) {
135                                 return;
136                         }
137
138                         Close (false);
139                 }
140
141                 void Close (bool force)
142                 {
143                         CheckDisposed ();
144                         EndPointManager.RemoveListener (this);
145                         Cleanup (force);
146                 }
147
148                 void Cleanup (bool close_existing)
149                 {
150                         lock (registry) {
151                                 if (close_existing) {
152                                         foreach (HttpListenerContext context in registry.Keys) {
153                                                 context.Connection.Close ();
154                                         }
155                                         registry.Clear (); // Just in case.
156                                 }
157
158                                 lock (ctx_queue) {
159                                         foreach (HttpListenerContext context in ctx_queue)
160                                                 context.Connection.Close ();
161
162                                         ctx_queue.Clear ();
163                                 }
164
165                                 lock (wait_queue) {
166                                         foreach (ListenerAsyncResult ares in wait_queue) {
167                                                 ares.Complete ("Listener was closed.");
168                                         }
169                                         wait_queue.Clear ();
170                                 }
171                         }
172                 }
173
174                 public IAsyncResult BeginGetContext (AsyncCallback callback, Object state)
175                 {
176                         CheckDisposed ();
177                         if (!listening)
178                                 throw new InvalidOperationException ("Please, call Start before using this method.");
179
180                         ListenerAsyncResult ares = new ListenerAsyncResult (callback, state);
181
182                         // lock wait_queue early to avoid race conditions
183                         lock (wait_queue) {
184                                 lock (ctx_queue) {
185                                         HttpListenerContext ctx = GetContextFromQueue ();
186                                         if (ctx != null) {
187                                                 ares.Complete (ctx, true);
188                                                 return ares;
189                                         }
190                                 }
191
192                                 wait_queue.Add (ares);
193                         }
194
195                         return ares;
196                 }
197
198                 public HttpListenerContext EndGetContext (IAsyncResult asyncResult)
199                 {
200                         CheckDisposed ();
201                         if (asyncResult == null)
202                                 throw new ArgumentNullException ("asyncResult");
203
204                         ListenerAsyncResult ares = asyncResult as ListenerAsyncResult;
205                         if (ares == null)
206                                 throw new ArgumentException ("Wrong IAsyncResult.", "asyncResult");
207
208                         if (!ares.IsCompleted)
209                                 ares.AsyncWaitHandle.WaitOne ();
210
211                         lock (wait_queue) {
212                                 int idx = wait_queue.IndexOf (ares);
213                                 if (idx >= 0)
214                                         wait_queue.RemoveAt (idx);
215                         }
216
217                         HttpListenerContext context = ares.GetContext ();
218                         if (auth_schemes != AuthenticationSchemes.Anonymous) {
219                                 context.ParseAuthentication ();
220                         }
221                         return context; // This will throw on error.
222                 }
223
224                 public HttpListenerContext GetContext ()
225                 {
226                         // The prefixes are not checked when using the async interface!?
227                         if (prefixes.Count == 0)
228                                 throw new InvalidOperationException ("Please, call AddPrefix before using this method.");
229
230                         IAsyncResult ares = BeginGetContext (null, null);
231                         return EndGetContext (ares);
232                 }
233
234                 public void Start ()
235                 {
236                         CheckDisposed ();
237                         if (listening)
238                                 return;
239
240                         EndPointManager.AddListener (this);
241                         listening = true;
242                 }
243
244                 public void Stop ()
245                 {
246                         CheckDisposed ();
247                         listening = false;
248                         Close (false);
249                 }
250
251                 void IDisposable.Dispose ()
252                 {
253                         if (disposed)
254                                 return;
255
256                         Close (true); //TODO: Should we force here or not?
257                         disposed = true;
258                 }
259
260                 internal void CheckDisposed ()
261                 {
262                         if (disposed)
263                                 throw new ObjectDisposedException (GetType ().ToString ());
264                 }
265
266                 // Must be called with a lock on ctx_queue
267                 HttpListenerContext GetContextFromQueue ()
268                 {
269                         if (ctx_queue.Count == 0)
270                                 return null;
271
272                         HttpListenerContext context = ctx_queue [0];
273                         ctx_queue.RemoveAt (0);
274                         return context;
275                 }
276
277                 internal void RegisterContext (HttpListenerContext context)
278                 {
279                         try {
280                                 Monitor.Enter (registry);
281                                 registry [context] = context;
282                                 Monitor.Enter (wait_queue);
283                                 Monitor.Enter (ctx_queue);
284                                 if (wait_queue.Count == 0) {
285                                         ctx_queue.Add (context);
286                                 } else {
287                                         ListenerAsyncResult ares = wait_queue [0];
288                                         wait_queue.RemoveAt (0);
289                                         ares.Complete (context);
290                                 }
291                         } finally {
292                                 Monitor.Exit (ctx_queue);
293                                 Monitor.Exit (wait_queue);
294                                 Monitor.Exit (registry);
295                         }
296                 }
297
298                 internal void UnregisterContext (HttpListenerContext context)
299                 {
300                         try {
301                                 Monitor.Enter (registry);
302                                 Monitor.Enter (ctx_queue);
303                                 int idx = ctx_queue.IndexOf (context);
304                                 if (idx >= 0)
305                                         ctx_queue.RemoveAt (idx);
306                                 registry.Remove (context);
307                         } finally {
308                                 Monitor.Exit (ctx_queue);
309                                 Monitor.Exit (registry);
310                         }
311                 }
312         }
313 }
314 #endif
315