2 // System.Net.HttpListener
5 // Gonzalo Paniagua Javier (gonzalo@novell.com)
6 // Marek Safar (marek.safar@gmail.com)
8 // Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
9 // Copyright 2011 Xamarin Inc.
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
32 #if MONO_SECURITY_ALIAS
33 extern alias MonoSecurity;
34 using MonoSecurity::Mono.Security.Authenticode;
35 using MSI = MonoSecurity::Mono.Security.Interface;
37 using Mono.Security.Authenticode;
38 using MSI = Mono.Security.Interface;
42 using System.Collections;
43 using System.Threading;
44 using System.Threading.Tasks;
45 using System.Security.Authentication.ExtendedProtection;
46 using System.Security.Cryptography;
47 using System.Security.Cryptography.X509Certificates;
49 using Mono.Net.Security;
53 namespace System.Net {
54 public sealed class HttpListener : IDisposable {
55 AuthenticationSchemes auth_schemes;
56 HttpListenerPrefixCollection prefixes;
57 AuthenticationSchemeSelector auth_selector;
59 bool ignore_write_exceptions;
60 bool unsafe_ntlm_auth;
64 IMonoTlsProvider tlsProvider;
65 MSI.MonoTlsSettings tlsSettings;
66 X509Certificate certificate;
68 Hashtable registry; // Dictionary<HttpListenerContext,HttpListenerContext>
69 ArrayList ctx_queue; // List<HttpListenerContext> ctx_queue;
70 ArrayList wait_queue; // List<ListenerAsyncResult> wait_queue;
71 Hashtable connections;
73 ServiceNameStore defaultServiceNames;
74 ExtendedProtectionPolicy extendedProtectionPolicy;
75 ExtendedProtectionSelector extendedProtectionSelectorDelegate;
77 public delegate ExtendedProtectionPolicy ExtendedProtectionSelector (HttpListenerRequest request);
79 public HttpListener ()
81 prefixes = new HttpListenerPrefixCollection (this);
82 registry = new Hashtable ();
83 connections = Hashtable.Synchronized (new Hashtable ());
84 ctx_queue = new ArrayList ();
85 wait_queue = new ArrayList ();
86 auth_schemes = AuthenticationSchemes.Anonymous;
87 defaultServiceNames = new ServiceNameStore ();
88 extendedProtectionPolicy = new ExtendedProtectionPolicy (PolicyEnforcement.Never);
91 internal HttpListener (X509Certificate certificate, IMonoTlsProvider tlsProvider, MSI.MonoTlsSettings tlsSettings)
94 this.certificate = certificate;
95 this.tlsProvider = tlsProvider;
96 this.tlsSettings = tlsSettings;
99 internal X509Certificate LoadCertificateAndKey (IPAddress addr, int port)
102 if (certificate != null)
105 // Actually load the certificate
107 string dirname = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData);
108 string path = Path.Combine (dirname, ".mono");
109 path = Path.Combine (path, "httplistener");
110 string cert_file = Path.Combine (path, String.Format ("{0}.cer", port));
111 if (!File.Exists (cert_file))
113 string pvk_file = Path.Combine (path, String.Format ("{0}.pvk", port));
114 if (!File.Exists (pvk_file))
116 var cert = new X509Certificate2 (cert_file);
117 cert.PrivateKey = PrivateKey.CreateFromFile (pvk_file).RSA;
128 internal IMonoSslStream CreateSslStream (Stream innerStream, bool ownsStream, MSI.MonoRemoteCertificateValidationCallback callback)
131 if (tlsProvider == null)
132 tlsProvider = MonoTlsProviderFactory.GetProviderInternal ();
133 if (tlsSettings == null)
134 tlsSettings = MSI.MonoTlsSettings.CopyDefaultSettings ();
135 if (tlsSettings.RemoteCertificateValidationCallback == null)
136 tlsSettings.RemoteCertificateValidationCallback = callback;
137 return tlsProvider.CreateSslStream (innerStream, ownsStream, tlsSettings);
141 // TODO: Digest, NTLM and Negotiate require ControlPrincipal
142 public AuthenticationSchemes AuthenticationSchemes {
143 get { return auth_schemes; }
146 auth_schemes = value;
150 public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate {
151 get { return auth_selector; }
154 auth_selector = value;
158 public ExtendedProtectionSelector ExtendedProtectionSelectorDelegate
160 get { return extendedProtectionSelectorDelegate; }
164 throw new ArgumentNullException ();
166 if (!AuthenticationManager.OSSupportsExtendedProtection)
167 throw new PlatformNotSupportedException (SR.GetString (SR.security_ExtendedProtection_NoOSSupport));
169 extendedProtectionSelectorDelegate = value;
173 public bool IgnoreWriteExceptions {
174 get { return ignore_write_exceptions; }
177 ignore_write_exceptions = value;
181 public bool IsListening {
182 get { return listening; }
185 public static bool IsSupported {
189 public HttpListenerPrefixCollection Prefixes {
197 public HttpListenerTimeoutManager TimeoutManager {
199 throw new NotImplementedException ();
203 [MonoTODO ("not used anywhere in the implementation")]
204 public ExtendedProtectionPolicy ExtendedProtectionPolicy
207 return extendedProtectionPolicy;
213 throw new ArgumentNullException ("value");
215 if (!AuthenticationManager.OSSupportsExtendedProtection && value.PolicyEnforcement == PolicyEnforcement.Always)
216 throw new PlatformNotSupportedException (SR.GetString(SR.security_ExtendedProtection_NoOSSupport));
218 if (value.CustomChannelBinding != null)
219 throw new ArgumentException (SR.GetString (SR.net_listener_cannot_set_custom_cbt), "CustomChannelBinding");
221 extendedProtectionPolicy = value;
225 public ServiceNameCollection DefaultServiceNames
228 return defaultServiceNames.ServiceNames;
233 public string Realm {
234 get { return realm; }
241 [MonoTODO ("Support for NTLM needs some loving.")]
242 public bool UnsafeConnectionNtlmAuthentication {
243 get { return unsafe_ntlm_auth; }
246 unsafe_ntlm_auth = value;
276 void Close (bool force)
279 EndPointManager.RemoveListener (this);
283 void Cleanup (bool close_existing)
286 if (close_existing) {
287 // Need to copy this since closing will call UnregisterContext
288 ICollection keys = registry.Keys;
289 var all = new HttpListenerContext [keys.Count];
290 keys.CopyTo (all, 0);
292 for (int i = all.Length - 1; i >= 0; i--)
293 all [i].Connection.Close (true);
296 lock (connections.SyncRoot) {
297 ICollection keys = connections.Keys;
298 var conns = new HttpConnection [keys.Count];
299 keys.CopyTo (conns, 0);
300 connections.Clear ();
301 for (int i = conns.Length - 1; i >= 0; i--)
302 conns [i].Close (true);
305 var ctxs = (HttpListenerContext []) ctx_queue.ToArray (typeof (HttpListenerContext));
307 for (int i = ctxs.Length - 1; i >= 0; i--)
308 ctxs [i].Connection.Close (true);
312 Exception exc = new ObjectDisposedException ("listener");
313 foreach (ListenerAsyncResult ares in wait_queue) {
321 public IAsyncResult BeginGetContext (AsyncCallback callback, Object state)
325 throw new InvalidOperationException ("Please, call Start before using this method.");
327 ListenerAsyncResult ares = new ListenerAsyncResult (callback, state);
329 // lock wait_queue early to avoid race conditions
332 HttpListenerContext ctx = GetContextFromQueue ();
334 ares.Complete (ctx, true);
339 wait_queue.Add (ares);
345 public HttpListenerContext EndGetContext (IAsyncResult asyncResult)
348 if (asyncResult == null)
349 throw new ArgumentNullException ("asyncResult");
351 ListenerAsyncResult ares = asyncResult as ListenerAsyncResult;
353 throw new ArgumentException ("Wrong IAsyncResult.", "asyncResult");
355 throw new ArgumentException ("Cannot reuse this IAsyncResult");
356 ares.EndCalled = true;
358 if (!ares.IsCompleted)
359 ares.AsyncWaitHandle.WaitOne ();
362 int idx = wait_queue.IndexOf (ares);
364 wait_queue.RemoveAt (idx);
367 HttpListenerContext context = ares.GetContext ();
368 context.ParseAuthentication (SelectAuthenticationScheme (context));
369 return context; // This will throw on error.
372 internal AuthenticationSchemes SelectAuthenticationScheme (HttpListenerContext context)
374 if (AuthenticationSchemeSelectorDelegate != null)
375 return AuthenticationSchemeSelectorDelegate (context.Request);
380 public HttpListenerContext GetContext ()
382 // The prefixes are not checked when using the async interface!?
383 if (prefixes.Count == 0)
384 throw new InvalidOperationException ("Please, call AddPrefix before using this method.");
386 ListenerAsyncResult ares = (ListenerAsyncResult) BeginGetContext (null, null);
388 return EndGetContext (ares);
397 EndPointManager.AddListener (this);
408 void IDisposable.Dispose ()
413 Close (true); //TODO: Should we force here or not?
417 public Task<HttpListenerContext> GetContextAsync ()
419 return Task<HttpListenerContext>.Factory.FromAsync (BeginGetContext, EndGetContext, null);
422 internal void CheckDisposed ()
425 throw new ObjectDisposedException (GetType ().ToString ());
428 // Must be called with a lock on ctx_queue
429 HttpListenerContext GetContextFromQueue ()
431 if (ctx_queue.Count == 0)
434 HttpListenerContext context = (HttpListenerContext) ctx_queue [0];
435 ctx_queue.RemoveAt (0);
439 internal void RegisterContext (HttpListenerContext context)
442 registry [context] = context;
444 ListenerAsyncResult ares = null;
446 if (wait_queue.Count == 0) {
448 ctx_queue.Add (context);
450 ares = (ListenerAsyncResult) wait_queue [0];
451 wait_queue.RemoveAt (0);
455 ares.Complete (context);
458 internal void UnregisterContext (HttpListenerContext context)
461 registry.Remove (context);
463 int idx = ctx_queue.IndexOf (context);
465 ctx_queue.RemoveAt (idx);
469 internal void AddConnection (HttpConnection cnc)
471 connections [cnc] = cnc;
474 internal void RemoveConnection (HttpConnection cnc)
476 connections.Remove (cnc);
480 #else // SECURITY_DEP
483 public sealed class HttpListener