//
// System.Net.HttpListener
//
-// Author:
+// Authors:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
+// Marek Safar (marek.safar@gmail.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+// Copyright 2011 Xamarin Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
-#if NET_2_0
-using System.Collections.Generic;
+
+#if SECURITY_DEP
+#if MONO_SECURITY_ALIAS
+extern alias MonoSecurity;
+using MonoSecurity::Mono.Security.Authenticode;
+using MSI = MonoSecurity::Mono.Security.Interface;
+#else
+using Mono.Security.Authenticode;
+using MSI = Mono.Security.Interface;
+#endif
+
+using System.IO;
+using System.Collections;
using System.Threading;
+using System.Threading.Tasks;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+
+using Mono.Net.Security;
+
+
//TODO: logging
namespace System.Net {
public sealed class HttpListener : IDisposable {
bool listening;
bool disposed;
- Dictionary<HttpListenerContext,HttpListenerContext> registry;
- List<HttpListenerContext> ctx_queue;
- List<ListenerAsyncResult> wait_queue;
+ IMonoTlsProvider tlsProvider;
+ MSI.MonoTlsSettings tlsSettings;
+ X509Certificate certificate;
+
+ Hashtable registry; // Dictionary<HttpListenerContext,HttpListenerContext>
+ ArrayList ctx_queue; // List<HttpListenerContext> ctx_queue;
+ ArrayList wait_queue; // List<ListenerAsyncResult> wait_queue;
+ Hashtable connections;
public HttpListener ()
{
prefixes = new HttpListenerPrefixCollection (this);
- registry = new Dictionary<HttpListenerContext,HttpListenerContext> ();
- ctx_queue = new List<HttpListenerContext> ();
- wait_queue = new List<ListenerAsyncResult> ();
+ registry = new Hashtable ();
+ connections = Hashtable.Synchronized (new Hashtable ());
+ ctx_queue = new ArrayList ();
+ wait_queue = new ArrayList ();
auth_schemes = AuthenticationSchemes.Anonymous;
}
+ internal HttpListener (X509Certificate certificate, IMonoTlsProvider tlsProvider, MSI.MonoTlsSettings tlsSettings)
+ : this ()
+ {
+ this.certificate = certificate;
+ this.tlsProvider = tlsProvider;
+ this.tlsSettings = tlsSettings;
+ }
+
+ internal X509Certificate LoadCertificateAndKey (IPAddress addr, int port)
+ {
+ lock (registry) {
+ if (certificate != null)
+ return certificate;
+
+ // Actually load the certificate
+ try {
+ string dirname = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData);
+ string path = Path.Combine (dirname, ".mono");
+ path = Path.Combine (path, "httplistener");
+ string cert_file = Path.Combine (path, String.Format ("{0}.cer", port));
+ if (!File.Exists (cert_file))
+ return null;
+ string pvk_file = Path.Combine (path, String.Format ("{0}.pvk", port));
+ if (!File.Exists (pvk_file))
+ return null;
+ var cert = new X509Certificate2 (cert_file);
+ cert.PrivateKey = PrivateKey.CreateFromFile (pvk_file).RSA;
+ certificate = cert;
+ return certificate;
+ } catch {
+ // ignore errors
+ certificate = null;
+ return null;
+ }
+ }
+ }
+
+ internal IMonoSslStream CreateSslStream (Stream innerStream, bool ownsStream, MSI.MonoRemoteCertificateValidationCallback callback)
+ {
+ lock (registry) {
+ if (tlsProvider == null)
+ tlsProvider = MonoTlsProviderFactory.GetProviderInternal ();
+ if (tlsSettings == null)
+ tlsSettings = MSI.MonoTlsSettings.CopyDefaultSettings ();
+ if (tlsSettings.RemoteCertificateValidationCallback == null)
+ tlsSettings.RemoteCertificateValidationCallback = callback;
+ return tlsProvider.CreateSslStream (innerStream, ownsStream, tlsSettings);
+ }
+ }
+
// TODO: Digest, NTLM and Negotiate require ControlPrincipal
public AuthenticationSchemes AuthenticationSchemes {
get { return auth_schemes; }
}
}
- //TODO: when is this called?
public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate {
get { return auth_selector; }
set {
return;
if (!listening) {
- disposed = true;
return;
}
return;
}
- Close (false);
+ Close (true);
+ disposed = true;
}
void Close (bool force)
CheckDisposed ();
EndPointManager.RemoveListener (this);
Cleanup (force);
- disposed = true;
}
void Cleanup (bool close_existing)
{
lock (registry) {
if (close_existing) {
- foreach (HttpListenerContext context in registry.Keys) {
- context.Connection.Close ();
- }
- registry.Clear (); // Just in case.
+ // Need to copy this since closing will call UnregisterContext
+ ICollection keys = registry.Keys;
+ var all = new HttpListenerContext [keys.Count];
+ keys.CopyTo (all, 0);
+ registry.Clear ();
+ for (int i = all.Length - 1; i >= 0; i--)
+ all [i].Connection.Close (true);
}
+ lock (connections.SyncRoot) {
+ ICollection keys = connections.Keys;
+ var conns = new HttpConnection [keys.Count];
+ keys.CopyTo (conns, 0);
+ connections.Clear ();
+ for (int i = conns.Length - 1; i >= 0; i--)
+ conns [i].Close (true);
+ }
lock (ctx_queue) {
- foreach (HttpListenerContext context in ctx_queue)
- context.Connection.Close ();
-
+ var ctxs = (HttpListenerContext []) ctx_queue.ToArray (typeof (HttpListenerContext));
ctx_queue.Clear ();
+ for (int i = ctxs.Length - 1; i >= 0; i--)
+ ctxs [i].Connection.Close (true);
}
lock (wait_queue) {
+ Exception exc = new ObjectDisposedException ("listener");
foreach (ListenerAsyncResult ares in wait_queue) {
- ares.Complete ("Listener was closed.");
+ ares.Complete (exc);
}
wait_queue.Clear ();
}
ListenerAsyncResult ares = asyncResult as ListenerAsyncResult;
if (ares == null)
throw new ArgumentException ("Wrong IAsyncResult.", "asyncResult");
+ if (ares.EndCalled)
+ throw new ArgumentException ("Cannot reuse this IAsyncResult");
+ ares.EndCalled = true;
if (!ares.IsCompleted)
ares.AsyncWaitHandle.WaitOne ();
wait_queue.RemoveAt (idx);
}
- return ares.GetContext (); // This will throw on error.
+ HttpListenerContext context = ares.GetContext ();
+ context.ParseAuthentication (SelectAuthenticationScheme (context));
+ return context; // This will throw on error.
+ }
+
+ internal AuthenticationSchemes SelectAuthenticationScheme (HttpListenerContext context)
+ {
+ if (AuthenticationSchemeSelectorDelegate != null)
+ return AuthenticationSchemeSelectorDelegate (context.Request);
+ else
+ return auth_schemes;
}
public HttpListenerContext GetContext ()
if (prefixes.Count == 0)
throw new InvalidOperationException ("Please, call AddPrefix before using this method.");
- IAsyncResult ares = BeginGetContext (null, null);
+ ListenerAsyncResult ares = (ListenerAsyncResult) BeginGetContext (null, null);
+ ares.InGet = true;
return EndGetContext (ares);
}
if (disposed)
return;
- disposed = true;
Close (true); //TODO: Should we force here or not?
+ disposed = true;
+ }
+
+ public Task<HttpListenerContext> GetContextAsync ()
+ {
+ return Task<HttpListenerContext>.Factory.FromAsync (BeginGetContext, EndGetContext, null);
}
internal void CheckDisposed ()
if (ctx_queue.Count == 0)
return null;
- HttpListenerContext context = ctx_queue [0];
+ HttpListenerContext context = (HttpListenerContext) ctx_queue [0];
ctx_queue.RemoveAt (0);
return context;
}
internal void RegisterContext (HttpListenerContext context)
{
- try {
- Monitor.Enter (registry);
+ lock (registry)
registry [context] = context;
- Monitor.Enter (wait_queue);
- Monitor.Enter (ctx_queue);
+
+ ListenerAsyncResult ares = null;
+ lock (wait_queue) {
if (wait_queue.Count == 0) {
- ctx_queue.Add (context);
+ lock (ctx_queue)
+ ctx_queue.Add (context);
} else {
- ListenerAsyncResult ares = wait_queue [0];
+ ares = (ListenerAsyncResult) wait_queue [0];
wait_queue.RemoveAt (0);
- ares.Complete (context);
}
- } finally {
- Monitor.Exit (ctx_queue);
- Monitor.Exit (wait_queue);
- Monitor.Exit (registry);
}
+ if (ares != null)
+ ares.Complete (context);
}
internal void UnregisterContext (HttpListenerContext context)
{
- try {
- Monitor.Enter (registry);
- Monitor.Enter (ctx_queue);
+ lock (registry)
+ registry.Remove (context);
+ lock (ctx_queue) {
int idx = ctx_queue.IndexOf (context);
if (idx >= 0)
ctx_queue.RemoveAt (idx);
- registry.Remove (context);
- } finally {
- Monitor.Exit (ctx_queue);
- Monitor.Exit (registry);
}
}
+
+ internal void AddConnection (HttpConnection cnc)
+ {
+ connections [cnc] = cnc;
+ }
+
+ internal void RemoveConnection (HttpConnection cnc)
+ {
+ connections.Remove (cnc);
+ }
+ }
+}
+#else // SECURITY_DEP
+namespace System.Net
+{
+ public sealed class HttpListener
+ {
}
}
#endif
-