| // | 
| // 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 | 
|     } | 
| } |