// // 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("