// 
 | 
// EndPointListener.cs 
 | 
//    Copied from System.Net.EndPointListener 
 | 
// 
 | 
// Author: 
 | 
//    Gonzalo Paniagua Javier (gonzalo@novell.com) 
 | 
// 
 | 
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com) 
 | 
// Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com) 
 | 
// 
 | 
// Permission is hereby granted, free of charge, to any person obtaining 
 | 
// a copy of this software and associated documentation files (the 
 | 
// "Software"), to deal in the Software without restriction, including 
 | 
// without limitation the rights to use, copy, modify, merge, publish, 
 | 
// distribute, sublicense, and/or sell copies of the Software, and to 
 | 
// permit persons to whom the Software is furnished to do so, subject to 
 | 
// the following conditions: 
 | 
//  
 | 
// The above copyright notice and this permission notice shall be 
 | 
// included in all copies or substantial portions of the Software. 
 | 
//  
 | 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 | 
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
 | 
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 | 
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
 | 
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 
 | 
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
 | 
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
 | 
// 
 | 
  
 | 
using System; 
 | 
using System.Collections; 
 | 
using System.Collections.Generic; 
 | 
using System.IO; 
 | 
using System.Net; 
 | 
using System.Net.Sockets; 
 | 
using System.Security.Cryptography; 
 | 
using System.Security.Cryptography.X509Certificates; 
 | 
using System.Threading; 
 | 
  
 | 
namespace WebSocketSharp.Net { 
 | 
  
 | 
    sealed class EndPointListener { 
 | 
  
 | 
        #region Fields 
 | 
  
 | 
        List<ListenerPrefix>                     all; // host = '+' 
 | 
        X509Certificate2                         cert; 
 | 
        IPEndPoint                               endpoint; 
 | 
        AsymmetricAlgorithm                      key; 
 | 
        Dictionary<ListenerPrefix, HttpListener> prefixes; 
 | 
        bool                                     secure; 
 | 
        Socket                                   sock; 
 | 
        List<ListenerPrefix>                     unhandled; // host = '*' 
 | 
        Hashtable                                unregistered; 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Constructor 
 | 
  
 | 
        public EndPointListener (IPAddress addr, int port, bool secure) 
 | 
        { 
 | 
            if (secure) { 
 | 
                this.secure = secure; 
 | 
                LoadCertificateAndKey (addr, port); 
 | 
            } 
 | 
  
 | 
            endpoint = new IPEndPoint (addr, port); 
 | 
            sock = new Socket (addr.AddressFamily, SocketType.Stream, ProtocolType.Tcp); 
 | 
            sock.Bind (endpoint); 
 | 
            sock.Listen (500); 
 | 
            var args = new SocketAsyncEventArgs (); 
 | 
            args.UserToken = this; 
 | 
            args.Completed += OnAccept; 
 | 
            sock.AcceptAsync (args); 
 | 
            prefixes = new Dictionary<ListenerPrefix, HttpListener> (); 
 | 
            unregistered = Hashtable.Synchronized (new Hashtable ()); 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Private Methods 
 | 
  
 | 
        void AddSpecial (List<ListenerPrefix> coll, ListenerPrefix prefix) 
 | 
        { 
 | 
            if (coll == null) 
 | 
                return; 
 | 
  
 | 
            foreach (ListenerPrefix p in coll) { 
 | 
                if (p.Path == prefix.Path) // TODO: code 
 | 
                    throw new HttpListenerException (400, "Prefix already in use."); 
 | 
            } 
 | 
            coll.Add (prefix); 
 | 
        } 
 | 
  
 | 
        void CheckIfRemove () 
 | 
        { 
 | 
            if (prefixes.Count > 0) 
 | 
                return; 
 | 
  
 | 
            var list = unhandled; 
 | 
            if (list != null && list.Count > 0) 
 | 
                return; 
 | 
  
 | 
            list = all; 
 | 
            if (list != null && list.Count > 0) 
 | 
                return; 
 | 
  
 | 
            EndPointManager.RemoveEndPoint (this, endpoint); 
 | 
        } 
 | 
  
 | 
        RSACryptoServiceProvider CreateRSAFromFile (string filename) 
 | 
        { 
 | 
            if (filename == null) 
 | 
                throw new ArgumentNullException ("filename"); 
 | 
  
 | 
            var rsa = new RSACryptoServiceProvider (); 
 | 
            byte[] pvk = null; 
 | 
            using (FileStream fs = File.Open (filename, FileMode.Open, FileAccess.Read, FileShare.Read)) 
 | 
            { 
 | 
                pvk = new byte [fs.Length]; 
 | 
                fs.Read (pvk, 0, pvk.Length); 
 | 
            } 
 | 
            rsa.ImportCspBlob (pvk); 
 | 
            return rsa; 
 | 
        } 
 | 
  
 | 
        void LoadCertificateAndKey (IPAddress addr, int port) 
 | 
        { 
 | 
            // 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)); 
 | 
                string pvk_file = Path.Combine (path, String.Format ("{0}.pvk", port)); 
 | 
                cert = new X509Certificate2 (cert_file); 
 | 
                key = CreateRSAFromFile (pvk_file); 
 | 
            } catch { 
 | 
                // ignore errors 
 | 
            } 
 | 
        } 
 | 
  
