From 11c576a8c7a39de3272b750a46655a253d701e3f Mon Sep 17 00:00:00 2001 From: sta Date: Mon, 1 Jul 2013 12:00:10 +0900 Subject: [PATCH] Refactored some classes in WebSocketSharp.Net namespace --- websocket-sharp/Ext.cs | 13 + websocket-sharp/Net/HttpConnection.cs | 472 ++++++++++--------- websocket-sharp/Net/HttpListenerContext.cs | 116 ++--- websocket-sharp/Net/HttpListenerRequest.cs | 513 +++++++++++---------- 4 files changed, 564 insertions(+), 550 deletions(-) diff --git a/websocket-sharp/Ext.cs b/websocket-sharp/Ext.cs index 6ba24ece..c67192b8 100644 --- a/websocket-sharp/Ext.cs +++ b/websocket-sharp/Ext.cs @@ -507,6 +507,19 @@ namespace WebSocketSharp { } } + internal static string Unquote(this string value) + { + var start = value.IndexOf('\"'); + var end = value.LastIndexOf('\"'); + if (start < end) + { + value = value.Substring(start + 1, end - start - 1) + .Replace("\\\"", "\""); + } + + return value.Trim(); + } + internal static void WriteBytes(this Stream stream, byte[] value) { using (var input = new MemoryStream(value)) diff --git a/websocket-sharp/Net/HttpConnection.cs b/websocket-sharp/Net/HttpConnection.cs index 5bde46cf..2ebf16a9 100644 --- a/websocket-sharp/Net/HttpConnection.cs +++ b/websocket-sharp/Net/HttpConnection.cs @@ -1,3 +1,4 @@ +#region License // // HttpConnection.cs // Copied from System.Net.HttpConnection.cs @@ -6,7 +7,7 @@ // Gonzalo Paniagua Javier (gonzalo@novell.com) // // Copyright (c) 2005 Novell, Inc. (http://www.novell.com) -// Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com) +// Copyright (c) 2012-2013 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 @@ -27,6 +28,7 @@ // 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 using System; using System.IO; @@ -41,7 +43,7 @@ using WebSocketSharp.Net.Security; namespace WebSocketSharp.Net { - sealed class HttpConnection { + internal sealed class HttpConnection { #region Enums @@ -60,170 +62,176 @@ namespace WebSocketSharp.Net { #region Private Const Field - const int BufferSize = 8192; - - #endregion - - #region Private Static Field - - static AsyncCallback onread_cb = new AsyncCallback (OnRead); + private const int BufferSize = 8192; #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; + private byte [] _buffer; + private bool _chunked; + private HttpListenerContext _context; + private bool _contextWasBound; + private StringBuilder _currentLine; + private EndPointListener _epListener; + private InputState _inputState; + private RequestStream _inputStream; + private AsymmetricAlgorithm _key; + private HttpListener _lastListener; + private LineState _lineState; + private ResponseStream _outputStream; + private int _position; + private ListenerPrefix _prefix; + private MemoryStream _requestBuffer; + private int _reuses; + private bool _secure; + private Socket _socket; + private Stream _stream; + private int _timeout; + private Timer _timer; #endregion - #region Constructor + #region Public Constructors public HttpConnection ( - Socket sock, - EndPointListener epl, + Socket socket, + EndPointListener listener, bool secure, X509Certificate2 cert, - AsymmetricAlgorithm key - ) + 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); + _socket = socket; + _epListener = listener; + _secure = secure; + _key = key; + + var netStream = new NetworkStream (socket, false); if (!secure) { - stream = net_stream; + _stream = netStream; } else { - var ssl_stream = new SslStream(net_stream, false); - ssl_stream.AuthenticateAsServer(cert); - stream = ssl_stream; + var sslStream = new SslStream (netStream, false); + sslStream.AuthenticateAsServer (cert); + _stream = sslStream; } - timer = new Timer (OnTimeout, null, Timeout.Infinite, Timeout.Infinite); + + _timer = new Timer (OnTimeout, null, Timeout.Infinite, Timeout.Infinite); Init (); } #endregion - #region Properties + #region Public Properties public bool IsClosed { - get { return (sock == null); } + get { + return _socket == null; + } } public bool IsSecure { - get { return secure; } + get { + return _secure; + } } public IPEndPoint LocalEndPoint { - get { return (IPEndPoint) sock.LocalEndPoint; } + get { + return (IPEndPoint) _socket.LocalEndPoint; + } } public ListenerPrefix Prefix { - get { return prefix; } - set { prefix = value; } + get { + return _prefix; + } + + set { + _prefix = value; + } } public IPEndPoint RemoteEndPoint { - get { return (IPEndPoint) sock.RemoteEndPoint; } + get { + return (IPEndPoint) _socket.RemoteEndPoint; + } } public int Reuses { - get { return reuses; } + get { + return _reuses; + } } public Stream Stream { - get { return stream; } + get { + return _stream; + } } #endregion #region Private Methods - void CloseSocket () + private void CloseSocket () { - if (sock == null) + if (_socket == null) return; try { - sock.Close (); + _socket.Close (); } catch { } finally { - sock = null; + _socket = null; } + RemoveConnection (); } - void Init () + private 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 + _chunked = false; + _context = new HttpListenerContext (this); + _contextWasBound = false; + _inputState = InputState.RequestLine; + _inputStream = null; + _lineState = LineState.None; + _outputStream = null; + _position = 0; + _prefix = null; + _requestBuffer = new MemoryStream (); + _timeout = 90000; // 90k ms for first request, 15k ms from then on. } - AsymmetricAlgorithm OnPVKSelection (X509Certificate certificate, string targetHost) + private AsymmetricAlgorithm OnPVKSelection (X509Certificate certificate, string targetHost) { - return key; + return _key; } - static void OnRead (IAsyncResult ares) + private static void OnRead (IAsyncResult asyncResult) { - HttpConnection cnc = (HttpConnection) ares.AsyncState; - cnc.OnReadInternal (ares); + var conn = (HttpConnection) asyncResult.AsyncState; + conn.OnReadInternal (asyncResult); } - void OnReadInternal (IAsyncResult ares) + private void OnReadInternal (IAsyncResult asyncResult) { - timer.Change (Timeout.Infinite, Timeout.Infinite); - int nread = -1; + _timer.Change (Timeout.Infinite, Timeout.Infinite); + var nread = -1; try { - nread = stream.EndRead (ares); - ms.Write (buffer, 0, nread); - if (ms.Length > 32768) { - SendError ("Bad request", 400); + nread = _stream.EndRead (asyncResult); + _requestBuffer.Write (_buffer, 0, nread); + if (_requestBuffer.Length > 32768) { + SendError (); Close (true); + return; } } catch { - if (ms != null && ms.Length > 0) + if (_requestBuffer != null && _requestBuffer.Length > 0) SendError (); - if (sock != null) { + if (_socket != null) { CloseSocket (); Unbind (); } @@ -232,156 +240,140 @@ namespace WebSocketSharp.Net { } if (nread == 0) { - //if (ms.Length > 0) + //if (_requestBuffer.Length > 0) // SendError (); // Why bother? CloseSocket (); Unbind (); + return; } - if (ProcessInput (ms)) { - if (!context.HaveError) - context.Request.FinishInitialization (); + if (ProcessInput (_requestBuffer.GetBuffer ())) { + if (!_context.HaveError) + _context.Request.FinishInitialization (); - if (context.HaveError) { + if (_context.HaveError) { SendError (); Close (true); + return; } - if (!epl.BindContext (context)) { + if (!_epListener.BindContext (_context)) { SendError ("Invalid host", 400); Close (true); + return; } - HttpListener listener = context.Listener; - if (last_listener != listener) { + var listener = _context.Listener; + if (_lastListener != listener) { RemoveConnection (); listener.AddConnection (this); - last_listener = listener; + _lastListener = listener; } - context_bound = true; - listener.RegisterContext (context); + listener.RegisterContext (_context); + _contextWasBound = true; + return; } - stream.BeginRead (buffer, 0, BufferSize, onread_cb, this); + _stream.BeginRead (_buffer, 0, BufferSize, OnRead, this); } - void OnTimeout (object unused) + private void OnTimeout (object unused) { CloseSocket (); Unbind (); } - // true -> done processing - // false -> need more input - bool ProcessInput (MemoryStream ms) + // true -> Done processing. + // false -> Need more input. + private bool ProcessInput (byte [] data) { - byte [] buffer = ms.GetBuffer (); - int len = (int) ms.Length; - int used = 0; + var length = data.Length; + var used = 0; string line; - try { - line = ReadLine (buffer, position, len - position, ref used); - position += used; - } catch { - context.ErrorMessage = "Bad request"; - context.ErrorStatus = 400; + while ((line = ReadLine (data, _position, length - _position, ref used)) != null) { + _position += used; + if (line.Length == 0) { + if (_inputState == InputState.RequestLine) + continue; + + _currentLine = null; + return true; + } + + if (_inputState == InputState.RequestLine) { + _context.Request.SetRequestLine (line); + _inputState = InputState.Headers; + } else { + _context.Request.AddHeader (line); + } + + if (_context.HaveError) + return true; + } + } catch (Exception e) { + _context.ErrorMessage = e.Message; 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; + _position += used; + if (used == length) { + _requestBuffer.SetLength (0); + _position = 0; } + return false; } - string ReadLine (byte [] buffer, int offset, int len, ref int used) + private string ReadLine (byte [] buffer, int offset, int length, ref int used) { - if (current_line == null) - current_line = new StringBuilder (); + if (_currentLine == null) + _currentLine = new StringBuilder (); - int last = offset + len; + var last = offset + length; used = 0; - for (int i = offset; i < last && line_state != LineState.LF; i++) { + for (int i = offset; i < last && _lineState != LineState.LF; i++) { used++; - byte b = buffer [i]; + var b = buffer [i]; if (b == 13) { - line_state = LineState.CR; - } else if (b == 10) { - line_state = LineState.LF; - } else { - current_line.Append ((char) b); + _lineState = LineState.CR; + } + else if (b == 10) { + _lineState = LineState.LF; + } + else { + _currentLine.Append ((char) b); } } string result = null; - if (line_state == LineState.LF) { - line_state = LineState.None; - result = current_line.ToString (); - current_line.Length = 0; + if (_lineState == LineState.LF) { + _lineState = LineState.None; + result = _currentLine.ToString (); + _currentLine.Length = 0; } return result; } - void RemoveConnection () + private void RemoveConnection () { - if (last_listener == null) - epl.RemoveConnection (this); + if (_lastListener == null) + _epListener.RemoveConnection (this); else - last_listener.RemoveConnection (this); + _lastListener.RemoveConnection (this); } - void Unbind () + private void Unbind () { - if (context_bound) { - epl.UnbindContext (context); - context_bound = false; + if (_contextWasBound) { + _epListener.UnbindContext (_context); + _contextWasBound = false; } } @@ -389,49 +381,51 @@ namespace WebSocketSharp.Net { #region Internal Method - internal void Close (bool force_close) + internal void Close (bool force) { - if (sock != null) { - Stream st = GetResponseStream (); - st.Close (); - o_stream = null; - } + if (_socket != null) { + if (_outputStream != null) { + _outputStream.Close (); + _outputStream = null; + } - if (sock != null) { - force_close |= !context.Request.KeepAlive; - if (!force_close) - force_close = (context.Response.Headers ["connection"] == "close"); + force |= !_context.Request.KeepAlive; + if (!force) + force = _context.Response.Headers ["Connection"] == "close"; - if (!force_close && context.Request.FlushInput ()) { - if (chunked && context.Response.ForceCloseChunked == false) { + if (!force && _context.Request.FlushInput ()) { + if (_chunked && !_context.Response.ForceCloseChunked) { // Don't close. Keep working. - reuses++; + _reuses++; Unbind (); Init (); BeginReadRequest (); + return; } - reuses++; - Unbind (); - Init (); - BeginReadRequest (); - return; +// _reuses++; +// Unbind (); +// Init (); +// BeginReadRequest (); +// +// return; } - Socket s = sock; - sock = null; + var socket = _socket; + _socket = null; try { - if (s != null) - s.Shutdown (SocketShutdown.Both); + if (socket != null) + socket.Shutdown (SocketShutdown.Both); } catch { } finally { - if (s != null) - s.Close (); + if (socket != null) + socket.Close (); } Unbind (); RemoveConnection (); + return; } } @@ -442,17 +436,17 @@ namespace WebSocketSharp.Net { public void BeginReadRequest () { - if (buffer == null) - buffer = new byte [BufferSize]; + if (_buffer == null) + _buffer = new byte [BufferSize]; try { - if (reuses == 1) - s_timeout = 15000; + if (_reuses == 1) + _timeout = 15000; - timer.Change (s_timeout, Timeout.Infinite); - stream.BeginRead (buffer, 0, BufferSize, onread_cb, this); + _timer.Change (_timeout, Timeout.Infinite); + _stream.BeginRead (_buffer, 0, BufferSize, OnRead, this); } catch { - timer.Change (Timeout.Infinite, Timeout.Infinite); + _timer.Change (Timeout.Infinite, Timeout.Infinite); CloseSocket (); Unbind (); } @@ -465,56 +459,54 @@ namespace WebSocketSharp.Net { public RequestStream GetRequestStream (bool chunked, long contentlength) { - if (i_stream == null) { - byte [] buffer = ms.GetBuffer (); - int length = (int) ms.Length; - ms = null; + if (_inputStream == null) { + var buffer = _requestBuffer.GetBuffer (); + var length = buffer.Length; + _requestBuffer = null; if (chunked) { - this.chunked = true; - context.Response.SendChunked = true; - i_stream = new ChunkedInputStream (context, stream, buffer, position, length - position); + _chunked = true; + _context.Response.SendChunked = true; + _inputStream = new ChunkedInputStream (_context, _stream, buffer, _position, length - _position); } else { - i_stream = new RequestStream (stream, buffer, position, length - position, contentlength); + _inputStream = new RequestStream (_stream, buffer, _position, length - _position, contentlength); } } - return i_stream; + return _inputStream; } 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); + // TODO: Can we get this stream before reading the input? + if (_outputStream == null) { + var listener = _context.Listener; + var ignore = listener == null ? true : listener.IgnoreWriteExceptions; + _outputStream = new ResponseStream (_stream, _context.Response, ignore); } - return o_stream; + return _outputStream; } public void SendError () { - SendError (context.ErrorMessage, context.ErrorStatus); + SendError (_context.ErrorMessage, _context.ErrorStatus); } - public void SendError (string msg, int status) + public void SendError (string message, int status) { try { - HttpListenerResponse response = context.Response; + var response = _context.Response; response.StatusCode = status; response.ContentType = "text/html"; - string description = status.GetStatusDescription (); - string str; - if (msg != null) - str = String.Format ("

{0} ({1})

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

{0}

", description); + var description = status.GetStatusDescription (); + var error = !message.IsNullOrEmpty () + ? String.Format ("

{0} ({1})

", description, message) + : String.Format ("

{0}

", description); - byte [] error = context.Response.ContentEncoding.GetBytes (str); - response.Close (error, false); + var entity = _context.Response.ContentEncoding.GetBytes (error); + response.Close (entity, false); } catch { - // response was already closed + // Response was already closed. } } diff --git a/websocket-sharp/Net/HttpListenerContext.cs b/websocket-sharp/Net/HttpListenerContext.cs index 77e5cd09..d178db87 100644 --- a/websocket-sharp/Net/HttpListenerContext.cs +++ b/websocket-sharp/Net/HttpListenerContext.cs @@ -1,3 +1,4 @@ +#region License // // HttpListenerContext.cs // Copied from System.Net.HttpListenerContext.cs @@ -27,6 +28,7 @@ // 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 using System; using System.Collections.Specialized; @@ -47,12 +49,12 @@ namespace WebSocketSharp.Net { #region Private Fields - HttpConnection cnc; - string error; - int err_status; - HttpListenerRequest request; - HttpListenerResponse response; - IPrincipal user; + private HttpConnection _connection; + private string _error; + private int _errorStatus; + private HttpListenerRequest _request; + private HttpListenerResponse _response; + private IPrincipal _user; #endregion @@ -64,12 +66,12 @@ namespace WebSocketSharp.Net { #region Constructor - internal HttpListenerContext (HttpConnection cnc) + internal HttpListenerContext (HttpConnection connection) { - this.cnc = cnc; - err_status = 400; - request = new HttpListenerRequest (this); - response = new HttpListenerResponse (this); + _connection = connection; + _errorStatus = 400; + _request = new HttpListenerRequest (this); + _response = new HttpListenerResponse (this); } #endregion @@ -77,21 +79,35 @@ namespace WebSocketSharp.Net { #region Internal Properties internal HttpConnection Connection { - get { return cnc; } + get { + return _connection; + } } internal string ErrorMessage { - get { return error; } - set { error = value; } + get { + return _error; + } + + set { + _error = value; + } } internal int ErrorStatus { - get { return err_status; } - set { err_status = value; } + get { + return _errorStatus; + } + + set { + _errorStatus = value; + } } internal bool HaveError { - get { return (error != null); } + get { + return _error != null; + } } #endregion @@ -105,7 +121,9 @@ namespace WebSocketSharp.Net { /// A that contains the HTTP request objects. /// public HttpListenerRequest Request { - get { return request; } + get { + return _request; + } } /// @@ -116,7 +134,9 @@ namespace WebSocketSharp.Net { /// A that contains the HTTP response objects. /// public HttpListenerResponse Response { - get { return response; } + get { + return _response; + } } /// @@ -126,7 +146,9 @@ namespace WebSocketSharp.Net { /// A contains the client information. /// public IPrincipal User { - get { return user; } + get { + return _user; + } } #endregion @@ -138,54 +160,40 @@ namespace WebSocketSharp.Net { if (expectedSchemes == AuthenticationSchemes.Anonymous) return; - // TODO: Handle NTLM/Digest modes - string header = request.Headers ["Authorization"]; + // TODO: Handle NTLM/Digest modes. + var header = _request.Headers ["Authorization"]; if (header == null || header.Length < 2) return; - string [] authenticationData = header.Split (new char [] {' '}, 2); - if (string.Compare (authenticationData [0], "basic", true) == 0) { - user = ParseBasicAuthentication (authenticationData [1]); - } - // TODO: throw if malformed -> 400 bad request + var authData = header.Split (new char [] {' '}, 2); + if (authData [0].ToLower () == "basic") + _user = ParseBasicAuthentication (authData [1]); + + // TODO: Throw if malformed -> 400 bad request. } internal IPrincipal ParseBasicAuthentication (string authData) { try { - // Basic AUTH Data is a formatted Base64 String - //string domain = null; - string user = null; - string password = null; - int pos = -1; - string authString = Encoding.Default.GetString (Convert.FromBase64String (authData)); + // HTTP Basic Authentication data is a formatted Base64 string. + var authString = Encoding.Default.GetString (Convert.FromBase64String (authData)); - // The format is DOMAIN\username:password - // Domain is optional + // The format is domain\username:password. + // Domain is optional. - pos = authString.IndexOf (':'); + var pos = authString.IndexOf (':'); + var user = authString.Substring (0, pos); + var password = authString.Substring (pos + 1); - // parse the password off the end - password = authString.Substring (pos+1); - - // discard the password - authString = authString.Substring (0, pos); - - // check if there is a domain - pos = authString.IndexOf ('\\'); - - if (pos > 0) { - //domain = authString.Substring (0, pos); - user = authString.Substring (pos); - } else { - user = authString; - } + // Check if there is a domain. + pos = user.IndexOf ('\\'); + if (pos > 0) + user = user.Substring (pos + 1); var identity = new System.Net.HttpListenerBasicIdentity (user, password); - // TODO: What are the roles MS sets + // TODO: What are the roles MS sets? return new GenericPrincipal (identity, new string [0]); - } catch (Exception) { - // Invalid auth data is swallowed silently + } catch { return null; } } diff --git a/websocket-sharp/Net/HttpListenerRequest.cs b/websocket-sharp/Net/HttpListenerRequest.cs index c9278e2f..50b6c2b8 100644 --- a/websocket-sharp/Net/HttpListenerRequest.cs +++ b/websocket-sharp/Net/HttpListenerRequest.cs @@ -1,3 +1,4 @@ +#region License // // HttpListenerRequest.cs // Copied from System.Net.HttpListenerRequest.cs @@ -27,12 +28,14 @@ // 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 using System; -using System.Collections; +using System.Collections.Generic; using System.Collections.Specialized; using System.Globalization; using System.IO; +using System.Linq; using System.Net; using System.Security.Cryptography.X509Certificates; using System.Text; @@ -49,58 +52,60 @@ namespace WebSocketSharp.Net { #region Private Static Fields - static char [] separators = new char [] { ' ' }; - static byte [] _100continue = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n"); + private static byte [] _100continue = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n"); #endregion #region Private Fields - string [] accept_types; -// int client_cert_error; - bool cl_set; - Encoding content_encoding; - long content_length; - HttpListenerContext context; - CookieCollection cookies; - WebHeaderCollection headers; - Stream input_stream; - bool is_chunked; - bool ka_set; - bool keep_alive; - string method; -// bool no_get_certificate; - Version version; - NameValueCollection query_string; // check if null is ok, check if read-only, check case-sensitiveness - string raw_url; - Uri referrer; - Uri url; - string [] user_languages; + private string [] _acceptTypes; + private bool _chunked; + private Encoding _contentEncoding; + private long _contentLength; + private bool _contentLengthWasSet; + private HttpListenerContext _context; + private CookieCollection _cookies; + private WebHeaderCollection _headers; + private Guid _identifier; + private Stream _inputStream; + private bool _keepAlive; + private bool _keepAliveWasSet; + private string _method; + private NameValueCollection _queryString; + private string _rawUrl; + private Uri _referer; + private Uri _url; + private string [] _userLanguages; + private Version _version; #endregion - #region Constructor + #region Internal Constructors internal HttpListenerRequest (HttpListenerContext context) { - this.context = context; - headers = new WebHeaderCollection (); - version = HttpVersion.Version10; + _context = context; + _contentLength = -1; + _headers = new WebHeaderCollection (); + _identifier = Guid.NewGuid (); + _version = HttpVersion.Version10; } #endregion - #region Properties + #region Public Properties /// /// Gets the media types which are acceptable for the response. /// /// - /// An array of that contains the media type names in the Accept request-header field + /// An array of that contains the media type names in the Accept request-header /// or if the request did not include an Accept header. /// public string [] AcceptTypes { - get { return accept_types; } + get { + return _acceptTypes; + } } /// @@ -110,12 +115,13 @@ namespace WebSocketSharp.Net { /// Always returns 0. /// public int ClientCertificateError { - // TODO: Always returns 0 get { + // TODO: Always returns 0. /* if (no_get_certificate) throw new InvalidOperationException ( - "Call GetClientCertificate() before calling this method."); + "Call GetClientCertificate method before accessing this property."); + return client_cert_error; */ return 0; @@ -123,17 +129,18 @@ namespace WebSocketSharp.Net { } /// - /// Gets the encoding that can be used with the entity body data included in the request. + /// Gets the encoding used with the entity body data included in the request. /// /// - /// A that contains the encoding that can be used with the entity body data. + /// A that indicates the encoding used with the entity body data + /// or if the request did not include the information about the encoding. /// public Encoding ContentEncoding { - // TODO: Always returns Encoding.Default get { - if (content_encoding == null) - content_encoding = Encoding.Default; - return content_encoding; + if (_contentEncoding == null) + _contentEncoding = Encoding.Default; + + return _contentEncoding; } } @@ -141,21 +148,25 @@ namespace WebSocketSharp.Net { /// Gets the size of the entity body data included in the request. /// /// - /// A that contains the value of the Content-Length entity-header field. + /// A that contains the value of the Content-Length entity-header. /// The value is a number of bytes in the entity body data. -1 if the size is not known. /// public long ContentLength64 { - get { return content_length; } + get { + return _contentLength; + } } /// /// Gets the media type of the entity body included in the request. /// /// - /// A that contains the value of the Content-Type entity-header field. + /// A that contains the value of the Content-Type entity-header. /// public string ContentType { - get { return headers ["content-type"]; } + get { + return _headers ["Content-Type"]; + } } /// @@ -166,10 +177,10 @@ namespace WebSocketSharp.Net { /// public CookieCollection Cookies { get { - // TODO: check if the collection is read-only - if (cookies == null) - cookies = new CookieCollection (); - return cookies; + if (_cookies == null) + _cookies = _headers.GetCookies (false); + + return _cookies; } } @@ -180,7 +191,9 @@ namespace WebSocketSharp.Net { /// true if the request has the entity body; otherwise, false. /// public bool HasEntityBody { - get { return (content_length > 0 || is_chunked); } + get { + return _contentLength > 0 || _chunked; + } } /// @@ -190,7 +203,9 @@ namespace WebSocketSharp.Net { /// A that contains the HTTP headers used in the request. /// public NameValueCollection Headers { - get { return headers; } + get { + return _headers; + } } /// @@ -200,7 +215,9 @@ namespace WebSocketSharp.Net { /// A that contains the HTTP method used in the request. /// public string HttpMethod { - get { return method; } + get { + return _method; + } } /// @@ -211,14 +228,12 @@ namespace WebSocketSharp.Net { /// public Stream InputStream { get { - if (input_stream == null) { - if (is_chunked || content_length > 0) - input_stream = context.Connection.GetRequestStream (is_chunked, content_length); - else - input_stream = Stream.Null; - } + if (_inputStream == null) + _inputStream = HasEntityBody + ? _context.Connection.GetRequestStream (_chunked, _contentLength) + : Stream.Null; - return input_stream; + return _inputStream; } } @@ -229,8 +244,10 @@ namespace WebSocketSharp.Net { /// Always returns false. /// public bool IsAuthenticated { - // TODO: Always returns false - get { return false; } + get { + // TODO: Always returns false. + return false; + } } /// @@ -240,7 +257,9 @@ namespace WebSocketSharp.Net { /// true if the request is sent from the local computer; otherwise, false. /// public bool IsLocal { - get { return RemoteEndPoint.Address.IsLocal(); } + get { + return RemoteEndPoint.Address.IsLocal (); + } } /// @@ -250,7 +269,9 @@ namespace WebSocketSharp.Net { /// true if the HTTP connection is secured; otherwise, false. /// public bool IsSecureConnection { - get { return context.Connection.IsSecure; } + get { + return _context.Connection.IsSecure; + } } /// @@ -261,19 +282,19 @@ namespace WebSocketSharp.Net { /// public bool IsWebSocketRequest { get { - return method != "GET" + return _method != "GET" ? false - : version < HttpVersion.Version11 + : _version < HttpVersion.Version11 ? false - : !headers.Contains("Upgrade", "websocket") + : !_headers.Contains("Upgrade", "websocket") ? false - : !headers.Contains("Connection", "Upgrade") + : !_headers.Contains("Connection", "Upgrade") ? false - : !headers.Contains("Host") + : !_headers.Contains("Host") ? false - : !headers.Contains("Sec-WebSocket-Key") + : !_headers.Contains("Sec-WebSocket-Key") ? false - : headers.Contains("Sec-WebSocket-Version"); + : _headers.Contains("Sec-WebSocket-Version"); } } @@ -285,24 +306,17 @@ namespace WebSocketSharp.Net { /// public bool KeepAlive { get { - if (ka_set) - return keep_alive; + if (!_keepAliveWasSet) { + _keepAlive = _headers.Contains ("Connection", "keep-alive") || _version == HttpVersion.Version11 + ? true + : _headers.Contains ("Keep-Alive") + ? !_headers.Contains ("Keep-Alive", "closed") + : false; - ka_set = true; - // 1. Connection header - // 2. Protocol (1.1 == keep-alive by default) - // 3. Keep-Alive header - string cnc = headers ["Connection"]; - if (!String.IsNullOrEmpty (cnc)) { - keep_alive = (0 == String.Compare (cnc, "keep-alive", StringComparison.OrdinalIgnoreCase)); - } else if (version == HttpVersion.Version11) { - keep_alive = true; - } else { - cnc = headers ["keep-alive"]; - if (!String.IsNullOrEmpty (cnc)) - keep_alive = (0 != String.Compare (cnc, "closed", StringComparison.OrdinalIgnoreCase)); + _keepAliveWasSet = true; } - return keep_alive; + + return _keepAlive; } } @@ -313,7 +327,9 @@ namespace WebSocketSharp.Net { /// A that contains the server endpoint. /// public IPEndPoint LocalEndPoint { - get { return context.Connection.LocalEndPoint; } + get { + return _context.Connection.LocalEndPoint; + } } /// @@ -323,7 +339,9 @@ namespace WebSocketSharp.Net { /// A that contains the HTTP version used in the request. /// public Version ProtocolVersion { - get { return version; } + get { + return _version; + } } /// @@ -333,7 +351,9 @@ namespace WebSocketSharp.Net { /// A that contains the collection of query string variables used in the request. /// public NameValueCollection QueryString { - get { return query_string; } + get { + return _queryString; + } } /// @@ -343,7 +363,9 @@ namespace WebSocketSharp.Net { /// A that contains the raw URL requested by the client. /// public string RawUrl { - get { return raw_url; } + get { + return _rawUrl; + } } /// @@ -353,18 +375,21 @@ namespace WebSocketSharp.Net { /// A that contains the client endpoint. /// public IPEndPoint RemoteEndPoint { - get { return context.Connection.RemoteEndPoint; } + get { + return _context.Connection.RemoteEndPoint; + } } /// - /// Gets the identifier of a request. + /// Gets the request identifier of a incoming HTTP request. /// /// /// A that contains the identifier of a request. /// public Guid RequestTraceIdentifier { - // TODO: Always returns Guid.Empty - get { return Guid.Empty; } + get { + return _identifier; + } } /// @@ -374,27 +399,34 @@ namespace WebSocketSharp.Net { /// A that contains the URL requested by the client. /// public Uri Url { - get { return url; } + get { + return _url; + } } /// /// Gets the URL of the resource from which the requested URL was obtained. /// /// - /// A that contains the value of the Referer request-header field. + /// A that contains the value of the Referer request-header + /// or if the request did not include an Referer header. /// public Uri UrlReferrer { - get { return referrer; } + get { + return _referer; + } } /// /// Gets the information about the user agent originating the request. /// /// - /// A that contains the value of the User-Agent request-header field. + /// A that contains the value of the User-Agent request-header. /// public string UserAgent { - get { return headers ["user-agent"]; } + get { + return _headers ["User-Agent"]; + } } /// @@ -404,52 +436,60 @@ namespace WebSocketSharp.Net { /// A that contains the server endpoint. /// public string UserHostAddress { - get { return LocalEndPoint.ToString (); } + get { + return LocalEndPoint.ToString (); + } } /// - /// Gets the internet host name and port number (if present) of the resource being requested. + /// Gets the internet host name and port number (if present) specified by the client. /// /// - /// A that contains the value of the Host request-header field. + /// A that contains the value of the Host request-header. /// public string UserHostName { - get { return headers ["host"]; } + get { + return _headers ["Host"]; + } } /// - /// Gets the natural languages that are preferred as a response to the request. + /// Gets the natural languages which are preferred for the response. /// /// - /// An array of that contains the natural language names in the Accept-Language request-header field. + /// An array of that contains the natural language names in the Accept-Language request-header + /// or if the request did not include an Accept-Language header. /// public string [] UserLanguages { - get { return user_languages; } + get { + return _userLanguages; + } } #endregion #region Private Methods - void CreateQueryString (string query) + private void CreateQueryString (string query) { if (query == null || query.Length == 0) { - query_string = new NameValueCollection (1); + _queryString = new NameValueCollection (1); return; } - query_string = new NameValueCollection (); + _queryString = new NameValueCollection (); if (query [0] == '?') query = query.Substring (1); - string [] components = query.Split ('&'); - foreach (string kv in components) { - int pos = kv.IndexOf ('='); + + var components = query.Split ('&'); + foreach (var kv in components) { + var pos = kv.IndexOf ('='); if (pos == -1) { - query_string.Add (null, HttpUtility.UrlDecode (kv)); + _queryString.Add (null, HttpUtility.UrlDecode (kv)); } else { - string key = HttpUtility.UrlDecode (kv.Substring (0, pos)); - string val = HttpUtility.UrlDecode (kv.Substring (pos + 1)); - query_string.Add (key, val); + var key = HttpUtility.UrlDecode (kv.Substring (0, pos)); + var val = HttpUtility.UrlDecode (kv.Substring (pos + 1)); + _queryString.Add (key, val); } } } @@ -460,164 +500,143 @@ namespace WebSocketSharp.Net { internal void AddHeader (string header) { - int colon = header.IndexOf (':'); - if (colon == -1 || colon == 0) { - context.ErrorMessage = "Bad Request"; - context.ErrorStatus = 400; + var colon = header.IndexOf (':'); + if (colon <= 0) { + _context.ErrorMessage = "Invalid header"; return; } - string name = header.Substring (0, colon).Trim (); - string val = header.Substring (colon + 1).Trim (); - string lower = name.ToLower (CultureInfo.InvariantCulture); - headers.SetInternal (name, val, false); - switch (lower) { - case "accept-language": - user_languages = val.Split (','); // yes, only split with a ',' - break; - case "accept": - accept_types = val.Split (','); // yes, only split with a ',' - break; - case "content-length": - try { - //TODO: max. content_length? - content_length = Int64.Parse (val.Trim ()); - if (content_length < 0) - context.ErrorMessage = "Invalid Content-Length."; - cl_set = true; - } catch { - context.ErrorMessage = "Invalid Content-Length."; - } + var name = header.Substring (0, colon).Trim (); + var val = header.Substring (colon + 1).Trim (); + var lower = name.ToLower (CultureInfo.InvariantCulture); + _headers.SetInternal (name, val, false); - break; - case "referer": - try { - referrer = new Uri (val); - } catch { - referrer = new Uri ("http://someone.is.screwing.with.the.headers.com/"); - } - break; - case "cookie": - if (cookies == null) - cookies = new CookieCollection(); - - string[] cookieStrings = val.Split(new char[] {',', ';'}); - Cookie current = null; - int version = 0; - foreach (string cookieString in cookieStrings) { - string str = cookieString.Trim (); - if (str.Length == 0) - continue; - if (str.StartsWith ("$Version")) { - version = Int32.Parse (Unquote (str.Substring (str.IndexOf ('=') + 1))); - } else if (str.StartsWith ("$Path")) { - if (current != null) - current.Path = str.Substring (str.IndexOf ('=') + 1).Trim (); - } else if (str.StartsWith ("$Domain")) { - if (current != null) - current.Domain = str.Substring (str.IndexOf ('=') + 1).Trim (); - } else if (str.StartsWith ("$Port")) { - if (current != null) - current.Port = str.Substring (str.IndexOf ('=') + 1).Trim (); - } else { - if (current != null) { - cookies.Add (current); - } - current = new Cookie (); - int idx = str.IndexOf ('='); - if (idx > 0) { - current.Name = str.Substring (0, idx).Trim (); - current.Value = str.Substring (idx + 1).Trim (); - } else { - current.Name = str.Trim (); - current.Value = String.Empty; - } - current.Version = version; - } - } - if (current != null) { - cookies.Add (current); - } - break; + if (lower == "accept") { + _acceptTypes = val.SplitHeaderValue (',').ToArray (); + return; } + + if (lower == "accept-language") { + _userLanguages = val.Split (','); + return; + } + + if (lower == "content-length") { + long length; + if (Int64.TryParse (val, out length) && length >= 0) { + _contentLength = length; + _contentLengthWasSet = true; + } else { + _context.ErrorMessage = "Invalid Content-Length header"; + } + + return; + } + + if (lower == "content-type") { + var contents = val.Split (';'); + foreach (var content in contents) { + var tmp = content.Trim (); + if (tmp.StartsWith ("charset")) { + var charset = tmp.GetValue ("="); + if (!charset.IsNullOrEmpty ()) { + try { + _contentEncoding = Encoding.GetEncoding (charset); + } catch { + _context.ErrorMessage = "Invalid Content-Type header"; + } + } + + break; + } + } + + return; + } + + if (lower == "referer") + _referer = val.ToUri (); } internal void FinishInitialization () { - string host = UserHostName; - if (version > HttpVersion.Version10 && (host == null || host.Length == 0)) { - context.ErrorMessage = "Invalid host name"; + var host = UserHostName; + if (_version > HttpVersion.Version10 && host.IsNullOrEmpty ()) { + _context.ErrorMessage = "Invalid Host header"; return; } - string path; - Uri raw_uri = null; - if (raw_url.MaybeUri () && Uri.TryCreate (raw_url, UriKind.Absolute, out raw_uri)) - path = raw_uri.PathAndQuery; - else - path = HttpUtility.UrlDecode (raw_url); + Uri rawUri = null; + var path = _rawUrl.MaybeUri () && Uri.TryCreate (_rawUrl, UriKind.Absolute, out rawUri) + ? rawUri.PathAndQuery + : HttpUtility.UrlDecode (_rawUrl); - if ((host == null || host.Length == 0)) + if (host.IsNullOrEmpty ()) host = UserHostAddress; - if (raw_uri != null) - host = raw_uri.Host; + if (rawUri != null) + host = rawUri.Host; - int colon = host.IndexOf (':'); + var colon = host.IndexOf (':'); if (colon >= 0) host = host.Substring (0, colon); - string base_uri = String.Format ("{0}://{1}:{2}", - (IsSecureConnection) ? "https" : "http", - host, - LocalEndPoint.Port); + var baseUri = String.Format ("{0}://{1}:{2}", + IsSecureConnection ? "https" : "http", + host, + LocalEndPoint.Port); - if (!Uri.TryCreate (base_uri + path, UriKind.Absolute, out url)){ - context.ErrorMessage = "Invalid url: " + base_uri + path; + if (!Uri.TryCreate (baseUri + path, UriKind.Absolute, out _url)) { + _context.ErrorMessage = "Invalid request url: " + baseUri + path; return; } - CreateQueryString (url.Query); + CreateQueryString (_url.Query); - if (version >= HttpVersion.Version11) { - string t_encoding = Headers ["Transfer-Encoding"]; - is_chunked = (t_encoding != null && String.Compare (t_encoding, "chunked", StringComparison.OrdinalIgnoreCase) == 0); + var encoding = Headers ["Transfer-Encoding"]; + if (_version >= HttpVersion.Version11 && !encoding.IsNullOrEmpty ()) { + _chunked = encoding.ToLower () == "chunked"; // 'identity' is not valid! - if (t_encoding != null && !is_chunked) { - context.Connection.SendError (null, 501); + if (!_chunked) { + _context.ErrorMessage = String.Empty; + _context.ErrorStatus = 501; + return; } } - if (!is_chunked && !cl_set) { - if (String.Compare (method, "POST", StringComparison.OrdinalIgnoreCase) == 0 || - String.Compare (method, "PUT", StringComparison.OrdinalIgnoreCase) == 0) { - context.Connection.SendError (null, 411); + if (!_chunked && !_contentLengthWasSet) { + var method = _method.ToLower (); + if (method == "post" || method == "put") { + _context.ErrorMessage = String.Empty; + _context.ErrorStatus = 411; + return; } } - if (String.Compare (Headers ["Expect"], "100-continue", StringComparison.OrdinalIgnoreCase) == 0) { - ResponseStream output = context.Connection.GetResponseStream (); + var expect = Headers ["Expect"]; + if (!expect.IsNullOrEmpty () && expect.ToLower () == "100-continue") { + var output = _context.Connection.GetResponseStream (); output.InternalWrite (_100continue, 0, _100continue.Length); } } - // returns true is the stream could be reused. + // Returns true is the stream could be reused. internal bool FlushInput () { if (!HasEntityBody) return true; - int length = 2048; - if (content_length > 0) - length = (int) Math.Min (content_length, (long) length); + var length = 2048; + if (_contentLength > 0) + length = (int) Math.Min (_contentLength, (long) length); - byte [] bytes = new byte [length]; + var buffer = new byte [length]; while (true) { - // TODO: test if MS has a timeout when doing this + // TODO: Test if MS has a timeout when doing this. try { - IAsyncResult ares = InputStream.BeginRead (bytes, 0, length, null, null); + var ares = InputStream.BeginRead (buffer, 0, length, null, null); if (!ares.IsCompleted && !ares.AsyncWaitHandle.WaitOne (100)) return false; @@ -629,54 +648,36 @@ namespace WebSocketSharp.Net { } } - internal void SetRequestLine (string req) + internal void SetRequestLine (string requestLine) { - string [] parts = req.Split (separators, 3); + var parts = requestLine.Split (new char [] { ' ' }, 3); if (parts.Length != 3) { - context.ErrorMessage = "Invalid request line (parts)."; + _context.ErrorMessage = "Invalid request line (parts)"; return; } - method = parts [0]; - foreach (char c in method){ - int ic = (int) c; - - if ((ic >= 'A' && ic <= 'Z') || - (ic > 32 && c < 127 && c != '(' && c != ')' && c != '<' && - c != '<' && c != '>' && c != '@' && c != ',' && c != ';' && - c != ':' && c != '\\' && c != '"' && c != '/' && c != '[' && - c != ']' && c != '?' && c != '=' && c != '{' && c != '}')) - continue; - - context.ErrorMessage = "(Invalid verb)"; + _method = parts [0]; + if (!_method.IsToken ()) { + _context.ErrorMessage = "Invalid request line (method)"; return; } - raw_url = parts [1]; + _rawUrl = parts [1]; + if (parts [2].Length != 8 || !parts [2].StartsWith ("HTTP/")) { - context.ErrorMessage = "Invalid request line (version)."; + _context.ErrorMessage = "Invalid request line (version)"; return; } try { - version = new Version (parts [2].Substring (5)); - if (version.Major < 1) + _version = new Version (parts [2].Substring (5)); + if (_version.Major < 1) throw new Exception (); } catch { - context.ErrorMessage = "Invalid request line (version)."; - return; + _context.ErrorMessage = "Invalid request line (version)"; } } - internal static string Unquote (String str) - { - int start = str.IndexOf ('\"'); - int end = str.LastIndexOf ('\"'); - if (start >= 0 && end >=0) - str = str.Substring (start + 1, end - 1); - return str.Trim (); - } - #endregion #region Public Methods