Moved ProviderCollectionTest.cs from System assembly to System.Configuration.
[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
49                 public HttpListener ()
50                 {
51                         prefixes = new HttpListenerPrefixCollection (this);
52                         registry = new Hashtable ();
53                         ctx_queue = new ArrayList ();
54                         wait_queue = new ArrayList ();
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                                 disposed = true;
136                                 return;
137                         }
138
139                         Close (false);
140                         disposed = true;
141                 }
142
143                 void Close (bool force)
144                 {
145                         CheckDisposed ();
146                         EndPointManager.RemoveListener (this);
147                         Cleanup (force);
148                 }
149
150                 void Cleanup (bool close_existing)
151                 {
152                         lock (registry) {
153                                 if (close_existing) {
154                                         foreach (HttpListenerContext context in registry.Keys) {
155                                                 context.Connection.Close ();
156                                         }
157                                         registry.Clear (); // Just in case.
158                                 }
159
160                                 lock (ctx_queue) {
161                                         foreach (HttpListenerContext context in ctx_queue)
162                                                 context.Connection.Close ();
163
164                                         ctx_queue.Clear ();
165                                 }
166
167                                 lock (wait_queue) {
168                                         foreach (ListenerAsyncResult ares in wait_queue) {
169                                                 ares.Complete ("Listener was closed.");
170                                         }
171                                         wait_queue.Clear ();
172                                 }
173                         }
174                 }
175
176                 public IAsyncResult BeginGetContext (AsyncCallback callback, Object state)
177                 {
178                         CheckDisposed ();
179                         if (!listening)
180                                 throw new InvalidOperationException ("Please, call Start before using this method.");
181
182                         ListenerAsyncResult ares = new ListenerAsyncResult (callback, state);
183
184                         // lock wait_queue early to avoid race conditions
185                         lock (wait_queue) {
186                                 lock (ctx_queue) {
187                                         HttpListenerContext ctx = GetContextFromQueue ();
188                                         if (ctx != null) {
189                                                 ares.Complete (ctx, true);
190                                                 return ares;
191                                         }
192                                 }
193
194                                 wait_queue.Add (ares);
195                         }
196
197                         return ares;
198                 }
199
200                 public HttpListenerContext EndGetContext (IAsyncResult asyncResult)
201                 {
202                         CheckDisposed ();
203                         if (asyncResult == null)
204                                 throw new ArgumentNullException ("asyncResult");
205
206                         ListenerAsyncResult ares = asyncResult as ListenerAsyncResult;
207                         if (ares == null)
208                                 throw new ArgumentException ("Wrong IAsyncResult.", "asyncResult");
209
210                         if (!ares.IsCompleted)
211                                 ares.AsyncWaitHandle.WaitOne ();
212
213                         lock (wait_queue) {
214                                 int idx = wait_queue.IndexOf (ares);
215                                 if (idx >= 0)
216                                         wait_queue.RemoveAt (idx);
217                         }
218
219                         HttpListenerContext context = ares.GetContext ();
220                         if (auth_schemes != AuthenticationSchemes.Anonymous) {
221                                 context.ParseAuthentication ();
222                         }
223                         return context; // This will throw on error.
224                 }
225
226                 public HttpListenerContext GetContext ()
227                 {
228                         // The prefixes are not checked when using the async interface!?
229                         if (prefixes.Count == 0)
230                                 throw new InvalidOperationException ("Please, call AddPrefix before using this method.");
231
232                         IAsyncResult ares = BeginGetContext (null, null);
233                         return EndGetContext (ares);
234                 }
235
236                 public void Start ()
237                 {
238                         CheckDisposed ();
239                         if (listening)
240                                 return;
241
242                         EndPointManager.AddListener (this);
243                         listening = true;
244                 }
245
246                 public void Stop ()
247                 {
248                         CheckDisposed ();
249                         listening = false;
250                         Close (false);
251                 }
252
253                 void IDisposable.Dispose ()
254                 {
255                         if (disposed)
256                                 return;
257
258                         Close (true); //TODO: Should we force here or not?
259                         disposed = true;
260                 }
261
262                 internal void CheckDisposed ()
263                 {
264                         if (disposed)
265                                 throw new ObjectDisposedException (GetType ().ToString ());
266                 }
267
268                 // Must be called with a lock on ctx_queue
269                 HttpListenerContext GetContextFromQueue ()
270                 {
271                         if (ctx_queue.Count == 0)
272                                 return null;
273
274                         HttpListenerContext context = (HttpListenerContext) ctx_queue [0];
275                         ctx_queue.RemoveAt (0);
276                         return context;
277                 }
278
279                 internal void RegisterContext (HttpListenerContext context)
280                 {
281                         try {
282                                 Monitor.Enter (registry);
283                                 registry [context] = context;
284                                 Monitor.Enter (wait_queue);
285                                 Monitor.Enter (ctx_queue);
286                                 if (wait_queue.Count == 0) {
287                                         ctx_queue.Add (context);
288                                 } else {
289                                         ListenerAsyncResult ares = (ListenerAsyncResult) wait_queue [0];
290                                         wait_queue.RemoveAt (0);
291                                         ares.Complete (context);
292                                 }
293                         } finally {
294                                 Monitor.Exit (ctx_queue);
295                                 Monitor.Exit (wait_queue);
296                                 Monitor.Exit (registry);
297                         }
298                 }
299
300                 internal void UnregisterContext (HttpListenerContext context)
301                 {
302                         try {
303                                 Monitor.Enter (registry);
304                                 Monitor.Enter (ctx_queue);
305                                 int idx = ctx_queue.IndexOf (context);
306                                 if (idx >= 0)
307                                         ctx_queue.RemoveAt (idx);
308                                 registry.Remove (context);
309                         } finally {
310                                 Monitor.Exit (ctx_queue);
311                                 Monitor.Exit (registry);
312                         }
313                 }
314         }
315 }
316 #endif
317