// 
 | 
// HttpListener.cs 
 | 
//    Copied from System.Net.HttpListener 
 | 
// 
 | 
// 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.Net; 
 | 
using System.Threading; 
 | 
  
 | 
// TODO: logging 
 | 
namespace WebSocketSharp.Net { 
 | 
  
 | 
    public sealed class HttpListener : IDisposable { 
 | 
  
 | 
        #region Fields 
 | 
  
 | 
        AuthenticationSchemes                                auth_schemes; 
 | 
        AuthenticationSchemeSelector                         auth_selector;  
 | 
        Dictionary<HttpConnection, HttpConnection>           connections; 
 | 
        List<HttpListenerContext>                            ctx_queue; 
 | 
        bool                                                 disposed; 
 | 
        bool                                                 ignore_write_exceptions; 
 | 
        bool                                                 listening; 
 | 
        HttpListenerPrefixCollection                         prefixes; 
 | 
        string                                               realm; 
 | 
        Dictionary<HttpListenerContext, HttpListenerContext> registry; 
 | 
        bool                                                 unsafe_ntlm_auth; 
 | 
        List<ListenerAsyncResult>                            wait_queue; 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Constructor 
 | 
  
 | 
        public HttpListener () 
 | 
        { 
 | 
            prefixes     = new HttpListenerPrefixCollection (this); 
 | 
            registry     = new Dictionary<HttpListenerContext, HttpListenerContext> (); 
 | 
            connections  = new Dictionary<HttpConnection, HttpConnection> (); 
 | 
            ctx_queue    = new List<HttpListenerContext> (); 
 | 
            wait_queue   = new List<ListenerAsyncResult> (); 
 | 
            auth_schemes = AuthenticationSchemes.Anonymous; 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Properties 
 | 
  
 | 
        // TODO: Digest, NTLM and Negotiate require ControlPrincipal 
 | 
        public AuthenticationSchemes AuthenticationSchemes { 
 | 
            get { return auth_schemes; } 
 | 
            set { 
 | 
                CheckDisposed (); 
 | 
                auth_schemes = value; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate { 
 | 
            get { return auth_selector; } 
 | 
            set { 
 | 
                CheckDisposed (); 
 | 
                auth_selector = value; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        public bool IgnoreWriteExceptions { 
 | 
            get { return ignore_write_exceptions; } 
 | 
            set { 
 | 
                CheckDisposed (); 
 | 
                ignore_write_exceptions = value; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        public bool IsListening { 
 | 
            get { return listening; } 
 | 
        } 
 | 
  
 | 
        public static bool IsSupported { 
 | 
            get { return true; } 
 | 
        } 
 | 
  
 | 
        public HttpListenerPrefixCollection Prefixes { 
 | 
            get { 
 | 
                CheckDisposed (); 
 | 
                return prefixes; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        // TODO: Use this 
 | 
        public string Realm { 
 | 
            get { return realm; } 
 | 
            set { 
 | 
                CheckDisposed (); 
 | 
                realm = value; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        // TODO: Support for NTLM needs some loving. 
 | 
        public bool UnsafeConnectionNtlmAuthentication { 
 | 
            get { return unsafe_ntlm_auth; } 
 | 
            set { 
 | 
                CheckDisposed (); 
 | 
                unsafe_ntlm_auth = value; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Private Methods 
 | 
  
 | 
        void Cleanup (bool close_existing) 
 | 
        { 
 | 
            lock (((ICollection)registry).SyncRoot) { 
 | 
                if (close_existing) { 
 | 
                    // 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 (((ICollection)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 (((ICollection)ctx_queue).SyncRoot) { 
 | 
                    var ctxs = ctx_queue.ToArray (); 
 | 
                    ctx_queue.Clear (); 
 | 
                    for (int i = ctxs.Length - 1; i >= 0; i--) 
 | 
                        ctxs [i].Connection.Close (true); 
 | 
                } 
 | 
  
 | 
                lock (((ICollection)wait_queue).SyncRoot) { 
 | 
                    Exception exc = new ObjectDisposedException ("listener"); 
 | 
                    foreach (ListenerAsyncResult ares in wait_queue) { 
 | 
                        ares.Complete (exc); 
 | 
                    } 
 | 
                    wait_queue.Clear (); 
 | 
                } 
 | 
            } 
 | 
        } 
 | 
  
 | 
        void Close (bool force) 
 | 
        { 
 | 
            CheckDisposed (); 
 | 
            EndPointManager.RemoveListener (this); 
 | 
            Cleanup (force); 
 | 
        } 
 | 
  
 | 
        // Must be called with a lock on ctx_queue 
 | 
        HttpListenerContext GetContextFromQueue () 
 | 
        { 
 | 
            if (ctx_queue.Count == 0) 
 | 
                return null; 
 | 
  
 | 
            var context = ctx_queue [0]; 
 | 
            ctx_queue.RemoveAt (0); 
 | 
            return context; 
 | 
        } 
 | 
  
 | 
        void IDisposable.Dispose () 
 | 
        { 
 | 
            if (disposed) 
 | 
                return; 
 | 
  
 | 
            Close (true); //TODO: Should we force here or not? 
 | 
            disposed = true; 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Internal Methods 
 | 
  
 | 
        internal void AddConnection (HttpConnection cnc) 
 | 
        { 
 | 
            connections [cnc] = cnc; 
 | 
        } 
 | 
  
 | 
        internal void CheckDisposed () 
 | 
        { 
 | 
            if (disposed) 
 | 
                throw new ObjectDisposedException (GetType ().ToString ()); 
 | 
        } 
 | 
  
 | 
        internal void RegisterContext (HttpListenerContext context) 
 | 
        { 
 | 
            lock (((ICollection)registry).SyncRoot) 
 | 
                registry [context] = context; 
 | 
  
 | 
            ListenerAsyncResult ares = null; 
 | 
            lock (((ICollection)wait_queue).SyncRoot) { 
 | 
                if (wait_queue.Count == 0) { 
 | 
                    lock (((ICollection)ctx_queue).SyncRoot) 
 | 
                        ctx_queue.Add (context); 
 | 
                } else { 
 | 
                    ares = wait_queue [0]; 
 | 
                    wait_queue.RemoveAt (0); 
 | 
                } 
 | 
            } 
 | 
            if (ares != null) 
 | 
                ares.Complete (context); 
 | 
        } 
 | 
  
 | 
        internal void RemoveConnection (HttpConnection cnc) 
 | 
        { 
 | 
            connections.Remove (cnc); 
 | 
        } 
 | 
  
 | 
        internal AuthenticationSchemes SelectAuthenticationScheme (HttpListenerContext context) 
 | 
        { 
 | 
            if (AuthenticationSchemeSelectorDelegate != null) 
 | 
                return AuthenticationSchemeSelectorDelegate (context.Request); 
 | 
            else 
 | 
                return auth_schemes; 
 | 
        } 
 | 
  
 | 
        internal void UnregisterContext (HttpListenerContext context) 
 | 
        { 
 | 
            lock (((ICollection)registry).SyncRoot) 
 | 
                registry.Remove (context); 
 | 
            lock (((ICollection)ctx_queue).SyncRoot) { 
 | 
                int idx = ctx_queue.IndexOf (context); 
 | 
                if (idx >= 0) 
 | 
                    ctx_queue.RemoveAt (idx); 
 | 
            } 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Public Methods 
 | 
  
 | 
        public void Abort () 
 | 
        { 
 | 
            if (disposed) 
 | 
                return; 
 | 
  
 | 
            if (!listening) { 
 | 
                return; 
 | 
            } 
 | 
  
 | 
            Close (true); 
 | 
        } 
 | 
  
 | 
        public IAsyncResult BeginGetContext (AsyncCallback callback, Object state) 
 | 
        { 
 | 
            CheckDisposed (); 
 | 
            if (!listening) 
 | 
                throw new InvalidOperationException ("Please, call Start before using this method."); 
 | 
  
 | 
            ListenerAsyncResult ares = new ListenerAsyncResult (callback, state); 
 | 
  
 | 
            // lock wait_queue early to avoid race conditions 
 | 
            lock (((ICollection)wait_queue).SyncRoot) { 
 | 
                lock (((ICollection)ctx_queue).SyncRoot) { 
 | 
                    HttpListenerContext ctx = GetContextFromQueue (); 
 | 
                    if (ctx != null) { 
 | 
                        ares.Complete (ctx, true); 
 | 
                        return ares; 
 | 
                    } 
 | 
                } 
 | 
  
 | 
                wait_queue.Add (ares); 
 | 
            } 
 | 
  
 | 
            return ares; 
 | 
        } 
 | 
  
 | 
        public void Close () 
 | 
        { 
 | 
            if (disposed) 
 | 
                return; 
 | 
  
 | 
            if (!listening) { 
 | 
                disposed = true; 
 | 
                return; 
 | 
            } 
 | 
  
 | 
            Close (true); 
 | 
            disposed = true; 
 | 
        } 
 | 
  
 | 
        public HttpListenerContext EndGetContext (IAsyncResult asyncResult) 
 | 
        { 
 | 
            CheckDisposed (); 
 | 
            if (asyncResult == null) 
 | 
                throw new ArgumentNullException ("asyncResult"); 
 | 
  
 | 
            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 (); 
 | 
  
 | 
            lock (((ICollection)wait_queue).SyncRoot) { 
 | 
                int idx = wait_queue.IndexOf (ares); 
 | 
                if (idx >= 0) 
 | 
                    wait_queue.RemoveAt (idx); 
 | 
            } 
 | 
  
 | 
            HttpListenerContext context = ares.GetContext (); 
 | 
            context.ParseAuthentication (SelectAuthenticationScheme (context)); 
 | 
            return context; // This will throw on error. 
 | 
        } 
 | 
  
 | 
        public HttpListenerContext GetContext () 
 | 
        { 
 | 
            // The prefixes are not checked when using the async interface!? 
 | 
            if (prefixes.Count == 0) 
 | 
                throw new InvalidOperationException ("Please, call AddPrefix before using this method."); 
 | 
  
 | 
            ListenerAsyncResult ares = (ListenerAsyncResult) BeginGetContext (null, null); 
 | 
            ares.InGet = true; 
 | 
            return EndGetContext (ares); 
 | 
        } 
 | 
  
 | 
        public void Start () 
 | 
        { 
 | 
            CheckDisposed (); 
 | 
            if (listening) 
 | 
                return; 
 | 
  
 | 
            EndPointManager.AddListener (this); 
 | 
            listening = true; 
 | 
        } 
 | 
  
 | 
        public void Stop () 
 | 
        { 
 | 
            CheckDisposed (); 
 | 
            listening = false; 
 | 
            Close (false); 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
    } 
 | 
} 
 |