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