 | 
        HttpListener MatchFromList ( 
 | 
            string host, string path, List<ListenerPrefix> list, out ListenerPrefix prefix) 
 | 
        { 
 | 
            prefix = null; 
 | 
            if (list == null) 
 | 
                return null; 
 | 
  
 | 
            HttpListener best_match = null; 
 | 
            int best_length = -1; 
 | 
             
 | 
            foreach (ListenerPrefix p in list) { 
 | 
                string ppath = p.Path; 
 | 
                if (ppath.Length < best_length) 
 | 
                    continue; 
 | 
  
 | 
                if (path.StartsWith (ppath)) { 
 | 
                    best_length = ppath.Length; 
 | 
                    best_match = p.Listener; 
 | 
                    prefix = p; 
 | 
                } 
 | 
            } 
 | 
  
 | 
            return best_match; 
 | 
        } 
 | 
  
 | 
        static void OnAccept (object sender, EventArgs e) 
 | 
        { 
 | 
            SocketAsyncEventArgs args = (SocketAsyncEventArgs) e; 
 | 
            EndPointListener epl = (EndPointListener) args.UserToken; 
 | 
            Socket accepted = null; 
 | 
            if (args.SocketError == SocketError.Success) { 
 | 
                accepted = args.AcceptSocket; 
 | 
                args.AcceptSocket = null; 
 | 
            } 
 | 
  
 | 
            try { 
 | 
                if (epl.sock != null) 
 | 
                    epl.sock.AcceptAsync (args); 
 | 
            } catch { 
 | 
                if (accepted != null) { 
 | 
                    try { 
 | 
                        accepted.Close (); 
 | 
                    } catch {} 
 | 
                    accepted = null; 
 | 
                } 
 | 
            }  
 | 
  
 | 
            if (accepted == null) 
 | 
                return; 
 | 
  
 | 
            if (epl.secure && (epl.cert == null || epl.key == null)) { 
 | 
                accepted.Close (); 
 | 
                return; 
 | 
            } 
 | 
            HttpConnection conn = new HttpConnection (accepted, epl, epl.secure, epl.cert, epl.key); 
 | 
            epl.unregistered [conn] = conn; 
 | 
            conn.BeginReadRequest (); 
 | 
        } 
 | 
  
 | 
        bool RemoveSpecial (List<ListenerPrefix> coll, ListenerPrefix prefix) 
 | 
        { 
 | 
            if (coll == null) 
 | 
                return false; 
 | 
  
 | 
            int c = coll.Count; 
 | 
            for (int i = 0; i < c; i++) { 
 | 
                ListenerPrefix p = coll [i]; 
 | 
                if (p.Path == prefix.Path) { 
 | 
                    coll.RemoveAt (i); 
 | 
                    return true; 
 | 
                } 
 | 
            } 
 | 
            return false; 
 | 
        } 
 | 
  
