Merge pull request #2223 from lobrien/master
[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 #if MONO_SECURITY_ALIAS
33 extern alias MonoSecurity;
34 using MonoSecurity::Mono.Security.Authenticode;
35 using MSI = MonoSecurity::Mono.Security.Interface;
36 #else
37 using Mono.Security.Authenticode;
38 using MSI = Mono.Security.Interface;
39 #endif
40
41 using System.IO;
42 using System.Collections;
43 using System.Threading;
44 using System.Threading.Tasks;
45 using System.Security.Cryptography;
46 using System.Security.Cryptography.X509Certificates;
47
48 using Mono.Net.Security;
49
50
51 //TODO: logging
52 namespace System.Net {
53         public sealed class HttpListener : IDisposable {
54                 AuthenticationSchemes auth_schemes;
55                 HttpListenerPrefixCollection prefixes;
56                 AuthenticationSchemeSelector auth_selector; 
57                 string realm;
58                 bool ignore_write_exceptions;
59                 bool unsafe_ntlm_auth;
60                 bool listening;
61                 bool disposed;
62
63                 IMonoTlsProvider tlsProvider;
64                 MSI.MonoTlsSettings tlsSettings;
65                 X509Certificate certificate;
66
67                 Hashtable registry;   // Dictionary<HttpListenerContext,HttpListenerContext> 
68                 ArrayList ctx_queue;  // List<HttpListenerContext> ctx_queue;
69                 ArrayList wait_queue; // List<ListenerAsyncResult> wait_queue;
70                 Hashtable connections;
71
72                 public HttpListener ()
73                 {
74                         prefixes = new HttpListenerPrefixCollection (this);
75                         registry = new Hashtable ();
76                         connections = Hashtable.Synchronized (new Hashtable ());
77                         ctx_queue = new ArrayList ();
78                         wait_queue = new ArrayList ();
79                         auth_schemes = AuthenticationSchemes.Anonymous;
80                 }
81
82                 internal HttpListener (X509Certificate certificate, IMonoTlsProvider tlsProvider, MSI.MonoTlsSettings tlsSettings)
83                         : this ()
84                 {
85                         this.certificate = certificate;
86                         this.tlsProvider = tlsProvider;
87                         this.tlsSettings = tlsSettings;
88                 }
89
90                 internal X509Certificate LoadCertificateAndKey (IPAddress addr, int port)
91                 {
92                         lock (registry) {
93                                 if (certificate != null)
94                                         return certificate;
95
96                                 // Actually load the certificate
97                                 try {
98                                         string dirname = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData);
99                                         string path = Path.Combine (dirname, ".mono");
100                                         path = Path.Combine (path, "httplistener");
101                                         string cert_file = Path.Combine (path, String.Format ("{0}.cer", port));
102                                         if (!File.Exists (cert_file))
103                                                 return null;
104                                         string pvk_file = Path.Combine (path, String.Format ("{0}.pvk", port));
105                                         if (!File.Exists (pvk_file))
106                                                 return null;
107                                         var cert = new X509Certificate2 (cert_file);
108                                         cert.PrivateKey = PrivateKey.CreateFromFile (pvk_file).RSA;
109                                         certificate = cert;
110                                         return certificate;
111                                 } catch {
112                                         // ignore errors
113                                         certificate = null;
114                                         return null;
115                                 }
116                         }
117                 }
118
119                 internal IMonoSslStream CreateSslStream (Stream innerStream, bool ownsStream, MSI.MonoRemoteCertificateValidationCallback callback)
120                 {
121                         lock (registry) {
122                                 if (tlsProvider == null)
123                                         tlsProvider = MonoTlsProviderFactory.GetProviderInternal ();
124                                 if (tlsSettings == null)
125                                         tlsSettings = MSI.MonoTlsSettings.CopyDefaultSettings ();
126                                 if (tlsSettings.RemoteCertificateValidationCallback == null)
127                                         tlsSettings.RemoteCertificateValidationCallback = callback;
128                                 return tlsProvider.CreateSslStream (innerStream, ownsStream, tlsSettings);
129                         }
130                 }
131
132                 // TODO: Digest, NTLM and Negotiate require ControlPrincipal
133                 public AuthenticationSchemes AuthenticationSchemes {
134                         get { return auth_schemes; }
135                         set {
136                                 CheckDisposed ();
137                                 auth_schemes = value;
138                         }
139                 }
140
141                 public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate {
142                         get { return auth_selector; }
143                         set {
144                                 CheckDisposed ();
145                                 auth_selector = value;
146                         }
147                 }
148
149                 public bool IgnoreWriteExceptions {
150                         get { return ignore_write_exceptions; }
151                         set {
152                                 CheckDisposed ();
153                                 ignore_write_exceptions = value;
154                         }
155                 }
156
157                 public bool IsListening {
158                         get { return listening; }
159                 }
160
161                 public static bool IsSupported {
162                         get { return true; }
163                 }
164
165                 public HttpListenerPrefixCollection Prefixes {
166                         get {
167                                 CheckDisposed ();
168                                 return prefixes;
169                         }
170                 }
171
172                 // TODO: use this
173                 public string Realm {
174                         get { return realm; }
175                         set {
176                                 CheckDisposed ();
177                                 realm = value;
178                         }
179                 }
180
181                 [MonoTODO ("Support for NTLM needs some loving.")]
182                 public bool UnsafeConnectionNtlmAuthentication {
183                         get { return unsafe_ntlm_auth; }
184                         set {
185                                 CheckDisposed ();
186                                 unsafe_ntlm_auth = value;
187                         }
188                 }
189
190                 public void Abort ()
191                 {
192                         if (disposed)
193                                 return;
194
195                         if (!listening) {
196                                 return;
197                         }
198
199                         Close (true);
200                 }
201
202                 public void Close ()
203                 {
204                         if (disposed)
205                                 return;
206
207                         if (!listening) {
208                                 disposed = true;
209                                 return;
210                         }
211
212                         Close (true);
213                         disposed = true;
214                 }
215
216                 void Close (bool force)
217                 {
218                         CheckDisposed ();
219                         EndPointManager.RemoveListener (this);
220                         Cleanup (force);
221                 }
222
223                 void Cleanup (bool close_existing)
224                 {
225                         lock (registry) {
226                                 if (close_existing) {
227                                         // Need to copy this since closing will call UnregisterContext
228                                         ICollection keys = registry.Keys;
229                                         var all = new HttpListenerContext [keys.Count];
230                                         keys.CopyTo (all, 0);
231                                         registry.Clear ();
232                                         for (int i = all.Length - 1; i >= 0; i--)
233                                                 all [i].Connection.Close (true);
234                                 }
235
236                                 lock (connections.SyncRoot) {
237                                         ICollection keys = connections.Keys;
238                                         var conns = new HttpConnection [keys.Count];
239                                         keys.CopyTo (conns, 0);
240                                         connections.Clear ();
241                                         for (int i = conns.Length - 1; i >= 0; i--)
242                                                 conns [i].Close (true);
243                                 }
244                                 lock (ctx_queue) {
245                                         var ctxs = (HttpListenerContext []) ctx_queue.ToArray (typeof (HttpListenerContext));
246                                         ctx_queue.Clear ();
247                                         for (int i = ctxs.Length - 1; i >= 0; i--)
248                                                 ctxs [i].Connection.Close (true);
249                                 }
250
251                                 lock (wait_queue) {
252                                         Exception exc = new ObjectDisposedException ("listener");
253                                         foreach (ListenerAsyncResult ares in wait_queue) {
254                                                 ares.Complete (exc);
255                                         }
256                                         wait_queue.Clear ();
257                                 }
258                         }
259                 }
260
261                 public IAsyncResult BeginGetContext (AsyncCallback callback, Object state)
262                 {
263                         CheckDisposed ();
264                         if (!listening)
265                                 throw new InvalidOperationException ("Please, call Start before using this method.");
266
267                         ListenerAsyncResult ares = new ListenerAsyncResult (callback, state);
268
269                         // lock wait_queue early to avoid race conditions
270                         lock (wait_queue) {
271                                 lock (ctx_queue) {
272                                         HttpListenerContext ctx = GetContextFromQueue ();
273                                         if (ctx != null) {
274                                                 ares.Complete (ctx, true);
275                                                 return ares;
276                                         }
277                                 }
278
279                                 wait_queue.Add (ares);
280                         }
281
282                         return ares;
283                 }
284
285                 public HttpListenerContext EndGetContext (IAsyncResult asyncResult)
286                 {
287                         CheckDisposed ();
288                         if (asyncResult == null)
289                                 throw new ArgumentNullException ("asyncResult");
290
291                         ListenerAsyncResult ares = asyncResult as ListenerAsyncResult;
292                         if (ares == null)
293                                 throw new ArgumentException ("Wrong IAsyncResult.", "asyncResult");
294                         if (ares.EndCalled)
295                                 throw new ArgumentException ("Cannot reuse this IAsyncResult");
296                         ares.EndCalled = true;
297
298                         if (!ares.IsCompleted)
299                                 ares.AsyncWaitHandle.WaitOne ();
300
301                         lock (wait_queue) {
302                                 int idx = wait_queue.IndexOf (ares);
303                                 if (idx >= 0)
304                                         wait_queue.RemoveAt (idx);
305                         }
306
307                         HttpListenerContext context = ares.GetContext ();
308                         context.ParseAuthentication (SelectAuthenticationScheme (context));
309                         return context; // This will throw on error.
310                 }
311
312                 internal AuthenticationSchemes SelectAuthenticationScheme (HttpListenerContext context)
313                 {
314                         if (AuthenticationSchemeSelectorDelegate != null)
315                                 return AuthenticationSchemeSelectorDelegate (context.Request);
316                         else
317                                 return auth_schemes;
318                 }
319
320                 public HttpListenerContext GetContext ()
321                 {
322                         // The prefixes are not checked when using the async interface!?
323                         if (prefixes.Count == 0)
324                                 throw new InvalidOperationException ("Please, call AddPrefix before using this method.");
325
326                         ListenerAsyncResult ares = (ListenerAsyncResult) BeginGetContext (null, null);
327                         ares.InGet = true;
328                         return EndGetContext (ares);
329                 }
330
331                 public void Start ()
332                 {
333                         CheckDisposed ();
334                         if (listening)
335                                 return;
336
337                         EndPointManager.AddListener (this);
338                         listening = true;
339                 }
340
341                 public void Stop ()
342                 {
343                         CheckDisposed ();
344                         listening = false;
345                         Close (false);
346                 }
347
348                 void IDisposable.Dispose ()
349                 {
350                         if (disposed)
351                                 return;
352
353                         Close (true); //TODO: Should we force here or not?
354                         disposed = true;
355                 }
356
357                 public Task<HttpListenerContext> GetContextAsync ()
358                 {
359                         return Task<HttpListenerContext>.Factory.FromAsync (BeginGetContext, EndGetContext, null);
360                 }
361
362                 internal void CheckDisposed ()
363                 {
364                         if (disposed)
365                                 throw new ObjectDisposedException (GetType ().ToString ());
366                 }
367
368                 // Must be called with a lock on ctx_queue
369                 HttpListenerContext GetContextFromQueue ()
370                 {
371                         if (ctx_queue.Count == 0)
372                                 return null;
373
374                         HttpListenerContext context = (HttpListenerContext) ctx_queue [0];
375                         ctx_queue.RemoveAt (0);
376                         return context;
377                 }
378
379                 internal void RegisterContext (HttpListenerContext context)
380                 {
381                         lock (registry)
382                                 registry [context] = context;
383
384                         ListenerAsyncResult ares = null;
385                         lock (wait_queue) {
386                                 if (wait_queue.Count == 0) {
387                                         lock (ctx_queue)
388                                                 ctx_queue.Add (context);
389                                 } else {
390                                         ares = (ListenerAsyncResult) wait_queue [0];
391                                         wait_queue.RemoveAt (0);
392                                 }
393                         }
394                         if (ares != null)
395                                 ares.Complete (context);
396                 }
397
398                 internal void UnregisterContext (HttpListenerContext context)
399                 {
400                         lock (registry)
401                                 registry.Remove (context);
402                         lock (ctx_queue) {
403                                 int idx = ctx_queue.IndexOf (context);
404                                 if (idx >= 0)
405                                         ctx_queue.RemoveAt (idx);
406                         }
407                 }
408
409                 internal void AddConnection (HttpConnection cnc)
410                 {
411                         connections [cnc] = cnc;
412                 }
413
414                 internal void RemoveConnection (HttpConnection cnc)
415                 {
416                         connections.Remove (cnc);
417                 }
418         }
419 }
420 #else // SECURITY_DEP
421 namespace System.Net
422 {
423         public sealed class HttpListener
424         {
425         }
426 }
427 #endif