123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636 |
- #region License
- /*
- * HttpConnection.cs
- *
- * This code is derived from HttpConnection.cs (System.Net) of Mono
- * (http://www.mono-project.com).
- *
- * The MIT License
- *
- * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
- * Copyright (c) 2012-2022 sta.blockhead
- *
- * 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.
- */
- #endregion
- #region Authors
- /*
- * Authors:
- * - Gonzalo Paniagua Javier <gonzalo@novell.com>
- */
- #endregion
- #region Contributors
- /*
- * Contributors:
- * - Liryna <liryna.stark@gmail.com>
- * - Rohan Singh <rohan-singh@hotmail.com>
- */
- #endregion
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Net;
- using System.Net.Security;
- using System.Net.Sockets;
- using System.Text;
- using System.Threading;
- namespace WebSocketSharp.Net
- {
- internal sealed class HttpConnection
- {
- #region Private Fields
- private int _attempts;
- private byte[] _buffer;
- private static readonly int _bufferLength;
- private HttpListenerContext _context;
- private StringBuilder _currentLine;
- private EndPointListener _endPointListener;
- private InputState _inputState;
- private RequestStream _inputStream;
- private LineState _lineState;
- private EndPoint _localEndPoint;
- private static readonly int _maxInputLength;
- private ResponseStream _outputStream;
- private int _position;
- private EndPoint _remoteEndPoint;
- private MemoryStream _requestBuffer;
- private int _reuses;
- private bool _secure;
- private Socket _socket;
- private Stream _stream;
- private object _sync;
- private int _timeout;
- private Dictionary<int, bool> _timeoutCanceled;
- private Timer _timer;
- #endregion
- #region Static Constructor
- static HttpConnection ()
- {
- _bufferLength = 8192;
- _maxInputLength = 32768;
- }
- #endregion
- #region Internal Constructors
- internal HttpConnection (Socket socket, EndPointListener listener)
- {
- _socket = socket;
- _endPointListener = listener;
- var netStream = new NetworkStream (socket, false);
- if (listener.IsSecure) {
- var sslConf = listener.SslConfiguration;
- var sslStream = new SslStream (
- netStream,
- false,
- sslConf.ClientCertificateValidationCallback
- );
- sslStream.AuthenticateAsServer (
- sslConf.ServerCertificate,
- sslConf.ClientCertificateRequired,
- sslConf.EnabledSslProtocols,
- sslConf.CheckCertificateRevocation
- );
- _secure = true;
- _stream = sslStream;
- }
- else {
- _stream = netStream;
- }
- _buffer = new byte[_bufferLength];
- _localEndPoint = socket.LocalEndPoint;
- _remoteEndPoint = socket.RemoteEndPoint;
- _sync = new object ();
- _timeoutCanceled = new Dictionary<int, bool> ();
- _timer = new Timer (onTimeout, this, Timeout.Infinite, Timeout.Infinite);
- // 90k ms for first request, 15k ms from then on.
- init (new MemoryStream (), 90000);
- }
- #endregion
- #region Public Properties
- public bool IsClosed {
- get {
- return _socket == null;
- }
- }
- public bool IsLocal {
- get {
- return ((IPEndPoint) _remoteEndPoint).Address.IsLocal ();
- }
- }
- public bool IsSecure {
- get {
- return _secure;
- }
- }
- public IPEndPoint LocalEndPoint {
- get {
- return (IPEndPoint) _localEndPoint;
- }
- }
- public IPEndPoint RemoteEndPoint {
- get {
- return (IPEndPoint) _remoteEndPoint;
- }
- }
- public int Reuses {
- get {
- return _reuses;
- }
- }
- public Stream Stream {
- get {
- return _stream;
- }
- }
- #endregion
- #region Private Methods
- private void close ()
- {
- lock (_sync) {
- if (_socket == null)
- return;
- disposeTimer ();
- disposeRequestBuffer ();
- disposeStream ();
- closeSocket ();
- }
- _context.Unregister ();
- _endPointListener.RemoveConnection (this);
- }
- private void closeSocket ()
- {
- try {
- _socket.Shutdown (SocketShutdown.Both);
- }
- catch {
- }
- _socket.Close ();
- _socket = null;
- }
- private static MemoryStream createRequestBuffer (
- RequestStream inputStream
- )
- {
- var ret = new MemoryStream ();
- if (inputStream is ChunkedRequestStream) {
- var crs = (ChunkedRequestStream) inputStream;
- if (crs.HasRemainingBuffer) {
- var buff = crs.RemainingBuffer;
- ret.Write (buff, 0, buff.Length);
- }
- return ret;
- }
- var cnt = inputStream.Count;
- if (cnt > 0)
- ret.Write (inputStream.InitialBuffer, inputStream.Offset, cnt);
- return ret;
- }
- private void disposeRequestBuffer ()
- {
- if (_requestBuffer == null)
- return;
- _requestBuffer.Dispose ();
- _requestBuffer = null;
- }
- private void disposeStream ()
- {
- if (_stream == null)
- return;
- _stream.Dispose ();
- _stream = null;
- }
- private void disposeTimer ()
- {
- if (_timer == null)
- return;
- try {
- _timer.Change (Timeout.Infinite, Timeout.Infinite);
- }
- catch {
- }
- _timer.Dispose ();
- _timer = null;
- }
- private void init (MemoryStream requestBuffer, int timeout)
- {
- _requestBuffer = requestBuffer;
- _timeout = timeout;
- _context = new HttpListenerContext (this);
- _currentLine = new StringBuilder (64);
- _inputState = InputState.RequestLine;
- _inputStream = null;
- _lineState = LineState.None;
- _outputStream = null;
- _position = 0;
- }
- private static void onRead (IAsyncResult asyncResult)
- {
- var conn = (HttpConnection) asyncResult.AsyncState;
- var current = conn._attempts;
- if (conn._socket == null)
- return;
- lock (conn._sync) {
- if (conn._socket == null)
- return;
- conn._timer.Change (Timeout.Infinite, Timeout.Infinite);
- conn._timeoutCanceled[current] = true;
- var nread = 0;
- try {
- nread = conn._stream.EndRead (asyncResult);
- }
- catch (Exception) {
- // TODO: Logging.
- conn.close ();
- return;
- }
- if (nread <= 0) {
- conn.close ();
- return;
- }
- conn._requestBuffer.Write (conn._buffer, 0, nread);
- if (conn.processRequestBuffer ())
- return;
- conn.BeginReadRequest ();
- }
- }
- private static void onTimeout (object state)
- {
- var conn = (HttpConnection) state;
- var current = conn._attempts;
- if (conn._socket == null)
- return;
- lock (conn._sync) {
- if (conn._socket == null)
- return;
- if (conn._timeoutCanceled[current])
- return;
- conn._context.SendError (408);
- }
- }
- private bool processInput (byte[] data, int length)
- {
- // This method returns a bool:
- // - true Done processing
- // - false Need more input
- var req = _context.Request;
- try {
- while (true) {
- int nread;
- var line = readLineFrom (data, _position, length, out nread);
- _position += nread;
- if (line == null)
- break;
- if (line.Length == 0) {
- if (_inputState == InputState.RequestLine)
- continue;
- if (_position > _maxInputLength)
- _context.ErrorMessage = "Headers too long";
- return true;
- }
- if (_inputState == InputState.RequestLine) {
- req.SetRequestLine (line);
- _inputState = InputState.Headers;
- }
- else {
- req.AddHeader (line);
- }
- if (_context.HasErrorMessage)
- return true;
- }
- }
- catch (Exception) {
- // TODO: Logging.
- _context.ErrorMessage = "Processing failure";
- return true;
- }
- if (_position >= _maxInputLength) {
- _context.ErrorMessage = "Headers too long";
- return true;
- }
- return false;
- }
- private bool processRequestBuffer ()
- {
- // This method returns a bool:
- // - true Done processing
- // - false Need more write
- var data = _requestBuffer.GetBuffer ();
- var len = (int) _requestBuffer.Length;
- if (!processInput (data, len))
- return false;
- var req = _context.Request;
- if (!_context.HasErrorMessage)
- req.FinishInitialization ();
- if (_context.HasErrorMessage) {
- _context.SendError ();
- return true;
- }
- var uri = req.Url;
- HttpListener httplsnr;
- if (!_endPointListener.TrySearchHttpListener (uri, out httplsnr)) {
- _context.SendError (404);
- return true;
- }
- httplsnr.RegisterContext (_context);
- return true;
- }
- private string readLineFrom (
- byte[] buffer, int offset, int length, out int nread
- )
- {
- nread = 0;
- for (var i = offset; i < length; i++) {
- nread++;
- var b = buffer[i];
- if (b == 13) {
- _lineState = LineState.Cr;
- continue;
- }
- if (b == 10) {
- _lineState = LineState.Lf;
- break;
- }
- _currentLine.Append ((char) b);
- }
- if (_lineState != LineState.Lf)
- return null;
- var ret = _currentLine.ToString ();
- _currentLine.Length = 0;
- _lineState = LineState.None;
- return ret;
- }
- private MemoryStream takeOverRequestBuffer ()
- {
- if (_inputStream != null)
- return createRequestBuffer (_inputStream);
- var ret = new MemoryStream ();
- var buff = _requestBuffer.GetBuffer ();
- var len = (int) _requestBuffer.Length;
- var cnt = len - _position;
- if (cnt > 0)
- ret.Write (buff, _position, cnt);
- disposeRequestBuffer ();
- return ret;
- }
- #endregion
- #region Internal Methods
- internal void BeginReadRequest ()
- {
- _attempts++;
- _timeoutCanceled.Add (_attempts, false);
- _timer.Change (_timeout, Timeout.Infinite);
- try {
- _stream.BeginRead (_buffer, 0, _bufferLength, onRead, this);
- }
- catch (Exception) {
- // TODO: Logging.
- close ();
- }
- }
- internal void Close (bool force)
- {
- if (_socket == null)
- return;
- lock (_sync) {
- if (_socket == null)
- return;
- if (force) {
- if (_outputStream != null)
- _outputStream.Close (true);
- close ();
- return;
- }
- GetResponseStream ().Close (false);
- if (_context.Response.CloseConnection) {
- close ();
- return;
- }
- if (!_context.Request.FlushInput ()) {
- close ();
- return;
- }
- _context.Unregister ();
- _reuses++;
- var buff = takeOverRequestBuffer ();
- var len = buff.Length;
- init (buff, 15000);
- if (len > 0) {
- if (processRequestBuffer ())
- return;
- }
- BeginReadRequest ();
- }
- }
- #endregion
- #region Public Methods
- public void Close ()
- {
- Close (false);
- }
- public RequestStream GetRequestStream (long contentLength, bool chunked)
- {
- lock (_sync) {
- if (_socket == null)
- return null;
- if (_inputStream != null)
- return _inputStream;
- var buff = _requestBuffer.GetBuffer ();
- var len = (int) _requestBuffer.Length;
- var cnt = len - _position;
- _inputStream = chunked
- ? new ChunkedRequestStream (
- _stream, buff, _position, cnt, _context
- )
- : new RequestStream (
- _stream, buff, _position, cnt, contentLength
- );
- disposeRequestBuffer ();
- return _inputStream;
- }
- }
- public ResponseStream GetResponseStream ()
- {
- lock (_sync) {
- if (_socket == null)
- return null;
- if (_outputStream != null)
- return _outputStream;
- var lsnr = _context.Listener;
- var ignore = lsnr != null ? lsnr.IgnoreWriteExceptions : true;
- _outputStream = new ResponseStream (_stream, _context.Response, ignore);
- return _outputStream;
- }
- }
- #endregion
- }
- }
|