 | 
        HttpListener SearchListener (Uri uri, out ListenerPrefix prefix) 
 | 
        { 
 | 
            prefix = null; 
 | 
            if (uri == null) 
 | 
                return null; 
 | 
  
 | 
            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; 
 | 
  
 | 
            if (host != null && host != "") { 
 | 
                var p_ro = prefixes; 
 | 
                foreach (ListenerPrefix p in p_ro.Keys) { 
 | 
                    string ppath = p.Path; 
 | 
                    if (ppath.Length < best_length) 
 | 
                        continue; 
 | 
  
 | 
                    if (p.Host != host || p.Port != port) 
 | 
                        continue; 
 | 
  
 | 
                    if (path.StartsWith (ppath) || path_slash.StartsWith (ppath)) { 
 | 
                        best_length = ppath.Length; 
 | 
                        best_match = p_ro [p]; 
 | 
                        prefix = p; 
 | 
                    } 
 | 
                } 
 | 
                if (best_length != -1) 
 | 
                    return best_match; 
 | 
            } 
 | 
  
 | 
            var 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; 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Internal Method 
 | 
  
 | 
        internal void RemoveConnection (HttpConnection conn) 
 | 
        { 
 | 
            unregistered.Remove (conn); 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Public Methods 
 | 
  
 | 
        public void AddPrefix (ListenerPrefix prefix, HttpListener listener) 
 | 
        { 
 | 
            List<ListenerPrefix> current; 
 | 
            List<ListenerPrefix> future; 
 | 
            if (prefix.Host == "*") { 
 | 
                do { 
 | 
                    current = unhandled; 
 | 
                    future = (current != null) 
 | 
                        ? new List<ListenerPrefix> (current) 
 | 
                        : new List<ListenerPrefix> (); 
 | 
                    prefix.Listener = listener; 
 | 
                    AddSpecial (future, prefix); 
 | 
                } while (Interlocked.CompareExchange (ref unhandled, future, current) != current); 
 | 
                return; 
 | 
            } 
 | 
  
 | 
            if (prefix.Host == "+") { 
 | 
                do { 
 | 
                    current = all; 
 | 
                    future = (current != null) 
 | 
                        ? new List<ListenerPrefix> (current) 
 | 
                        : new List<ListenerPrefix> (); 
 | 
                    prefix.Listener = listener; 
 | 
                    AddSpecial (future, prefix); 
 | 
                } while (Interlocked.CompareExchange (ref all, future, current) != current); 
 | 
                return; 
 | 
            } 
 | 
  
 | 
            Dictionary<ListenerPrefix, HttpListener> prefs, p2; 
 | 
            do { 
 | 
                prefs = prefixes; 
 | 
                if (prefs.ContainsKey (prefix)) { 
 | 
                    HttpListener other = prefs [prefix]; 
 | 
                    if (other != listener) // TODO: code. 
 | 
                        throw new HttpListenerException (400, "There's another listener for " + prefix); 
 | 
                    return; 
 | 
                } 
 | 
                p2 = new Dictionary<ListenerPrefix, HttpListener> (prefs); 
 | 
                p2 [prefix] = listener; 
 | 
            } while (Interlocked.CompareExchange (ref prefixes, p2, prefs) != prefs); 
 | 
        } 
 | 
  
 | 
        public bool BindContext (HttpListenerContext context) 
 | 
        { 
 | 
            HttpListenerRequest req = context.Request; 
 | 
            ListenerPrefix prefix; 
 | 
            HttpListener listener = SearchListener (req.Url, out prefix); 
 | 
            if (listener == null) 
 | 
                return false; 
 | 
  
 | 
            context.Listener = listener; 
 | 
            context.Connection.Prefix = prefix; 
 | 
            return true; 
 | 
        } 
 | 
  
 | 
        public void Close () 
 | 
        { 
 | 
            sock.Close (); 
 | 
            lock (unregistered.SyncRoot) { 
 | 
                Hashtable copy = (Hashtable) unregistered.Clone (); 
 | 
                foreach (HttpConnection c in copy.Keys) 
 | 
                    c.Close (true); 
 | 
                copy.Clear (); 
 | 
                unregistered.Clear (); 
 | 
            } 
 | 
        } 
 | 
  
 | 
        public void RemovePrefix (ListenerPrefix prefix, HttpListener listener) 
 | 
        { 
 | 
            List<ListenerPrefix> current; 
 | 
            List<ListenerPrefix> future; 
 | 
            if (prefix.Host == "*") { 
 | 
                do { 
 | 
                    current = unhandled; 
 | 
                    future = (current != null) 
 | 
                        ? new List<ListenerPrefix> (current) 
 | 
                        : new List<ListenerPrefix> (); 
 | 
                    if (!RemoveSpecial (future, prefix)) 
 | 
                        break; // Prefix not found 
 | 
                } while (Interlocked.CompareExchange (ref unhandled, future, current) != current); 
 | 
                CheckIfRemove (); 
 | 
                return; 
 | 
            } 
 | 
  
 | 
            if (prefix.Host == "+") { 
 | 
                do { 
 | 
                    current = all; 
 | 
                    future = (current != null) 
 | 
                        ? new List<ListenerPrefix> (current) 
 | 
                        : new List<ListenerPrefix> (); 
 | 
                    if (!RemoveSpecial (future, prefix)) 
 | 
                        break; // Prefix not found 
 | 
                } while (Interlocked.CompareExchange (ref all, future, current) != current); 
 | 
                CheckIfRemove (); 
 | 
                return; 
 | 
            } 
 | 
  
 | 
            Dictionary<ListenerPrefix, HttpListener> prefs, p2; 
 | 
            do { 
 | 
                prefs = prefixes; 
 | 
                if (!prefs.ContainsKey (prefix)) 
 | 
                    break; 
 | 
  
 | 
                p2 = new Dictionary<ListenerPrefix, HttpListener> (prefs); 
 | 
                p2.Remove (prefix); 
 | 
            } while (Interlocked.CompareExchange (ref prefixes, p2, prefs) != prefs); 
 | 
            CheckIfRemove (); 
 | 
        } 
 | 
  
 | 
        public void UnbindContext (HttpListenerContext context) 
 | 
        { 
 | 
            if (context == null || context.Request == null) 
 | 
                return; 
 | 
  
 | 
            context.Listener.UnregisterContext (context); 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
    } 
 | 
} 
 |