// 
 | 
// HttpConnection.cs 
 | 
//    Copied from System.Net.HttpConnection 
 | 
// 
 | 
// 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.IO; 
 | 
using System.Net; 
 | 
using System.Net.Sockets; 
 | 
using System.Reflection; 
 | 
using System.Security.Cryptography; 
 | 
using System.Security.Cryptography.X509Certificates; 
 | 
using System.Text; 
 | 
using System.Threading; 
 | 
using WebSocketSharp.Net.Security; 
 | 
  
 | 
namespace WebSocketSharp.Net 
 | 
{ 
 | 
    sealed class HttpConnection 
 | 
    { 
 | 
  
 | 
        #region Enums 
 | 
  
 | 
        enum InputState 
 | 
        { 
 | 
            RequestLine, 
 | 
            Headers 
 | 
        } 
 | 
  
 | 
        enum LineState 
 | 
        { 
 | 
            None, 
 | 
            CR, 
 | 
            LF 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Private Const Field 
 | 
  
 | 
        const int BufferSize = 8192; 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Private Static Field 
 | 
  
 | 
        static AsyncCallback onread_cb = new AsyncCallback(OnRead); 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Private Fields 
 | 
  
 | 
        byte[] buffer; 
 | 
        bool chunked; 
 | 
        HttpListenerContext context; 
 | 
        bool context_bound; 
 | 
        StringBuilder current_line; 
 | 
        EndPointListener epl; 
 | 
        InputState input_state; 
 | 
        RequestStream i_stream; 
 | 
        AsymmetricAlgorithm key; 
 | 
        HttpListener last_listener; 
 | 
        LineState line_state; 
 | 
        //        IPEndPoint          local_ep; // never used 
 | 
        MemoryStream ms; 
 | 
        ResponseStream o_stream; 
 | 
        int position; 
 | 
        ListenerPrefix prefix; 
 | 
        int reuses; 
 | 
        bool secure; 
 | 
        Socket sock; 
 | 
        Stream stream; 
 | 
        int s_timeout; 
 | 
        Timer timer; 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Constructor 
 | 
  
 | 
        public HttpConnection( 
 | 
            Socket sock, 
 | 
            EndPointListener epl, 
 | 
            bool secure, 
 | 
            X509Certificate2 cert, 
 | 
            AsymmetricAlgorithm key 
 | 
        ) 
 | 
        { 
 | 
            this.sock = sock; 
 | 
            this.epl = epl; 
 | 
            this.secure = secure; 
 | 
            this.key = key; 
 | 
            //            if (secure == false) { 
 | 
            //                stream = new NetworkStream (sock, false); 
 | 
            //            } else { 
 | 
            //                var ssl_stream = new SslServerStream (new NetworkStream (sock, false), cert, false, false); 
 | 
            //                ssl_stream.PrivateKeyCertSelectionDelegate += OnPVKSelection; 
 | 
            //                stream = ssl_stream; 
 | 
            //            } 
 | 
            var net_stream = new NetworkStream(sock, false); 
 | 
            if (!secure) 
 | 
            { 
 | 
                stream = net_stream; 
 | 
            } 
 | 
            else 
 | 
            { 
 | 
                var ssl_stream = new SslStream(net_stream, false); 
 | 
                ssl_stream.AuthenticateAsServer(cert); 
 | 
                stream = ssl_stream; 
 | 
            } 
 | 
            timer = new Timer(OnTimeout, null, Timeout.Infinite, Timeout.Infinite); 
 | 
            Init(); 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Properties 
 | 
  
 | 
        public bool IsClosed 
 | 
        { 
 | 
            get { return (sock == null); } 
 | 
        } 
 | 
  
 | 
        public bool IsSecure 
 | 
        { 
 | 
            get { return secure; } 
 | 
        } 
 | 
  
 | 
        public IPEndPoint LocalEndPoint 
 | 
        { 
 | 
            get { return (IPEndPoint)sock.LocalEndPoint; } 
 | 
        } 
 | 
  
 | 
        public ListenerPrefix Prefix 
 | 
        { 
 | 
            get { return prefix; } 
 | 
            set { prefix = value; } 
 | 
        } 
 | 
  
 | 
        public IPEndPoint RemoteEndPoint 
 | 
        { 
 | 
            get { return (IPEndPoint)sock.RemoteEndPoint; } 
 | 
        } 
 | 
  
 | 
        public int Reuses 
 | 
        { 
 | 
            get { return reuses; } 
 | 
        } 
 | 
  
 | 
        public Stream Stream 
 | 
        { 
 | 
            get { return stream; } 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Private Methods 
 | 
  
 | 
        void CloseSocket() 
 | 
        { 
 | 
            if (sock == null) 
 | 
                return; 
 | 
  
 | 
            try 
 | 
            { 
 | 
                sock.Close(); 
 | 
            } 
 | 
            catch 
 | 
            { 
 | 
            } 
 | 
            finally 
 | 
            { 
 | 
                sock = null; 
 | 
            } 
 | 
            RemoveConnection(); 
 | 
        } 
 | 
  
 | 
        void Init() 
 | 
        { 
 | 
            context_bound = false; 
 | 
            i_stream = null; 
 | 
            o_stream = null; 
 | 
            prefix = null; 
 | 
            chunked = false; 
 | 
            ms = new MemoryStream(); 
 | 
            position = 0; 
 | 
            input_state = InputState.RequestLine; 
 | 
            line_state = LineState.None; 
 | 
            context = new HttpListenerContext(this); 
 | 
            s_timeout = 90000; // 90k ms for first request, 15k ms from then on 
 | 
        } 
 | 
  
 | 
        AsymmetricAlgorithm OnPVKSelection(X509Certificate certificate, string targetHost) 
 | 
        { 
 | 
            return key; 
 | 
        } 
 | 
  
 | 
        static void OnRead(IAsyncResult ares) 
 | 
        { 
 | 
            HttpConnection cnc = (HttpConnection)ares.AsyncState; 
 | 
            cnc.OnReadInternal(ares); 
 | 
        } 
 | 
  
 | 
        void OnReadInternal(IAsyncResult ares) 
 | 
        { 
 | 
            timer.Change(Timeout.Infinite, Timeout.Infinite); 
 | 
            int nread = -1; 
 | 
            try 
 | 
            { 
 | 
                nread = stream.EndRead(ares); 
 | 
                ms.Write(buffer, 0, nread); 
 | 
                if (ms.Length > 32768) 
 | 
                { 
 | 
                    SendError("Bad request", 400); 
 | 
                    Close(true); 
 | 
                    return; 
 | 
                } 
 | 
            } 
 | 
            catch 
 | 
            { 
 | 
                if (ms != null && ms.Length > 0) 
 | 
                    SendError(); 
 | 
                if (sock != null) 
 | 
                { 
 | 
                    CloseSocket(); 
 | 
                    Unbind(); 
 | 
                } 
 | 
                return; 
 | 
            } 
 | 
  
 | 
            if (nread == 0) 
 | 
            { 
 | 
                //if (ms.Length > 0) 
 | 
                //    SendError (); // Why bother? 
 | 
                CloseSocket(); 
 | 
                Unbind(); 
 | 
                return; 
 | 
            } 
 | 
  
 | 
            if (ProcessInput(ms)) 
 | 
            { 
 | 
                if (!context.HaveError) 
 | 
                    context.Request.FinishInitialization(); 
 | 
  
 | 
                if (context.HaveError) 
 | 
                { 
 | 
                    SendError(); 
 | 
                    Close(true); 
 | 
                    return; 
 | 
                } 
 | 
  
 | 
                if (!epl.BindContext(context)) 
 | 
                { 
 | 
                    SendError("Invalid host", 400); 
 | 
                    Close(true); 
 | 
                    return; 
 | 
                } 
 | 
                HttpListener listener = context.Listener; 
 | 
                if (last_listener != listener) 
 | 
                { 
 | 
                    RemoveConnection(); 
 | 
                    listener.AddConnection(this); 
 | 
                    last_listener = listener; 
 | 
                } 
 | 
  
 | 
                context_bound = true; 
 | 
                listener.RegisterContext(context); 
 | 
                return; 
 | 
            } 
 | 
            stream.BeginRead(buffer, 0, BufferSize, onread_cb, this); 
 | 
        } 
 | 
  
 | 
        void OnTimeout(object unused) 
 | 
        { 
 | 
            CloseSocket(); 
 | 
            Unbind(); 
 | 
        } 
 | 
  
 | 
        // true -> done processing 
 | 
        // false -> need more input 
 | 
        bool ProcessInput(MemoryStream ms) 
 | 
        { 
 | 
            byte[] buffer = ms.GetBuffer(); 
 | 
            int len = (int)ms.Length; 
 | 
            int used = 0; 
 | 
            string line; 
 | 
  
 | 
            try 
 | 
            { 
 | 
                line = ReadLine(buffer, position, len - position, ref used); 
 | 
                position += used; 
 | 
            } 
 | 
            catch 
 | 
            { 
 | 
                context.ErrorMessage = "Bad request"; 
 | 
                context.ErrorStatus = 400; 
 | 
                return true; 
 | 
            } 
 | 
  
 | 
            do 
 | 
            { 
 | 
                if (line == null) 
 | 
                    break; 
 | 
                if (line == "") 
 | 
                { 
 | 
                    if (input_state == InputState.RequestLine) 
 | 
                        continue; 
 | 
                    current_line = null; 
 | 
                    ms = null; 
 | 
                    return true; 
 | 
                } 
 | 
  
 | 
                if (input_state == InputState.RequestLine) 
 | 
                { 
 | 
                    context.Request.SetRequestLine(line); 
 | 
                    input_state = InputState.Headers; 
 | 
                } 
 | 
                else 
 | 
                { 
 | 
                    try 
 | 
                    { 
 | 
                        context.Request.AddHeader(line); 
 | 
                    } 
 | 
                    catch (Exception e) 
 | 
                    { 
 | 
                        context.ErrorMessage = e.Message; 
 | 
                        context.ErrorStatus = 400; 
 | 
                        return true; 
 | 
                    } 
 | 
                } 
 | 
  
 | 
                if (context.HaveError) 
 | 
                    return true; 
 | 
  
 | 
                if (position >= len) 
 | 
                    break; 
 | 
                try 
 | 
                { 
 | 
                    line = ReadLine(buffer, position, len - position, ref used); 
 | 
                    position += used; 
 | 
                } 
 | 
                catch 
 | 
                { 
 | 
                    context.ErrorMessage = "Bad request"; 
 | 
                    context.ErrorStatus = 400; 
 | 
                    return true; 
 | 
                } 
 | 
            } while (line != null); 
 | 
  
 | 
            if (used == len) 
 | 
            { 
 | 
                ms.SetLength(0); 
 | 
                position = 0; 
 | 
            } 
 | 
            return false; 
 | 
        } 
 | 
  
 | 
        string ReadLine(byte[] buffer, int offset, int len, ref int used) 
 | 
        { 
 | 
            if (current_line == null) 
 | 
                current_line = new StringBuilder(); 
 | 
            int last = offset + len; 
 | 
            used = 0; 
 | 
            for (int i = offset; i < last && line_state != LineState.LF; i++) 
 | 
            { 
 | 
                used++; 
 | 
                byte b = buffer[i]; 
 | 
                if (b == 13) 
 | 
                { 
 | 
                    line_state = LineState.CR; 
 | 
                } 
 | 
                else if (b == 10) 
 | 
                { 
 | 
                    line_state = LineState.LF; 
 | 
                } 
 | 
                else 
 | 
                { 
 | 
                    current_line.Append((char)b); 
 | 
                } 
 | 
            } 
 | 
  
 | 
            string result = null; 
 | 
            if (line_state == LineState.LF) 
 | 
            { 
 | 
                line_state = LineState.None; 
 | 
                result = current_line.ToString(); 
 | 
                current_line.Length = 0; 
 | 
            } 
 | 
  
 | 
            return result; 
 | 
        } 
 | 
  
 | 
        void RemoveConnection() 
 | 
        { 
 | 
            if (last_listener == null) 
 | 
                epl.RemoveConnection(this); 
 | 
            else 
 | 
                last_listener.RemoveConnection(this); 
 | 
        } 
 | 
  
 | 
        void Unbind() 
 | 
        { 
 | 
            if (context_bound) 
 | 
            { 
 | 
                epl.UnbindContext(context); 
 | 
                context_bound = false; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Internal Method 
 | 
  
 | 
        internal void Close(bool force_close) 
 | 
        { 
 | 
            if (sock != null) 
 | 
            { 
 | 
                Stream st = GetResponseStream(); 
 | 
                st.Close(); 
 | 
                o_stream = null; 
 | 
            } 
 | 
  
 | 
            if (sock != null) 
 | 
            { 
 | 
                force_close |= !context.Request.KeepAlive; 
 | 
                if (!force_close) 
 | 
                    force_close = (context.Response.Headers["connection"] == "close"); 
 | 
                /* 
 | 
                if (!force_close) { 
 | 
//                    bool conn_close = (status_code == 400 || status_code == 408 || status_code == 411 || 
 | 
//                            status_code == 413 || status_code == 414 || status_code == 500 || 
 | 
//                            status_code == 503); 
 | 
  
 | 
                    force_close |= (context.Request.ProtocolVersion <= HttpVersion.Version10); 
 | 
                } 
 | 
                */ 
 | 
  
 | 
                if (!force_close && context.Request.FlushInput()) 
 | 
                { 
 | 
                    if (chunked && context.Response.ForceCloseChunked == false) 
 | 
                    { 
 | 
                        // Don't close. Keep working. 
 | 
                        reuses++; 
 | 
                        Unbind(); 
 | 
                        Init(); 
 | 
                        BeginReadRequest(); 
 | 
                        return; 
 | 
                    } 
 | 
  
 | 
                    reuses++; 
 | 
                    Unbind(); 
 | 
                    Init(); 
 | 
                    BeginReadRequest(); 
 | 
                    return; 
 | 
                } 
 | 
  
 | 
                Socket s = sock; 
 | 
                sock = null; 
 | 
                try 
 | 
                { 
 | 
                    if (s != null) 
 | 
                        s.Shutdown(SocketShutdown.Both); 
 | 
                } 
 | 
                catch 
 | 
                { 
 | 
                } 
 | 
                finally 
 | 
                { 
 | 
                    if (s != null) 
 | 
                        s.Close(); 
 | 
                } 
 | 
                Unbind(); 
 | 
                RemoveConnection(); 
 | 
                return; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
  
 | 
        #region Public Methods 
 | 
  
 | 
        public void BeginReadRequest() 
 | 
        { 
 | 
            if (buffer == null) 
 | 
                buffer = new byte[BufferSize]; 
 | 
            try 
 | 
            { 
 | 
                if (reuses == 1) 
 | 
                    s_timeout = 15000; 
 | 
                timer.Change(s_timeout, Timeout.Infinite); 
 | 
                stream.BeginRead(buffer, 0, BufferSize, onread_cb, this); 
 | 
            } 
 | 
            catch 
 | 
            { 
 | 
                timer.Change(Timeout.Infinite, Timeout.Infinite); 
 | 
                CloseSocket(); 
 | 
                Unbind(); 
 | 
            } 
 | 
        } 
 | 
  
 | 
        public void Close() 
 | 
        { 
 | 
            Close(false); 
 | 
        } 
 | 
  
 | 
        public RequestStream GetRequestStream(bool chunked, long contentlength) 
 | 
        { 
 | 
            if (i_stream == null) 
 | 
            { 
 | 
                byte[] buffer = ms.GetBuffer(); 
 | 
                int length = (int)ms.Length; 
 | 
                ms = null; 
 | 
                if (chunked) 
 | 
                { 
 | 
                    this.chunked = true; 
 | 
                    context.Response.SendChunked = true; 
 | 
                    i_stream = new ChunkedInputStream(context, stream, buffer, position, length - position); 
 | 
                } 
 | 
                else 
 | 
                { 
 | 
                    i_stream = new RequestStream(stream, buffer, position, length - position, contentlength); 
 | 
                } 
 | 
            } 
 | 
            return i_stream; 
 | 
        } 
 | 
  
 | 
        public ResponseStream GetResponseStream() 
 | 
        { 
 | 
            // TODO: can we get this stream before reading the input? 
 | 
            if (o_stream == null) 
 | 
            { 
 | 
                HttpListener listener = context.Listener; 
 | 
                bool ign = (listener == null) ? true : listener.IgnoreWriteExceptions; 
 | 
                o_stream = new ResponseStream(stream, context.Response, ign); 
 | 
            } 
 | 
            return o_stream; 
 | 
        } 
 | 
  
 | 
        public void SendError() 
 | 
        { 
 | 
            SendError(context.ErrorMessage, context.ErrorStatus); 
 | 
        } 
 | 
  
 | 
        public void SendError(string msg, int status) 
 | 
        { 
 | 
            try 
 | 
            { 
 | 
                HttpListenerResponse response = context.Response; 
 | 
                response.StatusCode = status; 
 | 
                response.ContentType = "text/html"; 
 | 
                string description = Ext.GetStatusDescription(status); 
 | 
                string str; 
 | 
                if (msg != null) 
 | 
                    str = String.Format("<h1>{0} ({1})</h1>", description, msg); 
 | 
                else 
 | 
                    str = String.Format("<h1>{0}</h1>", description); 
 | 
  
 | 
                byte[] error = context.Response.ContentEncoding.GetBytes(str); 
 | 
                response.Close(error, false); 
 | 
            } 
 | 
            catch 
 | 
            { 
 | 
                // response was already closed 
 | 
            } 
 | 
        } 
 | 
  
 | 
        #endregion 
 | 
    } 
 | 
} 
 |