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