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