[System]: WebRequest.GetSystemProxy(): Return custom proxy for monodroid.
[mono.git] / mcs / class / System / System.Net / EndPointListener.cs
index fb5c55daab87111ca8311e0ffb0229a87c054a3b..909101100999098d3e2f99bff94e6306de28fcf3 100644 (file)
@@ -2,9 +2,10 @@
 // System.Net.EndPointListener
 //
 // Author:
-//     Gonzalo Paniagua Javier (gonzalo@novell.com)
+//     Gonzalo Paniagua Javier (gonzalo.mono@gmail.com)
 //
 // Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
+// Copyright (c) 2012 Xamarin, Inc. (http://xamarin.com)
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 
-#if NET_2_0 && SECURITY_DEP
+#if SECURITY_DEP
+
+#if MONOTOUCH
+using Mono.Security.Authenticode;
+#else
+extern alias MonoSecurity;
+using MonoSecurity::Mono.Security.Authenticode;
+#endif
 
 using System.IO;
 using System.Net.Sockets;
+using System.Collections;
 using System.Collections.Generic;
 using System.Security.Cryptography;
 using System.Security.Cryptography.X509Certificates;
-using Mono.Security.Authenticode;
+using System.Threading;
 
 namespace System.Net {
        sealed class EndPointListener
        {
                IPEndPoint endpoint;
                Socket sock;
-               Dictionary<ListenerPrefix, HttpListener> prefixes;
-               List<ListenerPrefix> unhandled; // host = '*'
-               List<ListenerPrefix> all; // host = '+'
+               Hashtable prefixes;  // Dictionary <ListenerPrefix, HttpListener>
+               ArrayList unhandled; // List<ListenerPrefix> unhandled; host = '*'
+               ArrayList all;       // List<ListenerPrefix> all;  host = '+'
                X509Certificate2 cert;
                AsymmetricAlgorithm key;
                bool secure;
+               Dictionary<HttpConnection, HttpConnection> unregistered;
 
                public EndPointListener (IPAddress addr, int port, bool secure)
                {
@@ -58,8 +68,12 @@ namespace System.Net {
                        sock = new Socket (addr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                        sock.Bind (endpoint);
                        sock.Listen (500);
-                       sock.BeginAccept (OnAccept, this);
-                       prefixes = new Dictionary<ListenerPrefix, HttpListener> ();
+                       SocketAsyncEventArgs args = new SocketAsyncEventArgs ();
+                       args.UserToken = this;
+                       args.Completed += OnAccept;
+                       sock.AcceptAsync (args);
+                       prefixes = new Hashtable ();
+                       unregistered = new Dictionary<HttpConnection, HttpConnection> ();
                }
 
                void LoadCertificateAndKey (IPAddress addr, int port)
@@ -70,7 +84,11 @@ namespace System.Net {
                                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;
                                string pvk_file = Path.Combine (path, String.Format ("{0}.pvk", port));
+                               if (!File.Exists (pvk_file))
+                                       return;
                                cert = new X509Certificate2 (cert_file);
                                key = PrivateKey.CreateFromFile (pvk_file).RSA;
                        } catch {
@@ -78,26 +96,27 @@ namespace System.Net {
                        }
                }
 
-               static void OnAccept (IAsyncResult ares)
+               static void OnAccept (object sender, EventArgs e)
                {
-                       EndPointListener epl = (EndPointListener) ares.AsyncState;
+                       SocketAsyncEventArgs args = (SocketAsyncEventArgs) e;
+                       EndPointListener epl = (EndPointListener) args.UserToken;
                        Socket accepted = null;
+                       if (args.SocketError == SocketError.Success) {
+                               accepted = args.AcceptSocket;
+                               args.AcceptSocket = null;
+                       }
+
                        try {
-                               accepted = epl.sock.EndAccept (ares);
+                               if (epl.sock != null)
+                                       epl.sock.AcceptAsync (args);
                        } catch {
-                               // Anything to do here?
-                       } finally {
-                               try {
-                                       epl.sock.BeginAccept (OnAccept, epl);
-                               } catch {
-                                       if (accepted != null) {
-                                               try {
-                                                       accepted.Close ();
-                                               } catch {}
-                                               accepted = null;
-                                       }
-                               } 
-                       }
+                               if (accepted != null) {
+                                       try {
+                                               accepted.Close ();
+                                       } catch {}
+                                       accepted = null;
+                               }
+                       } 
 
                        if (accepted == null)
                                return;
@@ -107,20 +126,29 @@ namespace System.Net {
                                return;
                        }
                        HttpConnection conn = new HttpConnection (accepted, epl, epl.secure, epl.cert, epl.key);
+                       lock (epl.unregistered) {
+                               epl.unregistered [conn] = conn;
+                       }
                        conn.BeginReadRequest ();
                }
 
+               internal void RemoveConnection (HttpConnection conn)
+               {
+                       lock (unregistered) {
+                               unregistered.Remove (conn);
+                       }
+               }
+
                public bool BindContext (HttpListenerContext context)
                {
                        HttpListenerRequest req = context.Request;
                        ListenerPrefix prefix;
-                       HttpListener listener = SearchListener (req.UserHostName, req.RawUrl, out prefix);
+                       HttpListener listener = SearchListener (req.Url, out prefix);
                        if (listener == null)
                                return false;
 
                        context.Listener = listener;
                        context.Connection.Prefix = prefix;
-                       listener.RegisterContext (context);
                        return true;
                }
 
@@ -129,67 +157,61 @@ namespace System.Net {
                        if (context == null || context.Request == null)
                                return;
 
-                       HttpListenerRequest req = context.Request;
-                       ListenerPrefix prefix;
-                       HttpListener listener = SearchListener (req.UserHostName, req.RawUrl, out prefix);
-                       if (listener != null)
-                               listener.UnregisterContext (context);
+                       context.Listener.UnregisterContext (context);
                }
 
-               HttpListener SearchListener (string host, string raw_url, out ListenerPrefix prefix)
+               HttpListener SearchListener (Uri uri, out ListenerPrefix prefix)
                {
                        prefix = null;
-                       if (raw_url == null)
+                       if (uri == null)
                                return null;
 
-                       //TODO: We should use a ReaderWriterLock between this and the add/remove operations.
-                       if (host != null) {
-                               int colon = host.IndexOf (':');
-                               if (colon >= 0)
-                                       host = host.Substring (0, colon);
-                       }
-
-                       string path;
-                       Uri raw_uri;
-                       if (Uri.MaybeUri (raw_url) && Uri.TryCreate (raw_url, UriKind.Absolute, out raw_uri))
-                               path = raw_uri.PathAndQuery;
-                       else
-                               path = HttpUtility.UrlDecode (raw_url);
-                       
+                       string host = uri.Host;
+                       int port = uri.Port;
+                       string path = HttpUtility.UrlDecode (uri.AbsolutePath);
                        string path_slash = path [path.Length - 1] == '/' ? path : path + "/";
                        
                        HttpListener best_match = null;
                        int best_length = -1;
 
-                       lock (prefixes) {
-                               if (host != null && host != "") {
-                                       foreach (ListenerPrefix p in prefixes.Keys) {
-                                               string ppath = p.Path;
-                                               if (ppath.Length < best_length)
-                                                       continue;
-
-                                               if (p.Host == host && (path.StartsWith (ppath) || path_slash.StartsWith (ppath))) {
-                                                       best_length = ppath.Length;
-                                                       best_match = prefixes [p];
-                                                       prefix = p;
-                                               }
-                                       }
-                                       if (best_length != -1)
-                                               return best_match;
-                               }
+                       if (host != null && host != "") {
+                               Hashtable p_ro = prefixes;
+                               foreach (ListenerPrefix p in p_ro.Keys) {
+                                       string ppath = p.Path;
+                                       if (ppath.Length < best_length)
+                                               continue;
 
-                               best_match = MatchFromList (host, path, unhandled, out prefix);
-                               if (best_match != null)
-                                       return best_match;
+                                       if (p.Host != host || p.Port != port)
+                                               continue;
 
-                               best_match = MatchFromList (host, path, all, out prefix);
-                               if (best_match != null)
+                                       if (path.StartsWith (ppath) || path_slash.StartsWith (ppath)) {
+                                               best_length = ppath.Length;
+                                               best_match = (HttpListener) p_ro [p];
+                                               prefix = p;
+                                       }
+                               }
+                               if (best_length != -1)
                                        return best_match;
                        }
+
+                       ArrayList list = unhandled;
+                       best_match = MatchFromList (host, path, list, out prefix);
+                       if (path != path_slash && best_match == null)
+                               best_match = MatchFromList (host, path_slash, list, out prefix);
+                       if (best_match != null)
+                               return best_match;
+
+                       list = all;
+                       best_match = MatchFromList (host, path, list, out prefix);
+                       if (path != path_slash && best_match == null)
+                               best_match = MatchFromList (host, path_slash, list, out prefix);
+                       if (best_match != null)
+                               return best_match;
+
                        return null;
                }
 
-               HttpListener MatchFromList (string host, string path, List<ListenerPrefix> list, out ListenerPrefix prefix)
+               HttpListener MatchFromList (string host, string path, ArrayList list, out ListenerPrefix prefix)
                {
                        prefix = null;
                        if (list == null)
@@ -213,7 +235,7 @@ namespace System.Net {
                        return best_match;
                }
 
-               void AddSpecial (List<ListenerPrefix> coll, ListenerPrefix prefix)
+               void AddSpecial (ArrayList coll, ListenerPrefix prefix)
                {
                        if (coll == null)
                                return;
@@ -222,24 +244,23 @@ namespace System.Net {
                                if (p.Path == prefix.Path) //TODO: code
                                        throw new HttpListenerException (400, "Prefix already in use.");
                        }
-
                        coll.Add (prefix);
                }
 
-               void RemoveSpecial (List<ListenerPrefix> coll, ListenerPrefix prefix)
+               bool RemoveSpecial (ArrayList coll, ListenerPrefix prefix)
                {
                        if (coll == null)
-                               return;
+                               return false;
 
                        int c = coll.Count;
                        for (int i = 0; i < c; i++) {
-                               ListenerPrefix p = coll [i];
+                               ListenerPrefix p = (ListenerPrefix) coll [i];
                                if (p.Path == prefix.Path) {
                                        coll.RemoveAt (i);
-                                       CheckIfRemove ();
-                                       return;
+                                       return true;
                                }
                        }
+                       return false;
                }
 
                void CheckIfRemove ()
@@ -247,10 +268,12 @@ namespace System.Net {
                        if (prefixes.Count > 0)
                                return;
 
-                       if (unhandled != null && unhandled.Count > 0)
+                       ArrayList list = unhandled;
+                       if (list != null && list.Count > 0)
                                return;
 
-                       if (all != null && all.Count > 0)
+                       list = all;
+                       if (list != null && list.Count > 0)
                                return;
 
                        EndPointManager.RemoveEndPoint (this, endpoint);
@@ -259,56 +282,92 @@ namespace System.Net {
                public void Close ()
                {
                        sock.Close ();
+                       lock (unregistered) {
+                               //
+                               // Clone the list because RemoveConnection can be called from Close
+                               //
+                               var connections = new List<HttpConnection> (unregistered.Keys);
+
+                               foreach (HttpConnection c in connections)
+                                       c.Close (true);
+                               unregistered.Clear ();
+                       }
                }
 
                public void AddPrefix (ListenerPrefix prefix, HttpListener listener)
                {
-                       lock (prefixes) {
-                               if (prefix.Host == "*") {
-                                       if (unhandled == null)
-                                               unhandled = new List<ListenerPrefix> ();
-
+                       ArrayList current;
+                       ArrayList future;
+                       if (prefix.Host == "*") {
+                               do {
+                                       current = unhandled;
+                                       future = (current != null) ? (ArrayList) current.Clone () : new ArrayList ();
                                        prefix.Listener = listener;
-                                       AddSpecial (unhandled, prefix);
-                                       return;
-                               }
+                                       AddSpecial (future, prefix);
+                               } while (Interlocked.CompareExchange (ref unhandled, future, current) != current);
+                               return;
+                       }
 
-                               if (prefix.Host == "+") {
-                                       if (all == null)
-                                               all = new List<ListenerPrefix> ();
+                       if (prefix.Host == "+") {
+                               do {
+                                       current = all;
+                                       future = (current != null) ? (ArrayList) current.Clone () : new ArrayList ();
                                        prefix.Listener = listener;
-                                       AddSpecial (all, prefix);
-                                       return;
-                               }
+                                       AddSpecial (future, prefix);
+                               } while (Interlocked.CompareExchange (ref all, future, current) != current);
+                               return;
+                       }
 
-                               if (prefixes.ContainsKey (prefix)) {
-                                       HttpListener other = prefixes [prefix];
+                       Hashtable prefs, p2;
+                       do {
+                               prefs = prefixes;
+                               if (prefs.ContainsKey (prefix)) {
+                                       HttpListener other = (HttpListener) prefs [prefix];
                                        if (other != listener) // TODO: code.
                                                throw new HttpListenerException (400, "There's another listener for " + prefix);
                                        return;
                                }
-
-                               prefixes [prefix] = listener;
-                       }
+                               p2 = (Hashtable) prefs.Clone ();
+                               p2 [prefix] = listener;
+                       } while (Interlocked.CompareExchange (ref prefixes, p2, prefs) != prefs);
                }
 
                public void RemovePrefix (ListenerPrefix prefix, HttpListener listener)
                {
-                       lock (prefixes) {
-                               if (prefix.Host == "*") {
-                                       RemoveSpecial (unhandled, prefix);
-                                       return;
-                               }
-
-                               if (prefix.Host == "+") {
-                                       RemoveSpecial (all, prefix);
-                                       return;
-                               }
+                       ArrayList current;
+                       ArrayList future;
+                       if (prefix.Host == "*") {
+                               do {
+                                       current = unhandled;
+                                       future = (current != null) ? (ArrayList) current.Clone () : new ArrayList ();
+                                       if (!RemoveSpecial (future, prefix))
+                                               break; // Prefix not found
+                               } while (Interlocked.CompareExchange (ref unhandled, future, current) != current);
+                               CheckIfRemove ();
+                               return;
+                       }
 
-                               if (prefixes.ContainsKey (prefix)) {
-                                       prefixes.Remove (prefix);
-                               }
+                       if (prefix.Host == "+") {
+                               do {
+                                       current = all;
+                                       future = (current != null) ? (ArrayList) current.Clone () : new ArrayList ();
+                                       if (!RemoveSpecial (future, prefix))
+                                               break; // Prefix not found
+                               } while (Interlocked.CompareExchange (ref all, future, current) != current);
+                               CheckIfRemove ();
+                               return;
                        }
+
+                       Hashtable prefs, p2;
+                       do {
+                               prefs = prefixes;
+                               if (!prefs.ContainsKey (prefix))
+                                       break;
+
+                               p2 = (Hashtable) prefs.Clone ();
+                               p2.Remove (prefix);
+                       } while (Interlocked.CompareExchange (ref prefixes, p2, prefs) != prefs);
+                       CheckIfRemove ();
                }
        }
 }