// // System.Net.HttpConnection // // Author: // Gonzalo Paniagua Javier (gonzalo@novell.com) // // Copyright (c) 2005 Novell, Inc. (http://www.novell.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.Net; using System.IO; using System.Net.Sockets; using System.Text; namespace Beagle.Daemon { sealed class HttpConnection { const int BufferSize = 8192; Socket sock; NetworkStream stream; EndPointListener epl; MemoryStream ms; byte [] buffer; HttpListenerContext context; bool secure; StringBuilder current_line; ListenerPrefix prefix; RequestStream i_stream; ResponseStream o_stream; bool chunked; int chunked_uses; bool context_bound; public HttpConnection (Socket sock, EndPointListener epl, bool secure) { this.sock = sock; stream = new NetworkStream (sock, false); this.epl = epl; this.secure = secure; Init (); } 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); } public int ChunkedUses { get { return chunked_uses; } } public IPEndPoint LocalEndPoint { get { return (IPEndPoint) sock.LocalEndPoint; } } public IPEndPoint RemoteEndPoint { get { return (IPEndPoint) sock.RemoteEndPoint; } } public bool IsSecure { get { return secure; } } public ListenerPrefix Prefix { get { return prefix; } set { prefix = value; } } public void BeginReadRequest () { if (buffer == null) buffer = new byte [BufferSize]; stream.BeginRead (buffer, 0, BufferSize, OnRead, this); } 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, sock, buffer, position, length); } else { i_stream = new RequestStream (sock, buffer, position, length, 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; //FIXME: ign is for some reason always true with the mono code. Investigate //for now just set to false, since in Beagle there will always //be a listener attached to the context. bool ign = false; //(listener == null) ? true : listener.IgnoreWriteExceptions; o_stream = new ResponseStream (sock, context.Response, ign); } return o_stream; } void OnRead (IAsyncResult ares) { // TODO: set a limit on ms length. HttpConnection cnc = (HttpConnection) ares.AsyncState; int nread = -1; try { nread = stream.EndRead (ares); ms.Write (buffer, 0, nread); } catch { if (ms.Length > 0) SendError (); sock.Close (); return; } if (nread == 0) { //if (ms.Length > 0) // SendError (); // Why bother? sock.Close (); return; } if (ProcessInput (ms)) { if (!context.HaveError) context.Request.FinishInitialization (); if (context.HaveError) { SendError (); Close (); return; } if (!epl.BindContext (context)) { SendError ("Invalid host", 400); Close (); } context_bound = true; return; } stream.BeginRead (buffer, 0, BufferSize, OnRead, cnc); } enum InputState { RequestLine, Headers } enum LineState { None, CR, LF } InputState input_state = InputState.RequestLine; LineState line_state = LineState.None; int position; // 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; while ((line = ReadLine (buffer, position, len - position, ref used)) != null) { position += used; 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 { context.Request.AddHeader (line); } if (context.HaveError) return true; if (position >= len) break; } 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; } public void SendError (string msg, int status) { HttpListenerResponse response = context.Response; response.StatusCode = status; response.ContentType = "text/html"; string description = HttpListenerResponse.GetStatusDescription (status); string str; if (msg != null) str = String.Format ("

{0} ({1})

", description, msg); else str = String.Format ("

{0}

", description); byte [] error = context.Response.ContentEncoding.GetBytes (str); response.Close (error, false); } public void SendError () { SendError (context.ErrorMessage, context.ErrorStatus); } public void Close () { if (o_stream != null) { Stream st = o_stream; st.Close (); o_stream = null; } if (sock != null) { if (chunked && context.Response.ForceCloseChunked == false) { // Don't close. Keep working. chunked_uses++; Init (); BeginReadRequest (); return; } Socket s = sock; sock = null; s.Shutdown (SocketShutdown.Both); s.Close (); if (context_bound) epl.UnbindContext (context); } } } }