diff --git a/websocket-sharp/Ext.cs b/websocket-sharp/Ext.cs index b5c5f302a..aad65f0b4 100644 --- a/websocket-sharp/Ext.cs +++ b/websocket-sharp/Ext.cs @@ -76,7 +76,7 @@ public static class Ext private static byte[] compress (this byte[] data) { - if (data.LongLength == 0) + if (data.Length == 0) //return new byte[] { 0x00, 0x00, 0x00, 0xff, 0xff }; return data; @@ -93,7 +93,7 @@ private static MemoryStream compress (this Stream stream) stream.Position = 0; using (var ds = new DeflateStream (output, CompressionMode.Compress, true)) { stream.CopyTo (ds, 1024); - ds.Close (); // BFINAL set to 1. + ds.Dispose (); // BFINAL set to 1. output.Write (_last, 0, 1); output.Position = 0; @@ -104,14 +104,14 @@ private static MemoryStream compress (this Stream stream) private static byte[] compressToArray (this Stream stream) { using (var output = stream.compress ()) { - output.Close (); + output.Dispose (); return output.ToArray (); } } private static byte[] decompress (this byte[] data) { - if (data.LongLength == 0) + if (data.Length == 0) return data; using (var input = new MemoryStream (data)) @@ -136,7 +136,7 @@ private static MemoryStream decompress (this Stream stream) private static byte[] decompressToArray (this Stream stream) { using (var output = stream.decompress ()) { - output.Close (); + output.Dispose (); return output.ToArray (); } } @@ -230,7 +230,7 @@ internal static bool CheckWaitTime (this TimeSpan time, out string message) internal static void Close (this HttpListenerResponse response, HttpStatusCode code) { response.StatusCode = (int) code; - response.OutputStream.Close (); + response.OutputStream.Dispose (); } internal static void CloseWithAuthChallenge ( @@ -290,7 +290,7 @@ internal static bool ContainsTwice (this string[] values) return contains (0); } - internal static T[] Copy (this T[] source, long length) + internal static T[] Copy (this T[] source, int length) { var dest = new T[length]; Array.Copy (source, 0, dest, 0, length); @@ -318,7 +318,7 @@ internal static void CopyToAsync ( AsyncCallback callback = null; callback = ar => { try { - var nread = source.EndRead (ar); + var nread = TaskToApm.End (ar); if (nread <= 0) { if (completed != null) completed (); @@ -644,11 +644,31 @@ internal static byte[] ReadBytes (this Stream stream, long length, int bufferLen catch { } - dest.Close (); + dest.Dispose (); return dest.ToArray (); } } + internal static int EndWrite(this Stream stream, IAsyncResult ar) + { + return TaskToApm.End(ar); + } + + internal static int EndRead(this Stream stream, IAsyncResult ar) + { + return TaskToApm.End(ar); + } + + internal static IAsyncResult BeginWrite(this Stream stream, byte[] buffer, int offset, int length, AsyncCallback callback, object state) + { + return TaskToApm.Begin(stream.WriteAsync(buffer, offset, length), callback, state); + } + + internal static IAsyncResult BeginRead(this Stream stream, byte[] buffer, int offset, int length, AsyncCallback callback, object state) + { + return TaskToApm.Begin(stream.ReadAsync(buffer, offset, length), callback, state); + } + internal static void ReadBytesAsync ( this Stream stream, int length, Action completed, Action error ) @@ -735,7 +755,7 @@ Action error if (nread == 0 || nread == len) { if (completed != null) { - dest.Close (); + dest.Dispose (); completed (dest.ToArray ()); } @@ -836,7 +856,7 @@ internal static byte[] ToByteArray (this Stream stream) using (var output = new MemoryStream ()) { stream.Position = 0; stream.CopyTo (output, 1024); - output.Close (); + output.Dispose (); return output.ToArray (); } @@ -871,7 +891,7 @@ internal static System.Net.IPAddress ToIPAddress (this string hostnameOrAddress) return addr; try { - return System.Net.Dns.GetHostAddresses (hostnameOrAddress)[0]; + return System.Net.Dns.GetHostAddressesAsync (hostnameOrAddress).Result[0]; } catch { return null; @@ -1344,7 +1364,7 @@ public static bool IsLocal (this System.Net.IPAddress address) } var host = System.Net.Dns.GetHostName (); - var addrs = System.Net.Dns.GetHostAddresses (host); + var addrs = System.Net.Dns.GetHostAddressesAsync (host).Result; foreach (var addr in addrs) { if (address.Equals (addr)) return true; @@ -1433,13 +1453,13 @@ public static bool IsPredefinedScheme (this string value) public static bool IsUpgradeTo (this HttpListenerRequest request, string protocol) { if (request == null) - throw new ArgumentNullException ("request"); + throw new ArgumentNullException (nameof(request)); if (protocol == null) - throw new ArgumentNullException ("protocol"); + throw new ArgumentNullException (nameof(protocol)); if (protocol.Length == 0) - throw new ArgumentException ("An empty string.", "protocol"); + throw new ArgumentException ("An empty string.", nameof(protocol)); return request.Headers.Contains ("Upgrade", protocol) && request.Headers.Contains ("Connection", "Upgrade"); @@ -1490,24 +1510,35 @@ public static bool MaybeUri (this string value) /// /// The type of elements in . /// - public static T[] SubArray (this T[] array, int startIndex, int length) - { - int len; - if (array == null || (len = array.Length) == 0) - return new T[0]; + // public static T[] SubArray (this T[] array, int startIndex, int length) + // { + // int len; + // if (array == null || (len = array.Length) == 0) + // return new T[0]; - if (startIndex < 0 || length <= 0 || startIndex + length > len) - return new T[0]; + // if (startIndex < 0 || length <= 0 || startIndex + length > len) + // return new T[0]; - if (startIndex == 0 && length == len) - return array; + // if (startIndex == 0 && length == len) + // return array; - var subArray = new T[length]; - Array.Copy (array, startIndex, subArray, 0, length); + // var subArray = new T[length]; + // Array.Copy (array, startIndex, subArray, 0, length); - return subArray; + // return subArray; + // } + + public static byte[] GetBuffer(this MemoryStream stream) + { + ArraySegment buffer; + if(stream.TryGetBuffer(out buffer)) + { + return buffer.Array; + } + throw new Exception("Could not get buffer"); } + /// /// Retrieves a sub-array from the specified . A sub-array starts at /// the specified element position in . @@ -1529,10 +1560,10 @@ public static T[] SubArray (this T[] array, int startIndex, int length) /// /// The type of elements in . /// - public static T[] SubArray (this T[] array, long startIndex, long length) + public static T[] SubArray (this T[] array, int startIndex, int length) { - long len; - if (array == null || (len = array.LongLength) == 0) + int len; + if (array == null || (len = array.Length) == 0) return new T[0]; if (startIndex < 0 || length <= 0 || startIndex + length > len) @@ -1706,7 +1737,7 @@ public static T To (this byte[] source, ByteOrder sourceOrder) where T : struct { if (source == null) - throw new ArgumentNullException ("source"); + throw new ArgumentNullException (nameof(source)); if (source.Length == 0) return default (T); @@ -1805,7 +1836,7 @@ public static byte[] ToByteArray (this T value, ByteOrder order) public static byte[] ToHostOrder (this byte[] source, ByteOrder sourceOrder) { if (source == null) - throw new ArgumentNullException ("source"); + throw new ArgumentNullException (nameof(source)); return source.Length > 1 && !sourceOrder.IsHostOrder () ? source.Reverse () : source; } @@ -1834,7 +1865,7 @@ public static byte[] ToHostOrder (this byte[] source, ByteOrder sourceOrder) public static string ToString (this T[] array, string separator) { if (array == null) - throw new ArgumentNullException ("array"); + throw new ArgumentNullException (nameof(array)); var len = array.Length; if (len == 0) @@ -1924,12 +1955,12 @@ public static string UrlEncode (this string value) public static void WriteContent (this HttpListenerResponse response, byte[] content) { if (response == null) - throw new ArgumentNullException ("response"); + throw new ArgumentNullException (nameof(response)); if (content == null) - throw new ArgumentNullException ("content"); + throw new ArgumentNullException (nameof(content)); - var len = content.LongLength; + var len = content.Length; if (len == 0) { response.Close (); return; @@ -1942,7 +1973,7 @@ public static void WriteContent (this HttpListenerResponse response, byte[] cont else output.WriteBytes (content, 1024); - output.Close (); + output.Dispose(); } #endregion diff --git a/websocket-sharp/Helpers.cs b/websocket-sharp/Helpers.cs new file mode 100644 index 000000000..9c2145dd9 --- /dev/null +++ b/websocket-sharp/Helpers.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Helpers +{ + public static class Helpers + { + public static T WaitForResult(this Task task) + { + try + { + return AsyncHelpers.RunSync(() => task); + } + catch (Exception x) + { + throw task.Exception; + } + } + public static void WaitForResult(this Task task) + { + try + { + AsyncHelpers.RunSync(() => task); + } + catch (Exception x) + { + throw task.Exception; + } + } + } + + public static class AsyncHelpers + { + /// + /// Execute's an async Task method which has a void return value synchronously + /// + /// Task method to execute + public static void RunSync(Func task) + { + var oldContext = SynchronizationContext.Current; + var synch = new ExclusiveSynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(synch); + synch.Post(async _ => + { + try + { + await task(); + } + catch (Exception e) + { + synch.InnerException = e; + throw; + } + finally + { + synch.EndMessageLoop(); + } + }, null); + synch.BeginMessageLoop(); + + SynchronizationContext.SetSynchronizationContext(oldContext); + } + + /// + /// Execute's an async Task method which has a T return type synchronously + /// + /// Return Type + /// Task method to execute + /// + public static T RunSync(Func> task) + { + var oldContext = SynchronizationContext.Current; + var synch = new ExclusiveSynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(synch); + T ret = default(T); + synch.Post(async _ => + { + try + { + ret = await task(); + } + catch (Exception e) + { + synch.InnerException = e; + throw; + } + finally + { + synch.EndMessageLoop(); + } + }, null); + synch.BeginMessageLoop(); + SynchronizationContext.SetSynchronizationContext(oldContext); + return ret; + } + + private class ExclusiveSynchronizationContext : SynchronizationContext + { + private bool done; + public Exception InnerException { get; set; } + readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false); + readonly Queue> items = + new Queue>(); + + public override void Send(SendOrPostCallback d, object state) + { + throw new NotSupportedException("We cannot send to our same thread"); + } + + public override void Post(SendOrPostCallback d, object state) + { + lock (items) + { + items.Enqueue(Tuple.Create(d, state)); + } + workItemsWaiting.Set(); + } + + public void EndMessageLoop() + { + Post(_ => done = true, null); + } + + public void BeginMessageLoop() + { + while (!done) + { + Tuple task = null; + lock (items) + { + if (items.Count > 0) + { + task = items.Dequeue(); + } + } + if (task != null) + { + task.Item1(task.Item2); + if (InnerException != null) // the method threw an exeption + { + throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException); + } + } + else + { + workItemsWaiting.WaitOne(); + } + } + } + + public override SynchronizationContext CreateCopy() + { + return this; + } + } + } +} diff --git a/websocket-sharp/HttpBase.cs b/websocket-sharp/HttpBase.cs index a7dbd4026..4dd4e120d 100644 --- a/websocket-sharp/HttpBase.cs +++ b/websocket-sharp/HttpBase.cs @@ -72,7 +72,7 @@ protected HttpBase (Version version, NameValueCollection headers) public string EntityBody { get { - if (EntityBodyData == null || EntityBodyData.LongLength == 0) + if (EntityBodyData == null || EntityBodyData.Length == 0) return String.Empty; Encoding enc = null; @@ -105,10 +105,10 @@ private static byte[] readEntityBody (Stream stream, string length) { long len; if (!Int64.TryParse (length, out len)) - throw new ArgumentException ("Cannot be parsed.", "length"); + throw new ArgumentException ("Cannot be parsed.", nameof(length)); if (len < 0) - throw new ArgumentOutOfRangeException ("length", "Less than zero."); + throw new ArgumentOutOfRangeException (nameof(length), "Less than zero."); return len > 1024 ? stream.ReadBytes (len, 1024) @@ -160,7 +160,7 @@ protected static T Read (Stream stream, Func parser, int millise var timer = new Timer ( state => { timeout = true; - stream.Close (); + stream.Dispose(); }, null, millisecondsTimeout, diff --git a/websocket-sharp/HttpRequest.cs b/websocket-sharp/HttpRequest.cs index f9aa5cb33..8a98e0245 100644 --- a/websocket-sharp/HttpRequest.cs +++ b/websocket-sharp/HttpRequest.cs @@ -41,183 +41,186 @@ namespace WebSocketSharp { - internal class HttpRequest : HttpBase - { - #region Private Fields + internal class HttpRequest : HttpBase + { + #region Private Fields - private string _method; - private string _uri; - private bool _websocketRequest; - private bool _websocketRequestSet; + private string _method; + private string _uri; + private bool _websocketRequest; + private bool _websocketRequestSet; - #endregion + #endregion - #region Private Constructors + #region Private Constructors - private HttpRequest (string method, string uri, Version version, NameValueCollection headers) - : base (version, headers) - { - _method = method; - _uri = uri; - } + private HttpRequest(string method, string uri, Version version, NameValueCollection headers) + : base(version, headers) + { + _method = method; + _uri = uri; + } - #endregion + #endregion - #region Internal Constructors + #region Internal Constructors - internal HttpRequest (string method, string uri) - : this (method, uri, HttpVersion.Version11, new NameValueCollection ()) - { - Headers["User-Agent"] = "websocket-sharp/1.0"; - } + internal HttpRequest(string method, string uri) + : this(method, uri, HttpVersion.Version11, new NameValueCollection()) + { + Headers["User-Agent"] = "websocket-sharp/1.0"; + } - #endregion + #endregion - #region Public Properties + #region Public Properties - public AuthenticationResponse AuthenticationResponse { - get { - var res = Headers["Authorization"]; - return res != null && res.Length > 0 - ? AuthenticationResponse.Parse (res) - : null; - } - } + public AuthenticationResponse AuthenticationResponse + { + get + { + var res = Headers["Authorization"]; + return res != null && res.Length > 0 + ? AuthenticationResponse.Parse(res) + : null; + } + } - public CookieCollection Cookies { - get { - return Headers.GetCookies (false); - } - } + public CookieCollection Cookies + { + get { return Headers.GetCookies(false); } + } - public string HttpMethod { - get { - return _method; - } - } + public string HttpMethod + { + get { return _method; } + } - public bool IsWebSocketRequest { - get { - if (!_websocketRequestSet) { - var headers = Headers; - _websocketRequest = _method == "GET" && - ProtocolVersion > HttpVersion.Version10 && - headers.Contains ("Upgrade", "websocket") && - headers.Contains ("Connection", "Upgrade"); + public bool IsWebSocketRequest + { + get + { + if (!_websocketRequestSet) + { + var headers = Headers; + _websocketRequest = _method == "GET" && + ProtocolVersion > HttpVersion.Version10 && + headers.Contains("Upgrade", "websocket") && + headers.Contains("Connection", "Upgrade"); + + _websocketRequestSet = true; + } + + return _websocketRequest; + } + } - _websocketRequestSet = true; + public string RequestUri + { + get { return _uri; } } - return _websocketRequest; - } - } + #endregion - public string RequestUri { - get { - return _uri; - } - } + #region Internal Methods - #endregion + internal static HttpRequest CreateConnectRequest(Uri uri) + { + var host = uri.DnsSafeHost; + var port = uri.Port; + var authority = String.Format("{0}:{1}", host, port); + var req = new HttpRequest("CONNECT", authority); + req.Headers["Host"] = port == 80 ? host : authority; - #region Internal Methods + return req; + } - internal static HttpRequest CreateConnectRequest (Uri uri) - { - var host = uri.DnsSafeHost; - var port = uri.Port; - var authority = String.Format ("{0}:{1}", host, port); - var req = new HttpRequest ("CONNECT", authority); - req.Headers["Host"] = port == 80 ? host : authority; + internal static HttpRequest CreateWebSocketRequest(Uri uri) + { + var req = new HttpRequest("GET", uri.PathAndQuery); + var headers = req.Headers; - return req; - } + // Only includes a port number in the Host header value if it's non-default. + // See: https://tools.ietf.org/html/rfc6455#page-17 + var port = uri.Port; + var schm = uri.Scheme; + headers["Host"] = (port == 80 && schm == "ws") || (port == 443 && schm == "wss") + ? uri.DnsSafeHost + : uri.Authority; - internal static HttpRequest CreateWebSocketRequest (Uri uri) - { - var req = new HttpRequest ("GET", uri.PathAndQuery); - var headers = req.Headers; + headers["Upgrade"] = "websocket"; + headers["Connection"] = "Upgrade"; - // Only includes a port number in the Host header value if it's non-default. - // See: https://tools.ietf.org/html/rfc6455#page-17 - var port = uri.Port; - var schm = uri.Scheme; - headers["Host"] = (port == 80 && schm == "ws") || (port == 443 && schm == "wss") - ? uri.DnsSafeHost - : uri.Authority; + return req; + } - headers["Upgrade"] = "websocket"; - headers["Connection"] = "Upgrade"; + internal HttpResponse GetResponse(Stream stream, int millisecondsTimeout) + { + var buff = ToByteArray(); + stream.Write(buff, 0, buff.Length); - return req; - } + return Read(stream, HttpResponse.Parse, millisecondsTimeout); + } - internal HttpResponse GetResponse (Stream stream, int millisecondsTimeout) - { - var buff = ToByteArray (); - stream.Write (buff, 0, buff.Length); + internal static HttpRequest Parse(string[] headerParts) + { + var requestLine = headerParts[0].Split(new[] {' '}, 3); + if (requestLine.Length != 3) + throw new ArgumentException("Invalid request line: " + headerParts[0]); - return Read (stream, HttpResponse.Parse, millisecondsTimeout); - } + var headers = new WebHeaderCollection(); + for (int i = 1; i < headerParts.Length; i++) + headers.InternalSet(headerParts[i], false); - internal static HttpRequest Parse (string[] headerParts) - { - var requestLine = headerParts[0].Split (new[] { ' ' }, 3); - if (requestLine.Length != 3) - throw new ArgumentException ("Invalid request line: " + headerParts[0]); + return new HttpRequest( + requestLine[0], requestLine[1], new Version(requestLine[2].Substring(5)), headers); + } - var headers = new WebHeaderCollection (); - for (int i = 1; i < headerParts.Length; i++) - headers.InternalSet (headerParts[i], false); + internal static HttpRequest Read(Stream stream, int millisecondsTimeout) + { + return Read(stream, Parse, millisecondsTimeout); + } - return new HttpRequest ( - requestLine[0], requestLine[1], new Version (requestLine[2].Substring (5)), headers); - } + #endregion - internal static HttpRequest Read (Stream stream, int millisecondsTimeout) - { - return Read (stream, Parse, millisecondsTimeout); - } + #region Public Methods - #endregion + public void SetCookies(CookieCollection cookies) + { + if (cookies == null || cookies.Count == 0) + return; - #region Public Methods + var buff = new StringBuilder(64); + foreach (var cookie in cookies.Sorted) + if (!cookie.Expired) + buff.AppendFormat("{0}; ", cookie.ToString()); - public void SetCookies (CookieCollection cookies) - { - if (cookies == null || cookies.Count == 0) - return; - - var buff = new StringBuilder (64); - foreach (var cookie in cookies.Sorted) - if (!cookie.Expired) - buff.AppendFormat ("{0}; ", cookie.ToString ()); - - var len = buff.Length; - if (len > 2) { - buff.Length = len - 2; - Headers["Cookie"] = buff.ToString (); - } - } + var len = buff.Length; + if (len > 2) + { + buff.Length = len - 2; + Headers["Cookie"] = buff.ToString(); + } + } - public override string ToString () - { - var output = new StringBuilder (64); - output.AppendFormat ("{0} {1} HTTP/{2}{3}", _method, _uri, ProtocolVersion, CrLf); + public override string ToString() + { + var output = new StringBuilder(64); + output.AppendFormat("{0} {1} HTTP/{2}{3}", _method, _uri, ProtocolVersion, CrLf); - var headers = Headers; - foreach (var key in headers.AllKeys) - output.AppendFormat ("{0}: {1}{2}", key, headers[key], CrLf); + var headers = Headers; + foreach (var key in headers.AllKeys) + output.AppendFormat("{0}: {1}{2}", key, headers[key], CrLf); - output.Append (CrLf); + output.Append(CrLf); - var entity = EntityBody; - if (entity.Length > 0) - output.Append (entity); + var entity = EntityBody; + if (entity.Length > 0) + output.Append(entity); - return output.ToString (); - } + return output.ToString(); + } - #endregion - } + #endregion + } } diff --git a/websocket-sharp/Logger.cs b/websocket-sharp/Logger.cs index 17850e67e..23f287d81 100644 --- a/websocket-sharp/Logger.cs +++ b/websocket-sharp/Logger.cs @@ -29,6 +29,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Text; namespace WebSocketSharp { @@ -197,6 +198,7 @@ private static void defaultOutput (LogData data, string path) private void output (string message, LogLevel level) { + #if NET40 lock (_sync) { if (_level > level) return; @@ -205,19 +207,20 @@ private void output (string message, LogLevel level) try { data = new LogData (level, new StackFrame (2, true), message); _output (data, _file); + } catch (Exception ex) { data = new LogData (LogLevel.Fatal, new StackFrame (0, true), ex.Message); Console.WriteLine (data.ToString ()); } } + #endif } private static void writeToFile (string value, string path) { - using (var writer = new StreamWriter (path, true)) - using (var syncWriter = TextWriter.Synchronized (writer)) - syncWriter.WriteLine (value); + using (var writer = new StreamWriter (System.IO.File.OpenWrite(path), Encoding.UTF8)) + writer.WriteLine(value); } #endregion diff --git a/websocket-sharp/MessageEventArgs.cs b/websocket-sharp/MessageEventArgs.cs index 0639baf39..1255e5bd4 100644 --- a/websocket-sharp/MessageEventArgs.cs +++ b/websocket-sharp/MessageEventArgs.cs @@ -65,7 +65,7 @@ internal MessageEventArgs (WebSocketFrame frame) internal MessageEventArgs (Opcode opcode, byte[] rawData) { - if ((ulong) rawData.LongLength > PayloadData.MaxLength) + if ((ulong) rawData.Length > PayloadData.MaxLength) throw new WebSocketException (CloseStatusCode.TooBig); _opcode = opcode; diff --git a/websocket-sharp/Net/AuthenticationResponse.cs b/websocket-sharp/Net/AuthenticationResponse.cs index cc49b372e..82c538f01 100644 --- a/websocket-sharp/Net/AuthenticationResponse.cs +++ b/websocket-sharp/Net/AuthenticationResponse.cs @@ -250,7 +250,8 @@ internal static AuthenticationResponse Parse (string value) internal static NameValueCollection ParseBasicCredentials (string value) { // Decode the basic-credentials (a Base64 encoded string). - var userPass = Encoding.Default.GetString (Convert.FromBase64String (value)); + + var userPass = Encoding.UTF8.GetString (Convert.FromBase64String (value)); // The format is [\]:. var i = userPass.IndexOf (':'); diff --git a/websocket-sharp/Net/ChunkedRequestStream.cs b/websocket-sharp/Net/ChunkedRequestStream.cs index 913b505c3..cfd055f9a 100644 --- a/websocket-sharp/Net/ChunkedRequestStream.cs +++ b/websocket-sharp/Net/ChunkedRequestStream.cs @@ -121,13 +121,13 @@ public override IAsyncResult BeginRead ( throw new ObjectDisposedException (GetType ().ToString ()); if (buffer == null) - throw new ArgumentNullException ("buffer"); + throw new ArgumentNullException (nameof(buffer)); if (offset < 0) - throw new ArgumentOutOfRangeException ("offset", "A negative value."); + throw new ArgumentOutOfRangeException (nameof(offset), "A negative value."); if (count < 0) - throw new ArgumentOutOfRangeException ("count", "A negative value."); + throw new ArgumentOutOfRangeException (nameof(count), "A negative value."); var len = buffer.Length; if (offset + count > len) @@ -185,11 +185,11 @@ public override int EndRead (IAsyncResult asyncResult) throw new ObjectDisposedException (GetType ().ToString ()); if (asyncResult == null) - throw new ArgumentNullException ("asyncResult"); + throw new ArgumentNullException (nameof(asyncResult)); var ares = asyncResult as HttpStreamAsyncResult; if (ares == null) - throw new ArgumentException ("A wrong IAsyncResult.", "asyncResult"); + throw new ArgumentException ("A wrong IAsyncResult.", nameof(asyncResult)); if (!ares.IsCompleted) ares.AsyncWaitHandle.WaitOne (); diff --git a/websocket-sharp/Net/ClientSslConfiguration.cs b/websocket-sharp/Net/ClientSslConfiguration.cs index 5344164f6..b1bc17ebc 100644 --- a/websocket-sharp/Net/ClientSslConfiguration.cs +++ b/websocket-sharp/Net/ClientSslConfiguration.cs @@ -63,7 +63,7 @@ public class ClientSslConfiguration : SslConfiguration /// a secure connection. /// public ClientSslConfiguration (string targetHost) - : this (targetHost, null, SslProtocols.Default, false) + : this (targetHost, null, SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, false) { } diff --git a/websocket-sharp/Net/Cookie.cs b/websocket-sharp/Net/Cookie.cs index fffc52730..60dd2d805 100644 --- a/websocket-sharp/Net/Cookie.cs +++ b/websocket-sharp/Net/Cookie.cs @@ -570,7 +570,7 @@ public int Version { set { if (value < 0 || value > 1) - throw new ArgumentOutOfRangeException ("value", "Not 0 or 1."); + throw new ArgumentOutOfRangeException (nameof(value), "Not 0 or 1."); _version = value; } @@ -631,7 +631,7 @@ private string toResponseStringVersion0 () "; Expires={0}", _expires.ToUniversalTime ().ToString ( "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", - CultureInfo.CreateSpecificCulture ("en-US"))); + CultureInfo.InvariantCulture)); if (!_path.IsNullOrEmpty ()) output.AppendFormat ("; Path={0}", _path); @@ -776,10 +776,10 @@ public override bool Equals (Object comparand) { var cookie = comparand as Cookie; return cookie != null && - _name.Equals (cookie.Name, StringComparison.InvariantCultureIgnoreCase) && - _value.Equals (cookie.Value, StringComparison.InvariantCulture) && - _path.Equals (cookie.Path, StringComparison.InvariantCulture) && - _domain.Equals (cookie.Domain, StringComparison.InvariantCultureIgnoreCase) && + _name.Equals (cookie.Name, StringComparison.OrdinalIgnoreCase) && + _value.Equals (cookie.Value, StringComparison.Ordinal) && + _path.Equals (cookie.Path, StringComparison.Ordinal) && + _domain.Equals (cookie.Domain, StringComparison.OrdinalIgnoreCase) && _version == cookie.Version; } @@ -792,10 +792,10 @@ public override bool Equals (Object comparand) public override int GetHashCode () { return hash ( - StringComparer.InvariantCultureIgnoreCase.GetHashCode (_name), + StringComparer.OrdinalIgnoreCase.GetHashCode (_name), _value.GetHashCode (), _path.GetHashCode (), - StringComparer.InvariantCultureIgnoreCase.GetHashCode (_domain), + StringComparer.OrdinalIgnoreCase.GetHashCode (_domain), _version); } diff --git a/websocket-sharp/Net/CookieCollection.cs b/websocket-sharp/Net/CookieCollection.cs index a4caada92..e44c30328 100644 --- a/websocket-sharp/Net/CookieCollection.cs +++ b/websocket-sharp/Net/CookieCollection.cs @@ -43,6 +43,7 @@ using System.Collections; using System.Collections.Generic; using System.Globalization; +using System.Reflection; using System.Text; namespace WebSocketSharp.Net @@ -151,7 +152,7 @@ public bool IsSynchronized { public Cookie this[int index] { get { if (index < 0 || index >= _list.Count) - throw new ArgumentOutOfRangeException ("index"); + throw new ArgumentOutOfRangeException (nameof(index)); return _list[index]; } @@ -173,10 +174,10 @@ public Cookie this[int index] { public Cookie this[string name] { get { if (name == null) - throw new ArgumentNullException ("name"); + throw new ArgumentNullException (nameof(name)); foreach (var cookie in Sorted) - if (cookie.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase)) + if (cookie.Name.Equals (name, StringComparison.OrdinalIgnoreCase)) return cookie; return null; @@ -226,19 +227,19 @@ private static CookieCollection parseRequest (string value) if (pair.Length == 0) continue; - if (pair.StartsWith ("$version", StringComparison.InvariantCultureIgnoreCase)) { + if (pair.StartsWith ("$version", StringComparison.OrdinalIgnoreCase)) { ver = Int32.Parse (pair.GetValue ('=', true)); } - else if (pair.StartsWith ("$path", StringComparison.InvariantCultureIgnoreCase)) { + else if (pair.StartsWith ("$path", StringComparison.OrdinalIgnoreCase)) { if (cookie != null) cookie.Path = pair.GetValue ('='); } - else if (pair.StartsWith ("$domain", StringComparison.InvariantCultureIgnoreCase)) { + else if (pair.StartsWith ("$domain", StringComparison.OrdinalIgnoreCase)) { if (cookie != null) cookie.Domain = pair.GetValue ('='); } - else if (pair.StartsWith ("$port", StringComparison.InvariantCultureIgnoreCase)) { - var port = pair.Equals ("$port", StringComparison.InvariantCultureIgnoreCase) + else if (pair.StartsWith ("$port", StringComparison.OrdinalIgnoreCase)) { + var port = pair.Equals ("$port", StringComparison.OrdinalIgnoreCase) ? "\"\"" : pair.GetValue ('='); @@ -287,11 +288,11 @@ private static CookieCollection parseResponse (string value) if (pair.Length == 0) continue; - if (pair.StartsWith ("version", StringComparison.InvariantCultureIgnoreCase)) { + if (pair.StartsWith ("version", StringComparison.OrdinalIgnoreCase)) { if (cookie != null) cookie.Version = Int32.Parse (pair.GetValue ('=', true)); } - else if (pair.StartsWith ("expires", StringComparison.InvariantCultureIgnoreCase)) { + else if (pair.StartsWith ("expires", StringComparison.OrdinalIgnoreCase)) { var buff = new StringBuilder (pair.GetValue ('='), 32); if (i < pairs.Length - 1) buff.AppendFormat (", {0}", pairs[++i].Trim ()); @@ -300,7 +301,7 @@ private static CookieCollection parseResponse (string value) if (!DateTime.TryParseExact ( buff.ToString (), new[] { "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", "r" }, - CultureInfo.CreateSpecificCulture ("en-US"), + CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out expires)) expires = DateTime.Now; @@ -308,45 +309,45 @@ private static CookieCollection parseResponse (string value) if (cookie != null && cookie.Expires == DateTime.MinValue) cookie.Expires = expires.ToLocalTime (); } - else if (pair.StartsWith ("max-age", StringComparison.InvariantCultureIgnoreCase)) { + else if (pair.StartsWith ("max-age", StringComparison.OrdinalIgnoreCase)) { var max = Int32.Parse (pair.GetValue ('=', true)); var expires = DateTime.Now.AddSeconds ((double) max); if (cookie != null) cookie.Expires = expires; } - else if (pair.StartsWith ("path", StringComparison.InvariantCultureIgnoreCase)) { + else if (pair.StartsWith ("path", StringComparison.OrdinalIgnoreCase)) { if (cookie != null) cookie.Path = pair.GetValue ('='); } - else if (pair.StartsWith ("domain", StringComparison.InvariantCultureIgnoreCase)) { + else if (pair.StartsWith ("domain", StringComparison.OrdinalIgnoreCase)) { if (cookie != null) cookie.Domain = pair.GetValue ('='); } - else if (pair.StartsWith ("port", StringComparison.InvariantCultureIgnoreCase)) { - var port = pair.Equals ("port", StringComparison.InvariantCultureIgnoreCase) + else if (pair.StartsWith ("port", StringComparison.OrdinalIgnoreCase)) { + var port = pair.Equals ("port", StringComparison.OrdinalIgnoreCase) ? "\"\"" : pair.GetValue ('='); if (cookie != null) cookie.Port = port; } - else if (pair.StartsWith ("comment", StringComparison.InvariantCultureIgnoreCase)) { + else if (pair.StartsWith ("comment", StringComparison.OrdinalIgnoreCase)) { if (cookie != null) cookie.Comment = pair.GetValue ('=').UrlDecode (); } - else if (pair.StartsWith ("commenturl", StringComparison.InvariantCultureIgnoreCase)) { + else if (pair.StartsWith ("commenturl", StringComparison.OrdinalIgnoreCase)) { if (cookie != null) cookie.CommentUri = pair.GetValue ('=', true).ToUri (); } - else if (pair.StartsWith ("discard", StringComparison.InvariantCultureIgnoreCase)) { + else if (pair.StartsWith ("discard", StringComparison.OrdinalIgnoreCase)) { if (cookie != null) cookie.Discard = true; } - else if (pair.StartsWith ("secure", StringComparison.InvariantCultureIgnoreCase)) { + else if (pair.StartsWith ("secure", StringComparison.OrdinalIgnoreCase)) { if (cookie != null) cookie.Secure = true; } - else if (pair.StartsWith ("httponly", StringComparison.InvariantCultureIgnoreCase)) { + else if (pair.StartsWith ("httponly", StringComparison.OrdinalIgnoreCase)) { if (cookie != null) cookie.HttpOnly = true; } @@ -388,9 +389,9 @@ private int searchCookie (Cookie cookie) for (var i = _list.Count - 1; i >= 0; i--) { var c = _list[i]; - if (c.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase) && - c.Path.Equals (path, StringComparison.InvariantCulture) && - c.Domain.Equals (domain, StringComparison.InvariantCultureIgnoreCase) && + if (c.Name.Equals (name, StringComparison.OrdinalIgnoreCase) && + c.Path.Equals (path, StringComparison.Ordinal) && + c.Domain.Equals (domain, StringComparison.OrdinalIgnoreCase) && c.Version == ver) return i; } @@ -460,7 +461,7 @@ internal void Sort () public void Add (Cookie cookie) { if (cookie == null) - throw new ArgumentNullException ("cookie"); + throw new ArgumentNullException (nameof(cookie)); var pos = searchCookie (cookie); if (pos == -1) { @@ -483,7 +484,7 @@ public void Add (Cookie cookie) public void Add (CookieCollection cookies) { if (cookies == null) - throw new ArgumentNullException ("cookies"); + throw new ArgumentNullException (nameof(cookies)); foreach (Cookie cookie in cookies) Add (cookie); @@ -526,19 +527,19 @@ public void Add (CookieCollection cookies) public void CopyTo (Array array, int index) { if (array == null) - throw new ArgumentNullException ("array"); + throw new ArgumentNullException (nameof(array)); if (index < 0) - throw new ArgumentOutOfRangeException ("index", "Less than zero."); + throw new ArgumentOutOfRangeException (nameof(index), "Less than zero."); if (array.Rank > 1) - throw new ArgumentException ("Multidimensional.", "array"); + throw new ArgumentException ("Multidimensional.", nameof(array)); if (array.Length - index < _list.Count) throw new ArgumentException ( "The number of elements in this collection is greater than the available space of the destination array."); - if (!array.GetType ().GetElementType ().IsAssignableFrom (typeof (Cookie))) + if (!array.GetType ().GetElementType ().GetTypeInfo().IsAssignableFrom(typeof (Cookie))) throw new InvalidCastException ( "The elements in this collection cannot be cast automatically to the type of the destination array."); @@ -570,10 +571,10 @@ public void CopyTo (Array array, int index) public void CopyTo (Cookie[] array, int index) { if (array == null) - throw new ArgumentNullException ("array"); + throw new ArgumentNullException (nameof(array)); if (index < 0) - throw new ArgumentOutOfRangeException ("index", "Less than zero."); + throw new ArgumentOutOfRangeException (nameof(index), "Less than zero."); if (array.Length - index < _list.Count) throw new ArgumentException ( diff --git a/websocket-sharp/Net/CookieException.cs b/websocket-sharp/Net/CookieException.cs index 06123aec8..cd33bd2e1 100644 --- a/websocket-sharp/Net/CookieException.cs +++ b/websocket-sharp/Net/CookieException.cs @@ -38,7 +38,7 @@ using System; using System.Runtime.Serialization; -using System.Security.Permissions; +//using System.Security.Permissions; namespace WebSocketSharp.Net { @@ -46,7 +46,7 @@ namespace WebSocketSharp.Net /// The exception that is thrown when a gets an error. /// [Serializable] - public class CookieException : FormatException, ISerializable + public class CookieException : FormatException { #region Internal Constructors @@ -64,21 +64,7 @@ internal CookieException (string message, Exception innerException) #region Protected Constructors - /// - /// Initializes a new instance of the class from - /// the specified and . - /// - /// - /// A that contains the serialized object data. - /// - /// - /// A that specifies the source for the deserialization. - /// - protected CookieException ( - SerializationInfo serializationInfo, StreamingContext streamingContext) - : base (serializationInfo, streamingContext) - { - } + #endregion @@ -96,47 +82,13 @@ public CookieException () #region Public Methods - /// - /// Populates the specified with the data needed to serialize - /// the current . - /// - /// - /// A that holds the serialized object data. - /// - /// - /// A that specifies the destination for the serialization. - /// - [SecurityPermission ( - SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] - public override void GetObjectData ( - SerializationInfo serializationInfo, StreamingContext streamingContext) - { - base.GetObjectData (serializationInfo, streamingContext); - } + #endregion #region Explicit Interface Implementation - /// - /// Populates the specified with the data needed to serialize - /// the current . - /// - /// - /// A that holds the serialized object data. - /// - /// - /// A that specifies the destination for the serialization. - /// - [SecurityPermission ( - SecurityAction.LinkDemand, - Flags = SecurityPermissionFlag.SerializationFormatter, - SerializationFormatter = true)] - void ISerializable.GetObjectData ( - SerializationInfo serializationInfo, StreamingContext streamingContext) - { - base.GetObjectData (serializationInfo, streamingContext); - } + #endregion } diff --git a/websocket-sharp/Net/EndPointListener.cs b/websocket-sharp/Net/EndPointListener.cs index 63a6c11b5..a8751d4e8 100644 --- a/websocket-sharp/Net/EndPointListener.cs +++ b/websocket-sharp/Net/EndPointListener.cs @@ -57,467 +57,490 @@ namespace WebSocketSharp.Net { - internal sealed class EndPointListener - { - #region Private Fields - - private List _all; // host == '+' - private static readonly string _defaultCertFolderPath; - private IPEndPoint _endpoint; - private Dictionary _prefixes; - private bool _secure; - private Socket _socket; - private ServerSslConfiguration _sslConfig; - private List _unhandled; // host == '*' - private Dictionary _unregistered; - private object _unregisteredSync; - - #endregion - - #region Static Constructor - - static EndPointListener () + internal sealed class EndPointListener { - _defaultCertFolderPath = - Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData); - } - - #endregion - - #region Internal Constructors - - internal EndPointListener ( - IPEndPoint endpoint, - bool secure, - string certificateFolderPath, - ServerSslConfiguration sslConfig, - bool reuseAddress - ) - { - if (secure) { - var cert = - getCertificate (endpoint.Port, certificateFolderPath, sslConfig.ServerCertificate); - - if (cert == null) - throw new ArgumentException ("No server certificate could be found."); - - _secure = true; - _sslConfig = - new ServerSslConfiguration ( - cert, - sslConfig.ClientCertificateRequired, - sslConfig.EnabledSslProtocols, - sslConfig.CheckCertificateRevocation - ); - - _sslConfig.ClientCertificateValidationCallback = - sslConfig.ClientCertificateValidationCallback; - } - - _endpoint = endpoint; - _prefixes = new Dictionary (); - _unregistered = new Dictionary (); - _unregisteredSync = ((ICollection) _unregistered).SyncRoot; - _socket = - new Socket (endpoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); - - if (reuseAddress) - _socket.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); - - _socket.Bind (endpoint); - _socket.Listen (500); - _socket.BeginAccept (onAccept, this); - } - - #endregion - - #region Public Properties - - public IPAddress Address { - get { - return _endpoint.Address; - } - } - - public bool IsSecure { - get { - return _secure; - } - } - - public int Port { - get { - return _endpoint.Port; - } - } - - public ServerSslConfiguration SslConfiguration { - get { - return _sslConfig; - } - } - - #endregion - - #region Private Methods + #region Private Fields + + private List _all; // host == '+' + private static readonly string _defaultCertFolderPath; + private IPEndPoint _endpoint; + private Dictionary _prefixes; + private bool _secure; + private Socket _socket; + private ServerSslConfiguration _sslConfig; + private List _unhandled; // host == '*' + private Dictionary _unregistered; + private object _unregisteredSync; + + #endregion + + #region Static Constructor + + static EndPointListener() + { + _defaultCertFolderPath = Directory.GetCurrentDirectory(); + } - private static void addSpecial (List prefixes, HttpListenerPrefix prefix) - { - var path = prefix.Path; - foreach (var pref in prefixes) { - if (pref.Path == path) - throw new HttpListenerException (87, "The prefix is already in use."); - } + #endregion + + #region Internal Constructors + + internal EndPointListener( + IPEndPoint endpoint, + bool secure, + string certificateFolderPath, + ServerSslConfiguration sslConfig, + bool reuseAddress + ) + { + if (secure) + { + var cert = + getCertificate(endpoint.Port, certificateFolderPath, sslConfig.ServerCertificate); + + if (cert == null) + throw new ArgumentException("No server certificate could be found."); + + _secure = true; + _sslConfig = + new ServerSslConfiguration( + cert, + sslConfig.ClientCertificateRequired, + sslConfig.EnabledSslProtocols, + sslConfig.CheckCertificateRevocation + ); + + _sslConfig.ClientCertificateValidationCallback = + sslConfig.ClientCertificateValidationCallback; + } + + _endpoint = endpoint; + _prefixes = new Dictionary(); + _unregistered = new Dictionary(); + _unregisteredSync = ((ICollection) _unregistered).SyncRoot; + _socket = + new Socket(endpoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + + if (reuseAddress) + _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + + _socket.Bind(endpoint); + _socket.Listen(500); + Accept(); + } - prefixes.Add (prefix); - } + #endregion - private static RSACryptoServiceProvider createRSAFromFile (string filename) - { - byte[] pvk = null; - using (var fs = File.Open (filename, FileMode.Open, FileAccess.Read, FileShare.Read)) { - pvk = new byte[fs.Length]; - fs.Read (pvk, 0, pvk.Length); - } + #region Public Properties - var rsa = new RSACryptoServiceProvider (); - rsa.ImportCspBlob (pvk); - - return rsa; - } + public IPAddress Address + { + get { return _endpoint.Address; } + } - private static X509Certificate2 getCertificate ( - int port, string folderPath, X509Certificate2 defaultCertificate - ) - { - if (folderPath == null || folderPath.Length == 0) - folderPath = _defaultCertFolderPath; + public bool IsSecure + { + get { return _secure; } + } - try { - var cer = Path.Combine (folderPath, String.Format ("{0}.cer", port)); - var key = Path.Combine (folderPath, String.Format ("{0}.key", port)); - if (File.Exists (cer) && File.Exists (key)) { - var cert = new X509Certificate2 (cer); - cert.PrivateKey = createRSAFromFile (key); + public int Port + { + get { return _endpoint.Port; } + } - return cert; + public ServerSslConfiguration SslConfiguration + { + get { return _sslConfig; } } - } - catch { - } - return defaultCertificate; - } + #endregion - private void leaveIfNoPrefix () - { - if (_prefixes.Count > 0) - return; + #region Private Methods - var prefs = _unhandled; - if (prefs != null && prefs.Count > 0) - return; + private static void addSpecial(List prefixes, HttpListenerPrefix prefix) + { + var path = prefix.Path; + foreach (var pref in prefixes) + { + if (pref.Path == path) + throw new HttpListenerException(87, "The prefix is already in use."); + } - prefs = _all; - if (prefs != null && prefs.Count > 0) - return; + prefixes.Add(prefix); + } - EndPointManager.RemoveEndPoint (_endpoint); - } + private static RSACryptoServiceProvider createRSAFromFile(string filename) + { + byte[] pvk = null; + using (var fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + pvk = new byte[fs.Length]; + fs.Read(pvk, 0, pvk.Length); + } - private static void onAccept (IAsyncResult asyncResult) - { - var lsnr = (EndPointListener) asyncResult.AsyncState; - - Socket sock = null; - try { - sock = lsnr._socket.EndAccept (asyncResult); - } - catch (SocketException) { - // TODO: Should log the error code when this class has a logging. - } - catch (ObjectDisposedException) { - return; - } - - try { - lsnr._socket.BeginAccept (onAccept, lsnr); - } - catch { - if (sock != null) - sock.Close (); - - return; - } - - if (sock == null) - return; - - processAccepted (sock, lsnr); - } + var rsa = new RSACryptoServiceProvider(); + rsa.ImportCspBlob(pvk); - private static void processAccepted (Socket socket, EndPointListener listener) - { - HttpConnection conn = null; - try { - conn = new HttpConnection (socket, listener); - lock (listener._unregisteredSync) - listener._unregistered[conn] = conn; - - conn.BeginReadRequest (); - } - catch { - if (conn != null) { - conn.Close (true); - return; + return rsa; } - socket.Close (); - } - } - - private static bool removeSpecial (List prefixes, HttpListenerPrefix prefix) - { - var path = prefix.Path; - var cnt = prefixes.Count; - for (var i = 0; i < cnt; i++) { - if (prefixes[i].Path == path) { - prefixes.RemoveAt (i); - return true; + private static X509Certificate2 getCertificate( + int port, string folderPath, X509Certificate2 defaultCertificate + ) + { + if (string.IsNullOrEmpty(folderPath)) + folderPath = _defaultCertFolderPath; + + try + { + var cer = Path.Combine(folderPath, string.Format("{0}.cer", port)); + var key = Path.Combine(folderPath, string.Format("{0}.key", port)); + if (File.Exists(cer) && File.Exists(key)) + { + var cert = new X509Certificate2(cer); + + // .NET core: Private key must be in the machine's key store, hash tag sad face + //cert = createRSAFromFile (key); + + return cert; + } + } + catch + { + } + + return defaultCertificate; } - } - return false; - } + private void leaveIfNoPrefix() + { + if (_prefixes.Count > 0) + return; - private static HttpListener searchHttpListenerFromSpecial ( - string path, List prefixes - ) - { - if (prefixes == null) - return null; + var prefs = _unhandled; + if (prefs != null && prefs.Count > 0) + return; - HttpListener bestMatch = null; + prefs = _all; + if (prefs != null && prefs.Count > 0) + return; - var bestLen = -1; - foreach (var pref in prefixes) { - var prefPath = pref.Path; - - var len = prefPath.Length; - if (len < bestLen) - continue; + EndPointManager.RemoveEndPoint(_endpoint); + } - if (path.StartsWith (prefPath)) { - bestLen = len; - bestMatch = pref.Listener; + private async void Accept() + { + + Socket sock = null; + try + { + sock = await this._socket.AcceptAsync(); + } + catch (SocketException) + { + // TODO: Should log the error code when this class has a logging. + } + catch (ObjectDisposedException) + { + return; + } + + try + { + Accept(); + } + catch + { + sock?.Dispose(); + return; + } + + if (sock == null) + return; + + processAccepted(sock, this); } - } - return bestMatch; - } + private static void processAccepted(Socket socket, EndPointListener listener) + { + HttpConnection conn = null; + try + { + conn = new HttpConnection(socket, listener); + lock (listener._unregisteredSync) + listener._unregistered[conn] = conn; + + conn.BeginReadRequest(); + } + catch + { + if (conn != null) + { + conn.Close(true); + return; + } + + socket.Dispose(); + } + } - #endregion + private static bool removeSpecial(List prefixes, HttpListenerPrefix prefix) + { + var path = prefix.Path; + var cnt = prefixes.Count; + for (var i = 0; i < cnt; i++) + { + if (prefixes[i].Path == path) + { + prefixes.RemoveAt(i); + return true; + } + } + + return false; + } - #region Internal Methods + private static HttpListener searchHttpListenerFromSpecial( + string path, List prefixes + ) + { + if (prefixes == null) + return null; - internal static bool CertificateExists (int port, string folderPath) - { - if (folderPath == null || folderPath.Length == 0) - folderPath = _defaultCertFolderPath; + HttpListener bestMatch = null; - var cer = Path.Combine (folderPath, String.Format ("{0}.cer", port)); - var key = Path.Combine (folderPath, String.Format ("{0}.key", port)); + var bestLen = -1; + foreach (var pref in prefixes) + { + var prefPath = pref.Path; - return File.Exists (cer) && File.Exists (key); - } + var len = prefPath.Length; + if (len < bestLen) + continue; - internal void RemoveConnection (HttpConnection connection) - { - lock (_unregisteredSync) - _unregistered.Remove (connection); - } + if (path.StartsWith(prefPath)) + { + bestLen = len; + bestMatch = pref.Listener; + } + } - internal bool TrySearchHttpListener (Uri uri, out HttpListener listener) - { - listener = null; - - if (uri == null) - return false; - - var host = uri.Host; - var dns = Uri.CheckHostName (host) == UriHostNameType.Dns; - var port = uri.Port.ToString (); - var path = HttpUtility.UrlDecode (uri.AbsolutePath); - var pathSlash = path[path.Length - 1] != '/' ? path + "/" : path; - - if (host != null && host.Length > 0) { - var bestLen = -1; - foreach (var pref in _prefixes.Keys) { - if (dns) { - var prefHost = pref.Host; - if (Uri.CheckHostName (prefHost) == UriHostNameType.Dns && prefHost != host) - continue; - } - - if (pref.Port != port) - continue; - - var prefPath = pref.Path; - - var len = prefPath.Length; - if (len < bestLen) - continue; - - if (path.StartsWith (prefPath) || pathSlash.StartsWith (prefPath)) { - bestLen = len; - listener = _prefixes[pref]; - } + return bestMatch; } - if (bestLen != -1) - return true; - } - - var prefs = _unhandled; - listener = searchHttpListenerFromSpecial (path, prefs); - if (listener == null && pathSlash != path) - listener = searchHttpListenerFromSpecial (pathSlash, prefs); + #endregion - if (listener != null) - return true; + #region Internal Methods - prefs = _all; - listener = searchHttpListenerFromSpecial (path, prefs); - if (listener == null && pathSlash != path) - listener = searchHttpListenerFromSpecial (pathSlash, prefs); - - return listener != null; - } + internal static bool CertificateExists(int port, string folderPath) + { + if (folderPath == null || folderPath.Length == 0) + folderPath = _defaultCertFolderPath; - #endregion + var cer = Path.Combine(folderPath, String.Format("{0}.cer", port)); + var key = Path.Combine(folderPath, String.Format("{0}.key", port)); - #region Public Methods - - public void AddPrefix (HttpListenerPrefix prefix, HttpListener listener) - { - List current, future; - if (prefix.Host == "*") { - do { - current = _unhandled; - future = current != null - ? new List (current) - : new List (); - - prefix.Listener = listener; - addSpecial (future, prefix); + return File.Exists(cer) && File.Exists(key); } - while (Interlocked.CompareExchange (ref _unhandled, future, current) != current); - - return; - } - if (prefix.Host == "+") { - do { - current = _all; - future = current != null - ? new List (current) - : new List (); - - prefix.Listener = listener; - addSpecial (future, prefix); + internal void RemoveConnection(HttpConnection connection) + { + lock (_unregisteredSync) + _unregistered.Remove(connection); } - while (Interlocked.CompareExchange (ref _all, future, current) != current); - - return; - } - - Dictionary prefs, prefs2; - do { - prefs = _prefixes; - if (prefs.ContainsKey (prefix)) { - if (prefs[prefix] != listener) { - throw new HttpListenerException ( - 87, String.Format ("There's another listener for {0}.", prefix) - ); - } - - return; + + internal bool TrySearchHttpListener(Uri uri, out HttpListener listener) + { + listener = null; + + if (uri == null) + return false; + + var host = uri.Host; + var dns = Uri.CheckHostName(host) == UriHostNameType.Dns; + var port = uri.Port.ToString(); + var path = HttpUtility.UrlDecode(uri.AbsolutePath); + var pathSlash = path[path.Length - 1] != '/' ? path + "/" : path; + + if (host != null && host.Length > 0) + { + var bestLen = -1; + foreach (var pref in _prefixes.Keys) + { + if (dns) + { + var prefHost = pref.Host; + if (Uri.CheckHostName(prefHost) == UriHostNameType.Dns && prefHost != host) + continue; + } + + if (pref.Port != port) + continue; + + var prefPath = pref.Path; + + var len = prefPath.Length; + if (len < bestLen) + continue; + + if (path.StartsWith(prefPath) || pathSlash.StartsWith(prefPath)) + { + bestLen = len; + listener = _prefixes[pref]; + } + } + + if (bestLen != -1) + return true; + } + + var prefs = _unhandled; + listener = searchHttpListenerFromSpecial(path, prefs); + if (listener == null && pathSlash != path) + listener = searchHttpListenerFromSpecial(pathSlash, prefs); + + if (listener != null) + return true; + + prefs = _all; + listener = searchHttpListenerFromSpecial(path, prefs); + if (listener == null && pathSlash != path) + listener = searchHttpListenerFromSpecial(pathSlash, prefs); + + return listener != null; } - prefs2 = new Dictionary (prefs); - prefs2[prefix] = listener; - } - while (Interlocked.CompareExchange (ref _prefixes, prefs2, prefs) != prefs); - } + #endregion + + #region Public Methods + + public void AddPrefix(HttpListenerPrefix prefix, HttpListener listener) + { + List current, future; + if (prefix.Host == "*") + { + do + { + current = _unhandled; + future = current != null + ? new List(current) + : new List(); + + prefix.Listener = listener; + addSpecial(future, prefix); + } while (Interlocked.CompareExchange(ref _unhandled, future, current) != current); + + return; + } + + if (prefix.Host == "+") + { + do + { + current = _all; + future = current != null + ? new List(current) + : new List(); + + prefix.Listener = listener; + addSpecial(future, prefix); + } while (Interlocked.CompareExchange(ref _all, future, current) != current); + + return; + } + + Dictionary prefs, prefs2; + do + { + prefs = _prefixes; + if (prefs.ContainsKey(prefix)) + { + if (prefs[prefix] != listener) + { + throw new HttpListenerException( + 87, String.Format("There's another listener for {0}.", prefix) + ); + } + + return; + } + + prefs2 = new Dictionary(prefs); + prefs2[prefix] = listener; + } while (Interlocked.CompareExchange(ref _prefixes, prefs2, prefs) != prefs); + } - public void Close () - { - _socket.Close (); + public void Close() + { + _socket.Dispose(); - HttpConnection[] conns = null; - lock (_unregisteredSync) { - if (_unregistered.Count == 0) - return; + HttpConnection[] conns = null; + lock (_unregisteredSync) + { + if (_unregistered.Count == 0) + return; - var keys = _unregistered.Keys; - conns = new HttpConnection[keys.Count]; - keys.CopyTo (conns, 0); - _unregistered.Clear (); - } + var keys = _unregistered.Keys; + conns = new HttpConnection[keys.Count]; + keys.CopyTo(conns, 0); + _unregistered.Clear(); + } - for (var i = conns.Length - 1; i >= 0; i--) - conns[i].Close (true); - } - - public void RemovePrefix (HttpListenerPrefix prefix, HttpListener listener) - { - List current, future; - if (prefix.Host == "*") { - do { - current = _unhandled; - if (current == null) - break; - - future = new List (current); - if (!removeSpecial (future, prefix)) - break; // The prefix wasn't found. + for (var i = conns.Length - 1; i >= 0; i--) + conns[i].Close(true); } - while (Interlocked.CompareExchange (ref _unhandled, future, current) != current); - - leaveIfNoPrefix (); - return; - } - - if (prefix.Host == "+") { - do { - current = _all; - if (current == null) - break; - future = new List (current); - if (!removeSpecial (future, prefix)) - break; // The prefix wasn't found. + public void RemovePrefix(HttpListenerPrefix prefix, HttpListener listener) + { + List current, future; + if (prefix.Host == "*") + { + do + { + current = _unhandled; + if (current == null) + break; + + future = new List(current); + if (!removeSpecial(future, prefix)) + break; // The prefix wasn't found. + } while (Interlocked.CompareExchange(ref _unhandled, future, current) != current); + + leaveIfNoPrefix(); + return; + } + + if (prefix.Host == "+") + { + do + { + current = _all; + if (current == null) + break; + + future = new List(current); + if (!removeSpecial(future, prefix)) + break; // The prefix wasn't found. + } while (Interlocked.CompareExchange(ref _all, future, current) != current); + + leaveIfNoPrefix(); + return; + } + + Dictionary prefs, prefs2; + do + { + prefs = _prefixes; + if (!prefs.ContainsKey(prefix)) + break; + + prefs2 = new Dictionary(prefs); + prefs2.Remove(prefix); + } while (Interlocked.CompareExchange(ref _prefixes, prefs2, prefs) != prefs); + + leaveIfNoPrefix(); } - while (Interlocked.CompareExchange (ref _all, future, current) != current); - leaveIfNoPrefix (); - return; - } - - Dictionary prefs, prefs2; - do { - prefs = _prefixes; - if (!prefs.ContainsKey (prefix)) - break; - - prefs2 = new Dictionary (prefs); - prefs2.Remove (prefix); - } - while (Interlocked.CompareExchange (ref _prefixes, prefs2, prefs) != prefs); - - leaveIfNoPrefix (); + #endregion } - - #endregion - } } diff --git a/websocket-sharp/Net/HttpConnection.cs b/websocket-sharp/Net/HttpConnection.cs index 36d9a461e..9fbb21a20 100644 --- a/websocket-sharp/Net/HttpConnection.cs +++ b/websocket-sharp/Net/HttpConnection.cs @@ -96,12 +96,12 @@ internal HttpConnection (Socket socket, EndPointListener listener) if (_secure) { var conf = listener.SslConfiguration; var sslStream = new SslStream (netStream, false, conf.ClientCertificateValidationCallback); - sslStream.AuthenticateAsServer ( + sslStream.AuthenticateAsServerAsync ( conf.ServerCertificate, conf.ClientCertificateRequired, conf.EnabledSslProtocols, conf.CheckCertificateRevocation - ); + ).RunSynchronously(); _stream = sslStream; } @@ -185,7 +185,7 @@ private void closeSocket () catch { } - _socket.Close (); + _socket.Dispose(); _socket = null; } @@ -559,7 +559,7 @@ public void SendError (string message, int status) var enc = Encoding.UTF8; var entity = enc.GetBytes (content.ToString ()); res.ContentEncoding = enc; - res.ContentLength64 = entity.LongLength; + res.ContentLength64 = entity.Length; res.Close (entity, true); } diff --git a/websocket-sharp/Net/HttpListener.cs b/websocket-sharp/Net/HttpListener.cs index c83349871..0dbcd4d48 100644 --- a/websocket-sharp/Net/HttpListener.cs +++ b/websocket-sharp/Net/HttpListener.cs @@ -723,11 +723,11 @@ public HttpListenerContext EndGetContext (IAsyncResult asyncResult) { CheckDisposed (); if (asyncResult == null) - throw new ArgumentNullException ("asyncResult"); + throw new ArgumentNullException (nameof(asyncResult)); var ares = asyncResult as HttpListenerAsyncResult; if (ares == null) - throw new ArgumentException ("A wrong IAsyncResult.", "asyncResult"); + throw new ArgumentException ("A wrong IAsyncResult.", nameof(asyncResult)); if (ares.EndCalled) throw new InvalidOperationException ("This IAsyncResult cannot be reused."); diff --git a/websocket-sharp/Net/HttpListenerContext.cs b/websocket-sharp/Net/HttpListenerContext.cs index 638078d4f..cae36e83f 100644 --- a/websocket-sharp/Net/HttpListenerContext.cs +++ b/websocket-sharp/Net/HttpListenerContext.cs @@ -241,10 +241,10 @@ public HttpListenerWebSocketContext AcceptWebSocket (string protocol) if (protocol != null) { if (protocol.Length == 0) - throw new ArgumentException ("An empty string.", "protocol"); + throw new ArgumentException ("An empty string.", nameof(protocol)); if (!protocol.IsToken ()) - throw new ArgumentException ("Contains an invalid character.", "protocol"); + throw new ArgumentException ("Contains an invalid character.", nameof(protocol)); } _websocketContext = new HttpListenerWebSocketContext (this, protocol); diff --git a/websocket-sharp/Net/HttpListenerException.cs b/websocket-sharp/Net/HttpListenerException.cs index a52eeec03..31cc8f7ca 100644 --- a/websocket-sharp/Net/HttpListenerException.cs +++ b/websocket-sharp/Net/HttpListenerException.cs @@ -62,11 +62,7 @@ public class HttpListenerException : Win32Exception /// /// A that specifies the source for the deserialization. /// - protected HttpListenerException ( - SerializationInfo serializationInfo, StreamingContext streamingContext) - : base (serializationInfo, streamingContext) - { - } + #endregion @@ -116,7 +112,7 @@ public HttpListenerException (int errorCode, string message) /// /// An that identifies the error. /// - public override int ErrorCode { + public int ErrorCode { get { return NativeErrorCode; } diff --git a/websocket-sharp/Net/HttpListenerPrefix.cs b/websocket-sharp/Net/HttpListenerPrefix.cs index 960d02edf..3d2d8f84e 100644 --- a/websocket-sharp/Net/HttpListenerPrefix.cs +++ b/websocket-sharp/Net/HttpListenerPrefix.cs @@ -155,34 +155,34 @@ private void parse (string uriPrefix) public static void CheckPrefix (string uriPrefix) { if (uriPrefix == null) - throw new ArgumentNullException ("uriPrefix"); + throw new ArgumentNullException (nameof(uriPrefix)); var len = uriPrefix.Length; if (len == 0) - throw new ArgumentException ("An empty string.", "uriPrefix"); + throw new ArgumentException ("An empty string.", nameof(uriPrefix)); if (!(uriPrefix.StartsWith ("http://") || uriPrefix.StartsWith ("https://"))) - throw new ArgumentException ("The scheme isn't 'http' or 'https'.", "uriPrefix"); + throw new ArgumentException ("The scheme isn't 'http' or 'https'.", nameof(uriPrefix)); var startHost = uriPrefix.IndexOf (':') + 3; if (startHost >= len) - throw new ArgumentException ("No host is specified.", "uriPrefix"); + throw new ArgumentException ("No host is specified.", nameof(uriPrefix)); if (uriPrefix[startHost] == ':') - throw new ArgumentException ("No host is specified.", "uriPrefix"); + throw new ArgumentException ("No host is specified.", nameof(uriPrefix)); var root = uriPrefix.IndexOf ('/', startHost, len - startHost); if (root == startHost) - throw new ArgumentException ("No host is specified.", "uriPrefix"); + throw new ArgumentException ("No host is specified.", nameof(uriPrefix)); if (root == -1 || uriPrefix[len - 1] != '/') - throw new ArgumentException ("Ends without '/'.", "uriPrefix"); + throw new ArgumentException ("Ends without '/'.", nameof(uriPrefix)); if (uriPrefix[root - 1] == ':') - throw new ArgumentException ("No port is specified.", "uriPrefix"); + throw new ArgumentException ("No port is specified.", nameof(uriPrefix)); if (root == len - 2) - throw new ArgumentException ("No path is specified.", "uriPrefix"); + throw new ArgumentException ("No path is specified.", nameof(uriPrefix)); } /// diff --git a/websocket-sharp/Net/HttpListenerPrefixCollection.cs b/websocket-sharp/Net/HttpListenerPrefixCollection.cs index 6373b8d65..ed7376f3e 100644 --- a/websocket-sharp/Net/HttpListenerPrefixCollection.cs +++ b/websocket-sharp/Net/HttpListenerPrefixCollection.cs @@ -174,7 +174,7 @@ public bool Contains (string uriPrefix) { _listener.CheckDisposed (); if (uriPrefix == null) - throw new ArgumentNullException ("uriPrefix"); + throw new ArgumentNullException (nameof(uriPrefix)); return _prefixes.Contains (uriPrefix); } @@ -249,7 +249,7 @@ public bool Remove (string uriPrefix) { _listener.CheckDisposed (); if (uriPrefix == null) - throw new ArgumentNullException ("uriPrefix"); + throw new ArgumentNullException (nameof(uriPrefix)); var ret = _prefixes.Remove (uriPrefix); if (ret && _listener.IsListening) diff --git a/websocket-sharp/Net/HttpListenerRequest.cs b/websocket-sharp/Net/HttpListenerRequest.cs index 701128120..d3cfc84f1 100644 --- a/websocket-sharp/Net/HttpListenerRequest.cs +++ b/websocket-sharp/Net/HttpListenerRequest.cs @@ -141,7 +141,7 @@ public int ClientCertificateError { /// public Encoding ContentEncoding { get { - return _contentEncoding ?? (_contentEncoding = Encoding.Default); + return _contentEncoding ?? (_contentEncoding = Encoding.UTF8); } } @@ -491,7 +491,7 @@ internal void AddHeader (string header) var val = header.Substring (colon + 1).Trim (); _headers.InternalSet (name, val, false); - var lower = name.ToLower (CultureInfo.InvariantCulture); + var lower = name.ToLowerInvariant(); if (lower == "accept") { _acceptTypes = new List (val.SplitHeaderValue (',')).ToArray (); return; diff --git a/websocket-sharp/Net/HttpListenerResponse.cs b/websocket-sharp/Net/HttpListenerResponse.cs index 92c0a7970..b8512c1c8 100644 --- a/websocket-sharp/Net/HttpListenerResponse.cs +++ b/websocket-sharp/Net/HttpListenerResponse.cs @@ -192,7 +192,7 @@ public string ContentType { set { checkDisposed (); if (value != null && value.Length == 0) - throw new ArgumentException ("An empty string.", "value"); + throw new ArgumentException ("An empty string.", nameof(value)); _contentType = value; } @@ -304,10 +304,10 @@ public Version ProtocolVersion { set { checkDisposedOrHeadersSent (); if (value == null) - throw new ArgumentNullException ("value"); + throw new ArgumentNullException (nameof(value)); if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1)) - throw new ArgumentException ("Not 1.0 or 1.1.", "value"); + throw new ArgumentException ("Not 1.0 or 1.1.", nameof(value)); _version = value; } @@ -340,7 +340,7 @@ public string RedirectLocation { Uri uri = null; if (!value.MaybeUri () || !Uri.TryCreate (value, UriKind.Absolute, out uri)) - throw new ArgumentException ("Not an absolute URL.", "value"); + throw new ArgumentException ("Not an absolute URL.", nameof(value)); _location = value; } @@ -434,7 +434,7 @@ public string StatusDescription { } if (!value.IsText () || value.IndexOfAny (new[] { '\r', '\n' }) > -1) - throw new ArgumentException ("Contains invalid characters.", "value"); + throw new ArgumentException ("Contains invalid characters.", nameof(value)); _statusDescription = value; } @@ -565,7 +565,7 @@ internal WebHeaderCollection WriteHeadersTo (MemoryStream destination) foreach (Cookie cookie in _cookies) headers.InternalSet ("Set-Cookie", cookie.ToResponseString (), true); - var enc = _contentEncoding ?? Encoding.Default; + var enc = _contentEncoding ?? Encoding.UTF8; var writer = new StreamWriter (destination, enc, 256); writer.Write ("HTTP/{0} {1} {2}\r\n", _version, _statusCode, _statusDescription); writer.Write (headers.ToStringMultiValue (true)); @@ -709,7 +709,7 @@ public void Close (byte[] responseEntity, bool willBlock) { checkDisposed (); if (responseEntity == null) - throw new ArgumentNullException ("responseEntity"); + throw new ArgumentNullException (nameof(responseEntity)); var len = responseEntity.Length; var output = OutputStream; @@ -744,7 +744,7 @@ public void Close (byte[] responseEntity, bool willBlock) public void CopyFrom (HttpListenerResponse templateResponse) { if (templateResponse == null) - throw new ArgumentNullException ("templateResponse"); + throw new ArgumentNullException (nameof(templateResponse)); if (templateResponse._headers != null) { if (_headers != null) @@ -792,11 +792,11 @@ public void Redirect (string url) { checkDisposedOrHeadersSent (); if (url == null) - throw new ArgumentNullException ("url"); + throw new ArgumentNullException (nameof(url)); Uri uri = null; if (!url.MaybeUri () || !Uri.TryCreate (url, UriKind.Absolute, out uri)) - throw new ArgumentException ("Not an absolute URL.", "url"); + throw new ArgumentException ("Not an absolute URL.", nameof(url)); _location = url; _statusCode = 302; @@ -818,10 +818,10 @@ public void Redirect (string url) public void SetCookie (Cookie cookie) { if (cookie == null) - throw new ArgumentNullException ("cookie"); + throw new ArgumentNullException (nameof(cookie)); if (!canAddOrUpdate (cookie)) - throw new ArgumentException ("Cannot be replaced.", "cookie"); + throw new ArgumentException ("Cannot be replaced.", nameof(cookie)); Cookies.Add (cookie); } diff --git a/websocket-sharp/Net/HttpUtility.cs b/websocket-sharp/Net/HttpUtility.cs index 3e3c78cf6..11efd0167 100644 --- a/websocket-sharp/Net/HttpUtility.cs +++ b/websocket-sharp/Net/HttpUtility.cs @@ -51,1214 +51,1294 @@ namespace WebSocketSharp.Net { - internal sealed class HttpUtility - { - #region Private Fields - - private static Dictionary _entities; - private static char[] _hexChars = "0123456789abcdef".ToCharArray (); - private static object _sync = new object (); - - #endregion - - #region Private Methods - - private static int getChar (byte[] bytes, int offset, int length) + internal sealed class HttpUtility { - var val = 0; - var end = length + offset; - for (var i = offset; i < end; i++) { - var current = getInt (bytes[i]); - if (current == -1) - return -1; + #region Private Fields - val = (val << 4) + current; - } + private static Dictionary _entities; + private static char[] _hexChars = "0123456789abcdef".ToCharArray(); + private static object _sync = new object(); - return val; - } + #endregion - private static int getChar (string s, int offset, int length) - { - var val = 0; - var end = length + offset; - for (var i = offset; i < end; i++) { - var c = s[i]; - if (c > 127) - return -1; + #region Private Methods - var current = getInt ((byte) c); - if (current == -1) - return -1; + private static int getChar(byte[] bytes, int offset, int length) + { + var val = 0; + var end = length + offset; + for (var i = offset; i < end; i++) + { + var current = getInt(bytes[i]); + if (current == -1) + return -1; - val = (val << 4) + current; - } + val = (val << 4) + current; + } - return val; - } + return val; + } - private static char[] getChars (MemoryStream buffer, Encoding encoding) - { - return encoding.GetChars (buffer.GetBuffer (), 0, (int) buffer.Length); - } + private static int getChar(string s, int offset, int length) + { + var val = 0; + var end = length + offset; + for (var i = offset; i < end; i++) + { + var c = s[i]; + if (c > 127) + return -1; + + var current = getInt((byte) c); + if (current == -1) + return -1; + + val = (val << 4) + current; + } - private static Dictionary getEntities () - { - lock (_sync) { - if (_entities == null) - initEntities (); + return val; + } - return _entities; - } - } + private static char[] getChars(MemoryStream buffer, Encoding encoding) + { + return encoding.GetChars(buffer.GetBuffer(), 0, (int) buffer.Length); + } - private static int getInt (byte b) - { - var c = (char) b; - return c >= '0' && c <= '9' - ? c - '0' - : c >= 'a' && c <= 'f' - ? c - 'a' + 10 - : c >= 'A' && c <= 'F' - ? c - 'A' + 10 - : -1; - } + private static Dictionary getEntities() + { + lock (_sync) + { + if (_entities == null) + initEntities(); - private static void initEntities () - { - // Build the dictionary of HTML entity references. - // This list comes from the HTML 4.01 W3C recommendation. - _entities = new Dictionary (); - _entities.Add ("nbsp", '\u00A0'); - _entities.Add ("iexcl", '\u00A1'); - _entities.Add ("cent", '\u00A2'); - _entities.Add ("pound", '\u00A3'); - _entities.Add ("curren", '\u00A4'); - _entities.Add ("yen", '\u00A5'); - _entities.Add ("brvbar", '\u00A6'); - _entities.Add ("sect", '\u00A7'); - _entities.Add ("uml", '\u00A8'); - _entities.Add ("copy", '\u00A9'); - _entities.Add ("ordf", '\u00AA'); - _entities.Add ("laquo", '\u00AB'); - _entities.Add ("not", '\u00AC'); - _entities.Add ("shy", '\u00AD'); - _entities.Add ("reg", '\u00AE'); - _entities.Add ("macr", '\u00AF'); - _entities.Add ("deg", '\u00B0'); - _entities.Add ("plusmn", '\u00B1'); - _entities.Add ("sup2", '\u00B2'); - _entities.Add ("sup3", '\u00B3'); - _entities.Add ("acute", '\u00B4'); - _entities.Add ("micro", '\u00B5'); - _entities.Add ("para", '\u00B6'); - _entities.Add ("middot", '\u00B7'); - _entities.Add ("cedil", '\u00B8'); - _entities.Add ("sup1", '\u00B9'); - _entities.Add ("ordm", '\u00BA'); - _entities.Add ("raquo", '\u00BB'); - _entities.Add ("frac14", '\u00BC'); - _entities.Add ("frac12", '\u00BD'); - _entities.Add ("frac34", '\u00BE'); - _entities.Add ("iquest", '\u00BF'); - _entities.Add ("Agrave", '\u00C0'); - _entities.Add ("Aacute", '\u00C1'); - _entities.Add ("Acirc", '\u00C2'); - _entities.Add ("Atilde", '\u00C3'); - _entities.Add ("Auml", '\u00C4'); - _entities.Add ("Aring", '\u00C5'); - _entities.Add ("AElig", '\u00C6'); - _entities.Add ("Ccedil", '\u00C7'); - _entities.Add ("Egrave", '\u00C8'); - _entities.Add ("Eacute", '\u00C9'); - _entities.Add ("Ecirc", '\u00CA'); - _entities.Add ("Euml", '\u00CB'); - _entities.Add ("Igrave", '\u00CC'); - _entities.Add ("Iacute", '\u00CD'); - _entities.Add ("Icirc", '\u00CE'); - _entities.Add ("Iuml", '\u00CF'); - _entities.Add ("ETH", '\u00D0'); - _entities.Add ("Ntilde", '\u00D1'); - _entities.Add ("Ograve", '\u00D2'); - _entities.Add ("Oacute", '\u00D3'); - _entities.Add ("Ocirc", '\u00D4'); - _entities.Add ("Otilde", '\u00D5'); - _entities.Add ("Ouml", '\u00D6'); - _entities.Add ("times", '\u00D7'); - _entities.Add ("Oslash", '\u00D8'); - _entities.Add ("Ugrave", '\u00D9'); - _entities.Add ("Uacute", '\u00DA'); - _entities.Add ("Ucirc", '\u00DB'); - _entities.Add ("Uuml", '\u00DC'); - _entities.Add ("Yacute", '\u00DD'); - _entities.Add ("THORN", '\u00DE'); - _entities.Add ("szlig", '\u00DF'); - _entities.Add ("agrave", '\u00E0'); - _entities.Add ("aacute", '\u00E1'); - _entities.Add ("acirc", '\u00E2'); - _entities.Add ("atilde", '\u00E3'); - _entities.Add ("auml", '\u00E4'); - _entities.Add ("aring", '\u00E5'); - _entities.Add ("aelig", '\u00E6'); - _entities.Add ("ccedil", '\u00E7'); - _entities.Add ("egrave", '\u00E8'); - _entities.Add ("eacute", '\u00E9'); - _entities.Add ("ecirc", '\u00EA'); - _entities.Add ("euml", '\u00EB'); - _entities.Add ("igrave", '\u00EC'); - _entities.Add ("iacute", '\u00ED'); - _entities.Add ("icirc", '\u00EE'); - _entities.Add ("iuml", '\u00EF'); - _entities.Add ("eth", '\u00F0'); - _entities.Add ("ntilde", '\u00F1'); - _entities.Add ("ograve", '\u00F2'); - _entities.Add ("oacute", '\u00F3'); - _entities.Add ("ocirc", '\u00F4'); - _entities.Add ("otilde", '\u00F5'); - _entities.Add ("ouml", '\u00F6'); - _entities.Add ("divide", '\u00F7'); - _entities.Add ("oslash", '\u00F8'); - _entities.Add ("ugrave", '\u00F9'); - _entities.Add ("uacute", '\u00FA'); - _entities.Add ("ucirc", '\u00FB'); - _entities.Add ("uuml", '\u00FC'); - _entities.Add ("yacute", '\u00FD'); - _entities.Add ("thorn", '\u00FE'); - _entities.Add ("yuml", '\u00FF'); - _entities.Add ("fnof", '\u0192'); - _entities.Add ("Alpha", '\u0391'); - _entities.Add ("Beta", '\u0392'); - _entities.Add ("Gamma", '\u0393'); - _entities.Add ("Delta", '\u0394'); - _entities.Add ("Epsilon", '\u0395'); - _entities.Add ("Zeta", '\u0396'); - _entities.Add ("Eta", '\u0397'); - _entities.Add ("Theta", '\u0398'); - _entities.Add ("Iota", '\u0399'); - _entities.Add ("Kappa", '\u039A'); - _entities.Add ("Lambda", '\u039B'); - _entities.Add ("Mu", '\u039C'); - _entities.Add ("Nu", '\u039D'); - _entities.Add ("Xi", '\u039E'); - _entities.Add ("Omicron", '\u039F'); - _entities.Add ("Pi", '\u03A0'); - _entities.Add ("Rho", '\u03A1'); - _entities.Add ("Sigma", '\u03A3'); - _entities.Add ("Tau", '\u03A4'); - _entities.Add ("Upsilon", '\u03A5'); - _entities.Add ("Phi", '\u03A6'); - _entities.Add ("Chi", '\u03A7'); - _entities.Add ("Psi", '\u03A8'); - _entities.Add ("Omega", '\u03A9'); - _entities.Add ("alpha", '\u03B1'); - _entities.Add ("beta", '\u03B2'); - _entities.Add ("gamma", '\u03B3'); - _entities.Add ("delta", '\u03B4'); - _entities.Add ("epsilon", '\u03B5'); - _entities.Add ("zeta", '\u03B6'); - _entities.Add ("eta", '\u03B7'); - _entities.Add ("theta", '\u03B8'); - _entities.Add ("iota", '\u03B9'); - _entities.Add ("kappa", '\u03BA'); - _entities.Add ("lambda", '\u03BB'); - _entities.Add ("mu", '\u03BC'); - _entities.Add ("nu", '\u03BD'); - _entities.Add ("xi", '\u03BE'); - _entities.Add ("omicron", '\u03BF'); - _entities.Add ("pi", '\u03C0'); - _entities.Add ("rho", '\u03C1'); - _entities.Add ("sigmaf", '\u03C2'); - _entities.Add ("sigma", '\u03C3'); - _entities.Add ("tau", '\u03C4'); - _entities.Add ("upsilon", '\u03C5'); - _entities.Add ("phi", '\u03C6'); - _entities.Add ("chi", '\u03C7'); - _entities.Add ("psi", '\u03C8'); - _entities.Add ("omega", '\u03C9'); - _entities.Add ("thetasym", '\u03D1'); - _entities.Add ("upsih", '\u03D2'); - _entities.Add ("piv", '\u03D6'); - _entities.Add ("bull", '\u2022'); - _entities.Add ("hellip", '\u2026'); - _entities.Add ("prime", '\u2032'); - _entities.Add ("Prime", '\u2033'); - _entities.Add ("oline", '\u203E'); - _entities.Add ("frasl", '\u2044'); - _entities.Add ("weierp", '\u2118'); - _entities.Add ("image", '\u2111'); - _entities.Add ("real", '\u211C'); - _entities.Add ("trade", '\u2122'); - _entities.Add ("alefsym", '\u2135'); - _entities.Add ("larr", '\u2190'); - _entities.Add ("uarr", '\u2191'); - _entities.Add ("rarr", '\u2192'); - _entities.Add ("darr", '\u2193'); - _entities.Add ("harr", '\u2194'); - _entities.Add ("crarr", '\u21B5'); - _entities.Add ("lArr", '\u21D0'); - _entities.Add ("uArr", '\u21D1'); - _entities.Add ("rArr", '\u21D2'); - _entities.Add ("dArr", '\u21D3'); - _entities.Add ("hArr", '\u21D4'); - _entities.Add ("forall", '\u2200'); - _entities.Add ("part", '\u2202'); - _entities.Add ("exist", '\u2203'); - _entities.Add ("empty", '\u2205'); - _entities.Add ("nabla", '\u2207'); - _entities.Add ("isin", '\u2208'); - _entities.Add ("notin", '\u2209'); - _entities.Add ("ni", '\u220B'); - _entities.Add ("prod", '\u220F'); - _entities.Add ("sum", '\u2211'); - _entities.Add ("minus", '\u2212'); - _entities.Add ("lowast", '\u2217'); - _entities.Add ("radic", '\u221A'); - _entities.Add ("prop", '\u221D'); - _entities.Add ("infin", '\u221E'); - _entities.Add ("ang", '\u2220'); - _entities.Add ("and", '\u2227'); - _entities.Add ("or", '\u2228'); - _entities.Add ("cap", '\u2229'); - _entities.Add ("cup", '\u222A'); - _entities.Add ("int", '\u222B'); - _entities.Add ("there4", '\u2234'); - _entities.Add ("sim", '\u223C'); - _entities.Add ("cong", '\u2245'); - _entities.Add ("asymp", '\u2248'); - _entities.Add ("ne", '\u2260'); - _entities.Add ("equiv", '\u2261'); - _entities.Add ("le", '\u2264'); - _entities.Add ("ge", '\u2265'); - _entities.Add ("sub", '\u2282'); - _entities.Add ("sup", '\u2283'); - _entities.Add ("nsub", '\u2284'); - _entities.Add ("sube", '\u2286'); - _entities.Add ("supe", '\u2287'); - _entities.Add ("oplus", '\u2295'); - _entities.Add ("otimes", '\u2297'); - _entities.Add ("perp", '\u22A5'); - _entities.Add ("sdot", '\u22C5'); - _entities.Add ("lceil", '\u2308'); - _entities.Add ("rceil", '\u2309'); - _entities.Add ("lfloor", '\u230A'); - _entities.Add ("rfloor", '\u230B'); - _entities.Add ("lang", '\u2329'); - _entities.Add ("rang", '\u232A'); - _entities.Add ("loz", '\u25CA'); - _entities.Add ("spades", '\u2660'); - _entities.Add ("clubs", '\u2663'); - _entities.Add ("hearts", '\u2665'); - _entities.Add ("diams", '\u2666'); - _entities.Add ("quot", '\u0022'); - _entities.Add ("amp", '\u0026'); - _entities.Add ("lt", '\u003C'); - _entities.Add ("gt", '\u003E'); - _entities.Add ("OElig", '\u0152'); - _entities.Add ("oelig", '\u0153'); - _entities.Add ("Scaron", '\u0160'); - _entities.Add ("scaron", '\u0161'); - _entities.Add ("Yuml", '\u0178'); - _entities.Add ("circ", '\u02C6'); - _entities.Add ("tilde", '\u02DC'); - _entities.Add ("ensp", '\u2002'); - _entities.Add ("emsp", '\u2003'); - _entities.Add ("thinsp", '\u2009'); - _entities.Add ("zwnj", '\u200C'); - _entities.Add ("zwj", '\u200D'); - _entities.Add ("lrm", '\u200E'); - _entities.Add ("rlm", '\u200F'); - _entities.Add ("ndash", '\u2013'); - _entities.Add ("mdash", '\u2014'); - _entities.Add ("lsquo", '\u2018'); - _entities.Add ("rsquo", '\u2019'); - _entities.Add ("sbquo", '\u201A'); - _entities.Add ("ldquo", '\u201C'); - _entities.Add ("rdquo", '\u201D'); - _entities.Add ("bdquo", '\u201E'); - _entities.Add ("dagger", '\u2020'); - _entities.Add ("Dagger", '\u2021'); - _entities.Add ("permil", '\u2030'); - _entities.Add ("lsaquo", '\u2039'); - _entities.Add ("rsaquo", '\u203A'); - _entities.Add ("euro", '\u20AC'); - } + return _entities; + } + } - private static bool notEncoded (char c) - { - return c == '!' || - c == '\'' || - c == '(' || - c == ')' || - c == '*' || - c == '-' || - c == '.' || - c == '_'; - } + private static int getInt(byte b) + { + var c = (char) b; + return c >= '0' && c <= '9' + ? c - '0' + : c >= 'a' && c <= 'f' + ? c - 'a' + 10 + : c >= 'A' && c <= 'F' + ? c - 'A' + 10 + : -1; + } - private static void urlEncode (char c, Stream result, bool unicode) - { - if (c > 255) { - // FIXME: What happens when there is an internal error? - //if (!unicode) - // throw new ArgumentOutOfRangeException ("c", c, "Greater than 255."); - - result.WriteByte ((byte) '%'); - result.WriteByte ((byte) 'u'); - - var i = (int) c; - var idx = i >> 12; - result.WriteByte ((byte) _hexChars[idx]); - - idx = (i >> 8) & 0x0F; - result.WriteByte ((byte) _hexChars[idx]); - - idx = (i >> 4) & 0x0F; - result.WriteByte ((byte) _hexChars[idx]); - - idx = i & 0x0F; - result.WriteByte ((byte) _hexChars[idx]); - - return; - } - - if (c > ' ' && notEncoded (c)) { - result.WriteByte ((byte) c); - return; - } - - if (c == ' ') { - result.WriteByte ((byte) '+'); - return; - } - - if ((c < '0') || - (c < 'A' && c > '9') || - (c > 'Z' && c < 'a') || - (c > 'z')) { - if (unicode && c > 127) { - result.WriteByte ((byte) '%'); - result.WriteByte ((byte) 'u'); - result.WriteByte ((byte) '0'); - result.WriteByte ((byte) '0'); + private static void initEntities() + { + // Build the dictionary of HTML entity references. + // This list comes from the HTML 4.01 W3C recommendation. + _entities = new Dictionary(); + _entities.Add("nbsp", '\u00A0'); + _entities.Add("iexcl", '\u00A1'); + _entities.Add("cent", '\u00A2'); + _entities.Add("pound", '\u00A3'); + _entities.Add("curren", '\u00A4'); + _entities.Add("yen", '\u00A5'); + _entities.Add("brvbar", '\u00A6'); + _entities.Add("sect", '\u00A7'); + _entities.Add("uml", '\u00A8'); + _entities.Add("copy", '\u00A9'); + _entities.Add("ordf", '\u00AA'); + _entities.Add("laquo", '\u00AB'); + _entities.Add("not", '\u00AC'); + _entities.Add("shy", '\u00AD'); + _entities.Add("reg", '\u00AE'); + _entities.Add("macr", '\u00AF'); + _entities.Add("deg", '\u00B0'); + _entities.Add("plusmn", '\u00B1'); + _entities.Add("sup2", '\u00B2'); + _entities.Add("sup3", '\u00B3'); + _entities.Add("acute", '\u00B4'); + _entities.Add("micro", '\u00B5'); + _entities.Add("para", '\u00B6'); + _entities.Add("middot", '\u00B7'); + _entities.Add("cedil", '\u00B8'); + _entities.Add("sup1", '\u00B9'); + _entities.Add("ordm", '\u00BA'); + _entities.Add("raquo", '\u00BB'); + _entities.Add("frac14", '\u00BC'); + _entities.Add("frac12", '\u00BD'); + _entities.Add("frac34", '\u00BE'); + _entities.Add("iquest", '\u00BF'); + _entities.Add("Agrave", '\u00C0'); + _entities.Add("Aacute", '\u00C1'); + _entities.Add("Acirc", '\u00C2'); + _entities.Add("Atilde", '\u00C3'); + _entities.Add("Auml", '\u00C4'); + _entities.Add("Aring", '\u00C5'); + _entities.Add("AElig", '\u00C6'); + _entities.Add("Ccedil", '\u00C7'); + _entities.Add("Egrave", '\u00C8'); + _entities.Add("Eacute", '\u00C9'); + _entities.Add("Ecirc", '\u00CA'); + _entities.Add("Euml", '\u00CB'); + _entities.Add("Igrave", '\u00CC'); + _entities.Add("Iacute", '\u00CD'); + _entities.Add("Icirc", '\u00CE'); + _entities.Add("Iuml", '\u00CF'); + _entities.Add("ETH", '\u00D0'); + _entities.Add("Ntilde", '\u00D1'); + _entities.Add("Ograve", '\u00D2'); + _entities.Add("Oacute", '\u00D3'); + _entities.Add("Ocirc", '\u00D4'); + _entities.Add("Otilde", '\u00D5'); + _entities.Add("Ouml", '\u00D6'); + _entities.Add("times", '\u00D7'); + _entities.Add("Oslash", '\u00D8'); + _entities.Add("Ugrave", '\u00D9'); + _entities.Add("Uacute", '\u00DA'); + _entities.Add("Ucirc", '\u00DB'); + _entities.Add("Uuml", '\u00DC'); + _entities.Add("Yacute", '\u00DD'); + _entities.Add("THORN", '\u00DE'); + _entities.Add("szlig", '\u00DF'); + _entities.Add("agrave", '\u00E0'); + _entities.Add("aacute", '\u00E1'); + _entities.Add("acirc", '\u00E2'); + _entities.Add("atilde", '\u00E3'); + _entities.Add("auml", '\u00E4'); + _entities.Add("aring", '\u00E5'); + _entities.Add("aelig", '\u00E6'); + _entities.Add("ccedil", '\u00E7'); + _entities.Add("egrave", '\u00E8'); + _entities.Add("eacute", '\u00E9'); + _entities.Add("ecirc", '\u00EA'); + _entities.Add("euml", '\u00EB'); + _entities.Add("igrave", '\u00EC'); + _entities.Add("iacute", '\u00ED'); + _entities.Add("icirc", '\u00EE'); + _entities.Add("iuml", '\u00EF'); + _entities.Add("eth", '\u00F0'); + _entities.Add("ntilde", '\u00F1'); + _entities.Add("ograve", '\u00F2'); + _entities.Add("oacute", '\u00F3'); + _entities.Add("ocirc", '\u00F4'); + _entities.Add("otilde", '\u00F5'); + _entities.Add("ouml", '\u00F6'); + _entities.Add("divide", '\u00F7'); + _entities.Add("oslash", '\u00F8'); + _entities.Add("ugrave", '\u00F9'); + _entities.Add("uacute", '\u00FA'); + _entities.Add("ucirc", '\u00FB'); + _entities.Add("uuml", '\u00FC'); + _entities.Add("yacute", '\u00FD'); + _entities.Add("thorn", '\u00FE'); + _entities.Add("yuml", '\u00FF'); + _entities.Add("fnof", '\u0192'); + _entities.Add("Alpha", '\u0391'); + _entities.Add("Beta", '\u0392'); + _entities.Add("Gamma", '\u0393'); + _entities.Add("Delta", '\u0394'); + _entities.Add("Epsilon", '\u0395'); + _entities.Add("Zeta", '\u0396'); + _entities.Add("Eta", '\u0397'); + _entities.Add("Theta", '\u0398'); + _entities.Add("Iota", '\u0399'); + _entities.Add("Kappa", '\u039A'); + _entities.Add("Lambda", '\u039B'); + _entities.Add("Mu", '\u039C'); + _entities.Add("Nu", '\u039D'); + _entities.Add("Xi", '\u039E'); + _entities.Add("Omicron", '\u039F'); + _entities.Add("Pi", '\u03A0'); + _entities.Add("Rho", '\u03A1'); + _entities.Add("Sigma", '\u03A3'); + _entities.Add("Tau", '\u03A4'); + _entities.Add("Upsilon", '\u03A5'); + _entities.Add("Phi", '\u03A6'); + _entities.Add("Chi", '\u03A7'); + _entities.Add("Psi", '\u03A8'); + _entities.Add("Omega", '\u03A9'); + _entities.Add("alpha", '\u03B1'); + _entities.Add("beta", '\u03B2'); + _entities.Add("gamma", '\u03B3'); + _entities.Add("delta", '\u03B4'); + _entities.Add("epsilon", '\u03B5'); + _entities.Add("zeta", '\u03B6'); + _entities.Add("eta", '\u03B7'); + _entities.Add("theta", '\u03B8'); + _entities.Add("iota", '\u03B9'); + _entities.Add("kappa", '\u03BA'); + _entities.Add("lambda", '\u03BB'); + _entities.Add("mu", '\u03BC'); + _entities.Add("nu", '\u03BD'); + _entities.Add("xi", '\u03BE'); + _entities.Add("omicron", '\u03BF'); + _entities.Add("pi", '\u03C0'); + _entities.Add("rho", '\u03C1'); + _entities.Add("sigmaf", '\u03C2'); + _entities.Add("sigma", '\u03C3'); + _entities.Add("tau", '\u03C4'); + _entities.Add("upsilon", '\u03C5'); + _entities.Add("phi", '\u03C6'); + _entities.Add("chi", '\u03C7'); + _entities.Add("psi", '\u03C8'); + _entities.Add("omega", '\u03C9'); + _entities.Add("thetasym", '\u03D1'); + _entities.Add("upsih", '\u03D2'); + _entities.Add("piv", '\u03D6'); + _entities.Add("bull", '\u2022'); + _entities.Add("hellip", '\u2026'); + _entities.Add("prime", '\u2032'); + _entities.Add("Prime", '\u2033'); + _entities.Add("oline", '\u203E'); + _entities.Add("frasl", '\u2044'); + _entities.Add("weierp", '\u2118'); + _entities.Add("image", '\u2111'); + _entities.Add("real", '\u211C'); + _entities.Add("trade", '\u2122'); + _entities.Add("alefsym", '\u2135'); + _entities.Add("larr", '\u2190'); + _entities.Add("uarr", '\u2191'); + _entities.Add("rarr", '\u2192'); + _entities.Add("darr", '\u2193'); + _entities.Add("harr", '\u2194'); + _entities.Add("crarr", '\u21B5'); + _entities.Add("lArr", '\u21D0'); + _entities.Add("uArr", '\u21D1'); + _entities.Add("rArr", '\u21D2'); + _entities.Add("dArr", '\u21D3'); + _entities.Add("hArr", '\u21D4'); + _entities.Add("forall", '\u2200'); + _entities.Add("part", '\u2202'); + _entities.Add("exist", '\u2203'); + _entities.Add("empty", '\u2205'); + _entities.Add("nabla", '\u2207'); + _entities.Add("isin", '\u2208'); + _entities.Add("notin", '\u2209'); + _entities.Add("ni", '\u220B'); + _entities.Add("prod", '\u220F'); + _entities.Add("sum", '\u2211'); + _entities.Add("minus", '\u2212'); + _entities.Add("lowast", '\u2217'); + _entities.Add("radic", '\u221A'); + _entities.Add("prop", '\u221D'); + _entities.Add("infin", '\u221E'); + _entities.Add("ang", '\u2220'); + _entities.Add("and", '\u2227'); + _entities.Add("or", '\u2228'); + _entities.Add("cap", '\u2229'); + _entities.Add("cup", '\u222A'); + _entities.Add("int", '\u222B'); + _entities.Add("there4", '\u2234'); + _entities.Add("sim", '\u223C'); + _entities.Add("cong", '\u2245'); + _entities.Add("asymp", '\u2248'); + _entities.Add("ne", '\u2260'); + _entities.Add("equiv", '\u2261'); + _entities.Add("le", '\u2264'); + _entities.Add("ge", '\u2265'); + _entities.Add("sub", '\u2282'); + _entities.Add("sup", '\u2283'); + _entities.Add("nsub", '\u2284'); + _entities.Add("sube", '\u2286'); + _entities.Add("supe", '\u2287'); + _entities.Add("oplus", '\u2295'); + _entities.Add("otimes", '\u2297'); + _entities.Add("perp", '\u22A5'); + _entities.Add("sdot", '\u22C5'); + _entities.Add("lceil", '\u2308'); + _entities.Add("rceil", '\u2309'); + _entities.Add("lfloor", '\u230A'); + _entities.Add("rfloor", '\u230B'); + _entities.Add("lang", '\u2329'); + _entities.Add("rang", '\u232A'); + _entities.Add("loz", '\u25CA'); + _entities.Add("spades", '\u2660'); + _entities.Add("clubs", '\u2663'); + _entities.Add("hearts", '\u2665'); + _entities.Add("diams", '\u2666'); + _entities.Add("quot", '\u0022'); + _entities.Add("amp", '\u0026'); + _entities.Add("lt", '\u003C'); + _entities.Add("gt", '\u003E'); + _entities.Add("OElig", '\u0152'); + _entities.Add("oelig", '\u0153'); + _entities.Add("Scaron", '\u0160'); + _entities.Add("scaron", '\u0161'); + _entities.Add("Yuml", '\u0178'); + _entities.Add("circ", '\u02C6'); + _entities.Add("tilde", '\u02DC'); + _entities.Add("ensp", '\u2002'); + _entities.Add("emsp", '\u2003'); + _entities.Add("thinsp", '\u2009'); + _entities.Add("zwnj", '\u200C'); + _entities.Add("zwj", '\u200D'); + _entities.Add("lrm", '\u200E'); + _entities.Add("rlm", '\u200F'); + _entities.Add("ndash", '\u2013'); + _entities.Add("mdash", '\u2014'); + _entities.Add("lsquo", '\u2018'); + _entities.Add("rsquo", '\u2019'); + _entities.Add("sbquo", '\u201A'); + _entities.Add("ldquo", '\u201C'); + _entities.Add("rdquo", '\u201D'); + _entities.Add("bdquo", '\u201E'); + _entities.Add("dagger", '\u2020'); + _entities.Add("Dagger", '\u2021'); + _entities.Add("permil", '\u2030'); + _entities.Add("lsaquo", '\u2039'); + _entities.Add("rsaquo", '\u203A'); + _entities.Add("euro", '\u20AC'); } - else { - result.WriteByte ((byte) '%'); + + private static bool notEncoded(char c) + { + return c == '!' || + c == '\'' || + c == '(' || + c == ')' || + c == '*' || + c == '-' || + c == '.' || + c == '_'; } - var i = (int) c; - var idx = i >> 4; - result.WriteByte ((byte) _hexChars[idx]); + private static void urlEncode(char c, Stream result, bool unicode) + { + if (c > 255) + { + // FIXME: What happens when there is an internal error? + //if (!unicode) + // throw new ArgumentOutOfRangeException ("c", c, "Greater than 255."); - idx = i & 0x0F; - result.WriteByte ((byte) _hexChars[idx]); + result.WriteByte((byte) '%'); + result.WriteByte((byte) 'u'); - return; - } + var i = (int) c; + var idx = i >> 12; + result.WriteByte((byte) _hexChars[idx]); - result.WriteByte ((byte) c); - } + idx = (i >> 8) & 0x0F; + result.WriteByte((byte) _hexChars[idx]); - private static void urlPathEncode (char c, Stream result) - { - if (c < 33 || c > 126) { - var bytes = Encoding.UTF8.GetBytes (c.ToString ()); - foreach (var b in bytes) { - result.WriteByte ((byte) '%'); + idx = (i >> 4) & 0x0F; + result.WriteByte((byte) _hexChars[idx]); - var i = (int) b; - var idx = i >> 4; - result.WriteByte ((byte) _hexChars[idx]); + idx = i & 0x0F; + result.WriteByte((byte) _hexChars[idx]); - idx = i & 0x0F; - result.WriteByte ((byte) _hexChars[idx]); - } + return; + } - return; - } + if (c > ' ' && notEncoded(c)) + { + result.WriteByte((byte) c); + return; + } + + if (c == ' ') + { + result.WriteByte((byte) '+'); + return; + } - if (c == ' ') { - result.WriteByte ((byte) '%'); - result.WriteByte ((byte) '2'); - result.WriteByte ((byte) '0'); + if ((c < '0') || + (c < 'A' && c > '9') || + (c > 'Z' && c < 'a') || + (c > 'z')) + { + if (unicode && c > 127) + { + result.WriteByte((byte) '%'); + result.WriteByte((byte) 'u'); + result.WriteByte((byte) '0'); + result.WriteByte((byte) '0'); + } + else + { + result.WriteByte((byte) '%'); + } + + var i = (int) c; + var idx = i >> 4; + result.WriteByte((byte) _hexChars[idx]); + + idx = i & 0x0F; + result.WriteByte((byte) _hexChars[idx]); + + return; + } - return; - } + result.WriteByte((byte) c); + } - result.WriteByte ((byte) c); - } + private static void urlPathEncode(char c, Stream result) + { + if (c < 33 || c > 126) + { + var bytes = Encoding.UTF8.GetBytes(c.ToString()); + foreach (var b in bytes) + { + result.WriteByte((byte) '%'); - private static void writeCharBytes (char c, IList buffer, Encoding encoding) - { - if (c > 255) { - foreach (var b in encoding.GetBytes (new[] { c })) - buffer.Add (b); + var i = (int) b; + var idx = i >> 4; + result.WriteByte((byte) _hexChars[idx]); - return; - } + idx = i & 0x0F; + result.WriteByte((byte) _hexChars[idx]); + } - buffer.Add ((byte) c); - } + return; + } - #endregion + if (c == ' ') + { + result.WriteByte((byte) '%'); + result.WriteByte((byte) '2'); + result.WriteByte((byte) '0'); - #region Internal Methods + return; + } - internal static Uri CreateRequestUrl ( - string requestUri, string host, bool websocketRequest, bool secure) - { - if (requestUri == null || requestUri.Length == 0 || host == null || host.Length == 0) - return null; - - string schm = null; - string path = null; - if (requestUri.StartsWith ("/")) { - path = requestUri; - } - else if (requestUri.MaybeUri ()) { - Uri uri; - var valid = Uri.TryCreate (requestUri, UriKind.Absolute, out uri) && - (((schm = uri.Scheme).StartsWith ("http") && !websocketRequest) || - (schm.StartsWith ("ws") && websocketRequest)); - - if (!valid) - return null; - - host = uri.Authority; - path = uri.PathAndQuery; - } - else if (requestUri == "*") { - } - else { - // As authority form - host = requestUri; - } - - if (schm == null) - schm = (websocketRequest ? "ws" : "http") + (secure ? "s" : String.Empty); - - var colon = host.IndexOf (':'); - if (colon == -1) - host = String.Format ("{0}:{1}", host, schm == "http" || schm == "ws" ? 80 : 443); - - var url = String.Format ("{0}://{1}{2}", schm, host, path); - - Uri res; - if (!Uri.TryCreate (url, UriKind.Absolute, out res)) - return null; - - return res; - } + result.WriteByte((byte) c); + } - internal static IPrincipal CreateUser ( - string response, - AuthenticationSchemes scheme, - string realm, - string method, - Func credentialsFinder - ) - { - if (response == null || response.Length == 0) - return null; + private static void writeCharBytes(char c, IList buffer, Encoding encoding) + { + if (c > 255) + { + foreach (var b in encoding.GetBytes(new[] {c})) + buffer.Add(b); - if (credentialsFinder == null) - return null; + return; + } - if (!(scheme == AuthenticationSchemes.Basic || scheme == AuthenticationSchemes.Digest)) - return null; + buffer.Add((byte) c); + } - if (scheme == AuthenticationSchemes.Digest) { - if (realm == null || realm.Length == 0) - return null; + #endregion - if (method == null || method.Length == 0) - return null; - } + #region Internal Methods - if (!response.StartsWith (scheme.ToString (), StringComparison.OrdinalIgnoreCase)) - return null; + internal static Uri CreateRequestUrl( + string requestUri, string host, bool websocketRequest, bool secure) + { + if (requestUri == null || requestUri.Length == 0 || host == null || host.Length == 0) + return null; - var res = AuthenticationResponse.Parse (response); - if (res == null) - return null; + string schm = null; + string path = null; + if (requestUri.StartsWith("/")) + { + path = requestUri; + } + else if (requestUri.MaybeUri()) + { + Uri uri; + var valid = Uri.TryCreate(requestUri, UriKind.Absolute, out uri) && + (((schm = uri.Scheme).StartsWith("http") && !websocketRequest) || + (schm.StartsWith("ws") && websocketRequest)); + + if (!valid) + return null; + + host = uri.Authority; + path = uri.PathAndQuery; + } + else if (requestUri == "*") + { + } + else + { + // As authority form + host = requestUri; + } - var id = res.ToIdentity (); - if (id == null) - return null; + if (schm == null) + schm = (websocketRequest ? "ws" : "http") + (secure ? "s" : String.Empty); - NetworkCredential cred = null; - try { - cred = credentialsFinder (id); - } - catch { - } + var colon = host.IndexOf(':'); + if (colon == -1) + host = String.Format("{0}:{1}", host, schm == "http" || schm == "ws" ? 80 : 443); - if (cred == null) - return null; + var url = String.Format("{0}://{1}{2}", schm, host, path); - if (scheme == AuthenticationSchemes.Basic - && ((HttpBasicIdentity) id).Password != cred.Password - ) { - return null; - } + Uri res; + if (!Uri.TryCreate(url, UriKind.Absolute, out res)) + return null; - if (scheme == AuthenticationSchemes.Digest - && !((HttpDigestIdentity) id).IsValid (cred.Password, realm, method, null) - ) { - return null; - } + return res; + } - return new GenericPrincipal (id, cred.Roles); - } + internal static IPrincipal CreateUser( + string response, + AuthenticationSchemes scheme, + string realm, + string method, + Func credentialsFinder + ) + { + if (response == null || response.Length == 0) + return null; + + if (credentialsFinder == null) + return null; + + if (!(scheme == AuthenticationSchemes.Basic || scheme == AuthenticationSchemes.Digest)) + return null; + + if (scheme == AuthenticationSchemes.Digest) + { + if (realm == null || realm.Length == 0) + return null; + + if (method == null || method.Length == 0) + return null; + } - internal static Encoding GetEncoding (string contentType) - { - var parts = contentType.Split (';'); - foreach (var p in parts) { - var part = p.Trim (); - if (part.StartsWith ("charset", StringComparison.OrdinalIgnoreCase)) - return Encoding.GetEncoding (part.GetValue ('=', true)); - } - - return null; - } + if (!response.StartsWith(scheme.ToString(), StringComparison.OrdinalIgnoreCase)) + return null; - internal static NameValueCollection InternalParseQueryString (string query, Encoding encoding) - { - int len; - if (query == null || (len = query.Length) == 0 || (len == 1 && query[0] == '?')) - return new NameValueCollection (1); - - if (query[0] == '?') - query = query.Substring (1); - - var res = new QueryStringCollection (); - var components = query.Split ('&'); - foreach (var component in components) { - var i = component.IndexOf ('='); - if (i > -1) { - var name = UrlDecode (component.Substring (0, i), encoding); - var val = component.Length > i + 1 - ? UrlDecode (component.Substring (i + 1), encoding) - : String.Empty; - - res.Add (name, val); - } - else { - res.Add (null, UrlDecode (component, encoding)); - } - } + var res = AuthenticationResponse.Parse(response); + if (res == null) + return null; - return res; - } + var id = res.ToIdentity(); + if (id == null) + return null; - internal static string InternalUrlDecode ( - byte[] bytes, int offset, int count, Encoding encoding) - { - var output = new StringBuilder (); - using (var acc = new MemoryStream ()) { - var end = count + offset; - for (var i = offset; i < end; i++) { - if (bytes[i] == '%' && i + 2 < count && bytes[i + 1] != '%') { - int xchar; - if (bytes[i + 1] == (byte) 'u' && i + 5 < end) { - if (acc.Length > 0) { - output.Append (getChars (acc, encoding)); - acc.SetLength (0); - } - - xchar = getChar (bytes, i + 2, 4); - if (xchar != -1) { - output.Append ((char) xchar); - i += 5; - - continue; - } + NetworkCredential cred = null; + try + { + cred = credentialsFinder(id); } - else if ((xchar = getChar (bytes, i + 1, 2)) != -1) { - acc.WriteByte ((byte) xchar); - i += 2; - - continue; + catch + { } - } - if (acc.Length > 0) { - output.Append (getChars (acc, encoding)); - acc.SetLength (0); - } + if (cred == null) + return null; - if (bytes[i] == '+') { - output.Append (' '); - continue; - } + if (scheme == AuthenticationSchemes.Basic + && ((HttpBasicIdentity) id).Password != cred.Password + ) + { + return null; + } + + if (scheme == AuthenticationSchemes.Digest + && !((HttpDigestIdentity) id).IsValid(cred.Password, realm, method, null) + ) + { + return null; + } - output.Append ((char) bytes[i]); + return new GenericPrincipal(id, cred.Roles); } - if (acc.Length > 0) - output.Append (getChars (acc, encoding)); - } + internal static Encoding GetEncoding(string contentType) + { + var parts = contentType.Split(';'); + foreach (var p in parts) + { + var part = p.Trim(); + if (part.StartsWith("charset", StringComparison.OrdinalIgnoreCase)) + return Encoding.GetEncoding(part.GetValue('=', true)); + } - return output.ToString (); - } + return null; + } - internal static byte[] InternalUrlDecodeToBytes (byte[] bytes, int offset, int count) - { - using (var res = new MemoryStream ()) { - var end = offset + count; - for (var i = offset; i < end; i++) { - var c = (char) bytes[i]; - if (c == '+') { - c = ' '; - } - else if (c == '%' && i < end - 2) { - var xchar = getChar (bytes, i + 1, 2); - if (xchar != -1) { - c = (char) xchar; - i += 2; + internal static NameValueCollection InternalParseQueryString(string query, Encoding encoding) + { + int len; + if (query == null || (len = query.Length) == 0 || (len == 1 && query[0] == '?')) + return new NameValueCollection(1); + + if (query[0] == '?') + query = query.Substring(1); + + var res = new QueryStringCollection(); + var components = query.Split('&'); + foreach (var component in components) + { + var i = component.IndexOf('='); + if (i > -1) + { + var name = UrlDecode(component.Substring(0, i), encoding); + var val = component.Length > i + 1 + ? UrlDecode(component.Substring(i + 1), encoding) + : String.Empty; + + res.Add(name, val); + } + else + { + res.Add(null, UrlDecode(component, encoding)); + } } - } - res.WriteByte ((byte) c); + return res; } - res.Close (); - return res.ToArray (); - } - } + internal static string InternalUrlDecode( + byte[] bytes, int offset, int count, Encoding encoding) + { + var output = new StringBuilder(); + using (var acc = new MemoryStream()) + { + var end = count + offset; + for (var i = offset; i < end; i++) + { + if (bytes[i] == '%' && i + 2 < count && bytes[i + 1] != '%') + { + int xchar; + if (bytes[i + 1] == (byte) 'u' && i + 5 < end) + { + if (acc.Length > 0) + { + output.Append(getChars(acc, encoding)); + acc.SetLength(0); + } + + xchar = getChar(bytes, i + 2, 4); + if (xchar != -1) + { + output.Append((char) xchar); + i += 5; + + continue; + } + } + else if ((xchar = getChar(bytes, i + 1, 2)) != -1) + { + acc.WriteByte((byte) xchar); + i += 2; + + continue; + } + } + + if (acc.Length > 0) + { + output.Append(getChars(acc, encoding)); + acc.SetLength(0); + } + + if (bytes[i] == '+') + { + output.Append(' '); + continue; + } + + output.Append((char) bytes[i]); + } + + if (acc.Length > 0) + output.Append(getChars(acc, encoding)); + } - internal static byte[] InternalUrlEncodeToBytes (byte[] bytes, int offset, int count) - { - using (var res = new MemoryStream ()) { - var end = offset + count; - for (var i = offset; i < end; i++) - urlEncode ((char) bytes[i], res, false); - - res.Close (); - return res.ToArray (); - } - } + return output.ToString(); + } - internal static byte[] InternalUrlEncodeUnicodeToBytes (string s) - { - using (var res = new MemoryStream ()) { - foreach (var c in s) - urlEncode (c, res, true); + internal static byte[] InternalUrlDecodeToBytes(byte[] bytes, int offset, int count) + { + using (var res = new MemoryStream()) + { + var end = offset + count; + for (var i = offset; i < end; i++) + { + var c = (char) bytes[i]; + if (c == '+') + { + c = ' '; + } + else if (c == '%' && i < end - 2) + { + var xchar = getChar(bytes, i + 1, 2); + if (xchar != -1) + { + c = (char) xchar; + i += 2; + } + } + + res.WriteByte((byte) c); + } + + res.Dispose(); + return res.ToArray(); + } + } - res.Close (); - return res.ToArray (); - } - } + internal static byte[] InternalUrlEncodeToBytes(byte[] bytes, int offset, int count) + { + using (var res = new MemoryStream()) + { + var end = offset + count; + for (var i = offset; i < end; i++) + urlEncode((char) bytes[i], res, false); - #endregion + res.Dispose(); + return res.ToArray(); + } + } - #region Public Methods + internal static byte[] InternalUrlEncodeUnicodeToBytes(string s) + { + using (var res = new MemoryStream()) + { + foreach (var c in s) + urlEncode(c, res, true); - public static string HtmlAttributeEncode (string s) - { - if (s == null || s.Length == 0 || !s.Contains ('&', '"', '<', '>')) - return s; - - var output = new StringBuilder (); - foreach (var c in s) - output.Append ( - c == '&' - ? "&" - : c == '"' - ? """ - : c == '<' - ? "<" - : c == '>' - ? ">" - : c.ToString ()); - - return output.ToString (); - } + res.Dispose(); + return res.ToArray(); + } + } - public static void HtmlAttributeEncode (string s, TextWriter output) - { - if (output == null) - throw new ArgumentNullException ("output"); + #endregion + + #region Public Methods + + public static string HtmlAttributeEncode(string s) + { + if (s == null || s.Length == 0 || !s.Contains('&', '"', '<', '>')) + return s; + + var output = new StringBuilder(); + foreach (var c in s) + output.Append( + c == '&' + ? "&" + : c == '"' + ? """ + : c == '<' + ? "<" + : c == '>' + ? ">" + : c.ToString()); + + return output.ToString(); + } - output.Write (HtmlAttributeEncode (s)); - } + public static void HtmlAttributeEncode(string s, TextWriter output) + { + if (output == null) + throw new ArgumentNullException(nameof(output)); - /// - /// Decodes an HTML-encoded and returns the decoded . - /// - /// - /// A that represents the decoded string. - /// - /// - /// A to decode. - /// - public static string HtmlDecode (string s) - { - if (s == null || s.Length == 0 || !s.Contains ('&')) - return s; - - var entity = new StringBuilder (); - var output = new StringBuilder (); - - // 0 -> nothing, - // 1 -> right after '&' - // 2 -> between '&' and ';' but no '#' - // 3 -> '#' found after '&' and getting numbers - var state = 0; - - var number = 0; - var haveTrailingDigits = false; - foreach (var c in s) { - if (state == 0) { - if (c == '&') { - entity.Append (c); - state = 1; - } - else { - output.Append (c); - } - - continue; + output.Write(HtmlAttributeEncode(s)); } - if (c == '&') { - state = 1; - if (haveTrailingDigits) { - entity.Append (number.ToString (CultureInfo.InvariantCulture)); - haveTrailingDigits = false; - } + /// + /// Decodes an HTML-encoded and returns the decoded . + /// + /// + /// A that represents the decoded string. + /// + /// + /// A to decode. + /// + public static string HtmlDecode(string s) + { + if (s == null || s.Length == 0 || !s.Contains('&')) + return s; + + var entity = new StringBuilder(); + var output = new StringBuilder(); + + // 0 -> nothing, + // 1 -> right after '&' + // 2 -> between '&' and ';' but no '#' + // 3 -> '#' found after '&' and getting numbers + var state = 0; + + var number = 0; + var haveTrailingDigits = false; + foreach (var c in s) + { + if (state == 0) + { + if (c == '&') + { + entity.Append(c); + state = 1; + } + else + { + output.Append(c); + } + + continue; + } + + if (c == '&') + { + state = 1; + if (haveTrailingDigits) + { + entity.Append(number.ToString(CultureInfo.InvariantCulture)); + haveTrailingDigits = false; + } + + output.Append(entity.ToString()); + entity.Length = 0; + entity.Append('&'); + + continue; + } + + if (state == 1) + { + if (c == ';') + { + state = 0; + output.Append(entity.ToString()); + output.Append(c); + entity.Length = 0; + } + else + { + number = 0; + if (c != '#') + state = 2; + else + state = 3; + + entity.Append(c); + } + } + else if (state == 2) + { + entity.Append(c); + if (c == ';') + { + var key = entity.ToString(); + var entities = getEntities(); + if (key.Length > 1 && entities.ContainsKey(key.Substring(1, key.Length - 2))) + key = entities[key.Substring(1, key.Length - 2)].ToString(); + + output.Append(key); + state = 0; + entity.Length = 0; + } + } + else if (state == 3) + { + if (c == ';') + { + if (number > 65535) + { + output.Append("&#"); + output.Append(number.ToString(CultureInfo.InvariantCulture)); + output.Append(";"); + } + else + { + output.Append((char) number); + } + + state = 0; + entity.Length = 0; + haveTrailingDigits = false; + } + else if (Char.IsDigit(c)) + { + number = number*10 + ((int) c - '0'); + haveTrailingDigits = true; + } + else + { + state = 2; + if (haveTrailingDigits) + { + entity.Append(number.ToString(CultureInfo.InvariantCulture)); + haveTrailingDigits = false; + } + + entity.Append(c); + } + } + } - output.Append (entity.ToString ()); - entity.Length = 0; - entity.Append ('&'); + if (entity.Length > 0) + output.Append(entity.ToString()); + else if (haveTrailingDigits) + output.Append(number.ToString(CultureInfo.InvariantCulture)); - continue; + return output.ToString(); } - if (state == 1) { - if (c == ';') { - state = 0; - output.Append (entity.ToString ()); - output.Append (c); - entity.Length = 0; - } - else { - number = 0; - if (c != '#') - state = 2; - else - state = 3; - - entity.Append (c); - } - } - else if (state == 2) { - entity.Append (c); - if (c == ';') { - var key = entity.ToString (); - var entities = getEntities (); - if (key.Length > 1 && entities.ContainsKey (key.Substring (1, key.Length - 2))) - key = entities[key.Substring (1, key.Length - 2)].ToString (); - - output.Append (key); - state = 0; - entity.Length = 0; - } + /// + /// Decodes an HTML-encoded and sends the decoded + /// to the specified . + /// + /// + /// A to decode. + /// + /// + /// A that receives the decoded string. + /// + public static void HtmlDecode(string s, TextWriter output) + { + if (output == null) + throw new ArgumentNullException(nameof(output)); + + output.Write(HtmlDecode(s)); } - else if (state == 3) { - if (c == ';') { - if (number > 65535) { - output.Append ("&#"); - output.Append (number.ToString (CultureInfo.InvariantCulture)); - output.Append (";"); - } - else { - output.Append ((char) number); + + /// + /// HTML-encodes a and returns the encoded . + /// + /// + /// A that represents the encoded string. + /// + /// + /// A to encode. + /// + public static string HtmlEncode(string s) + { + if (s == null || s.Length == 0) + return s; + + var needEncode = false; + foreach (var c in s) + { + if (c == '&' || c == '"' || c == '<' || c == '>' || c > 159) + { + needEncode = true; + break; + } } - state = 0; - entity.Length = 0; - haveTrailingDigits = false; - } - else if (Char.IsDigit (c)) { - number = number * 10 + ((int) c - '0'); - haveTrailingDigits = true; - } - else { - state = 2; - if (haveTrailingDigits) { - entity.Append (number.ToString (CultureInfo.InvariantCulture)); - haveTrailingDigits = false; + if (!needEncode) + return s; + + var output = new StringBuilder(); + foreach (var c in s) + { + if (c == '&') + { + output.Append("&"); + } + else if (c == '"') + { + output.Append("""); + } + else if (c == '<') + { + output.Append("<"); + } + else if (c == '>') + { + output.Append(">"); + } + else if (c > 159) + { + // MS starts encoding with &# from 160 and stops at 255. + // We don't do that. One reason is the 65308/65310 unicode + // characters that look like '<' and '>'. + output.Append("&#"); + output.Append(((int) c).ToString(CultureInfo.InvariantCulture)); + output.Append(";"); + } + else + { + output.Append(c); + } } - entity.Append (c); - } + return output.ToString(); } - } - - if (entity.Length > 0) - output.Append (entity.ToString ()); - else if (haveTrailingDigits) - output.Append (number.ToString (CultureInfo.InvariantCulture)); - return output.ToString (); - } - - /// - /// Decodes an HTML-encoded and sends the decoded - /// to the specified . - /// - /// - /// A to decode. - /// - /// - /// A that receives the decoded string. - /// - public static void HtmlDecode (string s, TextWriter output) - { - if (output == null) - throw new ArgumentNullException ("output"); - - output.Write (HtmlDecode (s)); - } + /// + /// HTML-encodes a and sends the encoded + /// to the specified . + /// + /// + /// A to encode. + /// + /// + /// A that receives the encoded string. + /// + public static void HtmlEncode(string s, TextWriter output) + { + if (output == null) + throw new ArgumentNullException(nameof(output)); + + output.Write(HtmlEncode(s)); + } - /// - /// HTML-encodes a and returns the encoded . - /// - /// - /// A that represents the encoded string. - /// - /// - /// A to encode. - /// - public static string HtmlEncode (string s) - { - if (s == null || s.Length == 0) - return s; - - var needEncode = false; - foreach (var c in s) { - if (c == '&' || c == '"' || c == '<' || c == '>' || c > 159) { - needEncode = true; - break; + public static NameValueCollection ParseQueryString(string query) + { + return ParseQueryString(query, Encoding.UTF8); } - } - if (!needEncode) - return s; + public static NameValueCollection ParseQueryString(string query, Encoding encoding) + { + if (query == null) + throw new ArgumentNullException(nameof(query)); - var output = new StringBuilder (); - foreach (var c in s) { - if (c == '&') { - output.Append ("&"); - } - else if (c == '"') { - output.Append ("""); - } - else if (c == '<') { - output.Append ("<"); + return InternalParseQueryString(query, encoding ?? Encoding.UTF8); } - else if (c == '>') { - output.Append (">"); - } - else if (c > 159) { - // MS starts encoding with &# from 160 and stops at 255. - // We don't do that. One reason is the 65308/65310 unicode - // characters that look like '<' and '>'. - output.Append ("&#"); - output.Append (((int) c).ToString (CultureInfo.InvariantCulture)); - output.Append (";"); - } - else { - output.Append (c); + + public static string UrlDecode(string s) + { + return UrlDecode(s, Encoding.UTF8); } - } - return output.ToString (); - } + public static string UrlDecode(string s, Encoding encoding) + { + if (s == null || s.Length == 0 || !s.Contains('%', '+')) + return s; + + if (encoding == null) + encoding = Encoding.UTF8; + + var buff = new List(); + var len = s.Length; + for (var i = 0; i < len; i++) + { + var c = s[i]; + if (c == '%' && i + 2 < len && s[i + 1] != '%') + { + int xchar; + if (s[i + 1] == 'u' && i + 5 < len) + { + // Unicode hex sequence. + xchar = getChar(s, i + 2, 4); + if (xchar != -1) + { + writeCharBytes((char) xchar, buff, encoding); + i += 5; + } + else + { + writeCharBytes('%', buff, encoding); + } + } + else if ((xchar = getChar(s, i + 1, 2)) != -1) + { + writeCharBytes((char) xchar, buff, encoding); + i += 2; + } + else + { + writeCharBytes('%', buff, encoding); + } + + continue; + } + + if (c == '+') + { + writeCharBytes(' ', buff, encoding); + continue; + } + + writeCharBytes(c, buff, encoding); + } - /// - /// HTML-encodes a and sends the encoded - /// to the specified . - /// - /// - /// A to encode. - /// - /// - /// A that receives the encoded string. - /// - public static void HtmlEncode (string s, TextWriter output) - { - if (output == null) - throw new ArgumentNullException ("output"); + return encoding.GetString(buff.ToArray()); + } - output.Write (HtmlEncode (s)); - } + public static string UrlDecode(byte[] bytes, Encoding encoding) + { + int len; + return bytes == null + ? null + : (len = bytes.Length) == 0 + ? String.Empty + : InternalUrlDecode(bytes, 0, len, encoding ?? Encoding.UTF8); + } - public static NameValueCollection ParseQueryString (string query) - { - return ParseQueryString (query, Encoding.UTF8); - } + public static string UrlDecode(byte[] bytes, int offset, int count, Encoding encoding) + { + if (bytes == null) + return null; - public static NameValueCollection ParseQueryString (string query, Encoding encoding) - { - if (query == null) - throw new ArgumentNullException ("query"); + var len = bytes.Length; + if (len == 0 || count == 0) + return String.Empty; - return InternalParseQueryString (query, encoding ?? Encoding.UTF8); - } + if (offset < 0 || offset >= len) + throw new ArgumentOutOfRangeException(nameof(offset)); - public static string UrlDecode (string s) - { - return UrlDecode (s, Encoding.UTF8); - } + if (count < 0 || count > len - offset) + throw new ArgumentOutOfRangeException(nameof(count)); - public static string UrlDecode (string s, Encoding encoding) - { - if (s == null || s.Length == 0 || !s.Contains ('%', '+')) - return s; - - if (encoding == null) - encoding = Encoding.UTF8; - - var buff = new List (); - var len = s.Length; - for (var i = 0; i < len; i++) { - var c = s[i]; - if (c == '%' && i + 2 < len && s[i + 1] != '%') { - int xchar; - if (s[i + 1] == 'u' && i + 5 < len) { - // Unicode hex sequence. - xchar = getChar (s, i + 2, 4); - if (xchar != -1) { - writeCharBytes ((char) xchar, buff, encoding); - i += 5; - } - else { - writeCharBytes ('%', buff, encoding); - } - } - else if ((xchar = getChar (s, i + 1, 2)) != -1) { - writeCharBytes ((char) xchar, buff, encoding); - i += 2; - } - else { - writeCharBytes ('%', buff, encoding); - } - - continue; + return InternalUrlDecode(bytes, offset, count, encoding ?? Encoding.UTF8); } - if (c == '+') { - writeCharBytes (' ', buff, encoding); - continue; + public static byte[] UrlDecodeToBytes(byte[] bytes) + { + int len; + return bytes != null && (len = bytes.Length) > 0 + ? InternalUrlDecodeToBytes(bytes, 0, len) + : bytes; } - writeCharBytes (c, buff, encoding); - } - - return encoding.GetString (buff.ToArray ()); - } + public static byte[] UrlDecodeToBytes(string s) + { + return UrlDecodeToBytes(s, Encoding.UTF8); + } - public static string UrlDecode (byte[] bytes, Encoding encoding) - { - int len; - return bytes == null - ? null - : (len = bytes.Length) == 0 - ? String.Empty - : InternalUrlDecode (bytes, 0, len, encoding ?? Encoding.UTF8); - } + public static byte[] UrlDecodeToBytes(string s, Encoding encoding) + { + if (s == null) + return null; - public static string UrlDecode (byte[] bytes, int offset, int count, Encoding encoding) - { - if (bytes == null) - return null; + if (s.Length == 0) + return new byte[0]; - var len = bytes.Length; - if (len == 0 || count == 0) - return String.Empty; + var bytes = (encoding ?? Encoding.UTF8).GetBytes(s); + return InternalUrlDecodeToBytes(bytes, 0, bytes.Length); + } - if (offset < 0 || offset >= len) - throw new ArgumentOutOfRangeException ("offset"); + public static byte[] UrlDecodeToBytes(byte[] bytes, int offset, int count) + { + int len; + if (bytes == null || (len = bytes.Length) == 0) + return bytes; - if (count < 0 || count > len - offset) - throw new ArgumentOutOfRangeException ("count"); + if (count == 0) + return new byte[0]; - return InternalUrlDecode (bytes, offset, count, encoding ?? Encoding.UTF8); - } + if (offset < 0 || offset >= len) + throw new ArgumentOutOfRangeException(nameof(offset)); - public static byte[] UrlDecodeToBytes (byte[] bytes) - { - int len; - return bytes != null && (len = bytes.Length) > 0 - ? InternalUrlDecodeToBytes (bytes, 0, len) - : bytes; - } + if (count < 0 || count > len - offset) + throw new ArgumentOutOfRangeException(nameof(count)); - public static byte[] UrlDecodeToBytes (string s) - { - return UrlDecodeToBytes (s, Encoding.UTF8); - } - - public static byte[] UrlDecodeToBytes (string s, Encoding encoding) - { - if (s == null) - return null; - - if (s.Length == 0) - return new byte[0]; - - var bytes = (encoding ?? Encoding.UTF8).GetBytes (s); - return InternalUrlDecodeToBytes (bytes, 0, bytes.Length); - } + return InternalUrlDecodeToBytes(bytes, offset, count); + } - public static byte[] UrlDecodeToBytes (byte[] bytes, int offset, int count) - { - int len; - if (bytes == null || (len = bytes.Length) == 0) - return bytes; + public static string UrlEncode(byte[] bytes) + { + int len; + return bytes == null + ? null + : (len = bytes.Length) == 0 + ? String.Empty + : Encoding.ASCII.GetString(InternalUrlEncodeToBytes(bytes, 0, len)); + } - if (count == 0) - return new byte[0]; + public static string UrlEncode(string s) + { + return UrlEncode(s, Encoding.UTF8); + } - if (offset < 0 || offset >= len) - throw new ArgumentOutOfRangeException ("offset"); + public static string UrlEncode(string s, Encoding encoding) + { + int len; + if (s == null || (len = s.Length) == 0) + return s; + + var needEncode = false; + foreach (var c in s) + { + if ((c < '0') || (c < 'A' && c > '9') || (c > 'Z' && c < 'a') || (c > 'z')) + { + if (notEncoded(c)) + continue; + + needEncode = true; + break; + } + } - if (count < 0 || count > len - offset ) - throw new ArgumentOutOfRangeException ("count"); + if (!needEncode) + return s; - return InternalUrlDecodeToBytes (bytes, offset, count); - } + if (encoding == null) + encoding = Encoding.UTF8; - public static string UrlEncode (byte[] bytes) - { - int len; - return bytes == null - ? null - : (len = bytes.Length) == 0 - ? String.Empty - : Encoding.ASCII.GetString (InternalUrlEncodeToBytes (bytes, 0, len)); - } + // Avoided GetByteCount call. + var bytes = new byte[encoding.GetMaxByteCount(len)]; + var realLen = encoding.GetBytes(s, 0, len, bytes, 0); - public static string UrlEncode (string s) - { - return UrlEncode (s, Encoding.UTF8); - } - - public static string UrlEncode (string s, Encoding encoding) - { - int len; - if (s == null || (len = s.Length) == 0) - return s; - - var needEncode = false; - foreach (var c in s) { - if ((c < '0') || (c < 'A' && c > '9') || (c > 'Z' && c < 'a') || (c > 'z')) { - if (notEncoded (c)) - continue; - - needEncode = true; - break; + return Encoding.ASCII.GetString(InternalUrlEncodeToBytes(bytes, 0, realLen)); } - } - - if (!needEncode) - return s; - if (encoding == null) - encoding = Encoding.UTF8; - - // Avoided GetByteCount call. - var bytes = new byte[encoding.GetMaxByteCount (len)]; - var realLen = encoding.GetBytes (s, 0, len, bytes, 0); + public static string UrlEncode(byte[] bytes, int offset, int count) + { + var encoded = UrlEncodeToBytes(bytes, offset, count); + return encoded == null + ? null + : encoded.Length == 0 + ? String.Empty + : Encoding.ASCII.GetString(encoded); + } - return Encoding.ASCII.GetString (InternalUrlEncodeToBytes (bytes, 0, realLen)); - } - - public static string UrlEncode (byte[] bytes, int offset, int count) - { - var encoded = UrlEncodeToBytes (bytes, offset, count); - return encoded == null - ? null - : encoded.Length == 0 - ? String.Empty - : Encoding.ASCII.GetString (encoded); - } + public static byte[] UrlEncodeToBytes(byte[] bytes) + { + int len; + return bytes != null && (len = bytes.Length) > 0 + ? InternalUrlEncodeToBytes(bytes, 0, len) + : bytes; + } - public static byte[] UrlEncodeToBytes (byte[] bytes) - { - int len; - return bytes != null && (len = bytes.Length) > 0 - ? InternalUrlEncodeToBytes (bytes, 0, len) - : bytes; - } + public static byte[] UrlEncodeToBytes(string s) + { + return UrlEncodeToBytes(s, Encoding.UTF8); + } - public static byte[] UrlEncodeToBytes (string s) - { - return UrlEncodeToBytes (s, Encoding.UTF8); - } + public static byte[] UrlEncodeToBytes(string s, Encoding encoding) + { + if (s == null) + return null; - public static byte[] UrlEncodeToBytes (string s, Encoding encoding) - { - if (s == null) - return null; + if (s.Length == 0) + return new byte[0]; - if (s.Length == 0) - return new byte[0]; + var bytes = (encoding ?? Encoding.UTF8).GetBytes(s); + return InternalUrlEncodeToBytes(bytes, 0, bytes.Length); + } - var bytes = (encoding ?? Encoding.UTF8).GetBytes (s); - return InternalUrlEncodeToBytes (bytes, 0, bytes.Length); - } + public static byte[] UrlEncodeToBytes(byte[] bytes, int offset, int count) + { + int len; + if (bytes == null || (len = bytes.Length) == 0) + return bytes; - public static byte[] UrlEncodeToBytes (byte[] bytes, int offset, int count) - { - int len; - if (bytes == null || (len = bytes.Length) == 0) - return bytes; + if (count == 0) + return new byte[0]; - if (count == 0) - return new byte[0]; + if (offset < 0 || offset >= len) + throw new ArgumentOutOfRangeException(nameof(offset)); - if (offset < 0 || offset >= len) - throw new ArgumentOutOfRangeException ("offset"); + if (count < 0 || count > len - offset) + throw new ArgumentOutOfRangeException(nameof(count)); - if (count < 0 || count > len - offset) - throw new ArgumentOutOfRangeException ("count"); + return InternalUrlEncodeToBytes(bytes, offset, count); + } - return InternalUrlEncodeToBytes (bytes, offset, count); - } + public static string UrlEncodeUnicode(string s) + { + return !string.IsNullOrEmpty(s) + ? Encoding.ASCII.GetString(InternalUrlEncodeUnicodeToBytes(s)) + : s; + } - public static string UrlEncodeUnicode (string s) - { - return s != null && s.Length > 0 - ? Encoding.ASCII.GetString (InternalUrlEncodeUnicodeToBytes (s)) - : s; - } + public static byte[] UrlEncodeUnicodeToBytes(string s) + { + return s == null + ? null + : s.Length == 0 + ? new byte[0] + : InternalUrlEncodeUnicodeToBytes(s); + } - public static byte[] UrlEncodeUnicodeToBytes (string s) - { - return s == null - ? null - : s.Length == 0 - ? new byte[0] - : InternalUrlEncodeUnicodeToBytes (s); - } + public static string UrlPathEncode(string s) + { + if (string.IsNullOrEmpty(s)) + return s; - public static string UrlPathEncode (string s) - { - if (s == null || s.Length == 0) - return s; + using (var res = new MemoryStream()) + { + foreach (var c in s) + urlPathEncode(c, res); - using (var res = new MemoryStream ()) { - foreach (var c in s) - urlPathEncode (c, res); + res.Dispose(); + return Encoding.ASCII.GetString(res.ToArray()); + } + } - res.Close (); - return Encoding.ASCII.GetString (res.ToArray ()); - } + #endregion } - - #endregion - } } diff --git a/websocket-sharp/Net/NetworkCredential.cs b/websocket-sharp/Net/NetworkCredential.cs index b90bcad25..bb6d9b492 100644 --- a/websocket-sharp/Net/NetworkCredential.cs +++ b/websocket-sharp/Net/NetworkCredential.cs @@ -97,10 +97,10 @@ public NetworkCredential ( string userName, string password, string domain, params string[] roles) { if (userName == null) - throw new ArgumentNullException ("userName"); + throw new ArgumentNullException (nameof(userName)); if (userName.Length == 0) - throw new ArgumentException ("An empty string.", "userName"); + throw new ArgumentException ("An empty string.", nameof(userName)); _userName = userName; _password = password; diff --git a/websocket-sharp/Net/RequestStream.cs b/websocket-sharp/Net/RequestStream.cs index dd40b3784..e08199c04 100644 --- a/websocket-sharp/Net/RequestStream.cs +++ b/websocket-sharp/Net/RequestStream.cs @@ -120,13 +120,13 @@ public override long Position { private int fillFromBuffer (byte[] buffer, int offset, int count) { if (buffer == null) - throw new ArgumentNullException ("buffer"); + throw new ArgumentNullException (nameof(buffer)); if (offset < 0) - throw new ArgumentOutOfRangeException ("offset", "A negative value."); + throw new ArgumentOutOfRangeException (nameof(offset), "A negative value."); if (count < 0) - throw new ArgumentOutOfRangeException ("count", "A negative value."); + throw new ArgumentOutOfRangeException (nameof(count), "A negative value."); var len = buffer.Length; if (offset + count > len) @@ -158,7 +158,7 @@ private int fillFromBuffer (byte[] buffer, int offset, int count) #region Public Methods - public override IAsyncResult BeginRead ( + public virtual IAsyncResult BeginRead ( byte[] buffer, int offset, int count, AsyncCallback callback, object state) { if (_disposed) @@ -180,27 +180,30 @@ public override IAsyncResult BeginRead ( if (_bodyLeft >= 0 && count > _bodyLeft) count = (int) _bodyLeft; - return _stream.BeginRead (buffer, offset, count, callback, state); + return TaskToApm.Begin(_stream.ReadAsync(buffer, offset, count), callback, state); + + //return _stream.BeginRead (buffer, offset, count, callback, state); } - public override IAsyncResult BeginWrite ( + public IAsyncResult BeginWrite ( byte[] buffer, int offset, int count, AsyncCallback callback, object state) { throw new NotSupportedException (); } - public override void Close () + public virtual void Close () { + Dispose(); _disposed = true; } - public override int EndRead (IAsyncResult asyncResult) + public virtual int EndRead (IAsyncResult asyncResult) { if (_disposed) throw new ObjectDisposedException (GetType ().ToString ()); if (asyncResult == null) - throw new ArgumentNullException ("asyncResult"); + throw new ArgumentNullException (nameof(asyncResult)); if (asyncResult is HttpStreamAsyncResult) { var ares = (HttpStreamAsyncResult) asyncResult; @@ -211,14 +214,15 @@ public override int EndRead (IAsyncResult asyncResult) } // Close on exception? - var nread = _stream.EndRead (asyncResult); + var nread = TaskToApm.End(asyncResult); + //var nread = _stream.EndRead (asyncResult); if (nread > 0 && _bodyLeft > 0) _bodyLeft -= nread; return nread; } - public override void EndWrite (IAsyncResult asyncResult) + public void EndWrite (IAsyncResult asyncResult) { throw new NotSupportedException (); } diff --git a/websocket-sharp/Net/ResponseStream.cs b/websocket-sharp/Net/ResponseStream.cs index 85059a407..f0230ea6e 100644 --- a/websocket-sharp/Net/ResponseStream.cs +++ b/websocket-sharp/Net/ResponseStream.cs @@ -176,7 +176,10 @@ private bool flushHeaders (bool closing) if (!_response.SendChunked && _response.ContentLength64 != _body.Length) return false; - _write (buff.GetBuffer (), (int) start, (int) len); + + _write (buff.GetBuffer(), (int) start, (int) len); + + _response.CloseConnection = headers["Connection"] == "close"; _response.HeadersSent = true; } @@ -253,22 +256,24 @@ internal void InternalWrite (byte[] buffer, int offset, int count) #region Public Methods - public override IAsyncResult BeginRead ( + public virtual IAsyncResult BeginRead ( byte[] buffer, int offset, int count, AsyncCallback callback, object state) { throw new NotSupportedException (); } - public override IAsyncResult BeginWrite ( + public virtual IAsyncResult BeginWrite ( byte[] buffer, int offset, int count, AsyncCallback callback, object state) { if (_disposed) throw new ObjectDisposedException (GetType ().ToString ()); - return _body.BeginWrite (buffer, offset, count, callback, state); + return TaskToApm.Begin(_body.WriteAsync(buffer, offset, count), callback, state); + + //return _body.BeginWrite (buffer, offset, count, callback, state); } - public override void Close () + public virtual void Close () { Close (false); } @@ -278,17 +283,18 @@ protected override void Dispose (bool disposing) Close (!disposing); } - public override int EndRead (IAsyncResult asyncResult) + public virtual int EndRead (IAsyncResult asyncResult) { throw new NotSupportedException (); } - public override void EndWrite (IAsyncResult asyncResult) + public virtual void EndWrite (IAsyncResult asyncResult) { if (_disposed) throw new ObjectDisposedException (GetType ().ToString ()); - _body.EndWrite (asyncResult); + TaskToApm.End(asyncResult); + //_body.EndWrite (asyncResult); } public override void Flush () diff --git a/websocket-sharp/Net/ServerSslConfiguration.cs b/websocket-sharp/Net/ServerSslConfiguration.cs index 3f0883afe..49adba439 100644 --- a/websocket-sharp/Net/ServerSslConfiguration.cs +++ b/websocket-sharp/Net/ServerSslConfiguration.cs @@ -63,7 +63,7 @@ public class ServerSslConfiguration : SslConfiguration /// the server. /// public ServerSslConfiguration (X509Certificate2 serverCertificate) - : this (serverCertificate, false, SslProtocols.Default, false) + : this (serverCertificate, false, SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls, false) { } diff --git a/websocket-sharp/Net/WebHeaderCollection.cs b/websocket-sharp/Net/WebHeaderCollection.cs index 8423d2f17..d65df3715 100644 --- a/websocket-sharp/Net/WebHeaderCollection.cs +++ b/websocket-sharp/Net/WebHeaderCollection.cs @@ -46,7 +46,7 @@ using System.Collections.Specialized; using System.Runtime.InteropServices; using System.Runtime.Serialization; -using System.Security.Permissions; +//using System.Security.Permissions; using System.Text; namespace WebSocketSharp.Net @@ -71,7 +71,7 @@ public class WebHeaderCollection : NameValueCollection, ISerializable static WebHeaderCollection () { _headers = - new Dictionary (StringComparer.InvariantCultureIgnoreCase) { + new Dictionary (StringComparer.OrdinalIgnoreCase) { { "Accept", new HttpHeaderInfo ( @@ -481,7 +481,7 @@ protected WebHeaderCollection ( SerializationInfo serializationInfo, StreamingContext streamingContext) { if (serializationInfo == null) - throw new ArgumentNullException ("serializationInfo"); + throw new ArgumentNullException (nameof(serializationInfo)); try { _internallyUsed = serializationInfo.GetBoolean ("InternallyUsed"); @@ -495,7 +495,7 @@ protected WebHeaderCollection ( } } catch (SerializationException ex) { - throw new ArgumentException (ex.Message, "serializationInfo", ex); + throw new ArgumentException (ex.Message, nameof(serializationInfo), ex); } } @@ -664,7 +664,7 @@ private static int checkColonSeparated (string header) { var idx = header.IndexOf (':'); if (idx == -1) - throw new ArgumentException ("No colon could be found.", "header"); + throw new ArgumentException ("No colon could be found.", nameof(header)); return idx; } @@ -684,11 +684,11 @@ private static HttpHeaderType checkHeaderType (string name) private static string checkName (string name) { if (name == null || name.Length == 0) - throw new ArgumentNullException ("name"); + throw new ArgumentNullException (nameof(name)); name = name.Trim (); if (!IsHeaderName (name)) - throw new ArgumentException ("Contains invalid characters.", "name"); + throw new ArgumentException ("Contains invalid characters.", nameof(name)); return name; } @@ -720,10 +720,10 @@ private static string checkValue (string value) value = value.Trim (); if (value.Length > 65535) - throw new ArgumentOutOfRangeException ("value", "Greater than 65,535 characters."); + throw new ArgumentOutOfRangeException (nameof(value), "Greater than 65,535 characters."); if (!IsHeaderValue (value)) - throw new ArgumentException ("Contains invalid characters.", "value"); + throw new ArgumentException ("Contains invalid characters.", nameof(value)); return value; } @@ -764,7 +764,7 @@ private void doWithoutCheckingName (Action action, string name, private static HttpHeaderInfo getHeaderInfo (string name) { foreach (var info in _headers.Values) - if (info.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase)) + if (info.Name.Equals (name, StringComparison.OrdinalIgnoreCase)) return info; return null; @@ -930,7 +930,7 @@ protected void AddWithoutValidate (string headerName, string headerValue) public void Add (string header) { if (header == null || header.Length == 0) - throw new ArgumentNullException ("header"); + throw new ArgumentNullException (nameof(header)); var pos = checkColonSeparated (header); add (header.Substring (0, pos), header.Substring (pos + 1), false); @@ -1158,13 +1158,11 @@ public override string[] GetValues (string header) /// /// is . /// - [SecurityPermission ( - SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] - public override void GetObjectData ( + public void GetObjectData ( SerializationInfo serializationInfo, StreamingContext streamingContext) { if (serializationInfo == null) - throw new ArgumentNullException ("serializationInfo"); + throw new ArgumentNullException (nameof(serializationInfo)); serializationInfo.AddValue ("InternallyUsed", _internallyUsed); serializationInfo.AddValue ("State", (int) _state); @@ -1228,7 +1226,7 @@ public static bool IsRestricted (string headerName, bool response) /// /// An that represents the source of the deserialization event. /// - public override void OnDeserialization (object sender) + public void OnDeserialization (object sender) { } @@ -1444,10 +1442,7 @@ public override string ToString () /// /// is . /// - [SecurityPermission ( - SecurityAction.LinkDemand, - Flags = SecurityPermissionFlag.SerializationFormatter, - SerializationFormatter = true)] + void ISerializable.GetObjectData ( SerializationInfo serializationInfo, StreamingContext streamingContext) { diff --git a/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs b/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs index b177c1c51..2f97da4db 100644 --- a/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs +++ b/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs @@ -84,12 +84,12 @@ Logger logger var sslStream = new SslStream (netStream, false, sslConfig.ClientCertificateValidationCallback); - sslStream.AuthenticateAsServer ( + sslStream.AuthenticateAsServerAsync ( sslConfig.ServerCertificate, sslConfig.ClientCertificateRequired, sslConfig.EnabledSslProtocols, sslConfig.CheckCertificateRevocation - ); + ).RunSynchronously(); _stream = sslStream; } @@ -406,8 +406,8 @@ Func credentialsFinder internal void Close () { - _stream.Close (); - _tcpClient.Close (); + _stream.Dispose(); + _tcpClient.Dispose(); } internal void Close (HttpStatusCode code) diff --git a/websocket-sharp/PayloadData.cs b/websocket-sharp/PayloadData.cs index 6bbf6905d..471dbaadc 100644 --- a/websocket-sharp/PayloadData.cs +++ b/websocket-sharp/PayloadData.cs @@ -84,7 +84,7 @@ internal PayloadData () } internal PayloadData (byte[] data) - : this (data, data.LongLength) + : this (data, data.Length) { } @@ -118,29 +118,17 @@ internal bool IncludesReservedCloseStatusCode { #region Public Properties - public byte[] ApplicationData { - get { - return _extDataLength > 0 - ? _data.SubArray (_extDataLength, _length - _extDataLength) - : _data; - } - } + public byte[] ApplicationData => _extDataLength > 0 + ? _data.SubArray ((int)_extDataLength, (int)_length - (int)_extDataLength) + : _data; - public byte[] ExtensionData { - get { - return _extDataLength > 0 - ? _data.SubArray (0, _extDataLength) - : WebSocket.EmptyBytes; - } - } + public byte[] ExtensionData => _extDataLength > 0 + ? _data.SubArray (0, (int)_extDataLength) + : WebSocket.EmptyBytes; - public ulong Length { - get { - return (ulong) _length; - } - } + public ulong Length => (ulong) _length; - #endregion + #endregion #region Internal Methods diff --git a/websocket-sharp/Server/HttpServer.cs b/websocket-sharp/Server/HttpServer.cs index d52172cdd..118bad410 100644 --- a/websocket-sharp/Server/HttpServer.cs +++ b/websocket-sharp/Server/HttpServer.cs @@ -44,1036 +44,1012 @@ using System.Net.Sockets; using System.Security.Cryptography.X509Certificates; using System.Security.Principal; +using System.Text.RegularExpressions; using System.Threading; using WebSocketSharp.Net; using WebSocketSharp.Net.WebSockets; namespace WebSocketSharp.Server { - /// - /// Provides a simple HTTP server that allows to accept the WebSocket connection requests. - /// - /// - /// The HttpServer class can provide multiple WebSocket services. - /// - public class HttpServer - { - #region Private Fields - - private System.Net.IPAddress _address; - private string _hostname; - private HttpListener _listener; - private Logger _logger; - private int _port; - private Thread _receiveThread; - private string _rootPath; - private bool _secure; - private WebSocketServiceManager _services; - private volatile ServerState _state; - private object _sync; - private bool _windows; - - #endregion - - #region Public Constructors - - /// - /// Initializes a new instance of the class. - /// - /// - /// An instance initialized by this constructor listens for the incoming requests on port 80. - /// - public HttpServer () - { - init ("*", System.Net.IPAddress.Any, 80, false); - } - - /// - /// Initializes a new instance of the class with - /// the specified . - /// - /// - /// - /// An instance initialized by this constructor listens for the incoming requests on - /// . - /// - /// - /// If is 443, that instance provides a secure connection. - /// - /// - /// - /// An that represents the port number on which to listen. - /// - /// - /// isn't between 1 and 65535 inclusive. - /// - public HttpServer (int port) - : this (port, port == 443) - { - } - /// - /// Initializes a new instance of the class with - /// the specified HTTP URL. + /// Provides a simple HTTP server that allows to accept the WebSocket connection requests. /// /// - /// - /// An instance initialized by this constructor listens for the incoming requests on - /// the host name and port in . - /// - /// - /// If doesn't include a port, either port 80 or 443 is used on - /// which to listen. It's determined by the scheme (http or https) in . - /// (Port 80 if the scheme is http.) - /// + /// The HttpServer class can provide multiple WebSocket services. /// - /// - /// A that represents the HTTP URL of the server. - /// - /// - /// is . - /// - /// - /// - /// is empty. - /// - /// - /// -or- - /// - /// - /// is invalid. - /// - /// - public HttpServer (string url) + public class HttpServer { - if (url == null) - throw new ArgumentNullException ("url"); - - if (url.Length == 0) - throw new ArgumentException ("An empty string.", "url"); - - Uri uri; - string msg; - if (!tryCreateUri (url, out uri, out msg)) - throw new ArgumentException (msg, "url"); - - var host = getHost (uri); - var addr = host.ToIPAddress (); - if (!addr.IsLocal ()) - throw new ArgumentException ("The host part isn't a local host name: " + url, "url"); - - init (host, addr, uri.Port, uri.Scheme == "https"); - } - - /// - /// Initializes a new instance of the class with - /// the specified and . - /// - /// - /// An instance initialized by this constructor listens for the incoming requests on - /// . - /// - /// - /// An that represents the port number on which to listen. - /// - /// - /// A that indicates providing a secure connection or not. - /// (true indicates providing a secure connection.) - /// - /// - /// isn't between 1 and 65535 inclusive. - /// - public HttpServer (int port, bool secure) - { - if (!port.IsPortNumber ()) - throw new ArgumentOutOfRangeException ( - "port", "Not between 1 and 65535 inclusive: " + port); - - init ("*", System.Net.IPAddress.Any, port, secure); - } - - /// - /// Initializes a new instance of the class with - /// the specified and . - /// - /// - /// - /// An instance initialized by this constructor listens for the incoming requests on - /// and . - /// - /// - /// If is 443, that instance provides a secure connection. - /// - /// - /// - /// A that represents the local IP address of the server. - /// - /// - /// An that represents the port number on which to listen. - /// - /// - /// is . - /// - /// - /// isn't a local IP address. - /// - /// - /// isn't between 1 and 65535 inclusive. - /// - public HttpServer (System.Net.IPAddress address, int port) - : this (address, port, port == 443) - { - } - - /// - /// Initializes a new instance of the class with - /// the specified , , - /// and . - /// - /// - /// An instance initialized by this constructor listens for the incoming requests on - /// and . - /// - /// - /// A that represents the local IP address of the server. - /// - /// - /// An that represents the port number on which to listen. - /// - /// - /// A that indicates providing a secure connection or not. - /// (true indicates providing a secure connection.) - /// - /// - /// is . - /// - /// - /// isn't a local IP address. - /// - /// - /// isn't between 1 and 65535 inclusive. - /// - public HttpServer (System.Net.IPAddress address, int port, bool secure) - { - if (address == null) - throw new ArgumentNullException ("address"); - - if (!address.IsLocal ()) - throw new ArgumentException ("Not a local IP address: " + address, "address"); - - if (!port.IsPortNumber ()) - throw new ArgumentOutOfRangeException ( - "port", "Not between 1 and 65535 inclusive: " + port); - - init (null, address, port, secure); - } - - #endregion - - #region Public Properties - - /// - /// Gets the local IP address of the server. - /// - /// - /// A that represents the local IP address of the server. - /// - public System.Net.IPAddress Address { - get { - return _address; - } - } - - /// - /// Gets or sets the scheme used to authenticate the clients. - /// - /// - /// One of the enum values, - /// indicates the scheme used to authenticate the clients. The default value is - /// . - /// - public AuthenticationSchemes AuthenticationSchemes { - get { - return _listener.AuthenticationSchemes; - } - - set { - var msg = _state.CheckIfAvailable (true, false, false); - if (msg != null) { - _logger.Error (msg); - return; + #region Private Fields + + private System.Net.IPAddress _address; + private string _hostname; + private HttpListener _listener; + private Logger _logger; + private int _port; + private Thread _receiveThread; + private string _rootPath; + private bool _secure; + private WebSocketServiceManager _services; + private volatile ServerState _state; + private object _sync; + private bool _windows; + + #endregion + + #region Public Constructors + + /// + /// Initializes a new instance of the class. + /// + /// + /// An instance initialized by this constructor listens for the incoming requests on port 80. + /// + public HttpServer() + { + init("*", System.Net.IPAddress.Any, 80, false); } - _listener.AuthenticationSchemes = value; - } - } + /// + /// Initializes a new instance of the class with + /// the specified . + /// + /// + /// + /// An instance initialized by this constructor listens for the incoming requests on + /// . + /// + /// + /// If is 443, that instance provides a secure connection. + /// + /// + /// + /// An that represents the port number on which to listen. + /// + /// + /// isn't between 1 and 65535 inclusive. + /// + public HttpServer(int port) + : this(port, port == 443) + { + } - /// - /// Gets a value indicating whether the server has started. - /// - /// - /// true if the server has started; otherwise, false. - /// - public bool IsListening { - get { - return _state == ServerState.Start; - } - } + /// + /// Initializes a new instance of the class with + /// the specified HTTP URL. + /// + /// + /// + /// An instance initialized by this constructor listens for the incoming requests on + /// the host name and port in . + /// + /// + /// If doesn't include a port, either port 80 or 443 is used on + /// which to listen. It's determined by the scheme (http or https) in . + /// (Port 80 if the scheme is http.) + /// + /// + /// + /// A that represents the HTTP URL of the server. + /// + /// + /// is . + /// + /// + /// + /// is empty. + /// + /// + /// -or- + /// + /// + /// is invalid. + /// + /// + public HttpServer(string url) + { + if (url == null) + throw new ArgumentNullException(nameof(url)); + + if (url.Length == 0) + throw new ArgumentException("An empty string.", nameof(url)); + + Uri uri; + string msg; + if (!tryCreateUri(url, out uri, out msg)) + throw new ArgumentException(msg, nameof(url)); + + var host = getHost(uri); + var addr = host.ToIPAddress(); + if (!addr.IsLocal()) + throw new ArgumentException("The host part isn't a local host name: " + url, nameof(url)); + + init(host, addr, uri.Port, uri.Scheme == "https"); + } - /// - /// Gets a value indicating whether the server provides a secure connection. - /// - /// - /// true if the server provides a secure connection; otherwise, false. - /// - public bool IsSecure { - get { - return _secure; - } - } + /// + /// Initializes a new instance of the class with + /// the specified and . + /// + /// + /// An instance initialized by this constructor listens for the incoming requests on + /// . + /// + /// + /// An that represents the port number on which to listen. + /// + /// + /// A that indicates providing a secure connection or not. + /// (true indicates providing a secure connection.) + /// + /// + /// isn't between 1 and 65535 inclusive. + /// + public HttpServer(int port, bool secure) + { + if (!port.IsPortNumber()) + throw new ArgumentOutOfRangeException( + nameof(port), "Not between 1 and 65535 inclusive: " + port); + + init("*", System.Net.IPAddress.Any, port, secure); + } - /// - /// Gets or sets a value indicating whether the server cleans up - /// the inactive sessions in the WebSocket services periodically. - /// - /// - /// true if the server cleans up the inactive sessions every 60 seconds; - /// otherwise, false. The default value is true. - /// - public bool KeepClean { - get { - return _services.KeepClean; - } - - set { - var msg = _state.CheckIfAvailable (true, false, false); - if (msg != null) { - _logger.Error (msg); - return; + /// + /// Initializes a new instance of the class with + /// the specified and . + /// + /// + /// + /// An instance initialized by this constructor listens for the incoming requests on + /// and . + /// + /// + /// If is 443, that instance provides a secure connection. + /// + /// + /// + /// A that represents the local IP address of the server. + /// + /// + /// An that represents the port number on which to listen. + /// + /// + /// is . + /// + /// + /// isn't a local IP address. + /// + /// + /// isn't between 1 and 65535 inclusive. + /// + public HttpServer(System.Net.IPAddress address, int port) + : this(address, port, port == 443) + { } - _services.KeepClean = value; - } - } + /// + /// Initializes a new instance of the class with + /// the specified , , + /// and . + /// + /// + /// An instance initialized by this constructor listens for the incoming requests on + /// and . + /// + /// + /// A that represents the local IP address of the server. + /// + /// + /// An that represents the port number on which to listen. + /// + /// + /// A that indicates providing a secure connection or not. + /// (true indicates providing a secure connection.) + /// + /// + /// is . + /// + /// + /// isn't a local IP address. + /// + /// + /// isn't between 1 and 65535 inclusive. + /// + public HttpServer(System.Net.IPAddress address, int port, bool secure) + { + if (address == null) + throw new ArgumentNullException(nameof(address)); + + if (!address.IsLocal()) + throw new ArgumentException("Not a local IP address: " + address, nameof(address)); + + if (!port.IsPortNumber()) + throw new ArgumentOutOfRangeException( + nameof(port), "Not between 1 and 65535 inclusive: " + port); + + init(null, address, port, secure); + } - /// - /// Gets the logging functions. - /// - /// - /// The default logging level is . If you would like to change it, - /// you should set the Log.Level property to any of the enum - /// values. - /// - /// - /// A that provides the logging functions. - /// - public Logger Log { - get { - return _logger; - } - } + #endregion - /// - /// Gets the port on which to listen for incoming requests. - /// - /// - /// An that represents the port number on which to listen. - /// - public int Port { - get { - return _port; - } - } + #region Public Properties - /// - /// Gets or sets the name of the realm associated with the server. - /// - /// - /// If this property is or empty, "SECRET AREA" will be used as - /// the name of the realm. - /// - /// - /// A that represents the name of the realm. The default value is - /// . - /// - public string Realm { - get { - return _listener.Realm; - } - - set { - var msg = _state.CheckIfAvailable (true, false, false); - if (msg != null) { - _logger.Error (msg); - return; + /// + /// Gets the local IP address of the server. + /// + /// + /// A that represents the local IP address of the server. + /// + public System.Net.IPAddress Address + { + get { return _address; } } - _listener.Realm = value; - } - } + /// + /// Gets or sets the scheme used to authenticate the clients. + /// + /// + /// One of the enum values, + /// indicates the scheme used to authenticate the clients. The default value is + /// . + /// + public AuthenticationSchemes AuthenticationSchemes + { + get { return _listener.AuthenticationSchemes; } + + set + { + var msg = _state.CheckIfAvailable(true, false, false); + if (msg != null) + { + _logger.Error(msg); + return; + } - /// - /// Gets or sets a value indicating whether the server is allowed to be bound to - /// an address that is already in use. - /// - /// - /// If you would like to resolve to wait for socket in TIME_WAIT state, - /// you should set this property to true. - /// - /// - /// true if the server is allowed to be bound to an address that is already in use; - /// otherwise, false. The default value is false. - /// - public bool ReuseAddress { - get { - return _listener.ReuseAddress; - } - - set { - var msg = _state.CheckIfAvailable (true, false, false); - if (msg != null) { - _logger.Error (msg); - return; + _listener.AuthenticationSchemes = value; + } } - _listener.ReuseAddress = value; - } - } + /// + /// Gets a value indicating whether the server has started. + /// + /// + /// true if the server has started; otherwise, false. + /// + public bool IsListening + { + get { return _state == ServerState.Start; } + } - /// - /// Gets or sets the document root path of the server. - /// - /// - /// A that represents the document root path of the server. - /// The default value is "./Public". - /// - public string RootPath { - get { - return _rootPath != null && _rootPath.Length > 0 ? _rootPath : (_rootPath = "./Public"); - } - - set { - var msg = _state.CheckIfAvailable (true, false, false); - if (msg != null) { - _logger.Error (msg); - return; + /// + /// Gets a value indicating whether the server provides a secure connection. + /// + /// + /// true if the server provides a secure connection; otherwise, false. + /// + public bool IsSecure + { + get { return _secure; } } - _rootPath = value; - } - } + /// + /// Gets or sets a value indicating whether the server cleans up + /// the inactive sessions in the WebSocket services periodically. + /// + /// + /// true if the server cleans up the inactive sessions every 60 seconds; + /// otherwise, false. The default value is true. + /// + public bool KeepClean + { + get { return _services.KeepClean; } + + set + { + var msg = _state.CheckIfAvailable(true, false, false); + if (msg != null) + { + _logger.Error(msg); + return; + } - /// - /// Gets or sets the SSL configuration used to authenticate the server and - /// optionally the client for secure connection. - /// - /// - /// A that represents the configuration used to - /// authenticate the server and optionally the client for secure connection. - /// - public ServerSslConfiguration SslConfiguration { - get { - return _listener.SslConfiguration; - } - - set { - var msg = _state.CheckIfAvailable (true, false, false); - if (msg != null) { - _logger.Error (msg); - return; + _services.KeepClean = value; + } } - _listener.SslConfiguration = value; - } - } + /// + /// Gets the logging functions. + /// + /// + /// The default logging level is . If you would like to change it, + /// you should set the Log.Level property to any of the enum + /// values. + /// + /// + /// A that provides the logging functions. + /// + public Logger Log + { + get { return _logger; } + } - /// - /// Gets or sets the delegate called to find the credentials for an identity used to - /// authenticate a client. - /// - /// - /// A Func<, > delegate - /// that references the method(s) used to find the credentials. The default value is - /// . - /// - public Func UserCredentialsFinder { - get { - return _listener.UserCredentialsFinder; - } - - set { - var msg = _state.CheckIfAvailable (true, false, false); - if (msg != null) { - _logger.Error (msg); - return; + /// + /// Gets the port on which to listen for incoming requests. + /// + /// + /// An that represents the port number on which to listen. + /// + public int Port + { + get { return _port; } } - _listener.UserCredentialsFinder = value; - } - } + /// + /// Gets or sets the name of the realm associated with the server. + /// + /// + /// If this property is or empty, "SECRET AREA" will be used as + /// the name of the realm. + /// + /// + /// A that represents the name of the realm. The default value is + /// . + /// + public string Realm + { + get { return _listener.Realm; } + + set + { + var msg = _state.CheckIfAvailable(true, false, false); + if (msg != null) + { + _logger.Error(msg); + return; + } - /// - /// Gets or sets the wait time for the response to the WebSocket Ping or Close. - /// - /// - /// A that represents the wait time. The default value is - /// the same as 1 second. - /// - public TimeSpan WaitTime { - get { - return _services.WaitTime; - } - - set { - var msg = _state.CheckIfAvailable (true, false, false) ?? value.CheckIfValidWaitTime (); - if (msg != null) { - _logger.Error (msg); - return; + _listener.Realm = value; + } } - _services.WaitTime = value; - } - } + /// + /// Gets or sets a value indicating whether the server is allowed to be bound to + /// an address that is already in use. + /// + /// + /// If you would like to resolve to wait for socket in TIME_WAIT state, + /// you should set this property to true. + /// + /// + /// true if the server is allowed to be bound to an address that is already in use; + /// otherwise, false. The default value is false. + /// + public bool ReuseAddress + { + get { return _listener.ReuseAddress; } + + set + { + var msg = _state.CheckIfAvailable(true, false, false); + if (msg != null) + { + _logger.Error(msg); + return; + } - /// - /// Gets the access to the WebSocket services provided by the server. - /// - /// - /// A that manages the WebSocket services. - /// - public WebSocketServiceManager WebSocketServices { - get { - return _services; - } - } + _listener.ReuseAddress = value; + } + } - #endregion + /// + /// Gets or sets the document root path of the server. + /// + /// + /// A that represents the document root path of the server. + /// The default value is "./Public". + /// + public string RootPath + { + get { return _rootPath != null && _rootPath.Length > 0 ? _rootPath : (_rootPath = "./Public"); } + + set + { + var msg = _state.CheckIfAvailable(true, false, false); + if (msg != null) + { + _logger.Error(msg); + return; + } - #region Public Events + _rootPath = value; + } + } - /// - /// Occurs when the server receives an HTTP CONNECT request. - /// - public event EventHandler OnConnect; + /// + /// Gets or sets the SSL configuration used to authenticate the server and + /// optionally the client for secure connection. + /// + /// + /// A that represents the configuration used to + /// authenticate the server and optionally the client for secure connection. + /// + public ServerSslConfiguration SslConfiguration + { + get { return _listener.SslConfiguration; } + + set + { + var msg = _state.CheckIfAvailable(true, false, false); + if (msg != null) + { + _logger.Error(msg); + return; + } - /// - /// Occurs when the server receives an HTTP DELETE request. - /// - public event EventHandler OnDelete; + _listener.SslConfiguration = value; + } + } - /// - /// Occurs when the server receives an HTTP GET request. - /// - public event EventHandler OnGet; + /// + /// Gets or sets the delegate called to find the credentials for an identity used to + /// authenticate a client. + /// + /// + /// A Func<, > delegate + /// that references the method(s) used to find the credentials. The default value is + /// . + /// + public Func UserCredentialsFinder + { + get { return _listener.UserCredentialsFinder; } + + set + { + var msg = _state.CheckIfAvailable(true, false, false); + if (msg != null) + { + _logger.Error(msg); + return; + } - /// - /// Occurs when the server receives an HTTP HEAD request. - /// - public event EventHandler OnHead; + _listener.UserCredentialsFinder = value; + } + } - /// - /// Occurs when the server receives an HTTP OPTIONS request. - /// - public event EventHandler OnOptions; + /// + /// Gets or sets the wait time for the response to the WebSocket Ping or Close. + /// + /// + /// A that represents the wait time. The default value is + /// the same as 1 second. + /// + public TimeSpan WaitTime + { + get { return _services.WaitTime; } + + set + { + var msg = _state.CheckIfAvailable(true, false, false) ?? value.CheckIfValidWaitTime(); + if (msg != null) + { + _logger.Error(msg); + return; + } - /// - /// Occurs when the server receives an HTTP PATCH request. - /// - public event EventHandler OnPatch; + _services.WaitTime = value; + } + } - /// - /// Occurs when the server receives an HTTP POST request. - /// - public event EventHandler OnPost; + /// + /// Gets the access to the WebSocket services provided by the server. + /// + /// + /// A that manages the WebSocket services. + /// + public WebSocketServiceManager WebSocketServices + { + get { return _services; } + } - /// - /// Occurs when the server receives an HTTP PUT request. - /// - public event EventHandler OnPut; + #endregion - /// - /// Occurs when the server receives an HTTP TRACE request. - /// - public event EventHandler OnTrace; + #region Public Events - #endregion + /// + /// Occurs when the server receives an HTTP CONNECT request. + /// + public event EventHandler OnConnect; - #region Private Methods + /// + /// Occurs when the server receives an HTTP DELETE request. + /// + public event EventHandler OnDelete; - private void abort () - { - lock (_sync) { - if (!IsListening) - return; + /// + /// Occurs when the server receives an HTTP GET request. + /// + public event EventHandler OnGet; - _state = ServerState.ShuttingDown; - } + /// + /// Occurs when the server receives an HTTP HEAD request. + /// + public event EventHandler OnHead; - _services.Stop (new CloseEventArgs (CloseStatusCode.ServerError), true, false); - _listener.Abort (); + /// + /// Occurs when the server receives an HTTP OPTIONS request. + /// + public event EventHandler OnOptions; - _state = ServerState.Stop; - } + /// + /// Occurs when the server receives an HTTP PATCH request. + /// + public event EventHandler OnPatch; - private bool checkIfAvailable ( - bool ready, bool start, bool shutting, bool stop, out string message - ) - { - message = null; + /// + /// Occurs when the server receives an HTTP POST request. + /// + public event EventHandler OnPost; - if (!ready && _state == ServerState.Ready) { - message = "This operation is not available in: ready"; - return false; - } + /// + /// Occurs when the server receives an HTTP PUT request. + /// + public event EventHandler OnPut; - if (!start && _state == ServerState.Start) { - message = "This operation is not available in: start"; - return false; - } + /// + /// Occurs when the server receives an HTTP TRACE request. + /// + public event EventHandler OnTrace; - if (!shutting && _state == ServerState.ShuttingDown) { - message = "This operation is not available in: shutting down"; - return false; - } + #endregion - if (!stop && _state == ServerState.Stop) { - message = "This operation is not available in: stop"; - return false; - } + #region Private Methods - return true; - } + private void abort() + { + lock (_sync) + { + if (!IsListening) + return; - private string checkIfCertificateExists () - { - if (!_secure) - return null; + _state = ServerState.ShuttingDown; + } - var usr = _listener.SslConfiguration.ServerCertificate != null; - var port = EndPointListener.CertificateExists (_port, _listener.CertificateFolderPath); - if (usr && port) { - _logger.Warn ("The server certificate associated with the port number already exists."); - return null; - } + _services.Stop(new CloseEventArgs(CloseStatusCode.ServerError), true, false); + _listener.Abort(); - return !(usr || port) ? "The secure connection requires a server certificate." : null; - } + _state = ServerState.Stop; + } - private static string convertToString (System.Net.IPAddress address) - { - return address.AddressFamily == AddressFamily.InterNetworkV6 - ? String.Format ("[{0}]", address.ToString ()) - : address.ToString (); - } + private string checkIfCertificateExists() + { + if (!_secure) + return null; - private static string getHost (Uri uri) - { - return uri.HostNameType == UriHostNameType.IPv6 ? uri.Host : uri.DnsSafeHost; - } + var usr = _listener.SslConfiguration.ServerCertificate != null; + var port = EndPointListener.CertificateExists(_port, _listener.CertificateFolderPath); + if (usr && port) + { + _logger.Warn("The server certificate associated with the port number already exists."); + return null; + } - private void init (string hostname, System.Net.IPAddress address, int port, bool secure) - { - _hostname = hostname ?? convertToString (address); - _address = address; - _port = port; - _secure = secure; + return !(usr || port) ? "The secure connection requires a server certificate." : null; + } - _listener = new HttpListener (); - _listener.Prefixes.Add ( - String.Format ("http{0}://{1}:{2}/", secure ? "s" : "", _hostname, port)); + private static string convertToString(System.Net.IPAddress address) + { + return address.AddressFamily == AddressFamily.InterNetworkV6 + ? String.Format("[{0}]", address.ToString()) + : address.ToString(); + } - _logger = _listener.Log; - _services = new WebSocketServiceManager (_logger); - _sync = new object (); + private static string getHost(Uri uri) + { + return uri.HostNameType == UriHostNameType.IPv6 ? uri.Host : uri.DnsSafeHost; + } - var os = Environment.OSVersion; - _windows = os.Platform != PlatformID.Unix && os.Platform != PlatformID.MacOSX; - } + private static readonly Regex WindowsDriveRegex = new Regex(@"^[A-Za-z]:\\$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private void init(string hostname, System.Net.IPAddress address, int port, bool secure) + { + _hostname = hostname ?? convertToString(address); + _address = address; + _port = port; + _secure = secure; + + _listener = new HttpListener(); + _listener.Prefixes.Add( String.Format("http{0}://{1}:{2}/", secure ? "s" : "", _hostname, port)); + + _logger = _listener.Log; + _services = new WebSocketServiceManager(_logger); + _sync = new object(); + + _windows = WindowsDriveRegex.IsMatch(Directory.GetDirectoryRoot(Directory.GetCurrentDirectory())); + } - private void processRequest (HttpListenerContext context) - { - var method = context.Request.HttpMethod; - var evt = method == "GET" + private void processRequest(HttpListenerContext context) + { + var method = context.Request.HttpMethod; + var evt = method == "GET" ? OnGet : method == "HEAD" - ? OnHead - : method == "POST" - ? OnPost - : method == "PUT" - ? OnPut - : method == "DELETE" - ? OnDelete - : method == "OPTIONS" - ? OnOptions - : method == "TRACE" - ? OnTrace - : method == "CONNECT" - ? OnConnect - : method == "PATCH" - ? OnPatch - : null; - - if (evt != null) - evt (this, new HttpRequestEventArgs (context)); - else - context.Response.StatusCode = (int) HttpStatusCode.NotImplemented; - - context.Response.Close (); - } + ? OnHead + : method == "POST" + ? OnPost + : method == "PUT" + ? OnPut + : method == "DELETE" + ? OnDelete + : method == "OPTIONS" + ? OnOptions + : method == "TRACE" + ? OnTrace + : method == "CONNECT" + ? OnConnect + : method == "PATCH" + ? OnPatch + : null; + + if (evt != null) + evt(this, new HttpRequestEventArgs(context)); + else + context.Response.StatusCode = (int) HttpStatusCode.NotImplemented; + + context.Response.Close(); + } - private void processRequest (HttpListenerWebSocketContext context) - { - WebSocketServiceHost host; - if (!_services.InternalTryGetServiceHost (context.RequestUri.AbsolutePath, out host)) { - context.Close (HttpStatusCode.NotImplemented); - return; - } + private void processRequest(HttpListenerWebSocketContext context) + { + WebSocketServiceHost host; + if (!_services.InternalTryGetServiceHost(context.RequestUri.AbsolutePath, out host)) + { + context.Close(HttpStatusCode.NotImplemented); + return; + } - host.StartSession (context); - } + host.StartSession(context); + } - private void receiveRequest () - { - while (true) { - try { - var ctx = _listener.GetContext (); - ThreadPool.QueueUserWorkItem ( - state => { - try { - if (ctx.Request.IsUpgradeTo ("websocket")) { - processRequest (ctx.AcceptWebSocket (null)); - return; + private void receiveRequest() + { + while (true) + { + try + { + var ctx = _listener.GetContext(); + ThreadPool.QueueUserWorkItem( + state => + { + try + { + if (ctx.Request.IsUpgradeTo("websocket")) + { + processRequest(ctx.AcceptWebSocket(null)); + return; + } + + processRequest(ctx); + } + catch (Exception ex) + { + _logger.Fatal(ex.ToString()); + ctx.Connection.Close(true); + } + }); + } + catch (HttpListenerException ex) + { + _logger.Warn("Receiving has been stopped.\n reason: " + ex.Message); + break; + } + catch (Exception ex) + { + _logger.Fatal(ex.ToString()); + break; } + } - processRequest (ctx); - } - catch (Exception ex) { - _logger.Fatal (ex.ToString ()); - ctx.Connection.Close (true); - } - }); + if (IsListening) + abort(); } - catch (HttpListenerException ex) { - _logger.Warn ("Receiving has been stopped.\n reason: " + ex.Message); - break; - } - catch (Exception ex) { - _logger.Fatal (ex.ToString ()); - break; - } - } - - if (IsListening) - abort (); - } - private void startReceiving () - { - _listener.Start (); - _receiveThread = new Thread (new ThreadStart (receiveRequest)); - _receiveThread.IsBackground = true; - _receiveThread.Start (); - } + private void startReceiving() + { + _listener.Start(); + _receiveThread = new Thread(new ThreadStart(receiveRequest)); + _receiveThread.IsBackground = true; + _receiveThread.Start(); + } - private void stopReceiving (int millisecondsTimeout) - { - _listener.Close (); - _receiveThread.Join (millisecondsTimeout); - } + private void stopReceiving(int millisecondsTimeout) + { + _listener.Close(); + _receiveThread.Join(millisecondsTimeout); + } - private static bool tryCreateUri (string uriString, out Uri result, out string message) - { - result = null; - - var uri = uriString.ToUri (); - if (uri == null) { - message = "An invalid URI string: " + uriString; - return false; - } - - if (!uri.IsAbsoluteUri) { - message = "Not an absolute URI: " + uriString; - return false; - } - - var schm = uri.Scheme; - if (!(schm == "http" || schm == "https")) { - message = "The scheme part isn't 'http' or 'https': " + uriString; - return false; - } - - if (uri.PathAndQuery != "/") { - message = "Includes the path or query component: " + uriString; - return false; - } - - if (uri.Fragment.Length > 0) { - message = "Includes the fragment component: " + uriString; - return false; - } - - if (uri.Port == 0) { - message = "The port part is zero: " + uriString; - return false; - } - - result = uri; - message = String.Empty; - - return true; - } + private static bool tryCreateUri(string uriString, out Uri result, out string message) + { + result = null; + + var uri = uriString.ToUri(); + if (uri == null) + { + message = "An invalid URI string: " + uriString; + return false; + } + + if (!uri.IsAbsoluteUri) + { + message = "Not an absolute URI: " + uriString; + return false; + } + + var schm = uri.Scheme; + if (!(schm == "http" || schm == "https")) + { + message = "The scheme part isn't 'http' or 'https': " + uriString; + return false; + } + + if (uri.PathAndQuery != "/") + { + message = "Includes the path or query component: " + uriString; + return false; + } + + if (uri.Fragment.Length > 0) + { + message = "Includes the fragment component: " + uriString; + return false; + } + + if (uri.Port == 0) + { + message = "The port part is zero: " + uriString; + return false; + } + + result = uri; + message = String.Empty; + + return true; + } - #endregion + #endregion + + #region Public Methods + + /// + /// Adds the WebSocket service with the specified behavior, , + /// and . + /// + /// + /// + /// This method converts to URL-decoded string, + /// and removes '/' from tail end of . + /// + /// + /// returns an initialized specified typed + /// instance. + /// + /// + /// + /// A that represents the absolute path to the service to add. + /// + /// + /// A Func<T> delegate that references the method used to initialize + /// a new specified typed instance (a new + /// instance). + /// + /// + /// The type of the behavior of the service to add. The TBehavior must inherit + /// the class. + /// + public void AddWebSocketService(string path, Func initializer) + where TBehavior : WebSocketBehavior + { + var msg = path.CheckIfValidServicePath() ?? + (initializer == null ? "'initializer' is null." : null); + + if (msg != null) + { + _logger.Error(msg); + return; + } + + _services.Add(path, initializer); + } - #region Public Methods + /// + /// Adds a WebSocket service with the specified behavior and . + /// + /// + /// This method converts to URL-decoded string, + /// and removes '/' from tail end of . + /// + /// + /// A that represents the absolute path to the service to add. + /// + /// + /// The type of the behavior of the service to add. The TBehaviorWithNew must inherit + /// the class, and must have a public parameterless + /// constructor. + /// + public void AddWebSocketService(string path) + where TBehaviorWithNew : WebSocketBehavior, new() + { + AddWebSocketService(path, () => new TBehaviorWithNew()); + } - /// - /// Adds the WebSocket service with the specified behavior, , - /// and . - /// - /// - /// - /// This method converts to URL-decoded string, - /// and removes '/' from tail end of . - /// - /// - /// returns an initialized specified typed - /// instance. - /// - /// - /// - /// A that represents the absolute path to the service to add. - /// - /// - /// A Func<T> delegate that references the method used to initialize - /// a new specified typed instance (a new - /// instance). - /// - /// - /// The type of the behavior of the service to add. The TBehavior must inherit - /// the class. - /// - public void AddWebSocketService (string path, Func initializer) - where TBehavior : WebSocketBehavior - { - var msg = path.CheckIfValidServicePath () ?? - (initializer == null ? "'initializer' is null." : null); + /// + /// Gets the contents of the file with the specified . + /// + /// + /// An array of that receives the contents of the file, + /// or if it doesn't exist. + /// + /// + /// A that represents the virtual path to the file to find. + /// + public byte[] GetFile(string path) + { + path = RootPath + path; + if (_windows) + path = path.Replace("/", "\\"); + + return File.Exists(path) ? File.ReadAllBytes(path) : null; + } - if (msg != null) { - _logger.Error (msg); - return; - } + /// + /// Removes the WebSocket service with the specified . + /// + /// + /// This method converts to URL-decoded string, + /// and removes '/' from tail end of . + /// + /// + /// true if the service is successfully found and removed; otherwise, false. + /// + /// + /// A that represents the absolute path to the service to find. + /// + public bool RemoveWebSocketService(string path) + { + var msg = path.CheckIfValidServicePath(); + if (msg != null) + { + _logger.Error(msg); + return false; + } + + return _services.Remove(path); + } - _services.Add (path, initializer); - } + /// + /// Starts receiving the HTTP requests. + /// + public void Start() + { + lock (_sync) + { + var msg = _state.CheckIfAvailable(true, false, false) ?? checkIfCertificateExists(); + if (msg != null) + { + _logger.Error(msg); + return; + } - /// - /// Adds a WebSocket service with the specified behavior and . - /// - /// - /// This method converts to URL-decoded string, - /// and removes '/' from tail end of . - /// - /// - /// A that represents the absolute path to the service to add. - /// - /// - /// The type of the behavior of the service to add. The TBehaviorWithNew must inherit - /// the class, and must have a public parameterless - /// constructor. - /// - public void AddWebSocketService (string path) - where TBehaviorWithNew : WebSocketBehavior, new () - { - AddWebSocketService (path, () => new TBehaviorWithNew ()); - } + _services.Start(); + startReceiving(); - /// - /// Gets the contents of the file with the specified . - /// - /// - /// An array of that receives the contents of the file, - /// or if it doesn't exist. - /// - /// - /// A that represents the virtual path to the file to find. - /// - public byte[] GetFile (string path) - { - path = RootPath + path; - if (_windows) - path = path.Replace ("/", "\\"); + _state = ServerState.Start; + } + } - return File.Exists (path) ? File.ReadAllBytes (path) : null; - } + /// + /// Stops receiving the HTTP requests. + /// + public void Stop() + { + lock (_sync) + { + var msg = _state.CheckIfAvailable(false, true, false); + if (msg != null) + { + _logger.Error(msg); + return; + } - /// - /// Removes the WebSocket service with the specified . - /// - /// - /// This method converts to URL-decoded string, - /// and removes '/' from tail end of . - /// - /// - /// true if the service is successfully found and removed; otherwise, false. - /// - /// - /// A that represents the absolute path to the service to find. - /// - public bool RemoveWebSocketService (string path) - { - var msg = path.CheckIfValidServicePath (); - if (msg != null) { - _logger.Error (msg); - return false; - } + _state = ServerState.ShuttingDown; + } - return _services.Remove (path); - } + _services.Stop(new CloseEventArgs(), true, true); + stopReceiving(5000); - /// - /// Starts receiving the HTTP requests. - /// - public void Start () - { - lock (_sync) { - var msg = _state.CheckIfAvailable (true, false, false) ?? checkIfCertificateExists (); - if (msg != null) { - _logger.Error (msg); - return; + _state = ServerState.Stop; } - _services.Start (); - startReceiving (); - - _state = ServerState.Start; - } - } - - /// - /// Stops receiving the incoming requests, and closes the connections. - /// - public void Stop () - { - string msg; - if (!checkIfAvailable (false, true, false, false, out msg)) { - _logger.Error (msg); - return; - } - - lock (_sync) { - if (!checkIfAvailable (false, true, false, false, out msg)) { - _logger.Error (msg); - return; - } + /// + /// Stops receiving the HTTP requests with the specified and + /// used to stop the WebSocket services. + /// + /// + /// A that represents the status code indicating the reason for the stop. + /// + /// + /// A that represents the reason for the stop. + /// + public void Stop(ushort code, string reason) + { + lock (_sync) + { + var msg = _state.CheckIfAvailable(false, true, false) ?? + WebSocket.CheckCloseParameters(code, reason, false); + + if (msg != null) + { + _logger.Error(msg); + return; + } - _state = ServerState.ShuttingDown; - } + _state = ServerState.ShuttingDown; + } - _services.Stop (new CloseEventArgs (), true, true); - stopReceiving (5000); + if (code == (ushort) CloseStatusCode.NoStatus) + { + _services.Stop(new CloseEventArgs(), true, true); + } + else + { + var send = !code.IsReserved(); + _services.Stop(new CloseEventArgs(code, reason), send, send); + } - _state = ServerState.Stop; - } + stopReceiving(5000); - /// - /// Stops receiving the incoming requests, and closes the connections with - /// the specified and for - /// the WebSocket connection close. - /// - /// - /// A that represents the status code indicating - /// the reason for the WebSocket connection close. The status codes are - /// defined in - /// Section 7.4 of RFC 6455. - /// - /// - /// A that represents the reason for the WebSocket - /// connection close. The size must be 123 bytes or less. - /// - public void Stop (ushort code, string reason) - { - string msg; - if (!checkIfAvailable (false, true, false, false, out msg)) { - _logger.Error (msg); - return; - } - - if (!WebSocket.CheckParametersForClose (code, reason, false, out msg)) { - _logger.Error (msg); - return; - } - - lock (_sync) { - if (!checkIfAvailable (false, true, false, false, out msg)) { - _logger.Error (msg); - return; + _state = ServerState.Stop; } - _state = ServerState.ShuttingDown; - } + /// + /// Stops receiving the HTTP requests with the specified and + /// used to stop the WebSocket services. + /// + /// + /// One of the enum values, represents the status code indicating + /// the reason for the stop. + /// + /// + /// A that represents the reason for the stop. + /// + public void Stop(CloseStatusCode code, string reason) + { + lock (_sync) + { + var msg = _state.CheckIfAvailable(false, true, false) ?? + WebSocket.CheckCloseParameters(code, reason, false); + + if (msg != null) + { + _logger.Error(msg); + return; + } - if (code == (ushort) CloseStatusCode.NoStatus) { - _services.Stop (new CloseEventArgs (), true, true); - } - else { - var send = !code.IsReserved (); - _services.Stop (new CloseEventArgs (code, reason), send, send); - } + _state = ServerState.ShuttingDown; + } - stopReceiving (5000); + if (code == CloseStatusCode.NoStatus) + { + _services.Stop(new CloseEventArgs(), true, true); + } + else + { + var send = !code.IsReserved(); + _services.Stop(new CloseEventArgs(code, reason), send, send); + } - _state = ServerState.Stop; - } + stopReceiving(5000); - /// - /// Stops receiving the incoming requests, and closes the connections with - /// the specified and for - /// the WebSocket connection close. - /// - /// - /// One of the enum values that represents - /// the status code indicating the reason for the WebSocket connection close. - /// - /// - /// A that represents the reason for the WebSocket - /// connection close. The size must be 123 bytes or less. - /// - public void Stop (CloseStatusCode code, string reason) - { - string msg; - if (!checkIfAvailable (false, true, false, false, out msg)) { - _logger.Error (msg); - return; - } - - if (!WebSocket.CheckParametersForClose (code, reason, false, out msg)) { - _logger.Error (msg); - return; - } - - lock (_sync) { - if (!checkIfAvailable (false, true, false, false, out msg)) { - _logger.Error (msg); - return; + _state = ServerState.Stop; } - _state = ServerState.ShuttingDown; - } - - if (code == CloseStatusCode.NoStatus) { - _services.Stop (new CloseEventArgs (), true, true); - } - else { - var send = !code.IsReserved (); - _services.Stop (new CloseEventArgs (code, reason), send, send); - } - - stopReceiving (5000); - - _state = ServerState.Stop; + #endregion } - - #endregion - } } diff --git a/websocket-sharp/Server/WebSocketServer.cs b/websocket-sharp/Server/WebSocketServer.cs index fba17307b..c9f81d9dc 100644 --- a/websocket-sharp/Server/WebSocketServer.cs +++ b/websocket-sharp/Server/WebSocketServer.cs @@ -162,20 +162,20 @@ public WebSocketServer (int port) public WebSocketServer (string url) { if (url == null) - throw new ArgumentNullException ("url"); + throw new ArgumentNullException (nameof(url)); if (url.Length == 0) - throw new ArgumentException ("An empty string.", "url"); + throw new ArgumentException ("An empty string.", nameof(url)); Uri uri; string msg; if (!tryCreateUri (url, out uri, out msg)) - throw new ArgumentException (msg, "url"); + throw new ArgumentException (msg, nameof(url)); var host = uri.DnsSafeHost; var addr = host.ToIPAddress (); if (!addr.IsLocal ()) - throw new ArgumentException ("The host part isn't a local host name: " + url, "url"); + throw new ArgumentException ("The host part isn't a local host name: " + url, nameof(url)); init (host, addr, uri.Port, uri.Scheme == "wss"); } @@ -202,7 +202,7 @@ public WebSocketServer (int port, bool secure) { if (!port.IsPortNumber ()) throw new ArgumentOutOfRangeException ( - "port", "Not between 1 and 65535 inclusive: " + port); + nameof(port), "Not between 1 and 65535 inclusive: " + port); init (null, System.Net.IPAddress.Any, port, secure); } @@ -271,14 +271,14 @@ public WebSocketServer (System.Net.IPAddress address, int port) public WebSocketServer (System.Net.IPAddress address, int port, bool secure) { if (address == null) - throw new ArgumentNullException ("address"); + throw new ArgumentNullException (nameof(address)); if (!address.IsLocal ()) - throw new ArgumentException ("Not a local IP address: " + address, "address"); + throw new ArgumentException ("Not a local IP address: " + address, nameof(address)); if (!port.IsPortNumber ()) throw new ArgumentOutOfRangeException ( - "port", "Not between 1 and 65535 inclusive: " + port); + nameof(port), "Not between 1 and 65535 inclusive: " + port); init (null, address, port, secure); } @@ -558,35 +558,6 @@ private void abort () _state = ServerState.Stop; } - private bool checkIfAvailable ( - bool ready, bool start, bool shutting, bool stop, out string message - ) - { - message = null; - - if (!ready && _state == ServerState.Ready) { - message = "This operation is not available in: ready"; - return false; - } - - if (!start && _state == ServerState.Start) { - message = "This operation is not available in: start"; - return false; - } - - if (!shutting && _state == ServerState.ShuttingDown) { - message = "This operation is not available in: shutting down"; - return false; - } - - if (!stop && _state == ServerState.Stop) { - message = "This operation is not available in: stop"; - return false; - } - - return true; - } - private string checkIfCertificateExists () { return _secure && (_sslConfig == null || _sslConfig.ServerCertificate == null) @@ -640,11 +611,11 @@ private void processRequest (TcpListenerWebSocketContext context) host.StartSession (context); } - private void receiveRequest () + private async void receiveRequest () { while (true) { try { - var cl = _listener.AcceptTcpClient (); + var cl = await _listener.AcceptTcpClientAsync(); ThreadPool.QueueUserWorkItem ( state => { try { @@ -656,7 +627,7 @@ private void receiveRequest () } catch (Exception ex) { _logger.Fatal (ex.ToString ()); - cl.Close (); + cl.Dispose(); } } ); @@ -817,19 +788,13 @@ public void Start () } /// - /// Stops receiving the WebSocket handshake requests, and closes - /// the WebSocket connections. + /// Stops receiving the WebSocket connection requests. /// public void Stop () { - string msg; - if (!checkIfAvailable (false, true, false, false, out msg)) { - _logger.Error (msg); - return; - } - lock (_sync) { - if (!checkIfAvailable (false, true, false, false, out msg)) { + var msg = _state.CheckIfAvailable (false, true, false); + if (msg != null) { _logger.Error (msg); return; } @@ -844,35 +809,22 @@ public void Stop () } /// - /// Stops receiving the WebSocket handshake requests, and closes - /// the WebSocket connections with the specified and - /// . + /// Stops receiving the WebSocket connection requests with + /// the specified and . /// /// - /// A that represents the status code indicating - /// the reason for the close. The status codes are defined in - /// - /// Section 7.4 of RFC 6455. + /// A that represents the status code indicating the reason for the stop. /// /// - /// A that represents the reason for the close. - /// The size must be 123 bytes or less. + /// A that represents the reason for the stop. /// public void Stop (ushort code, string reason) { - string msg; - if (!checkIfAvailable (false, true, false, false, out msg)) { - _logger.Error (msg); - return; - } - - if (!WebSocket.CheckParametersForClose (code, reason, false, out msg)) { - _logger.Error (msg); - return; - } - lock (_sync) { - if (!checkIfAvailable (false, true, false, false, out msg)) { + var msg = _state.CheckIfAvailable (false, true, false) ?? + WebSocket.CheckCloseParameters (code, reason, false); + + if (msg != null) { _logger.Error (msg); return; } @@ -881,7 +833,6 @@ public void Stop (ushort code, string reason) } stopReceiving (5000); - if (code == (ushort) CloseStatusCode.NoStatus) { _services.Stop (new CloseEventArgs (), true, true); } @@ -894,33 +845,23 @@ public void Stop (ushort code, string reason) } /// - /// Stops receiving the WebSocket handshake requests, and closes - /// the WebSocket connections with the specified and - /// . + /// Stops receiving the WebSocket connection requests with + /// the specified and . /// /// - /// One of the enum values that represents - /// the status code indicating the reason for the close. + /// One of the enum values, represents the status code indicating + /// the reason for the stop. /// /// - /// A that represents the reason for the close. - /// The size must be 123 bytes or less. + /// A that represents the reason for the stop. /// public void Stop (CloseStatusCode code, string reason) { - string msg; - if (!checkIfAvailable (false, true, false, false, out msg)) { - _logger.Error (msg); - return; - } - - if (!WebSocket.CheckParametersForClose (code, reason, false, out msg)) { - _logger.Error (msg); - return; - } - lock (_sync) { - if (!checkIfAvailable (false, true, false, false, out msg)) { + var msg = _state.CheckIfAvailable (false, true, false) ?? + WebSocket.CheckCloseParameters (code, reason, false); + + if (msg != null) { _logger.Error (msg); return; } @@ -929,7 +870,6 @@ public void Stop (CloseStatusCode code, string reason) } stopReceiving (5000); - if (code == CloseStatusCode.NoStatus) { _services.Stop (new CloseEventArgs (), true, true); } diff --git a/websocket-sharp/Server/WebSocketServiceManager.cs b/websocket-sharp/Server/WebSocketServiceManager.cs index 8ed76b335..db8870200 100644 --- a/websocket-sharp/Server/WebSocketServiceManager.cs +++ b/websocket-sharp/Server/WebSocketServiceManager.cs @@ -390,7 +390,7 @@ public void Broadcast (byte[] data) return; } - if (data.LongLength <= WebSocket.FragmentLength) + if (data.Length <= WebSocket.FragmentLength) broadcast (Opcode.Binary, data, null); else broadcast (Opcode.Binary, new MemoryStream (data), null); @@ -413,7 +413,7 @@ public void Broadcast (string data) } var bytes = data.UTF8Encode (); - if (bytes.LongLength <= WebSocket.FragmentLength) + if (bytes.Length <= WebSocket.FragmentLength) broadcast (Opcode.Text, bytes, null); else broadcast (Opcode.Text, new MemoryStream (bytes), null); @@ -443,7 +443,7 @@ public void BroadcastAsync (byte[] data, Action completed) return; } - if (data.LongLength <= WebSocket.FragmentLength) + if (data.Length <= WebSocket.FragmentLength) broadcastAsync (Opcode.Binary, data, completed); else broadcastAsync (Opcode.Binary, new MemoryStream (data), completed); @@ -474,7 +474,7 @@ public void BroadcastAsync (string data, Action completed) } var bytes = data.UTF8Encode (); - if (bytes.LongLength <= WebSocket.FragmentLength) + if (bytes.Length <= WebSocket.FragmentLength) broadcastAsync (Opcode.Text, bytes, completed); else broadcastAsync (Opcode.Text, new MemoryStream (bytes), completed); diff --git a/websocket-sharp/Server/WebSocketSessionManager.cs b/websocket-sharp/Server/WebSocketSessionManager.cs index 88271994a..ed6fdd9d2 100644 --- a/websocket-sharp/Server/WebSocketSessionManager.cs +++ b/websocket-sharp/Server/WebSocketSessionManager.cs @@ -32,7 +32,7 @@ using System.IO; using System.Text; using System.Threading; -using System.Timers; +//using System.Timers; namespace WebSocketSharp.Server { @@ -49,9 +49,10 @@ public class WebSocketSessionManager private Dictionary _sessions; private volatile ServerState _state; private volatile bool _sweeping; - private System.Timers.Timer _sweepTimer; + private Timer _sweepTimer; private object _sync; private TimeSpan _waitTime; + private const int _sweepTime = 60000; #endregion @@ -73,7 +74,7 @@ internal WebSocketSessionManager (Logger logger) _sync = ((ICollection) _sessions).SyncRoot; _waitTime = TimeSpan.FromSeconds (1); - setSweepTimer (60000); + setSweepTimer (_sweepTime, false); } #endregion @@ -188,7 +189,7 @@ internal set { _clean = value; if (_state == ServerState.Start) - _sweepTimer.Enabled = value; + setSweepTimer(_sweepTime, true); } } @@ -284,10 +285,21 @@ private static string createID () return Guid.NewGuid ().ToString ("N"); } - private void setSweepTimer (double interval) + private void setSweepTimer (int interval, bool start) { - _sweepTimer = new System.Timers.Timer (interval); - _sweepTimer.Elapsed += (sender, e) => Sweep (); + if(_sweepTimer == null) + _sweepTimer = new Timer ((sender) => Sweep(), null, Timeout.Infinite, interval); + + if(start) + { + _sweepTimer.Change(0, interval); + } + else + { + _sweepTimer.Change(Timeout.Infinite, interval); + } + + //_sweepTimer.Elapsed += (sender, e) => Sweep (); } private bool tryGetSession (string id, out IWebSocketSession session) @@ -363,7 +375,8 @@ internal bool Remove (string id) internal void Start () { lock (_sync) { - _sweepTimer.Enabled = _clean; + setSweepTimer(_sweepTime, _clean); + //_sweepTimer.Enabled = _clean; _state = ServerState.Start; } } @@ -373,7 +386,7 @@ internal void Stop (CloseEventArgs e, byte[] frameAsBytes, bool receive) lock (_sync) { _state = ServerState.ShuttingDown; - _sweepTimer.Enabled = false; + setSweepTimer(_sweepTime, false); foreach (var session in _sessions.Values.ToList ()) session.Context.WebSocket.Close (e, frameAsBytes, receive); @@ -401,7 +414,7 @@ public void Broadcast (byte[] data) return; } - if (data.LongLength <= WebSocket.FragmentLength) + if (data.Length <= WebSocket.FragmentLength) broadcast (Opcode.Binary, data, null); else broadcast (Opcode.Binary, new MemoryStream (data), null); @@ -424,7 +437,7 @@ public void Broadcast (string data) } var bytes = data.UTF8Encode (); - if (bytes.LongLength <= WebSocket.FragmentLength) + if (bytes.Length <= WebSocket.FragmentLength) broadcast (Opcode.Text, bytes, null); else broadcast (Opcode.Text, new MemoryStream (bytes), null); @@ -454,7 +467,7 @@ public void BroadcastAsync (byte[] data, Action completed) return; } - if (data.LongLength <= WebSocket.FragmentLength) + if (data.Length <= WebSocket.FragmentLength) broadcastAsync (Opcode.Binary, data, completed); else broadcastAsync (Opcode.Binary, new MemoryStream (data), completed); @@ -485,7 +498,7 @@ public void BroadcastAsync (string data, Action completed) } var bytes = data.UTF8Encode (); - if (bytes.LongLength <= WebSocket.FragmentLength) + if (bytes.Length <= WebSocket.FragmentLength) broadcastAsync (Opcode.Text, bytes, completed); else broadcastAsync (Opcode.Text, new MemoryStream (bytes), completed); diff --git a/websocket-sharp/TaskToApm.cs b/websocket-sharp/TaskToApm.cs new file mode 100644 index 000000000..32bfbb285 --- /dev/null +++ b/websocket-sharp/TaskToApm.cs @@ -0,0 +1,204 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// Helper methods for using Tasks to implement the APM pattern. +// +// Example usage, wrapping a Task-returning FooAsync method with Begin/EndFoo methods: +// +// public IAsyncResult BeginFoo(..., AsyncCallback callback, object state) +// { +// Task t = FooAsync(...); +// return TaskToApm.Begin(t, callback, state); +// } +// public int EndFoo(IAsyncResult asyncResult) +// { +// return TaskToApm.End(asyncResult); +// } + +using System.Diagnostics; +using System.IO; +using System; +using System.Threading.Tasks; +using System.Threading; + +namespace WebSocketSharp +{ + /// + /// Provides support for efficiently using Tasks to implement the APM (Begin/End) pattern. + /// + internal static class TaskToApm + { + /// + /// Marshals the Task as an IAsyncResult, using the supplied callback and state + /// to implement the APM pattern. + /// + /// The Task to be marshaled. + /// The callback to be invoked upon completion. + /// The state to be stored in the IAsyncResult. + /// An IAsyncResult to represent the task's asynchronous operation. + public static IAsyncResult Begin(Task task, AsyncCallback callback, object state) + { + Debug.Assert(task != null); + + // If the task has already completed, then since the Task's CompletedSynchronously==false + // and we want it to be true, we need to create a new IAsyncResult. (We also need the AsyncState to match.) + IAsyncResult asyncResult; + if (task.IsCompleted) + { + // Synchronous completion. + asyncResult = new TaskWrapperAsyncResult(task, state, completedSynchronously: true); + if (callback != null) + { + callback(asyncResult); + } + } + else + { + // For asynchronous completion we need to schedule a callback. Whether we can use the Task as the IAsyncResult + // depends on whether the Task's AsyncState has reference equality with the requested state. + asyncResult = task.AsyncState == state ? (IAsyncResult)task : new TaskWrapperAsyncResult(task, state, completedSynchronously: false); + if (callback != null) + { + InvokeCallbackWhenTaskCompletes(task, callback, asyncResult); + } + } + return asyncResult; + } + + /// Processes an IAsyncResult returned by Begin. + /// The IAsyncResult to unwrap. + public static void End(IAsyncResult asyncResult) + { + Task task; + + // If the IAsyncResult is our task-wrapping IAsyncResult, extract the Task. + var twar = asyncResult as TaskWrapperAsyncResult; + if (twar != null) + { + task = twar.Task; + Debug.Assert(task != null, "TaskWrapperAsyncResult should never wrap a null Task."); + } + else + { + // Otherwise, the IAsyncResult should be a Task. + task = asyncResult as Task; + } + + // Make sure we actually got a task, then complete the operation by waiting on it. + if (task == null) + { + throw new ArgumentNullException(); + } + + task.GetAwaiter().GetResult(); + } + + /// Processes an IAsyncResult returned by Begin. + /// The IAsyncResult to unwrap. + public static TResult End(IAsyncResult asyncResult) + { + Task task; + + // If the IAsyncResult is our task-wrapping IAsyncResult, extract the Task. + var twar = asyncResult as TaskWrapperAsyncResult; + if (twar != null) + { + task = twar.Task as Task; + Debug.Assert(twar.Task != null, "TaskWrapperAsyncResult should never wrap a null Task."); + } + else + { + // Otherwise, the IAsyncResult should be a Task. + task = asyncResult as Task; + } + + // Make sure we actually got a task, then complete the operation by waiting on it. + if (task == null) + { + throw new ArgumentNullException(); + } + + return task.GetAwaiter().GetResult(); + } + + /// Invokes the callback asynchronously when the task has completed. + /// The Task to await. + /// The callback to invoke when the Task completes. + /// The Task used as the IAsyncResult. + private static void InvokeCallbackWhenTaskCompletes(Task antecedent, AsyncCallback callback, IAsyncResult asyncResult) + { + Debug.Assert(antecedent != null); + Debug.Assert(callback != null); + Debug.Assert(asyncResult != null); + + // We use OnCompleted rather than ContinueWith in order to avoid running synchronously + // if the task has already completed by the time we get here. This is separated out into + // its own method currently so that we only pay for the closure if necessary. + antecedent.ConfigureAwait(continueOnCapturedContext: false) + .GetAwaiter() + .OnCompleted(() => callback(asyncResult)); + + // PERFORMANCE NOTE: + // Assuming we're in the default ExecutionContext, the "slow path" of an incomplete + // task will result in four allocations: the new IAsyncResult, the delegate+closure + // in this method, and the continuation object inside of OnCompleted (necessary + // to capture both the Action delegate and the ExecutionContext in a single object). + // In the future, if performance requirements drove a need, those four + // allocations could be reduced to one. This would be achieved by having TaskWrapperAsyncResult + // also implement ITaskCompletionAction (and optionally IThreadPoolWorkItem). It would need + // additional fields to store the AsyncCallback and an ExecutionContext. Once configured, + // it would be set into the Task as a continuation. Its Invoke method would then be run when + // the antecedent completed, and, doing all of the necessary work to flow ExecutionContext, + // it would invoke the AsyncCallback. It could also have a field on it for the antecedent, + // so that the End method would have access to the completed antecedent. For related examples, + // see other implementations of ITaskCompletionAction, and in particular ReadWriteTask + // used in Stream.Begin/EndXx's implementation. + } + + /// + /// Provides a simple IAsyncResult that wraps a Task. This, in effect, allows + /// for overriding what's seen for the CompletedSynchronously and AsyncState values. + /// + private sealed class TaskWrapperAsyncResult : IAsyncResult + { + /// The wrapped Task. + internal readonly Task Task; + /// The new AsyncState value. + private readonly object _state; + /// The new CompletedSynchronously value. + private readonly bool _completedSynchronously; + + /// Initializes the IAsyncResult with the Task to wrap and the overriding AsyncState and CompletedSynchronously values. + /// The Task to wrap. + /// The new AsyncState value + /// The new CompletedSynchronously value. + internal TaskWrapperAsyncResult(Task task, object state, bool completedSynchronously) + { + Debug.Assert(task != null); + Debug.Assert(!completedSynchronously || task.IsCompleted, "If completedSynchronously is true, the task must be completed."); + + this.Task = task; + _state = state; + _completedSynchronously = completedSynchronously; + } + + // The IAsyncResult implementation. + // - IsCompleted and AsyncWaitHandle just pass through to the Task. + // - AsyncState and CompletedSynchronously return the corresponding values stored in this object. + + object IAsyncResult.AsyncState { get { return _state; } } + bool IAsyncResult.CompletedSynchronously { get { return _completedSynchronously; } } + bool IAsyncResult.IsCompleted { get { return this.Task.IsCompleted; } } + WaitHandle IAsyncResult.AsyncWaitHandle { get { return ((IAsyncResult)this.Task).AsyncWaitHandle; } } + + public WaitHandle AsyncWaitHandle + { + get + { + return ((IAsyncResult)this.Task).AsyncWaitHandle; + } + } + } + } +} diff --git a/websocket-sharp/WebSocket.cs b/websocket-sharp/WebSocket.cs index 555d69f54..42e765cd8 100644 --- a/websocket-sharp/WebSocket.cs +++ b/websocket-sharp/WebSocket.cs @@ -52,3019 +52,3090 @@ using System.Security.Cryptography; using System.Text; using System.Threading; +using Helpers; using WebSocketSharp.Net; using WebSocketSharp.Net.WebSockets; namespace WebSocketSharp { - /// - /// Implements the WebSocket interface. - /// - /// - /// The WebSocket class provides a set of methods and properties for two-way communication using - /// the WebSocket protocol (RFC 6455). - /// - public class WebSocket : IDisposable - { - #region Private Fields - - private AuthenticationChallenge _authChallenge; - private string _base64Key; - private bool _client; - private Action _closeContext; - private CompressionMethod _compression; - private WebSocketContext _context; - private CookieCollection _cookies; - private NetworkCredential _credentials; - private bool _emitOnPing; - private bool _enableRedirection; - private AutoResetEvent _exitReceiving; - private string _extensions; - private bool _extensionsRequested; - private object _forMessageEventQueue; - private object _forSend; - private object _forState; - private MemoryStream _fragmentsBuffer; - private bool _fragmentsCompressed; - private Opcode _fragmentsOpcode; - private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - private Func _handshakeRequestChecker; - private bool _ignoreExtensions; - private bool _inContinuation; - private volatile bool _inMessage; - private volatile Logger _logger; - private Action _message; - private Queue _messageEventQueue; - private uint _nonceCount; - private string _origin; - private bool _preAuth; - private string _protocol; - private string[] _protocols; - private bool _protocolsRequested; - private NetworkCredential _proxyCredentials; - private Uri _proxyUri; - private volatile WebSocketState _readyState; - private AutoResetEvent _receivePong; - private bool _secure; - private ClientSslConfiguration _sslConfig; - private Stream _stream; - private TcpClient _tcpClient; - private Uri _uri; - private const string _version = "13"; - private TimeSpan _waitTime; - - #endregion - - #region Internal Fields - - /// - /// Represents the empty array of used internally. - /// - internal static readonly byte[] EmptyBytes; - /// - /// Represents the length used to determine whether the data should be fragmented in sending. + /// Implements the WebSocket interface. /// /// - /// - /// The data will be fragmented if that length is greater than the value of this field. - /// - /// - /// If you would like to change the value, you must set it to a value between 125 and - /// Int32.MaxValue - 14 inclusive. - /// + /// The WebSocket class provides a set of methods and properties for two-way communication using + /// the WebSocket protocol (RFC 6455). /// - internal static readonly int FragmentLength; + public class WebSocket : IDisposable + { + #region Private Fields + + private AuthenticationChallenge _authChallenge; + private string _base64Key; + private bool _client; + private Action _closeContext; + private CompressionMethod _compression; + private WebSocketContext _context; + private CookieCollection _cookies; + private NetworkCredential _credentials; + private bool _emitOnPing; + private bool _enableRedirection; + private AutoResetEvent _exitReceiving; + private string _extensions; + private bool _extensionsRequested; + private object _forConn; + private object _forMessageEventQueue; + private object _forSend; + private MemoryStream _fragmentsBuffer; + private bool _fragmentsCompressed; + private Opcode _fragmentsOpcode; + private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + private Func _handshakeRequestChecker; + private bool _ignoreExtensions; + private bool _inContinuation; + private volatile bool _inMessage; + private volatile Logger _logger; + private Action _message; + private Queue _messageEventQueue; + private uint _nonceCount; + private string _origin; + private bool _preAuth; + private string _protocol; + private string[] _protocols; + private bool _protocolsRequested; + private NetworkCredential _proxyCredentials; + private Uri _proxyUri; + private volatile WebSocketState _readyState; + private AutoResetEvent _receivePong; + private bool _secure; + private ClientSslConfiguration _sslConfig; + private Stream _stream; + private TcpClient _tcpClient; + private Uri _uri; + private const string _version = "13"; + private TimeSpan _waitTime; + + #endregion + + #region Internal Fields + + /// + /// Represents the empty array of used internally. + /// + internal static readonly byte[] EmptyBytes; + + /// + /// Represents the length used to determine whether the data should be fragmented in sending. + /// + /// + /// + /// The data will be fragmented if that length is greater than the value of this field. + /// + /// + /// If you would like to change the value, you must set it to a value between 125 and + /// Int32.MaxValue - 14 inclusive. + /// + /// + internal static readonly int FragmentLength; + + /// + /// Represents the random number generator used internally. + /// + internal static readonly RandomNumberGenerator RandomNumber; + + #endregion + + #region Static Constructor + + static WebSocket() + { + EmptyBytes = new byte[0]; + FragmentLength = 1016; + RandomNumber = RandomNumberGenerator.Create(); + } - /// - /// Represents the random number generator used internally. - /// - internal static readonly RandomNumberGenerator RandomNumber; + #endregion - #endregion + #region Internal Constructors - #region Static Constructor + // As server + internal WebSocket(HttpListenerWebSocketContext context, string protocol) + { + _context = context; + _protocol = protocol; - static WebSocket () - { - EmptyBytes = new byte[0]; - FragmentLength = 1016; - RandomNumber = new RNGCryptoServiceProvider (); - } + _closeContext = context.Close; + _logger = context.Log; + _message = messages; + _secure = context.IsSecureConnection; + _stream = context.Stream; + _waitTime = TimeSpan.FromSeconds(1); - #endregion + init(); + } - #region Internal Constructors + // As server + internal WebSocket(TcpListenerWebSocketContext context, string protocol) + { + _context = context; + _protocol = protocol; - // As server - internal WebSocket (HttpListenerWebSocketContext context, string protocol) - { - _context = context; - _protocol = protocol; + _closeContext = context.Close; + _logger = context.Log; + _message = messages; + _secure = context.IsSecureConnection; + _stream = context.Stream; + _waitTime = TimeSpan.FromSeconds(1); - _closeContext = context.Close; - _logger = context.Log; - _message = messages; - _secure = context.IsSecureConnection; - _stream = context.Stream; - _waitTime = TimeSpan.FromSeconds (1); + init(); + } - init (); - } + #endregion + + #region Public Constructors + + /// + /// Initializes a new instance of the class with + /// the specified WebSocket URL and subprotocols. + /// + /// + /// A that represents the WebSocket URL to connect. + /// + /// + /// An array of that contains the WebSocket subprotocols if any. + /// Each value of must be a token defined in + /// RFC 2616. + /// + /// + /// is . + /// + /// + /// + /// is invalid. + /// + /// + /// -or- + /// + /// + /// is invalid. + /// + /// + public WebSocket(string url, params string[] protocols) + { + if (url == null) + throw new ArgumentNullException(nameof(url)); + + if (url.Length == 0) + throw new ArgumentException("An empty string.", nameof(url)); + + string msg; + if (!url.TryCreateWebSocketUri(out _uri, out msg)) + throw new ArgumentException(msg, nameof(url)); + + if (protocols != null && protocols.Length > 0) + { + msg = protocols.CheckIfValidProtocols(); + if (msg != null) + throw new ArgumentException(msg, nameof(protocols)); + + _protocols = protocols; + } - // As server - internal WebSocket (TcpListenerWebSocketContext context, string protocol) - { - _context = context; - _protocol = protocol; + _base64Key = CreateBase64Key(); + _client = true; + _logger = new Logger(); + _message = messagec; + _secure = _uri.Scheme == "wss"; + _waitTime = TimeSpan.FromSeconds(5); - _closeContext = context.Close; - _logger = context.Log; - _message = messages; - _secure = context.IsSecureConnection; - _stream = context.Stream; - _waitTime = TimeSpan.FromSeconds (1); + init(); + } - init (); - } + #endregion - #endregion + #region Internal Properties - #region Public Constructors + internal CookieCollection CookieCollection + { + get { return _cookies; } + } - /// - /// Initializes a new instance of the class with - /// the specified WebSocket URL and subprotocols. - /// - /// - /// A that represents the WebSocket URL to connect. - /// - /// - /// An array of that contains the WebSocket subprotocols if any. - /// Each value of must be a token defined in - /// RFC 2616. - /// - /// - /// is . - /// - /// - /// - /// is invalid. - /// - /// - /// -or- - /// - /// - /// is invalid. - /// - /// - public WebSocket (string url, params string[] protocols) - { - if (url == null) - throw new ArgumentNullException ("url"); + // As server + internal Func CustomHandshakeRequestChecker + { + get { return _handshakeRequestChecker; } - if (url.Length == 0) - throw new ArgumentException ("An empty string.", "url"); + set { _handshakeRequestChecker = value; } + } - string msg; - if (!url.TryCreateWebSocketUri (out _uri, out msg)) - throw new ArgumentException (msg, "url"); + internal bool HasMessage + { + get + { + lock (_forMessageEventQueue) + return _messageEventQueue.Count > 0; + } + } - if (protocols != null && protocols.Length > 0) { - msg = protocols.CheckIfValidProtocols (); - if (msg != null) - throw new ArgumentException (msg, "protocols"); + // As server + internal bool IgnoreExtensions + { + get { return _ignoreExtensions; } - _protocols = protocols; - } + set { _ignoreExtensions = value; } + } - _base64Key = CreateBase64Key (); - _client = true; - _logger = new Logger (); - _message = messagec; - _secure = _uri.Scheme == "wss"; - _waitTime = TimeSpan.FromSeconds (5); + internal bool IsConnected + { + get { return _readyState == WebSocketState.Open || _readyState == WebSocketState.Closing; } + } - init (); - } + #endregion + + #region Public Properties + + /// + /// Gets or sets the compression method used to compress a message on the WebSocket connection. + /// + /// + /// One of the enum values, specifies the compression method + /// used to compress a message. The default value is . + /// + public CompressionMethod Compression + { + get { return _compression; } + + set + { + lock (_forConn) + { + string msg; + if (!checkIfAvailable(true, false, true, false, false, true, out msg)) + { + _logger.Error(msg); + error("An error has occurred in setting the compression.", null); + + return; + } + + _compression = value; + } + } + } - #endregion + /// + /// Gets the HTTP cookies included in the WebSocket handshake request and response. + /// + /// + /// An + /// instance that provides an enumerator which supports the iteration over the collection of + /// the cookies. + /// + public IEnumerable Cookies + { + get + { + lock (_cookies.SyncRoot) + foreach (Cookie cookie in _cookies) + yield return cookie; + } + } - #region Internal Properties + /// + /// Gets the credentials for the HTTP authentication (Basic/Digest). + /// + /// + /// A that represents the credentials for + /// the authentication. The default value is . + /// + public NetworkCredential Credentials + { + get { return _credentials; } + } - internal CookieCollection CookieCollection { - get { - return _cookies; - } - } + /// + /// Gets or sets a value indicating whether the emits + /// a event when receives a ping. + /// + /// + /// true if the emits a event + /// when receives a ping; otherwise, false. The default value is false. + /// + public bool EmitOnPing + { + get { return _emitOnPing; } + + set { _emitOnPing = value; } + } - // As server - internal Func CustomHandshakeRequestChecker { - get { - return _handshakeRequestChecker; - } + /// + /// Gets or sets a value indicating whether the redirects + /// the handshake request to the new URL located in the handshake response. + /// + /// + /// true if the redirects the handshake request to + /// the new URL; otherwise, false. The default value is false. + /// + public bool EnableRedirection + { + get { return _enableRedirection; } + + set + { + lock (_forConn) + { + string msg; + if (!checkIfAvailable(true, false, true, false, false, true, out msg)) + { + _logger.Error(msg); + error("An error has occurred in setting the enable redirection.", null); + + return; + } + + _enableRedirection = value; + } + } + } - set { - _handshakeRequestChecker = value; - } - } + /// + /// Gets the WebSocket extensions selected by the server. + /// + /// + /// A that represents the extensions if any. + /// The default value is . + /// + public string Extensions + { + get { return _extensions ?? String.Empty; } + } - internal bool HasMessage { - get { - lock (_forMessageEventQueue) - return _messageEventQueue.Count > 0; - } - } + /// + /// Gets a value indicating whether the WebSocket connection is alive. + /// + /// + /// true if the connection is alive; otherwise, false. + /// + public bool IsAlive + { + get { return Ping(); } + } - // As server - internal bool IgnoreExtensions { - get { - return _ignoreExtensions; - } + /// + /// Gets a value indicating whether the WebSocket connection is secure. + /// + /// + /// true if the connection is secure; otherwise, false. + /// + public bool IsSecure + { + get { return _secure; } + } - set { - _ignoreExtensions = value; - } - } + /// + /// Gets the logging functions. + /// + /// + /// The default logging level is . If you would like to change it, + /// you should set this Log.Level property to any of the enum + /// values. + /// + /// + /// A that provides the logging functions. + /// + public Logger Log + { + get { return _logger; } + + internal set { _logger = value; } + } - internal bool IsConnected { - get { - return _readyState == WebSocketState.Open || _readyState == WebSocketState.Closing; - } - } + /// + /// Gets or sets the value of the HTTP Origin header to send with + /// the WebSocket handshake request to the server. + /// + /// + /// The sends the Origin header if this property has any. + /// + /// + /// + /// A that represents the value of + /// the Origin header to send. + /// The default value is . + /// + /// + /// The Origin header has the following syntax: + /// <scheme>://<host>[:<port>] + /// + /// + public string Origin + { + get { return _origin; } + + set + { + lock (_forConn) + { + string msg; + if (!checkIfAvailable(true, false, true, false, false, true, out msg)) + { + _logger.Error(msg); + error("An error has occurred in setting the origin.", null); + + return; + } + + if (value.IsNullOrEmpty()) + { + _origin = value; + return; + } + + Uri origin; + if (!Uri.TryCreate(value, UriKind.Absolute, out origin) || origin.Segments.Length > 1) + { + _logger.Error("The syntax of an origin must be '://[:]'."); + error("An error has occurred in setting the origin.", null); + + return; + } + + _origin = value.TrimEnd('/'); + } + } + } - #endregion + /// + /// Gets the WebSocket subprotocol selected by the server. + /// + /// + /// A that represents the subprotocol if any. + /// The default value is . + /// + public string Protocol + { + get { return _protocol ?? String.Empty; } + + internal set { _protocol = value; } + } - #region Public Properties + /// + /// Gets the state of the WebSocket connection. + /// + /// + /// One of the enum values, indicates the state of the connection. + /// The default value is . + /// + public WebSocketState ReadyState + { + get { return _readyState; } + } - /// - /// Gets or sets the compression method used to compress a message on the WebSocket connection. - /// - /// - /// One of the enum values, specifies the compression method - /// used to compress a message. The default value is . - /// - public CompressionMethod Compression { - get { - return _compression; - } - - set { - lock (_forState) { - string msg; - if (!checkIfAvailable (true, false, true, false, false, true, out msg)) { - _logger.Error (msg); - error ("An error has occurred in setting the compression.", null); - - return; - } - - _compression = value; - } - } - } + /// + /// Gets or sets the SSL configuration used to authenticate the server and + /// optionally the client for secure connection. + /// + /// + /// A that represents the configuration used + /// to authenticate the server and optionally the client for secure connection, + /// or if the is used in a server. + /// + public ClientSslConfiguration SslConfiguration + { + get + { + return _client + ? (_sslConfig ?? (_sslConfig = new ClientSslConfiguration(_uri.DnsSafeHost))) + : null; + } - /// - /// Gets the HTTP cookies included in the WebSocket handshake request and response. - /// - /// - /// An - /// instance that provides an enumerator which supports the iteration over the collection of - /// the cookies. - /// - public IEnumerable Cookies { - get { - lock (_cookies.SyncRoot) - foreach (Cookie cookie in _cookies) - yield return cookie; - } - } + set + { + lock (_forConn) + { + string msg; + if (!checkIfAvailable(true, false, true, false, false, true, out msg)) + { + _logger.Error(msg); + error("An error has occurred in setting the ssl configuration.", null); + + return; + } + + _sslConfig = value; + } + } + } - /// - /// Gets the credentials for the HTTP authentication (Basic/Digest). - /// - /// - /// A that represents the credentials for - /// the authentication. The default value is . - /// - public NetworkCredential Credentials { - get { - return _credentials; - } - } + /// + /// Gets the WebSocket URL used to connect, or accepted. + /// + /// + /// A that represents the URL used to connect, or accepted. + /// + public Uri Url + { + get { return _client ? _uri : _context.RequestUri; } + } - /// - /// Gets or sets a value indicating whether the emits - /// a event when receives a ping. - /// - /// - /// true if the emits a event - /// when receives a ping; otherwise, false. The default value is false. - /// - public bool EmitOnPing { - get { - return _emitOnPing; - } - - set { - _emitOnPing = value; - } - } + /// + /// Gets or sets the wait time for the response to the Ping or Close. + /// + /// + /// A that represents the wait time. The default value is the same as + /// 5 seconds, or 1 second if the is used in a server. + /// + public TimeSpan WaitTime + { + get { return _waitTime; } + + set + { + lock (_forConn) + { + string msg; + if (!checkIfAvailable(true, true, true, false, false, true, out msg) + || !value.CheckWaitTime(out msg) + ) + { + _logger.Error(msg); + error("An error has occurred in setting the wait time.", null); + + return; + } + + _waitTime = value; + } + } + } - /// - /// Gets or sets a value indicating whether the redirects - /// the handshake request to the new URL located in the handshake response. - /// - /// - /// true if the redirects the handshake request to - /// the new URL; otherwise, false. The default value is false. - /// - public bool EnableRedirection { - get { - return _enableRedirection; - } - - set { - lock (_forState) { - string msg; - if (!checkIfAvailable (true, false, true, false, false, true, out msg)) { - _logger.Error (msg); - error ("An error has occurred in setting the enable redirection.", null); - - return; - } - - _enableRedirection = value; - } - } - } + #endregion - /// - /// Gets the WebSocket extensions selected by the server. - /// - /// - /// A that represents the extensions if any. - /// The default value is . - /// - public string Extensions { - get { - return _extensions ?? String.Empty; - } - } + #region Public Events - /// - /// Gets a value indicating whether the WebSocket connection is alive. - /// - /// - /// true if the connection is alive; otherwise, false. - /// - public bool IsAlive { - get { - return Ping (); - } - } + /// + /// Occurs when the WebSocket connection has been closed. + /// + public event EventHandler OnClose; - /// - /// Gets a value indicating whether the WebSocket connection is secure. - /// - /// - /// true if the connection is secure; otherwise, false. - /// - public bool IsSecure { - get { - return _secure; - } - } + /// + /// Occurs when the gets an error. + /// + public event EventHandler OnError; - /// - /// Gets the logging functions. - /// - /// - /// The default logging level is . If you would like to change it, - /// you should set this Log.Level property to any of the enum - /// values. - /// - /// - /// A that provides the logging functions. - /// - public Logger Log { - get { - return _logger; - } - - internal set { - _logger = value; - } - } + /// + /// Occurs when the receives a message. + /// + public event EventHandler OnMessage; - /// - /// Gets or sets the value of the HTTP Origin header to send with - /// the WebSocket handshake request to the server. - /// - /// - /// The sends the Origin header if this property has any. - /// - /// - /// - /// A that represents the value of - /// the Origin header to send. - /// The default value is . - /// - /// - /// The Origin header has the following syntax: - /// <scheme>://<host>[:<port>] - /// - /// - public string Origin { - get { - return _origin; - } - - set { - lock (_forState) { - string msg; - if (!checkIfAvailable (true, false, true, false, false, true, out msg)) { - _logger.Error (msg); - error ("An error has occurred in setting the origin.", null); - - return; - } - - if (value.IsNullOrEmpty ()) { - _origin = value; - return; - } - - Uri origin; - if (!Uri.TryCreate (value, UriKind.Absolute, out origin) || origin.Segments.Length > 1) { - _logger.Error ("The syntax of an origin must be '://[:]'."); - error ("An error has occurred in setting the origin.", null); - - return; - } - - _origin = value.TrimEnd ('/'); - } - } - } + /// + /// Occurs when the WebSocket connection has been established. + /// + public event EventHandler OnOpen; - /// - /// Gets the WebSocket subprotocol selected by the server. - /// - /// - /// A that represents the subprotocol if any. - /// The default value is . - /// - public string Protocol { - get { - return _protocol ?? String.Empty; - } - - internal set { - _protocol = value; - } - } + #endregion - /// - /// Gets the state of the WebSocket connection. - /// - /// - /// One of the enum values, indicates the state of the connection. - /// The default value is . - /// - public WebSocketState ReadyState { - get { - return _readyState; - } - } + #region Private Methods - /// - /// Gets or sets the SSL configuration used to authenticate the server and - /// optionally the client for secure connection. - /// - /// - /// A that represents the configuration used - /// to authenticate the server and optionally the client for secure connection, - /// or if the is used in a server. - /// - public ClientSslConfiguration SslConfiguration { - get { - return _client - ? (_sslConfig ?? (_sslConfig = new ClientSslConfiguration (_uri.DnsSafeHost))) - : null; - } - - set { - lock (_forState) { - string msg; - if (!checkIfAvailable (true, false, true, false, false, true, out msg)) { - _logger.Error (msg); - error ("An error has occurred in setting the ssl configuration.", null); - - return; - } - - _sslConfig = value; - } - } - } + // As server + private bool accept() + { + lock (_forConn) + { + string msg; + if (!checkIfAvailable(true, false, false, false, out msg)) + { + _logger.Error(msg); + error("An error has occurred in accepting.", null); - /// - /// Gets the WebSocket URL used to connect, or accepted. - /// - /// - /// A that represents the URL used to connect, or accepted. - /// - public Uri Url { - get { - return _client ? _uri : _context.RequestUri; - } - } + return false; + } - /// - /// Gets or sets the wait time for the response to the Ping or Close. - /// - /// - /// A that represents the wait time. The default value is the same as - /// 5 seconds, or 1 second if the is used in a server. - /// - public TimeSpan WaitTime { - get { - return _waitTime; - } - - set { - lock (_forState) { - string msg; - if (!checkIfAvailable (true, true, true, false, false, true, out msg) - || !value.CheckWaitTime (out msg) - ) { - _logger.Error (msg); - error ("An error has occurred in setting the wait time.", null); - - return; - } - - _waitTime = value; - } - } - } + try + { + if (!acceptHandshake()) + return false; - #endregion + _readyState = WebSocketState.Open; + } + catch (Exception ex) + { + _logger.Fatal(ex.ToString()); + fatal("An exception has occurred while accepting.", ex); - #region Public Events + return false; + } - /// - /// Occurs when the WebSocket connection has been closed. - /// - public event EventHandler OnClose; + return true; + } + } - /// - /// Occurs when the gets an error. - /// - public event EventHandler OnError; + // As server + private bool acceptHandshake() + { + _logger.Debug(String.Format("A request from {0}:\n{1}", _context.UserEndPoint, _context)); - /// - /// Occurs when the receives a message. - /// - public event EventHandler OnMessage; + string msg; + if (!checkHandshakeRequest(_context, out msg)) + { + sendHttpResponse(createHandshakeFailureResponse(HttpStatusCode.BadRequest)); - /// - /// Occurs when the WebSocket connection has been established. - /// - public event EventHandler OnOpen; + _logger.Fatal(msg); + fatal("An error has occurred while accepting.", CloseStatusCode.ProtocolError); - #endregion + return false; + } - #region Private Methods + if (!customCheckHandshakeRequest(_context, out msg)) + { + sendHttpResponse(createHandshakeFailureResponse(HttpStatusCode.BadRequest)); - // As server - private bool accept () - { - lock (_forState) { - string msg; - if (!checkIfAvailable (true, false, false, false, out msg)) { - _logger.Error (msg); - error ("An error has occurred in accepting.", null); + _logger.Fatal(msg); + fatal("An error has occurred while accepting.", CloseStatusCode.PolicyViolation); - return false; - } + return false; + } - try { - if (!acceptHandshake ()) - return false; + _base64Key = _context.Headers["Sec-WebSocket-Key"]; - _readyState = WebSocketState.Open; - } - catch (Exception ex) { - _logger.Fatal (ex.ToString ()); - fatal ("An exception has occurred while accepting.", ex); + if (_protocol != null) + processSecWebSocketProtocolHeader(_context.SecWebSocketProtocols); + + if (!_ignoreExtensions) + processSecWebSocketExtensionsClientHeader(_context.Headers["Sec-WebSocket-Extensions"]); - return false; + return sendHttpResponse(createHandshakeResponse()); } - return true; - } - } + // As server + private bool checkHandshakeRequest(WebSocketContext context, out string message) + { + message = null; - // As server - private bool acceptHandshake () - { - _logger.Debug (String.Format ("A request from {0}:\n{1}", _context.UserEndPoint, _context)); + if (context.RequestUri == null) + { + message = "Specifies an invalid Request-URI."; + return false; + } + + if (!context.IsWebSocketRequest) + { + message = "Not a WebSocket handshake request."; + return false; + } - string msg; - if (!checkHandshakeRequest (_context, out msg)) { - sendHttpResponse (createHandshakeFailureResponse (HttpStatusCode.BadRequest)); + var headers = context.Headers; + if (!validateSecWebSocketKeyHeader(headers["Sec-WebSocket-Key"])) + { + message = "Includes no Sec-WebSocket-Key header, or it has an invalid value."; + return false; + } - _logger.Fatal (msg); - fatal ("An error has occurred while accepting.", CloseStatusCode.ProtocolError); + if (!validateSecWebSocketVersionClientHeader(headers["Sec-WebSocket-Version"])) + { + message = "Includes no Sec-WebSocket-Version header, or it has an invalid value."; + return false; + } - return false; - } + if (!validateSecWebSocketProtocolClientHeader(headers["Sec-WebSocket-Protocol"])) + { + message = "Includes an invalid Sec-WebSocket-Protocol header."; + return false; + } - if (!customCheckHandshakeRequest (_context, out msg)) { - sendHttpResponse (createHandshakeFailureResponse (HttpStatusCode.BadRequest)); + if (!_ignoreExtensions + && !validateSecWebSocketExtensionsClientHeader(headers["Sec-WebSocket-Extensions"]) + ) + { + message = "Includes an invalid Sec-WebSocket-Extensions header."; + return false; + } - _logger.Fatal (msg); - fatal ("An error has occurred while accepting.", CloseStatusCode.PolicyViolation); + return true; + } - return false; - } + // As client + private bool checkHandshakeResponse(HttpResponse response, out string message) + { + message = null; - _base64Key = _context.Headers["Sec-WebSocket-Key"]; + if (response.IsRedirect) + { + message = "Indicates the redirection."; + return false; + } - if (_protocol != null) - processSecWebSocketProtocolHeader (_context.SecWebSocketProtocols); + if (response.IsUnauthorized) + { + message = "Requires the authentication."; + return false; + } - if (!_ignoreExtensions) - processSecWebSocketExtensionsClientHeader (_context.Headers["Sec-WebSocket-Extensions"]); + if (!response.IsWebSocketResponse) + { + message = "Not a WebSocket handshake response."; + return false; + } - return sendHttpResponse (createHandshakeResponse ()); - } + var headers = response.Headers; + if (!validateSecWebSocketAcceptHeader(headers["Sec-WebSocket-Accept"])) + { + message = "Includes no Sec-WebSocket-Accept header, or it has an invalid value."; + return false; + } - // As server - private bool checkHandshakeRequest (WebSocketContext context, out string message) - { - message = null; - - if (context.RequestUri == null) { - message = "Specifies an invalid Request-URI."; - return false; - } - - if (!context.IsWebSocketRequest) { - message = "Not a WebSocket handshake request."; - return false; - } - - var headers = context.Headers; - if (!validateSecWebSocketKeyHeader (headers["Sec-WebSocket-Key"])) { - message = "Includes no Sec-WebSocket-Key header, or it has an invalid value."; - return false; - } - - if (!validateSecWebSocketVersionClientHeader (headers["Sec-WebSocket-Version"])) { - message = "Includes no Sec-WebSocket-Version header, or it has an invalid value."; - return false; - } - - if (!validateSecWebSocketProtocolClientHeader (headers["Sec-WebSocket-Protocol"])) { - message = "Includes an invalid Sec-WebSocket-Protocol header."; - return false; - } - - if (!_ignoreExtensions - && !validateSecWebSocketExtensionsClientHeader (headers["Sec-WebSocket-Extensions"]) - ) { - message = "Includes an invalid Sec-WebSocket-Extensions header."; - return false; - } - - return true; - } + if (!validateSecWebSocketProtocolServerHeader(headers["Sec-WebSocket-Protocol"])) + { + message = "Includes no Sec-WebSocket-Protocol header, or it has an invalid value."; + return false; + } - // As client - private bool checkHandshakeResponse (HttpResponse response, out string message) - { - message = null; - - if (response.IsRedirect) { - message = "Indicates the redirection."; - return false; - } - - if (response.IsUnauthorized) { - message = "Requires the authentication."; - return false; - } - - if (!response.IsWebSocketResponse) { - message = "Not a WebSocket handshake response."; - return false; - } - - var headers = response.Headers; - if (!validateSecWebSocketAcceptHeader (headers["Sec-WebSocket-Accept"])) { - message = "Includes no Sec-WebSocket-Accept header, or it has an invalid value."; - return false; - } - - if (!validateSecWebSocketProtocolServerHeader (headers["Sec-WebSocket-Protocol"])) { - message = "Includes no Sec-WebSocket-Protocol header, or it has an invalid value."; - return false; - } - - if (!validateSecWebSocketExtensionsServerHeader (headers["Sec-WebSocket-Extensions"])) { - message = "Includes an invalid Sec-WebSocket-Extensions header."; - return false; - } - - if (!validateSecWebSocketVersionServerHeader (headers["Sec-WebSocket-Version"])) { - message = "Includes an invalid Sec-WebSocket-Version header."; - return false; - } - - return true; - } + if (!validateSecWebSocketExtensionsServerHeader(headers["Sec-WebSocket-Extensions"])) + { + message = "Includes an invalid Sec-WebSocket-Extensions header."; + return false; + } - private bool checkIfAvailable ( - bool connecting, bool open, bool closing, bool closed, out string message - ) - { - message = null; + if (!validateSecWebSocketVersionServerHeader(headers["Sec-WebSocket-Version"])) + { + message = "Includes an invalid Sec-WebSocket-Version header."; + return false; + } - if (!connecting && _readyState == WebSocketState.Connecting) { - message = "This operation is not available in: connecting"; - return false; - } + return true; + } - if (!open && _readyState == WebSocketState.Open) { - message = "This operation is not available in: open"; - return false; - } + private bool checkIfAvailable( + bool connecting, bool open, bool closing, bool closed, out string message + ) + { + message = null; - if (!closing && _readyState == WebSocketState.Closing) { - message = "This operation is not available in: closing"; - return false; - } + if (!connecting && _readyState == WebSocketState.Connecting) + { + message = "This operation isn't available in: connecting"; + return false; + } - if (!closed && _readyState == WebSocketState.Closed) { - message = "This operation is not available in: closed"; - return false; - } + if (!open && _readyState == WebSocketState.Open) + { + message = "This operation isn't available in: open"; + return false; + } - return true; - } + if (!closing && _readyState == WebSocketState.Closing) + { + message = "This operation isn't available in: closing"; + return false; + } - private bool checkIfAvailable ( - bool client, - bool server, - bool connecting, - bool open, - bool closing, - bool closed, - out string message - ) - { - message = null; + if (!closed && _readyState == WebSocketState.Closed) + { + message = "This operation isn't available in: closed"; + return false; + } - if (!client && _client) { - message = "This operation is not available in: client"; - return false; - } + return true; + } - if (!server && !_client) { - message = "This operation is not available in: server"; - return false; - } + private bool checkIfAvailable( + bool client, + bool server, + bool connecting, + bool open, + bool closing, + bool closed, + out string message + ) + { + message = null; + + if (!client && _client) + { + message = "This operation isn't available in: client"; + return false; + } - return checkIfAvailable (connecting, open, closing, closed, out message); - } + if (!server && !_client) + { + message = "This operation isn't available in: server"; + return false; + } - private static bool checkParametersForSetCredentials ( - string username, string password, out string message - ) - { - message = null; + return checkIfAvailable(connecting, open, closing, closed, out message); + } - if (username.IsNullOrEmpty ()) - return true; + private bool checkReceivedFrame(WebSocketFrame frame, out string message) + { + message = null; - if (username.Contains (':') || !username.IsText ()) { - message = "'username' contains an invalid character."; - return false; - } + var masked = frame.IsMasked; + if (_client && masked) + { + message = "A frame from the server is masked."; + return false; + } - if (password.IsNullOrEmpty ()) - return true; + if (!_client && !masked) + { + message = "A frame from a client isn't masked."; + return false; + } - if (!password.IsText ()) { - message = "'password' contains an invalid character."; - return false; - } + if (_inContinuation && frame.IsData) + { + message = "A data frame has been received while receiving continuation frames."; + return false; + } - return true; - } + if (frame.IsCompressed && _compression == CompressionMethod.None) + { + message = "A compressed frame has been received without any agreement for it."; + return false; + } - private static bool checkParametersForSetProxy ( - string url, string username, string password, out string message - ) - { - message = null; + if (frame.Rsv2 == Rsv.On) + { + message = "The RSV2 of a frame is non-zero without any negotiation for it."; + return false; + } - if (url.IsNullOrEmpty ()) - return true; + if (frame.Rsv3 == Rsv.On) + { + message = "The RSV3 of a frame is non-zero without any negotiation for it."; + return false; + } - Uri uri; - if (!Uri.TryCreate (url, UriKind.Absolute, out uri) - || uri.Scheme != "http" - || uri.Segments.Length > 1 - ) { - message = "'url' is an invalid URL."; - return false; - } + return true; + } - if (username.IsNullOrEmpty ()) - return true; + private void close(CloseEventArgs e, bool send, bool receive, bool received) + { + lock (_forConn) + { + if (_readyState == WebSocketState.Closing) + { + _logger.Info("The closing is already in progress."); + return; + } + + if (_readyState == WebSocketState.Closed) + { + _logger.Info("The connection has been closed."); + return; + } + + send = send && _readyState == WebSocketState.Open; + receive = receive && send; + + _readyState = WebSocketState.Closing; + } - if (username.Contains (':') || !username.IsText ()) { - message = "'username' contains an invalid character."; - return false; - } + _logger.Trace("Begin closing the connection."); - if (password.IsNullOrEmpty ()) - return true; + var bytes = send ? WebSocketFrame.CreateCloseFrame(e.PayloadData, _client).ToArray() : null; + e.WasClean = closeHandshake(bytes, receive, received); + releaseResources(); - if (!password.IsText ()) { - message = "'password' contains an invalid character."; - return false; - } + _logger.Trace("End closing the connection."); - return true; - } + _readyState = WebSocketState.Closed; + try + { + OnClose.Emit(this, e); + } + catch (Exception ex) + { + _logger.Error(ex.ToString()); + error("An exception has occurred during the OnClose event.", ex); + } + } - private bool checkReceivedFrame (WebSocketFrame frame, out string message) - { - message = null; - - var masked = frame.IsMasked; - if (_client && masked) { - message = "A frame from the server is masked."; - return false; - } - - if (!_client && !masked) { - message = "A frame from a client is not masked."; - return false; - } - - if (_inContinuation && frame.IsData) { - message = "A data frame has been received while receiving continuation frames."; - return false; - } - - if (frame.IsCompressed && _compression == CompressionMethod.None) { - message = "A compressed frame has been received without any agreement for it."; - return false; - } - - if (frame.Rsv2 == Rsv.On) { - message = "The RSV2 of a frame is non-zero without any negotiation for it."; - return false; - } - - if (frame.Rsv3 == Rsv.On) { - message = "The RSV3 of a frame is non-zero without any negotiation for it."; - return false; - } - - return true; - } + private void closeAsync(CloseEventArgs e, bool send, bool receive, bool received) + { + Action closer = close; + closer.BeginInvoke(e, send, receive, received, ar => closer.EndInvoke(ar), null); + } - private void close (ushort code, string reason) - { - if (code == 1005) { // == no status - close (new CloseEventArgs (), true, true, false); - return; - } + private bool closeHandshake(byte[] frameAsBytes, bool receive, bool received) + { + var sent = frameAsBytes != null && sendBytes(frameAsBytes); + received = received || + (receive && sent && _exitReceiving != null && _exitReceiving.WaitOne(_waitTime)); - var send = !code.IsReserved (); - close (new CloseEventArgs (code, reason), send, send, false); - } + var ret = sent && received; + _logger.Debug( + String.Format("Was clean?: {0}\n sent: {1}\n received: {2}", ret, sent, received)); - private void close (CloseEventArgs e, bool send, bool receive, bool received) - { - lock (_forState) { - if (_readyState == WebSocketState.Closing) { - _logger.Info ("The closing is already in progress."); - return; + return ret; } - if (_readyState == WebSocketState.Closed) { - _logger.Info ("The connection has been closed."); - return; + // As client + private bool connect() + { + lock (_forConn) + { + string msg; + if (!checkIfAvailable(true, false, false, true, out msg)) + { + _logger.Error(msg); + error("An error has occurred in connecting.", null); + + return false; + } + + try + { + _readyState = WebSocketState.Connecting; + if (!doHandshake()) + return false; + + _readyState = WebSocketState.Open; + } + catch (Exception ex) + { + _logger.Fatal(ex.ToString()); + fatal("An exception has occurred while connecting.", ex); + + return false; + } + + return true; + } } - send = send && _readyState == WebSocketState.Open; - receive = receive && send; + // As client + private string createExtensions() + { + var buff = new StringBuilder(80); - _readyState = WebSocketState.Closing; - } + if (_compression != CompressionMethod.None) + { + var str = _compression.ToExtensionString( + "server_no_context_takeover", "client_no_context_takeover"); - _logger.Trace ("Begin closing the connection."); + buff.AppendFormat("{0}, ", str); + } - var bytes = send ? WebSocketFrame.CreateCloseFrame (e.PayloadData, _client).ToArray () : null; - e.WasClean = closeHandshake (bytes, receive, received); - releaseResources (); + var len = buff.Length; + if (len > 2) + { + buff.Length = len - 2; + return buff.ToString(); + } - _logger.Trace ("End closing the connection."); + return null; + } - _readyState = WebSocketState.Closed; - try { - OnClose.Emit (this, e); - } - catch (Exception ex) { - _logger.Error (ex.ToString ()); - error ("An exception has occurred during the OnClose event.", ex); - } - } + // As server + private HttpResponse createHandshakeFailureResponse(HttpStatusCode code) + { + var ret = HttpResponse.CreateCloseResponse(code); + ret.Headers["Sec-WebSocket-Version"] = _version; - private void closeAsync (ushort code, string reason) - { - if (code == 1005) { // == no status - closeAsync (new CloseEventArgs (), true, true, false); - return; - } + return ret; + } - var send = !code.IsReserved (); - closeAsync (new CloseEventArgs (code, reason), send, send, false); - } + // As client + private HttpRequest createHandshakeRequest() + { + var ret = HttpRequest.CreateWebSocketRequest(_uri); - private void closeAsync (CloseEventArgs e, bool send, bool receive, bool received) - { - Action closer = close; - closer.BeginInvoke (e, send, receive, received, ar => closer.EndInvoke (ar), null); - } + var headers = ret.Headers; + if (!_origin.IsNullOrEmpty()) + headers["Origin"] = _origin; - private bool closeHandshake (byte[] frameAsBytes, bool receive, bool received) - { - var sent = frameAsBytes != null && sendBytes (frameAsBytes); - received = received || - (receive && sent && _exitReceiving != null && _exitReceiving.WaitOne (_waitTime)); + headers["Sec-WebSocket-Key"] = _base64Key; - var ret = sent && received; - _logger.Debug ( - String.Format ("Was clean?: {0}\n sent: {1}\n received: {2}", ret, sent, received)); + _protocolsRequested = _protocols != null; + if (_protocolsRequested) + headers["Sec-WebSocket-Protocol"] = _protocols.ToString(", "); - return ret; - } + _extensionsRequested = _compression != CompressionMethod.None; + if (_extensionsRequested) + headers["Sec-WebSocket-Extensions"] = createExtensions(); - // As client - private bool connect () - { - lock (_forState) { - string msg; - if (!checkIfAvailable (true, false, false, true, out msg)) { - _logger.Error (msg); - error ("An error has occurred in connecting.", null); + headers["Sec-WebSocket-Version"] = _version; - return false; - } + AuthenticationResponse authRes = null; + if (_authChallenge != null && _credentials != null) + { + authRes = new AuthenticationResponse(_authChallenge, _credentials, _nonceCount); + _nonceCount = authRes.NonceCount; + } + else if (_preAuth) + { + authRes = new AuthenticationResponse(_credentials); + } - try { - _readyState = WebSocketState.Connecting; - if (!doHandshake ()) - return false; + if (authRes != null) + headers["Authorization"] = authRes.ToString(); - _readyState = WebSocketState.Open; - } - catch (Exception ex) { - _logger.Fatal (ex.ToString ()); - fatal ("An exception has occurred while connecting.", ex); + if (_cookies.Count > 0) + ret.SetCookies(_cookies); - return false; + return ret; } - return true; - } - } + // As server + private HttpResponse createHandshakeResponse() + { + var ret = HttpResponse.CreateWebSocketResponse(); - // As client - private string createExtensions () - { - var buff = new StringBuilder (80); + var headers = ret.Headers; + headers["Sec-WebSocket-Accept"] = CreateResponseKey(_base64Key); - if (_compression != CompressionMethod.None) { - var str = _compression.ToExtensionString ( - "server_no_context_takeover", "client_no_context_takeover"); + if (_protocol != null) + headers["Sec-WebSocket-Protocol"] = _protocol; - buff.AppendFormat ("{0}, ", str); - } + if (_extensions != null) + headers["Sec-WebSocket-Extensions"] = _extensions; - var len = buff.Length; - if (len > 2) { - buff.Length = len - 2; - return buff.ToString (); - } + if (_cookies.Count > 0) + ret.SetCookies(_cookies); - return null; - } + return ret; + } - // As server - private HttpResponse createHandshakeFailureResponse (HttpStatusCode code) - { - var ret = HttpResponse.CreateCloseResponse (code); - ret.Headers["Sec-WebSocket-Version"] = _version; + // As server + private bool customCheckHandshakeRequest(WebSocketContext context, out string message) + { + message = null; + return _handshakeRequestChecker == null + || (message = _handshakeRequestChecker(context)) == null; + } - return ret; - } + private MessageEventArgs dequeueFromMessageEventQueue() + { + lock (_forMessageEventQueue) + return _messageEventQueue.Count > 0 ? _messageEventQueue.Dequeue() : null; + } - // As client - private HttpRequest createHandshakeRequest () - { - var ret = HttpRequest.CreateWebSocketRequest (_uri); + // As client + private bool doHandshake() + { + SetClientStream(); + var res = sendHandshakeRequest(); - var headers = ret.Headers; - if (!_origin.IsNullOrEmpty ()) - headers["Origin"] = _origin; + string msg; + if (!checkHandshakeResponse(res, out msg)) + { + _logger.Fatal(msg); + fatal("An error has occurred while connecting.", CloseStatusCode.ProtocolError); - headers["Sec-WebSocket-Key"] = _base64Key; + return false; + } - _protocolsRequested = _protocols != null; - if (_protocolsRequested) - headers["Sec-WebSocket-Protocol"] = _protocols.ToString (", "); + if (_protocolsRequested) + _protocol = res.Headers["Sec-WebSocket-Protocol"]; - _extensionsRequested = _compression != CompressionMethod.None; - if (_extensionsRequested) - headers["Sec-WebSocket-Extensions"] = createExtensions (); + if (_extensionsRequested) + processSecWebSocketExtensionsServerHeader(res.Headers["Sec-WebSocket-Extensions"]); - headers["Sec-WebSocket-Version"] = _version; + processCookies(res.Cookies); - AuthenticationResponse authRes = null; - if (_authChallenge != null && _credentials != null) { - authRes = new AuthenticationResponse (_authChallenge, _credentials, _nonceCount); - _nonceCount = authRes.NonceCount; - } - else if (_preAuth) { - authRes = new AuthenticationResponse (_credentials); - } + return true; + } - if (authRes != null) - headers["Authorization"] = authRes.ToString (); + private void enqueueToMessageEventQueue(MessageEventArgs e) + { + lock (_forMessageEventQueue) + _messageEventQueue.Enqueue(e); + } - if (_cookies.Count > 0) - ret.SetCookies (_cookies); + private void error(string message, Exception exception) + { + try + { + OnError.Emit(this, new ErrorEventArgs(message, exception)); + } + catch (Exception ex) + { + _logger.Error(ex.ToString()); + } + } - return ret; - } + private void fatal(string message, Exception exception) + { + var code = exception is WebSocketException + ? ((WebSocketException) exception).Code + : CloseStatusCode.Abnormal; - // As server - private HttpResponse createHandshakeResponse () - { - var ret = HttpResponse.CreateWebSocketResponse (); + fatal(message, code); + } - var headers = ret.Headers; - headers["Sec-WebSocket-Accept"] = CreateResponseKey (_base64Key); + private void fatal(string message, CloseStatusCode code) + { + close(new CloseEventArgs(code, message), !code.IsReserved(), false, false); + } - if (_protocol != null) - headers["Sec-WebSocket-Protocol"] = _protocol; + private void init() + { + _compression = CompressionMethod.None; + _cookies = new CookieCollection(); + _forConn = new object(); + _forSend = new object(); + _messageEventQueue = new Queue(); + _forMessageEventQueue = ((ICollection) _messageEventQueue).SyncRoot; + _readyState = WebSocketState.Connecting; + } - if (_extensions != null) - headers["Sec-WebSocket-Extensions"] = _extensions; + private void message() + { + MessageEventArgs e = null; + lock (_forMessageEventQueue) + { + if (_inMessage || _messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) + return; - if (_cookies.Count > 0) - ret.SetCookies (_cookies); + _inMessage = true; + e = _messageEventQueue.Dequeue(); + } - return ret; - } + _message(e); + } - // As server - private bool customCheckHandshakeRequest (WebSocketContext context, out string message) - { - message = null; - return _handshakeRequestChecker == null - || (message = _handshakeRequestChecker (context)) == null; - } + private void messagec(MessageEventArgs e) + { + do + { + try + { + OnMessage.Emit(this, e); + } + catch (Exception ex) + { + _logger.Error(ex.ToString()); + error("An exception has occurred during an OnMessage event.", ex); + } + + lock (_forMessageEventQueue) + { + if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) + { + _inMessage = false; + break; + } + + e = _messageEventQueue.Dequeue(); + } + } while (true); + } - private MessageEventArgs dequeueFromMessageEventQueue () - { - lock (_forMessageEventQueue) - return _messageEventQueue.Count > 0 ? _messageEventQueue.Dequeue () : null; - } + private void messages(MessageEventArgs e) + { + try + { + OnMessage.Emit(this, e); + } + catch (Exception ex) + { + _logger.Error(ex.ToString()); + error("An exception has occurred during an OnMessage event.", ex); + } - // As client - private bool doHandshake () - { - setClientStream (); - var res = sendHandshakeRequest (); + lock (_forMessageEventQueue) + { + if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) + { + _inMessage = false; + return; + } - string msg; - if (!checkHandshakeResponse (res, out msg)) { - _logger.Fatal (msg); - fatal ("An error has occurred while connecting.", CloseStatusCode.ProtocolError); + e = _messageEventQueue.Dequeue(); + } - return false; - } + ThreadPool.QueueUserWorkItem(state => messages(e)); + } - if (_protocolsRequested) - _protocol = res.Headers["Sec-WebSocket-Protocol"]; + private void open() + { + _inMessage = true; + startReceiving(); + try + { + OnOpen.Emit(this, EventArgs.Empty); + } + catch (Exception ex) + { + _logger.Error(ex.ToString()); + error("An exception has occurred during the OnOpen event.", ex); + } - if (_extensionsRequested) - processSecWebSocketExtensionsServerHeader (res.Headers["Sec-WebSocket-Extensions"]); + MessageEventArgs e = null; + lock (_forMessageEventQueue) + { + if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) + { + _inMessage = false; + return; + } - processCookies (res.Cookies); + e = _messageEventQueue.Dequeue(); + } + _message.Invoke(e); + // _message.BeginInvoke(e, ar => _message.EndInvoke(ar), null); + } - return true; - } + private bool processCloseFrame(WebSocketFrame frame) + { + var payload = frame.PayloadData; + close(new CloseEventArgs(payload), !payload.IncludesReservedCloseStatusCode, false, true); - private void enqueueToMessageEventQueue (MessageEventArgs e) - { - lock (_forMessageEventQueue) - _messageEventQueue.Enqueue (e); - } + return false; + } - private void error (string message, Exception exception) - { - try { - OnError.Emit (this, new ErrorEventArgs (message, exception)); - } - catch (Exception ex) { - _logger.Error (ex.ToString ()); - } - } + // As client + private void processCookies(CookieCollection cookies) + { + if (cookies.Count == 0) + return; - private void fatal (string message, Exception exception) - { - var code = exception is WebSocketException - ? ((WebSocketException) exception).Code - : CloseStatusCode.Abnormal; + _cookies.SetOrRemove(cookies); + } - fatal (message, code); - } + private bool processDataFrame(WebSocketFrame frame) + { + enqueueToMessageEventQueue( + frame.IsCompressed + ? new MessageEventArgs( + frame.Opcode, frame.PayloadData.ApplicationData.Decompress(_compression)) + : new MessageEventArgs(frame)); - private void fatal (string message, CloseStatusCode code) - { - close (new CloseEventArgs (code, message), !code.IsReserved (), false, false); - } + return true; + } - private void init () - { - _compression = CompressionMethod.None; - _cookies = new CookieCollection (); - _forSend = new object (); - _forState = new object (); - _messageEventQueue = new Queue (); - _forMessageEventQueue = ((ICollection) _messageEventQueue).SyncRoot; - _readyState = WebSocketState.Connecting; - } + private bool processFragmentFrame(WebSocketFrame frame) + { + if (!_inContinuation) + { + // Must process first fragment. + if (frame.IsContinuation) + return true; + + _fragmentsOpcode = frame.Opcode; + _fragmentsCompressed = frame.IsCompressed; + _fragmentsBuffer = new MemoryStream(); + _inContinuation = true; + } - private void message () - { - MessageEventArgs e = null; - lock (_forMessageEventQueue) { - if (_inMessage || _messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) - return; + _fragmentsBuffer.WriteBytes(frame.PayloadData.ApplicationData, 1024); + if (frame.IsFinal) + { + using (_fragmentsBuffer) + { + var data = _fragmentsCompressed + ? _fragmentsBuffer.DecompressToArray(_compression) + : _fragmentsBuffer.ToArray(); - _inMessage = true; - e = _messageEventQueue.Dequeue (); - } + enqueueToMessageEventQueue(new MessageEventArgs(_fragmentsOpcode, data)); + } - _message (e); - } + _fragmentsBuffer = null; + _inContinuation = false; + } - private void messagec (MessageEventArgs e) - { - do { - try { - OnMessage.Emit (this, e); - } - catch (Exception ex) { - _logger.Error (ex.ToString ()); - error ("An exception has occurred during an OnMessage event.", ex); + return true; } - lock (_forMessageEventQueue) { - if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) { - _inMessage = false; - break; - } + private bool processPingFrame(WebSocketFrame frame) + { + if (send(new WebSocketFrame(Opcode.Pong, frame.PayloadData, _client).ToArray())) + _logger.Trace("Returned a pong."); - e = _messageEventQueue.Dequeue (); + if (_emitOnPing) + enqueueToMessageEventQueue(new MessageEventArgs(frame)); + + return true; } - } - while (true); - } - private void messages (MessageEventArgs e) - { - try { - OnMessage.Emit (this, e); - } - catch (Exception ex) { - _logger.Error (ex.ToString ()); - error ("An exception has occurred during an OnMessage event.", ex); - } + private bool processPongFrame(WebSocketFrame frame) + { + _receivePong.Set(); + _logger.Trace("Received a pong."); - lock (_forMessageEventQueue) { - if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) { - _inMessage = false; - return; + return true; } - e = _messageEventQueue.Dequeue (); - } - - ThreadPool.QueueUserWorkItem (state => messages (e)); - } - - private void open () - { - _inMessage = true; - startReceiving (); - try { - OnOpen.Emit (this, EventArgs.Empty); - } - catch (Exception ex) { - _logger.Error (ex.ToString ()); - error ("An exception has occurred during the OnOpen event.", ex); - } - - MessageEventArgs e = null; - lock (_forMessageEventQueue) { - if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) { - _inMessage = false; - return; - } - - e = _messageEventQueue.Dequeue (); - } - - _message.BeginInvoke (e, ar => _message.EndInvoke (ar), null); - } - - private bool processCloseFrame (WebSocketFrame frame) - { - var payload = frame.PayloadData; - close (new CloseEventArgs (payload), !payload.IncludesReservedCloseStatusCode, false, true); - - return false; - } - - // As client - private void processCookies (CookieCollection cookies) - { - if (cookies.Count == 0) - return; - - _cookies.SetOrRemove (cookies); - } - - private bool processDataFrame (WebSocketFrame frame) - { - enqueueToMessageEventQueue ( - frame.IsCompressed - ? new MessageEventArgs ( - frame.Opcode, frame.PayloadData.ApplicationData.Decompress (_compression)) - : new MessageEventArgs (frame)); - - return true; - } - - private bool processFragmentFrame (WebSocketFrame frame) - { - if (!_inContinuation) { - // Must process first fragment. - if (frame.IsContinuation) - return true; - - _fragmentsOpcode = frame.Opcode; - _fragmentsCompressed = frame.IsCompressed; - _fragmentsBuffer = new MemoryStream (); - _inContinuation = true; - } - - _fragmentsBuffer.WriteBytes (frame.PayloadData.ApplicationData, 1024); - if (frame.IsFinal) { - using (_fragmentsBuffer) { - var data = _fragmentsCompressed - ? _fragmentsBuffer.DecompressToArray (_compression) - : _fragmentsBuffer.ToArray (); - - enqueueToMessageEventQueue (new MessageEventArgs (_fragmentsOpcode, data)); + private bool processReceivedFrame(WebSocketFrame frame) + { + string msg; + if (!checkReceivedFrame(frame, out msg)) + throw new WebSocketException(CloseStatusCode.ProtocolError, msg); + + frame.Unmask(); + return frame.IsFragment + ? processFragmentFrame(frame) + : frame.IsData + ? processDataFrame(frame) + : frame.IsPing + ? processPingFrame(frame) + : frame.IsPong + ? processPongFrame(frame) + : frame.IsClose + ? processCloseFrame(frame) + : processUnsupportedFrame(frame); } - _fragmentsBuffer = null; - _inContinuation = false; - } - - return true; - } - - private bool processPingFrame (WebSocketFrame frame) - { - if (send (new WebSocketFrame (Opcode.Pong, frame.PayloadData, _client).ToArray ())) - _logger.Trace ("Returned a pong."); - - if (_emitOnPing) - enqueueToMessageEventQueue (new MessageEventArgs (frame)); - - return true; - } - - private bool processPongFrame (WebSocketFrame frame) - { - _receivePong.Set (); - _logger.Trace ("Received a pong."); - - return true; - } - - private bool processReceivedFrame (WebSocketFrame frame) - { - string msg; - if (!checkReceivedFrame (frame, out msg)) - throw new WebSocketException (CloseStatusCode.ProtocolError, msg); - - frame.Unmask (); - return frame.IsFragment - ? processFragmentFrame (frame) - : frame.IsData - ? processDataFrame (frame) - : frame.IsPing - ? processPingFrame (frame) - : frame.IsPong - ? processPongFrame (frame) - : frame.IsClose - ? processCloseFrame (frame) - : processUnsupportedFrame (frame); - } + // As server + private void processSecWebSocketExtensionsClientHeader(string value) + { + if (value == null) + return; - // As server - private void processSecWebSocketExtensionsClientHeader (string value) - { - if (value == null) - return; - - var buff = new StringBuilder (80); - - var comp = false; - foreach (var e in value.SplitHeaderValue (',')) { - var ext = e.Trim (); - if (!comp && ext.IsCompressionExtension (CompressionMethod.Deflate)) { - _compression = CompressionMethod.Deflate; - buff.AppendFormat ( - "{0}, ", - _compression.ToExtensionString ( - "client_no_context_takeover", "server_no_context_takeover" - ) - ); + var buff = new StringBuilder(80); + + var comp = false; + foreach (var e in value.SplitHeaderValue(',')) + { + var ext = e.Trim(); + if (!comp && ext.IsCompressionExtension(CompressionMethod.Deflate)) + { + _compression = CompressionMethod.Deflate; + buff.AppendFormat( + "{0}, ", + _compression.ToExtensionString( + "client_no_context_takeover", "server_no_context_takeover" + ) + ); + + comp = true; + } + } - comp = true; + var len = buff.Length; + if (len > 2) + { + buff.Length = len - 2; + _extensions = buff.ToString(); + } } - } - var len = buff.Length; - if (len > 2) { - buff.Length = len - 2; - _extensions = buff.ToString (); - } - } - - // As client - private void processSecWebSocketExtensionsServerHeader (string value) - { - if (value == null) { - _compression = CompressionMethod.None; - return; - } + // As client + private void processSecWebSocketExtensionsServerHeader(string value) + { + if (value == null) + { + _compression = CompressionMethod.None; + return; + } - _extensions = value; - } + _extensions = value; + } - // As server - private void processSecWebSocketProtocolHeader (IEnumerable values) - { - if (values.Contains (p => p == _protocol)) - return; + // As server + private void processSecWebSocketProtocolHeader(IEnumerable values) + { + if (values.Contains(p => p == _protocol)) + return; - _protocol = null; - } + _protocol = null; + } - private bool processUnsupportedFrame (WebSocketFrame frame) - { - _logger.Fatal ("An unsupported frame:" + frame.PrintToString (false)); - fatal ("There is no way to handle it.", CloseStatusCode.PolicyViolation); + private bool processUnsupportedFrame(WebSocketFrame frame) + { + _logger.Fatal("An unsupported frame:" + frame.PrintToString(false)); + fatal("There is no way to handle it.", CloseStatusCode.PolicyViolation); - return false; - } + return false; + } - // As client - private void releaseClientResources () - { - if (_stream != null) { - _stream.Dispose (); - _stream = null; - } - - if (_tcpClient != null) { - _tcpClient.Close (); - _tcpClient = null; - } - } + // As client + private void releaseClientResources() + { + if (_stream != null) + { + _stream.Dispose(); + _stream = null; + } - private void releaseCommonResources () - { - if (_fragmentsBuffer != null) { - _fragmentsBuffer.Dispose (); - _fragmentsBuffer = null; - _inContinuation = false; - } - - if (_receivePong != null) { - _receivePong.Close (); - _receivePong = null; - } - - if (_exitReceiving != null) { - _exitReceiving.Close (); - _exitReceiving = null; - } - } + if (_tcpClient != null) + { + _tcpClient.Dispose(); + _tcpClient = null; + } + } - private void releaseResources () - { - if (_client) - releaseClientResources (); - else - releaseServerResources (); + private void releaseCommonResources() + { + if (_fragmentsBuffer != null) + { + _fragmentsBuffer.Dispose(); + _fragmentsBuffer = null; + _inContinuation = false; + } - releaseCommonResources (); - } + if (_receivePong != null) + { + _receivePong.Dispose(); + _receivePong = null; + } - // As server - private void releaseServerResources () - { - if (_closeContext == null) - return; + if (_exitReceiving != null) + { + _exitReceiving.Dispose(); + ; + _exitReceiving = null; + } + } - _closeContext (); - _closeContext = null; - _stream = null; - _context = null; - } + private void releaseResources() + { + if (_client) + releaseClientResources(); + else + releaseServerResources(); - private bool send (byte[] frameAsBytes) - { - lock (_forState) { - if (_readyState != WebSocketState.Open) { - _logger.Error ("The sending has been interrupted."); - return false; + releaseCommonResources(); } - return sendBytes (frameAsBytes); - } - } - - private bool send (Opcode opcode, Stream stream) - { - lock (_forSend) { - var src = stream; - var compressed = false; - var sent = false; - try { - if (_compression != CompressionMethod.None) { - stream = stream.Compress (_compression); - compressed = true; - } + // As server + private void releaseServerResources() + { + if (_closeContext == null) + return; - sent = send (opcode, stream, compressed); - if (!sent) - error ("The sending has been interrupted.", null); + _closeContext(); + _closeContext = null; + _stream = null; + _context = null; } - catch (Exception ex) { - _logger.Error (ex.ToString ()); - error ("An exception has occurred while sending data.", ex); - } - finally { - if (compressed) - stream.Dispose (); - src.Dispose (); + private bool send(byte[] frameAsBytes) + { + lock (_forConn) + { + if (_readyState != WebSocketState.Open) + { + _logger.Error("The sending has been interrupted."); + return false; + } + + return sendBytes(frameAsBytes); + } } - return sent; - } - } + private bool send(Opcode opcode, Stream stream) + { + lock (_forSend) + { + var src = stream; + var compressed = false; + var sent = false; + try + { + if (_compression != CompressionMethod.None) + { + stream = stream.Compress(_compression); + compressed = true; + } + + sent = send(opcode, stream, compressed); + if (!sent) + error("The sending has been interrupted.", null); + } + catch (Exception ex) + { + _logger.Error(ex.ToString()); + error("An exception has occurred while sending data.", ex); + } + finally + { + if (compressed) + stream.Dispose(); + + src.Dispose(); + } + + return sent; + } + } - private bool send (Opcode opcode, Stream stream, bool compressed) - { - var len = stream.Length; + private bool send(Opcode opcode, Stream stream, bool compressed) + { + var len = stream.Length; - /* Not fragmented */ + /* Not fragmented */ - if (len == 0) - return send (Fin.Final, opcode, EmptyBytes, compressed); + if (len == 0) + return send(Fin.Final, opcode, EmptyBytes, compressed); - var quo = len / FragmentLength; - var rem = (int) (len % FragmentLength); + var quo = len/FragmentLength; + var rem = (int) (len%FragmentLength); - byte[] buff = null; - if (quo == 0) { - buff = new byte[rem]; - return stream.Read (buff, 0, rem) == rem && - send (Fin.Final, opcode, buff, compressed); - } + byte[] buff = null; + if (quo == 0) + { + buff = new byte[rem]; + return stream.Read(buff, 0, rem) == rem && + send(Fin.Final, opcode, buff, compressed); + } - buff = new byte[FragmentLength]; - if (quo == 1 && rem == 0) - return stream.Read (buff, 0, FragmentLength) == FragmentLength && - send (Fin.Final, opcode, buff, compressed); + buff = new byte[FragmentLength]; + if (quo == 1 && rem == 0) + return stream.Read(buff, 0, FragmentLength) == FragmentLength && + send(Fin.Final, opcode, buff, compressed); - /* Send fragmented */ + /* Send fragmented */ - // Begin - if (stream.Read (buff, 0, FragmentLength) != FragmentLength || - !send (Fin.More, opcode, buff, compressed)) - return false; + // Begin + if (stream.Read(buff, 0, FragmentLength) != FragmentLength || + !send(Fin.More, opcode, buff, compressed)) + return false; - var n = rem == 0 ? quo - 2 : quo - 1; - for (long i = 0; i < n; i++) - if (stream.Read (buff, 0, FragmentLength) != FragmentLength || - !send (Fin.More, Opcode.Cont, buff, compressed)) - return false; + var n = rem == 0 ? quo - 2 : quo - 1; + for (long i = 0; i < n; i++) + if (stream.Read(buff, 0, FragmentLength) != FragmentLength || + !send(Fin.More, Opcode.Cont, buff, compressed)) + return false; - // End - if (rem == 0) - rem = FragmentLength; - else - buff = new byte[rem]; + // End + if (rem == 0) + rem = FragmentLength; + else + buff = new byte[rem]; - return stream.Read (buff, 0, rem) == rem && send (Fin.Final, Opcode.Cont, buff, compressed); - } + return stream.Read(buff, 0, rem) == rem && send(Fin.Final, Opcode.Cont, buff, compressed); + } - private bool send (Fin fin, Opcode opcode, byte[] data, bool compressed) - { - lock (_forState) { - if (_readyState != WebSocketState.Open) { - _logger.Error ("The sending has been interrupted."); - return false; + private bool send(Fin fin, Opcode opcode, byte[] data, bool compressed) + { + lock (_forConn) + { + if (_readyState != WebSocketState.Open) + { + _logger.Error("The sending has been interrupted."); + return false; + } + + return sendBytes(new WebSocketFrame(fin, opcode, data, compressed, _client).ToArray()); + } } - return sendBytes (new WebSocketFrame (fin, opcode, data, compressed, _client).ToArray ()); - } - } + private void sendAsync(Opcode opcode, Stream stream, Action completed) + { + Func sender = send; + sender.BeginInvoke( + opcode, + stream, + ar => + { + try + { + var sent = sender.EndInvoke(ar); + if (completed != null) + completed(sent); + } + catch (Exception ex) + { + _logger.Error(ex.ToString()); + error("An exception has occurred during a send callback.", ex); + } + }, + null); + } - private void sendAsync (Opcode opcode, Stream stream, Action completed) - { - Func sender = send; - sender.BeginInvoke ( - opcode, - stream, - ar => { - try { - var sent = sender.EndInvoke (ar); - if (completed != null) - completed (sent); - } - catch (Exception ex) { - _logger.Error (ex.ToString ()); - error ("An exception has occurred during a send callback.", ex); - } - }, - null); - } + private bool sendBytes(byte[] bytes) + { + try + { + _stream.Write(bytes, 0, bytes.Length); + return true; + } + catch (Exception ex) + { + _logger.Error(ex.ToString()); + return false; + } + } - private bool sendBytes (byte[] bytes) - { - try { - _stream.Write (bytes, 0, bytes.Length); - return true; - } - catch (Exception ex) { - _logger.Error (ex.ToString ()); - return false; - } - } + // As client + private HttpResponse sendHandshakeRequest() + { + var req = createHandshakeRequest(); + var res = sendHttpRequest(req, 90000); + if (res.IsUnauthorized) + { + var chal = res.Headers["WWW-Authenticate"]; + _logger.Warn(String.Format("Received an authentication requirement for '{0}'.", chal)); + if (chal.IsNullOrEmpty()) + { + _logger.Error("No authentication challenge is specified."); + return res; + } + + _authChallenge = AuthenticationChallenge.Parse(chal); + if (_authChallenge == null) + { + _logger.Error("An invalid authentication challenge is specified."); + return res; + } + + if (_credentials != null && + (!_preAuth || _authChallenge.Scheme == AuthenticationSchemes.Digest)) + { + if (res.HasConnectionClose) + { + releaseClientResources(); + SetClientStream(); + } + + var authRes = new AuthenticationResponse(_authChallenge, _credentials, _nonceCount); + _nonceCount = authRes.NonceCount; + req.Headers["Authorization"] = authRes.ToString(); + res = sendHttpRequest(req, 15000); + } + } - // As client - private HttpResponse sendHandshakeRequest () - { - var req = createHandshakeRequest (); - var res = sendHttpRequest (req, 90000); - if (res.IsUnauthorized) { - var chal = res.Headers["WWW-Authenticate"]; - _logger.Warn (String.Format ("Received an authentication requirement for '{0}'.", chal)); - if (chal.IsNullOrEmpty ()) { - _logger.Error ("No authentication challenge is specified."); - return res; - } - - _authChallenge = AuthenticationChallenge.Parse (chal); - if (_authChallenge == null) { - _logger.Error ("An invalid authentication challenge is specified."); - return res; - } - - if (_credentials != null && - (!_preAuth || _authChallenge.Scheme == AuthenticationSchemes.Digest)) { - if (res.HasConnectionClose) { - releaseClientResources (); - setClientStream (); - } - - var authRes = new AuthenticationResponse (_authChallenge, _credentials, _nonceCount); - _nonceCount = authRes.NonceCount; - req.Headers["Authorization"] = authRes.ToString (); - res = sendHttpRequest (req, 15000); - } - } - - if (res.IsRedirect) { - var url = res.Headers["Location"]; - _logger.Warn (String.Format ("Received a redirection to '{0}'.", url)); - if (_enableRedirection) { - if (url.IsNullOrEmpty ()) { - _logger.Error ("No url to redirect is located."); - return res; - } + if (res.IsRedirect) + { + var url = res.Headers["Location"]; + _logger.Warn(String.Format("Received a redirection to '{0}'.", url)); + if (_enableRedirection) + { + if (url.IsNullOrEmpty()) + { + _logger.Error("No url to redirect is located."); + return res; + } + + Uri uri; + string msg; + if (!url.TryCreateWebSocketUri(out uri, out msg)) + { + _logger.Error("An invalid url to redirect is located: " + msg); + return res; + } + + releaseClientResources(); + + _uri = uri; + _secure = uri.Scheme == "wss"; + + SetClientStream(); + return sendHandshakeRequest(); + } + } - Uri uri; - string msg; - if (!url.TryCreateWebSocketUri (out uri, out msg)) { - _logger.Error ("An invalid url to redirect is located: " + msg); return res; - } - - releaseClientResources (); - - _uri = uri; - _secure = uri.Scheme == "wss"; - - setClientStream (); - return sendHandshakeRequest (); } - } - - return res; - } - - // As client - private HttpResponse sendHttpRequest (HttpRequest request, int millisecondsTimeout) - { - _logger.Debug ("A request to the server:\n" + request.ToString ()); - var res = request.GetResponse (_stream, millisecondsTimeout); - _logger.Debug ("A response to this request:\n" + res.ToString ()); - - return res; - } - - // As server - private bool sendHttpResponse (HttpResponse response) - { - _logger.Debug ("A response to this request:\n" + response.ToString ()); - return sendBytes (response.ToByteArray ()); - } - // As client - private void sendProxyConnectRequest () - { - var req = HttpRequest.CreateConnectRequest (_uri); - var res = sendHttpRequest (req, 90000); - if (res.IsProxyAuthenticationRequired) { - var chal = res.Headers["Proxy-Authenticate"]; - _logger.Warn ( - String.Format ("Received a proxy authentication requirement for '{0}'.", chal)); - - if (chal.IsNullOrEmpty ()) - throw new WebSocketException ("No proxy authentication challenge is specified."); - - var authChal = AuthenticationChallenge.Parse (chal); - if (authChal == null) - throw new WebSocketException ("An invalid proxy authentication challenge is specified."); - - if (_proxyCredentials != null) { - if (res.HasConnectionClose) { - releaseClientResources (); - _tcpClient = new TcpClient (_proxyUri.DnsSafeHost, _proxyUri.Port); - _stream = _tcpClient.GetStream (); - } - - var authRes = new AuthenticationResponse (authChal, _proxyCredentials, 0); - req.Headers["Proxy-Authorization"] = authRes.ToString (); - res = sendHttpRequest (req, 15000); - } - - if (res.IsProxyAuthenticationRequired) - throw new WebSocketException ("A proxy authentication is required."); - } - - if (res.StatusCode[0] != '2') - throw new WebSocketException ( - "The proxy has failed a connection to the requested host and port."); - } - - // As client - private void setClientStream () - { - if (_proxyUri != null) { - _tcpClient = new TcpClient (_proxyUri.DnsSafeHost, _proxyUri.Port); - _stream = _tcpClient.GetStream (); - sendProxyConnectRequest (); - } - else { - _tcpClient = new TcpClient (_uri.DnsSafeHost, _uri.Port); - _stream = _tcpClient.GetStream (); - } - - if (_secure) { - var conf = SslConfiguration; - var host = conf.TargetHost; - if (host != _uri.DnsSafeHost) - throw new WebSocketException ( - CloseStatusCode.TlsHandshakeFailure, "An invalid host name is specified."); - - try { - var sslStream = new SslStream ( - _stream, - false, - conf.ServerCertificateValidationCallback, - conf.ClientCertificateSelectionCallback); - - sslStream.AuthenticateAsClient ( - host, - conf.ClientCertificates, - conf.EnabledSslProtocols, - conf.CheckCertificateRevocation); - - _stream = sslStream; - } - catch (Exception ex) { - throw new WebSocketException (CloseStatusCode.TlsHandshakeFailure, ex); - } - } - } - - private void startReceiving () - { - if (_messageEventQueue.Count > 0) - _messageEventQueue.Clear (); - - _exitReceiving = new AutoResetEvent (false); - _receivePong = new AutoResetEvent (false); - - Action receive = null; - receive = - () => - WebSocketFrame.ReadFrameAsync ( - _stream, - false, - frame => { - if (!processReceivedFrame (frame) || _readyState == WebSocketState.Closed) { - var exit = _exitReceiving; - if (exit != null) - exit.Set (); - - return; - } + // As client + private HttpResponse sendHttpRequest(HttpRequest request, int millisecondsTimeout) + { + _logger.Debug("A request to the server:\n" + request.ToString()); + var res = request.GetResponse(_stream, millisecondsTimeout); + _logger.Debug("A response to this request:\n" + res.ToString()); - // Receive next asap because the Ping or Close needs a response to it. - receive (); + return res; + } - if (_inMessage || !HasMessage || _readyState != WebSocketState.Open) - return; + // As server + private bool sendHttpResponse(HttpResponse response) + { + _logger.Debug("A response to this request:\n" + response.ToString()); + return sendBytes(response.ToByteArray()); + } - message (); - }, - ex => { - _logger.Fatal (ex.ToString ()); - fatal ("An exception has occurred while receiving.", ex); + // As client + private async void sendProxyConnectRequest() + { + var req = HttpRequest.CreateConnectRequest(_uri); + var res = sendHttpRequest(req, 90000); + if (res.IsProxyAuthenticationRequired) + { + var chal = res.Headers["Proxy-Authenticate"]; + _logger.Warn( + String.Format("Received a proxy authentication requirement for '{0}'.", chal)); + + if (chal.IsNullOrEmpty()) + throw new WebSocketException("No proxy authentication challenge is specified."); + + var authChal = AuthenticationChallenge.Parse(chal); + if (authChal == null) + throw new WebSocketException("An invalid proxy authentication challenge is specified."); + + if (_proxyCredentials != null) + { + if (res.HasConnectionClose) + { + releaseClientResources(); + _tcpClient = new TcpClient(); + await _tcpClient.ConnectAsync(_proxyUri.DnsSafeHost, _proxyUri.Port); + _stream = _tcpClient.GetStream(); + } + + var authRes = new AuthenticationResponse(authChal, _proxyCredentials, 0); + req.Headers["Proxy-Authorization"] = authRes.ToString(); + res = sendHttpRequest(req, 15000); + } + + if (res.IsProxyAuthenticationRequired) + throw new WebSocketException("A proxy authentication is required."); } - ); - - receive (); - } - - // As client - private bool validateSecWebSocketAcceptHeader (string value) - { - return value != null && value == CreateResponseKey (_base64Key); - } - - // As server - private bool validateSecWebSocketExtensionsClientHeader (string value) - { - return value == null || value.Length > 0; - } - - // As client - private bool validateSecWebSocketExtensionsServerHeader (string value) - { - if (value == null) - return true; - - if (value.Length == 0) - return false; - - if (!_extensionsRequested) - return false; - var comp = _compression != CompressionMethod.None; - foreach (var e in value.SplitHeaderValue (',')) { - var ext = e.Trim (); - if (comp && ext.IsCompressionExtension (_compression)) { - if (!ext.Contains ("server_no_context_takeover")) { - _logger.Error ("The server hasn't sent back 'server_no_context_takeover'."); - return false; - } - - if (!ext.Contains ("client_no_context_takeover")) - _logger.Warn ("The server hasn't sent back 'client_no_context_takeover'."); - - var method = _compression.ToExtensionString (); - var invalid = - ext.SplitHeaderValue (';').Contains ( - t => { - t = t.Trim (); - return t != method - && t != "server_no_context_takeover" - && t != "client_no_context_takeover"; - } - ); - - if (invalid) - return false; + if (res.StatusCode[0] != '2') + throw new WebSocketException( + "The proxy has failed a connection to the requested host and port."); } - else { - return false; - } - } - - return true; - } - - // As server - private bool validateSecWebSocketKeyHeader (string value) - { - return value != null && value.Length > 0; - } - - // As server - private bool validateSecWebSocketProtocolClientHeader (string value) - { - return value == null || value.Length > 0; - } - - // As client - private bool validateSecWebSocketProtocolServerHeader (string value) - { - if (value == null) - return !_protocolsRequested; - if (value.Length == 0) - return false; - - return _protocolsRequested && _protocols.Contains (p => p == value); - } - - // As server - private bool validateSecWebSocketVersionClientHeader (string value) - { - return value != null && value == _version; - } - - // As client - private bool validateSecWebSocketVersionServerHeader (string value) - { - return value == null || value == _version; - } - - #endregion - - #region Internal Methods - - internal static bool CheckParametersForClose ( - ushort code, string reason, bool client, out string message - ) - { - message = null; - - if (!code.IsCloseStatusCode ()) { - message = "'code' is an invalid status code."; - return false; - } - - if (code == (ushort) CloseStatusCode.NoStatus && !reason.IsNullOrEmpty ()) { - message = "'code' cannot have a reason."; - return false; - } - - if (code == (ushort) CloseStatusCode.MandatoryExtension && !client) { - message = "'code' cannot be used by a server."; - return false; - } - - if (code == (ushort) CloseStatusCode.ServerError && client) { - message = "'code' cannot be used by a client."; - return false; - } - - if (!reason.IsNullOrEmpty () && reason.UTF8Encode ().Length > 123) { - message = "The size of 'reason' is greater than the allowable max size."; - return false; - } - - return true; - } - - internal static bool CheckParametersForClose ( - CloseStatusCode code, string reason, bool client, out string message - ) - { - message = null; - - if (code == CloseStatusCode.NoStatus && !reason.IsNullOrEmpty ()) { - message = "'code' cannot have a reason."; - return false; - } - - if (code == CloseStatusCode.MandatoryExtension && !client) { - message = "'code' cannot be used by a server."; - return false; - } - - if (code == CloseStatusCode.ServerError && client) { - message = "'code' cannot be used by a client."; - return false; - } - - if (!reason.IsNullOrEmpty () && reason.UTF8Encode ().Length > 123) { - message = "The size of 'reason' is greater than the allowable max size."; - return false; - } + // As client + private void SetClientStream() + { + if (_proxyUri != null) + { + _tcpClient = new TcpClient(); + _tcpClient.ConnectAsync(_proxyUri.DnsSafeHost, _proxyUri.Port).WaitForResult(); + _stream = _tcpClient.GetStream(); + sendProxyConnectRequest(); + } + else + { + _tcpClient = new TcpClient(); + _tcpClient.ConnectAsync(_uri.DnsSafeHost, _uri.Port).WaitForResult(); + _stream = _tcpClient.GetStream(); + } - return true; - } + if (_secure) + { + var conf = SslConfiguration; + var host = conf.TargetHost; + if (host != _uri.DnsSafeHost) + throw new WebSocketException( + CloseStatusCode.TlsHandshakeFailure, "An invalid host name is specified."); + + try + { + var sslStream = new SslStream( + _stream, + false, + conf.ServerCertificateValidationCallback, + conf.ClientCertificateSelectionCallback); + + sslStream.AuthenticateAsClientAsync( + host, + conf.ClientCertificates, + conf.EnabledSslProtocols, + conf.CheckCertificateRevocation).WaitForResult(); + + _stream = sslStream; + } + catch (Exception ex) + { + throw new WebSocketException(CloseStatusCode.TlsHandshakeFailure, ex); + } + } + } - internal static string CheckPingParameter (string message, out byte[] bytes) - { - bytes = message.UTF8Encode (); - return bytes.Length > 125 ? "A message has greater than the allowable max size." : null; - } + private void startReceiving() + { + if (_messageEventQueue.Count > 0) + _messageEventQueue.Clear(); + + _exitReceiving = new AutoResetEvent(false); + _receivePong = new AutoResetEvent(false); + + Action receive = null; + receive = + () => + WebSocketFrame.ReadFrameAsync( + _stream, + false, + frame => + { + if (!processReceivedFrame(frame) || _readyState == WebSocketState.Closed) + { + var exit = _exitReceiving; + if (exit != null) + exit.Set(); + + return; + } + + // Receive next asap because the Ping or Close needs a response to it. + receive(); + + if (_inMessage || !HasMessage || _readyState != WebSocketState.Open) + return; + + message(); + }, + ex => + { + _logger.Fatal(ex.ToString()); + fatal("An exception has occurred while receiving.", ex); + } + ); + + receive(); + } - internal static string CheckSendParameter (byte[] data) - { - return data == null ? "'data' is null." : null; - } + // As client + private bool validateSecWebSocketAcceptHeader(string value) + { + return value != null && value == CreateResponseKey(_base64Key); + } - internal static string CheckSendParameter (FileInfo file) - { - return file == null ? "'file' is null." : null; - } + // As server + private bool validateSecWebSocketExtensionsClientHeader(string value) + { + return value == null || value.Length > 0; + } - internal static string CheckSendParameter (string data) - { - return data == null ? "'data' is null." : null; - } + // As client + private bool validateSecWebSocketExtensionsServerHeader(string value) + { + if (value == null) + return true; + + if (value.Length == 0) + return false; + + if (!_extensionsRequested) + return false; + + var comp = _compression != CompressionMethod.None; + foreach (var e in value.SplitHeaderValue(',')) + { + var ext = e.Trim(); + if (comp && ext.IsCompressionExtension(_compression)) + { + if (!ext.Contains("server_no_context_takeover")) + { + _logger.Error("The server hasn't sent back 'server_no_context_takeover'."); + return false; + } + + if (!ext.Contains("client_no_context_takeover")) + _logger.Warn("The server hasn't sent back 'client_no_context_takeover'."); + + var method = _compression.ToExtensionString(); + var invalid = + ext.SplitHeaderValue(';').Contains( + t => + { + t = t.Trim(); + return t != method + && t != "server_no_context_takeover" + && t != "client_no_context_takeover"; + } + ); + + if (invalid) + return false; + } + else + { + return false; + } + } - internal static string CheckSendParameters (Stream stream, int length) - { - return stream == null - ? "'stream' is null." - : !stream.CanRead - ? "'stream' cannot be read." - : length < 1 - ? "'length' is less than 1." - : null; - } + return true; + } - // As server - internal void Close (HttpResponse response) - { - _readyState = WebSocketState.Closing; + // As server + private bool validateSecWebSocketKeyHeader(string value) + { + return !string.IsNullOrEmpty(value); + } - sendHttpResponse (response); - releaseServerResources (); + // As server + private bool validateSecWebSocketProtocolClientHeader(string value) + { + return value == null || value.Length > 0; + } - _readyState = WebSocketState.Closed; - } + // As client + private bool validateSecWebSocketProtocolServerHeader(string value) + { + if (value == null) + return !_protocolsRequested; - // As server - internal void Close (HttpStatusCode code) - { - Close (createHandshakeFailureResponse (code)); - } + if (value.Length == 0) + return false; - // As server - internal void Close (CloseEventArgs e, byte[] frameAsBytes, bool receive) - { - lock (_forState) { - if (_readyState == WebSocketState.Closing) { - _logger.Info ("The closing is already in progress."); - return; + return _protocolsRequested && _protocols.Contains(p => p == value); } - if (_readyState == WebSocketState.Closed) { - _logger.Info ("The connection has been closed."); - return; + // As server + private bool validateSecWebSocketVersionClientHeader(string value) + { + return value != null && value == _version; } - _readyState = WebSocketState.Closing; - } - - e.WasClean = closeHandshake (frameAsBytes, receive, false); - releaseServerResources (); - releaseCommonResources (); - - _readyState = WebSocketState.Closed; - try { - OnClose.Emit (this, e); - } - catch (Exception ex) { - _logger.Error (ex.ToString ()); - } - } + // As client + private bool validateSecWebSocketVersionServerHeader(string value) + { + return value == null || value == _version; + } - // As client - internal static string CreateBase64Key () - { - var src = new byte[16]; - RandomNumber.GetBytes (src); + #endregion + + #region Internal Methods + + internal static string CheckCloseParameters(ushort code, string reason, bool client) + { + return !code.IsCloseStatusCode() + ? "An invalid close status code." + : code == (ushort) CloseStatusCode.NoStatus + ? (!reason.IsNullOrEmpty() ? "NoStatus cannot have a reason." : null) + : code == (ushort) CloseStatusCode.MandatoryExtension && !client + ? "MandatoryExtension cannot be used by a server." + : code == (ushort) CloseStatusCode.ServerError && client + ? "ServerError cannot be used by a client." + : !reason.IsNullOrEmpty() && reason.UTF8Encode().Length > 123 + ? "A reason has greater than the allowable max size." + : null; + } - return Convert.ToBase64String (src); - } + internal static string CheckCloseParameters(CloseStatusCode code, string reason, bool client) + { + return code == CloseStatusCode.NoStatus + ? (!reason.IsNullOrEmpty() ? "NoStatus cannot have a reason." : null) + : code == CloseStatusCode.MandatoryExtension && !client + ? "MandatoryExtension cannot be used by a server." + : code == CloseStatusCode.ServerError && client + ? "ServerError cannot be used by a client." + : !reason.IsNullOrEmpty() && reason.UTF8Encode().Length > 123 + ? "A reason has greater than the allowable max size." + : null; + } - internal static string CreateResponseKey (string base64Key) - { - var buff = new StringBuilder (base64Key, 64); - buff.Append (_guid); - SHA1 sha1 = new SHA1CryptoServiceProvider (); - var src = sha1.ComputeHash (buff.ToString ().UTF8Encode ()); + internal static string CheckPingParameter(string message, out byte[] bytes) + { + bytes = message.UTF8Encode(); + return bytes.Length > 125 ? "A message has greater than the allowable max size." : null; + } - return Convert.ToBase64String (src); - } + internal static string CheckSendParameter(byte[] data) + { + return data == null ? "'data' is null." : null; + } - // As server - internal void InternalAccept () - { - try { - if (!acceptHandshake ()) - return; + internal static string CheckSendParameter(FileInfo file) + { + return file == null ? "'file' is null." : null; + } - _readyState = WebSocketState.Open; - } - catch (Exception ex) { - _logger.Fatal (ex.ToString ()); - fatal ("An exception has occurred while accepting.", ex); + internal static string CheckSendParameter(string data) + { + return data == null ? "'data' is null." : null; + } - return; - } + internal static string CheckSendParameters(Stream stream, int length) + { + return stream == null + ? "'stream' is null." + : !stream.CanRead + ? "'stream' cannot be read." + : length < 1 + ? "'length' is less than 1." + : null; + } - open (); - } + // As server + internal void Close(HttpResponse response) + { + _readyState = WebSocketState.Closing; - internal bool Ping (byte[] frameAsBytes, TimeSpan timeout) - { - if (_readyState != WebSocketState.Open) - return false; + sendHttpResponse(response); + releaseServerResources(); - if (!send (frameAsBytes)) - return false; + _readyState = WebSocketState.Closed; + } - var receivePong = _receivePong; - if (receivePong == null) - return false; + // As server + internal void Close(HttpStatusCode code) + { + Close(createHandshakeFailureResponse(code)); + } - return receivePong.WaitOne (timeout); - } + // As server + internal void Close(CloseEventArgs e, byte[] frameAsBytes, bool receive) + { + lock (_forConn) + { + if (_readyState == WebSocketState.Closing) + { + _logger.Info("The closing is already in progress."); + return; + } + + if (_readyState == WebSocketState.Closed) + { + _logger.Info("The connection has been closed."); + return; + } + + _readyState = WebSocketState.Closing; + } - // As server, used to broadcast - internal void Send (Opcode opcode, byte[] data, Dictionary cache) - { - lock (_forSend) { - lock (_forState) { - if (_readyState != WebSocketState.Open) { - _logger.Error ("The sending has been interrupted."); - return; - } - - try { - byte[] found; - if (!cache.TryGetValue (_compression, out found)) { - found = - new WebSocketFrame ( - Fin.Final, - opcode, - data.Compress (_compression), - _compression != CompressionMethod.None, - false - ) - .ToArray (); + e.WasClean = closeHandshake(frameAsBytes, receive, false); + releaseServerResources(); + releaseCommonResources(); - cache.Add (_compression, found); + _readyState = WebSocketState.Closed; + try + { + OnClose.Emit(this, e); + } + catch (Exception ex) + { + _logger.Error(ex.ToString()); } - - sendBytes (found); - } - catch (Exception ex) { - _logger.Error (ex.ToString ()); - } } - } - } - - // As server, used to broadcast - internal void Send (Opcode opcode, Stream stream, Dictionary cache) - { - lock (_forSend) { - try { - Stream found; - if (!cache.TryGetValue (_compression, out found)) { - found = stream.Compress (_compression); - cache.Add (_compression, found); - } - else { - found.Position = 0; - } - - send (opcode, found, _compression != CompressionMethod.None); - } - catch (Exception ex) { - _logger.Error (ex.ToString ()); - } - } - } - #endregion + // As client + internal static string CreateBase64Key() + { + var src = new byte[16]; + RandomNumber.GetBytes(src); - #region Public Methods + return Convert.ToBase64String(src); + } - /// - /// Accepts the WebSocket handshake request. - /// - /// - /// This method is not available in a client. - /// - public void Accept () - { - string msg; - if (!checkIfAvailable (false, true, true, false, false, false, out msg)) { - _logger.Error (msg); - error ("An error has occurred in accepting.", null); + internal static string CreateResponseKey(string base64Key) + { + var buff = new StringBuilder(base64Key, 64); + buff.Append(_guid); + using (var sha1 = SHA1.Create()) + { + var src = sha1.ComputeHash(buff.ToString().UTF8Encode()); + return Convert.ToBase64String(src); + } + } - return; - } + // As server + internal void InternalAccept() + { + try + { + if (!acceptHandshake()) + return; - if (accept ()) - open (); - } + _readyState = WebSocketState.Open; + } + catch (Exception ex) + { + _logger.Fatal(ex.ToString()); + fatal("An exception has occurred while accepting.", ex); - /// - /// Accepts the WebSocket handshake request asynchronously. - /// - /// - /// - /// This method does not wait for the accept to be complete. - /// - /// - /// This method is not available in a client. - /// - /// - public void AcceptAsync () - { - string msg; - if (!checkIfAvailable (false, true, true, false, false, false, out msg)) { - _logger.Error (msg); - error ("An error has occurred in accepting.", null); - - return; - } - - Func acceptor = accept; - acceptor.BeginInvoke ( - ar => { - if (acceptor.EndInvoke (ar)) - open (); - }, - null - ); - } + return; + } - /// - /// Closes the WebSocket connection, and releases all associated resources. - /// - public void Close () - { - string msg; - if (!checkIfAvailable (true, true, false, false, out msg)) { - _logger.Error (msg); - error ("An error has occurred in closing the connection.", null); + open(); + } - return; - } + internal bool Ping(byte[] frameAsBytes, TimeSpan timeout) + { + if (_readyState != WebSocketState.Open) + return false; - close (new CloseEventArgs (), true, true, false); - } + if (!send(frameAsBytes)) + return false; - /// - /// Closes the WebSocket connection with the specified , - /// and releases all associated resources. - /// - /// - /// A that represents the status code indicating - /// the reason for the close. The status codes are defined in - /// - /// Section 7.4 of RFC 6455. - /// - public void Close (ushort code) - { - string msg; - if (!checkIfAvailable (true, true, false, false, out msg)) { - _logger.Error (msg); - error ("An error has occurred in closing the connection.", null); + var receivePong = _receivePong; + if (receivePong == null) + return false; - return; - } + return receivePong.WaitOne(timeout); + } - if (!CheckParametersForClose (code, null, _client, out msg)) { - _logger.Error (msg); - error ("An error has occurred in closing the connection.", null); + // As server, used to broadcast + internal void Send(Opcode opcode, byte[] data, Dictionary cache) + { + lock (_forSend) + { + lock (_forConn) + { + if (_readyState != WebSocketState.Open) + { + _logger.Error("The sending has been interrupted."); + return; + } + + try + { + byte[] found; + if (!cache.TryGetValue(_compression, out found)) + { + found = + new WebSocketFrame( + Fin.Final, + opcode, + data.Compress(_compression), + _compression != CompressionMethod.None, + false + ) + .ToArray(); + + cache.Add(_compression, found); + } + + sendBytes(found); + } + catch (Exception ex) + { + _logger.Error(ex.ToString()); + } + } + } + } - return; - } + // As server, used to broadcast + internal void Send(Opcode opcode, Stream stream, Dictionary cache) + { + lock (_forSend) + { + try + { + Stream found; + if (!cache.TryGetValue(_compression, out found)) + { + found = stream.Compress(_compression); + cache.Add(_compression, found); + } + else + { + found.Position = 0; + } + + send(opcode, found, _compression != CompressionMethod.None); + } + catch (Exception ex) + { + _logger.Error(ex.ToString()); + } + } + } - close (code, null); - } + #endregion - /// - /// Closes the WebSocket connection with the specified , - /// and releases all associated resources. - /// - /// - /// One of the enum values that represents - /// the status code indicating the reason for the close. - /// - public void Close (CloseStatusCode code) - { - string msg; - if (!checkIfAvailable (true, true, false, false, out msg)) { - _logger.Error (msg); - error ("An error has occurred in closing the connection.", null); + #region Public Methods - return; - } + /// + /// Accepts the WebSocket handshake request. + /// + /// + /// This method isn't available in a client. + /// + public void Accept() + { + string msg; + if (!checkIfAvailable(false, true, true, false, false, false, out msg)) + { + _logger.Error(msg); + error("An error has occurred in accepting.", null); - if (!CheckParametersForClose (code, null, _client, out msg)) { - _logger.Error (msg); - error ("An error has occurred in closing the connection.", null); + return; + } - return; - } + if (accept()) + open(); + } - close ((ushort) code, null); - } + /// + /// Accepts the WebSocket handshake request asynchronously. + /// + /// + /// + /// This method doesn't wait for the accept to be complete. + /// + /// + /// This method isn't available in a client. + /// + /// + public void AcceptAsync() + { + string msg; + if (!checkIfAvailable(false, true, true, false, false, false, out msg)) + { + _logger.Error(msg); + error("An error has occurred in accepting.", null); - /// - /// Closes the WebSocket connection with the specified and - /// , and releases all associated resources. - /// - /// - /// A that represents the status code indicating - /// the reason for the close. The status codes are defined in - /// - /// Section 7.4 of RFC 6455. - /// - /// - /// A that represents the reason for the close. - /// The size must be 123 bytes or less. - /// - public void Close (ushort code, string reason) - { - string msg; - if (!checkIfAvailable (true, true, false, false, out msg)) { - _logger.Error (msg); - error ("An error has occurred in closing the connection.", null); + return; + } - return; - } + Func acceptor = accept; + acceptor.BeginInvoke( + ar => + { + if (acceptor.EndInvoke(ar)) + open(); + }, + null + ); + } - if (!CheckParametersForClose (code, reason, _client, out msg)) { - _logger.Error (msg); - error ("An error has occurred in closing the connection.", null); + /// + /// Closes the WebSocket connection, and releases all associated resources. + /// + public void Close() + { + string msg; + if (!checkIfAvailable(true, true, false, false, out msg)) + { + _logger.Error(msg); + error("An error has occurred in closing the connection.", null); - return; - } + return; + } - close (code, reason); - } + close(new CloseEventArgs(), true, true, false); + } - /// - /// Closes the WebSocket connection with the specified and - /// , and releases all associated resources. - /// - /// - /// One of the enum values that represents - /// the status code indicating the reason for the close. - /// - /// - /// A that represents the reason for the close. - /// The size must be 123 bytes or less. - /// - public void Close (CloseStatusCode code, string reason) - { - string msg; - if (!checkIfAvailable (true, true, false, false, out msg)) { - _logger.Error (msg); - error ("An error has occurred in closing the connection.", null); + /// + /// Closes the WebSocket connection with the specified , + /// and releases all associated resources. + /// + /// + /// This method emits a event if isn't in + /// the allowable range of the close status code. + /// + /// + /// A that represents the status code indicating the reason for the close. + /// + public void Close(ushort code) + { + var msg = _readyState.CheckIfAvailable(true, true, false, false) ?? + CheckCloseParameters(code, null, _client); + + if (msg != null) + { + _logger.Error(msg); + error("An error has occurred in closing the connection.", null); - return; - } + return; + } - if (!CheckParametersForClose (code, reason, _client, out msg)) { - _logger.Error (msg); - error ("An error has occurred in closing the connection.", null); + if (code == (ushort) CloseStatusCode.NoStatus) + { + close(new CloseEventArgs(), true, true, false); + return; + } - return; - } + var send = !code.IsReserved(); + close(new CloseEventArgs(code), send, send, false); + } - close ((ushort) code, reason); - } + /// + /// Closes the WebSocket connection with the specified , + /// and releases all associated resources. + /// + /// + /// One of the enum values, represents the status code indicating + /// the reason for the close. + /// + public void Close(CloseStatusCode code) + { + var msg = _readyState.CheckIfAvailable(true, true, false, false) ?? + CheckCloseParameters(code, null, _client); + + if (msg != null) + { + _logger.Error(msg); + error("An error has occurred in closing the connection.", null); - /// - /// Closes the WebSocket connection asynchronously, and releases - /// all associated resources. - /// - /// - /// This method does not wait for the close to be complete. - /// - public void CloseAsync () - { - string msg; - if (!checkIfAvailable (true, true, false, false, out msg)) { - _logger.Error (msg); - error ("An error has occurred in closing the connection.", null); + return; + } - return; - } + if (code == CloseStatusCode.NoStatus) + { + close(new CloseEventArgs(), true, true, false); + return; + } - closeAsync (new CloseEventArgs (), true, true, false); - } + var send = !code.IsReserved(); + close(new CloseEventArgs(code), send, send, false); + } - /// - /// Closes the WebSocket connection asynchronously with the specified - /// , and releases all associated resources. - /// - /// - /// This method does not wait for the close to be complete. - /// - /// - /// A that represents the status code indicating - /// the reason for the close. The status codes are defined in - /// - /// Section 7.4 of RFC 6455. - /// - public void CloseAsync (ushort code) - { - string msg; - if (!checkIfAvailable (true, true, false, false, out msg)) { - _logger.Error (msg); - error ("An error has occurred in closing the connection.", null); + /// + /// Closes the WebSocket connection with the specified and + /// , and releases all associated resources. + /// + /// + /// This method emits a event if isn't in + /// the allowable range of the close status code or the size of is + /// greater than 123 bytes. + /// + /// + /// A that represents the status code indicating the reason for the close. + /// + /// + /// A that represents the reason for the close. + /// + public void Close(ushort code, string reason) + { + var msg = _readyState.CheckIfAvailable(true, true, false, false) ?? + CheckCloseParameters(code, reason, _client); + + if (msg != null) + { + _logger.Error(msg); + error("An error has occurred in closing the connection.", null); - return; - } + return; + } - if (!CheckParametersForClose (code, null, _client, out msg)) { - _logger.Error (msg); - error ("An error has occurred in closing the connection.", null); + if (code == (ushort) CloseStatusCode.NoStatus) + { + close(new CloseEventArgs(), true, true, false); + return; + } - return; - } + var send = !code.IsReserved(); + close(new CloseEventArgs(code, reason), send, send, false); + } - closeAsync (code, null); - } + /// + /// Closes the WebSocket connection with the specified and + /// , and releases all associated resources. + /// + /// + /// This method emits a event if the size of is + /// greater than 123 bytes. + /// + /// + /// One of the enum values, represents the status code indicating + /// the reason for the close. + /// + /// + /// A that represents the reason for the close. + /// + public void Close(CloseStatusCode code, string reason) + { + var msg = _readyState.CheckIfAvailable(true, true, false, false) ?? + CheckCloseParameters(code, reason, _client); + + if (msg != null) + { + _logger.Error(msg); + error("An error has occurred in closing the connection.", null); - /// - /// Closes the WebSocket connection asynchronously with the specified - /// , and releases all associated resources. - /// - /// - /// This method does not wait for the close to be complete. - /// - /// - /// One of the enum values that represents - /// the status code indicating the reason for the close. - /// - public void CloseAsync (CloseStatusCode code) - { - string msg; - if (!checkIfAvailable (true, true, false, false, out msg)) { - _logger.Error (msg); - error ("An error has occurred in closing the connection.", null); + return; + } - return; - } + if (code == CloseStatusCode.NoStatus) + { + close(new CloseEventArgs(), true, true, false); + return; + } - if (!CheckParametersForClose (code, null, _client, out msg)) { - _logger.Error (msg); - error ("An error has occurred in closing the connection.", null); + var send = !code.IsReserved(); + close(new CloseEventArgs(code, reason), send, send, false); + } - return; - } + /// + /// Closes the WebSocket connection asynchronously, and releases all associated resources. + /// + /// + /// This method doesn't wait for the close to be complete. + /// + public void CloseAsync() + { + string msg; + if (!checkIfAvailable(true, true, false, false, out msg)) + { + _logger.Error(msg); + error("An error has occurred in closing the connection.", null); - if (code == CloseStatusCode.NoStatus) { - closeAsync (new CloseEventArgs (), true, true, false); - return; - } + return; + } - var send = !code.IsReserved (); - closeAsync (new CloseEventArgs (code), send, send, false); - } + closeAsync(new CloseEventArgs(), true, true, false); + } - /// - /// Closes the WebSocket connection asynchronously with the specified - /// and , and releases - /// all associated resources. - /// - /// - /// This method does not wait for the close to be complete. - /// - /// - /// A that represents the status code indicating - /// the reason for the close. The status codes are defined in - /// - /// Section 7.4 of RFC 6455. - /// - /// - /// A that represents the reason for the close. - /// The size must be 123 bytes or less. - /// - public void CloseAsync (ushort code, string reason) - { - string msg; - if (!checkIfAvailable (true, true, false, false, out msg)) { - _logger.Error (msg); - error ("An error has occurred in closing the connection.", null); + /// + /// Closes the WebSocket connection asynchronously with the specified , + /// and releases all associated resources. + /// + /// + /// + /// This method doesn't wait for the close to be complete. + /// + /// + /// This method emits a event if isn't in + /// the allowable range of the close status code. + /// + /// + /// + /// A that represents the status code indicating the reason for the close. + /// + public void CloseAsync(ushort code) + { + var msg = _readyState.CheckIfAvailable(true, true, false, false) ?? + CheckCloseParameters(code, null, _client); + + if (msg != null) + { + _logger.Error(msg); + error("An error has occurred in closing the connection.", null); - return; - } + return; + } - if (!CheckParametersForClose (code, reason, _client, out msg)) { - _logger.Error (msg); - error ("An error has occurred in closing the connection.", null); + if (code == (ushort) CloseStatusCode.NoStatus) + { + closeAsync(new CloseEventArgs(), true, true, false); + return; + } - return; - } + var send = !code.IsReserved(); + closeAsync(new CloseEventArgs(code), send, send, false); + } - closeAsync (code, reason); - } + /// + /// Closes the WebSocket connection asynchronously with the specified + /// , and releases all associated resources. + /// + /// + /// This method doesn't wait for the close to be complete. + /// + /// + /// One of the enum values, represents the status code indicating + /// the reason for the close. + /// + public void CloseAsync(CloseStatusCode code) + { + var msg = _readyState.CheckIfAvailable(true, true, false, false) ?? + CheckCloseParameters(code, null, _client); + + if (msg != null) + { + _logger.Error(msg); + error("An error has occurred in closing the connection.", null); - /// - /// Closes the WebSocket connection asynchronously with the specified - /// and , and releases - /// all associated resources. - /// - /// - /// This method does not wait for the close to be complete. - /// - /// - /// One of the enum values that represents - /// the status code indicating the reason for the close. - /// - /// - /// A that represents the reason for the close. - /// The size must be 123 bytes or less. - /// - public void CloseAsync (CloseStatusCode code, string reason) - { - string msg; - if (!checkIfAvailable (true, true, false, false, out msg)) { - _logger.Error (msg); - error ("An error has occurred in closing the connection.", null); + return; + } - return; - } + if (code == CloseStatusCode.NoStatus) + { + closeAsync(new CloseEventArgs(), true, true, false); + return; + } - if (!CheckParametersForClose (code, reason, _client, out msg)) { - _logger.Error (msg); - error ("An error has occurred in closing the connection.", null); + var send = !code.IsReserved(); + closeAsync(new CloseEventArgs(code), send, send, false); + } - return; - } + /// + /// Closes the WebSocket connection asynchronously with the specified and + /// , and releases all associated resources. + /// + /// + /// + /// This method doesn't wait for the close to be complete. + /// + /// + /// This method emits a event if isn't in + /// the allowable range of the close status code or the size of is + /// greater than 123 bytes. + /// + /// + /// + /// A that represents the status code indicating the reason for the close. + /// + /// + /// A that represents the reason for the close. + /// + public void CloseAsync(ushort code, string reason) + { + var msg = _readyState.CheckIfAvailable(true, true, false, false) ?? + CheckCloseParameters(code, reason, _client); + + if (msg != null) + { + _logger.Error(msg); + error("An error has occurred in closing the connection.", null); - if (code == CloseStatusCode.NoStatus) { - closeAsync (new CloseEventArgs (), true, true, false); - return; - } + return; + } - var send = !code.IsReserved (); - closeAsync (new CloseEventArgs (code, reason), send, send, false); - } + if (code == (ushort) CloseStatusCode.NoStatus) + { + closeAsync(new CloseEventArgs(), true, true, false); + return; + } - /// - /// Establishes a WebSocket connection. - /// - /// - /// This method is not available in a server. - /// - public void Connect () - { - string msg; - if (!checkIfAvailable (true, false, true, false, false, true, out msg)) { - _logger.Error (msg); - error ("An error has occurred in connecting.", null); + var send = !code.IsReserved(); + closeAsync(new CloseEventArgs(code, reason), send, send, false); + } - return; - } + /// + /// Closes the WebSocket connection asynchronously with the specified + /// and , and releases + /// all associated resources. + /// + /// + /// + /// This method doesn't wait for the close to be complete. + /// + /// + /// This method emits a event if the size of + /// is greater than 123 bytes. + /// + /// + /// + /// One of the enum values, represents the status code indicating + /// the reason for the close. + /// + /// + /// A that represents the reason for the close. + /// + public void CloseAsync(CloseStatusCode code, string reason) + { + var msg = _readyState.CheckIfAvailable(true, true, false, false) ?? + CheckCloseParameters(code, reason, _client); + + if (msg != null) + { + _logger.Error(msg); + error("An error has occurred in closing the connection.", null); - if (connect ()) - open (); - } + return; + } - /// - /// Establishes a WebSocket connection asynchronously. - /// - /// - /// - /// This method does not wait for the connect to be complete. - /// - /// - /// This method is not available in a server. - /// - /// - public void ConnectAsync () - { - string msg; - if (!checkIfAvailable (true, false, true, false, false, true, out msg)) { - _logger.Error (msg); - error ("An error has occurred in connecting.", null); - - return; - } - - Func connector = connect; - connector.BeginInvoke ( - ar => { - if (connector.EndInvoke (ar)) - open (); - }, - null - ); - } + if (code == CloseStatusCode.NoStatus) + { + closeAsync(new CloseEventArgs(), true, true, false); + return; + } - /// - /// Sends a ping using the WebSocket connection. - /// - /// - /// true if the receives a pong to this ping in a time; - /// otherwise, false. - /// - public bool Ping () - { - var bytes = _client - ? WebSocketFrame.CreatePingFrame (true).ToArray () - : WebSocketFrame.EmptyPingBytes; + var send = !code.IsReserved(); + closeAsync(new CloseEventArgs(code, reason), send, send, false); + } - return Ping (bytes, _waitTime); - } + /// + /// Establishes a WebSocket connection. + /// + /// + /// This method isn't available in a server. + /// + public void Connect() + { + string msg; + if (!checkIfAvailable(true, false, true, false, false, true, out msg)) + { + _logger.Error(msg); + error("An error has occurred in connecting.", null); - /// - /// Sends a ping with the specified using the WebSocket connection. - /// - /// - /// true if the receives a pong to this ping in a time; - /// otherwise, false. - /// - /// - /// A that represents the message to send. - /// - public bool Ping (string message) - { - if (message == null || message.Length == 0) - return Ping (); + return; + } - byte[] data; - var msg = CheckPingParameter (message, out data); - if (msg != null) { - _logger.Error (msg); - error ("An error has occurred in sending a ping.", null); + if (connect()) + open(); + } - return false; - } + /// + /// Establishes a WebSocket connection asynchronously. + /// + /// + /// + /// This method doesn't wait for the connect to be complete. + /// + /// + /// This method isn't available in a server. + /// + /// + public void ConnectAsync() + { + string msg; + if (!checkIfAvailable(true, false, true, false, false, true, out msg)) + { + _logger.Error(msg); + error("An error has occurred in connecting.", null); - return Ping (WebSocketFrame.CreatePingFrame (data, _client).ToArray (), _waitTime); - } + return; + } - /// - /// Sends binary using the WebSocket connection. - /// - /// - /// An array of that represents the binary data to send. - /// - public void Send (byte[] data) - { - var msg = _readyState.CheckIfAvailable (false, true, false, false) ?? - CheckSendParameter (data); + Func connector = connect; + connector.BeginInvoke( + ar => + { + if (connector.EndInvoke(ar)) + open(); + }, + null + ); + } - if (msg != null) { - _logger.Error (msg); - error ("An error has occurred in sending data.", null); + /// + /// Sends a ping using the WebSocket connection. + /// + /// + /// true if the receives a pong to this ping in a time; + /// otherwise, false. + /// + public bool Ping() + { + var bytes = _client + ? WebSocketFrame.CreatePingFrame(true).ToArray() + : WebSocketFrame.EmptyPingBytes; + + return Ping(bytes, _waitTime); + } - return; - } + /// + /// Sends a ping with the specified using the WebSocket connection. + /// + /// + /// true if the receives a pong to this ping in a time; + /// otherwise, false. + /// + /// + /// A that represents the message to send. + /// + public bool Ping(string message) + { + if (message == null || message.Length == 0) + return Ping(); + + byte[] data; + var msg = CheckPingParameter(message, out data); + if (msg != null) + { + _logger.Error(msg); + error("An error has occurred in sending a ping.", null); + + return false; + } - send (Opcode.Binary, new MemoryStream (data)); - } + return Ping(WebSocketFrame.CreatePingFrame(data, _client).ToArray(), _waitTime); + } - /// - /// Sends the specified as binary data using the WebSocket connection. - /// - /// - /// A that represents the file to send. - /// - public void Send (FileInfo file) - { - var msg = _readyState.CheckIfAvailable (false, true, false, false) ?? - CheckSendParameter (file); + /// + /// Sends binary using the WebSocket connection. + /// + /// + /// An array of that represents the binary data to send. + /// + public void Send(byte[] data) + { + var msg = _readyState.CheckIfAvailable(false, true, false, false) ?? + CheckSendParameter(data); + + if (msg != null) + { + _logger.Error(msg); + error("An error has occurred in sending data.", null); - if (msg != null) { - _logger.Error (msg); - error ("An error has occurred in sending data.", null); + return; + } - return; - } + send(Opcode.Binary, new MemoryStream(data)); + } - send (Opcode.Binary, file.OpenRead ()); - } + /// + /// Sends the specified as binary data using the WebSocket connection. + /// + /// + /// A that represents the file to send. + /// + public void Send(FileInfo file) + { + var msg = _readyState.CheckIfAvailable(false, true, false, false) ?? + CheckSendParameter(file); + + if (msg != null) + { + _logger.Error(msg); + error("An error has occurred in sending data.", null); - /// - /// Sends text using the WebSocket connection. - /// - /// - /// A that represents the text data to send. - /// - public void Send (string data) - { - var msg = _readyState.CheckIfAvailable (false, true, false, false) ?? - CheckSendParameter (data); + return; + } - if (msg != null) { - _logger.Error (msg); - error ("An error has occurred in sending data.", null); + send(Opcode.Binary, file.OpenRead()); + } - return; - } + /// + /// Sends text using the WebSocket connection. + /// + /// + /// A that represents the text data to send. + /// + public void Send(string data) + { + var msg = _readyState.CheckIfAvailable(false, true, false, false) ?? + CheckSendParameter(data); + + if (msg != null) + { + _logger.Error(msg); + error("An error has occurred in sending data.", null); - send (Opcode.Text, new MemoryStream (data.UTF8Encode ())); - } + return; + } - /// - /// Sends binary asynchronously using the WebSocket connection. - /// - /// - /// This method doesn't wait for the send to be complete. - /// - /// - /// An array of that represents the binary data to send. - /// - /// - /// An Action<bool> delegate that references the method(s) called when - /// the send is complete. A passed to this delegate is true - /// if the send is complete successfully. - /// - public void SendAsync (byte[] data, Action completed) - { - var msg = _readyState.CheckIfAvailable (false, true, false, false) ?? - CheckSendParameter (data); + send(Opcode.Text, new MemoryStream(data.UTF8Encode())); + } - if (msg != null) { - _logger.Error (msg); - error ("An error has occurred in sending data.", null); + /// + /// Sends binary asynchronously using the WebSocket connection. + /// + /// + /// This method doesn't wait for the send to be complete. + /// + /// + /// An array of that represents the binary data to send. + /// + /// + /// An Action<bool> delegate that references the method(s) called when + /// the send is complete. A passed to this delegate is true + /// if the send is complete successfully. + /// + public void SendAsync(byte[] data, Action completed) + { + var msg = _readyState.CheckIfAvailable(false, true, false, false) ?? + CheckSendParameter(data); + + if (msg != null) + { + _logger.Error(msg); + error("An error has occurred in sending data.", null); - return; - } + return; + } - sendAsync (Opcode.Binary, new MemoryStream (data), completed); - } + sendAsync(Opcode.Binary, new MemoryStream(data), completed); + } - /// - /// Sends the specified as binary data asynchronously using - /// the WebSocket connection. - /// - /// - /// This method doesn't wait for the send to be complete. - /// - /// - /// A that represents the file to send. - /// - /// - /// An Action<bool> delegate that references the method(s) called when - /// the send is complete. A passed to this delegate is true - /// if the send is complete successfully. - /// - public void SendAsync (FileInfo file, Action completed) - { - var msg = _readyState.CheckIfAvailable (false, true, false, false) ?? - CheckSendParameter (file); + /// + /// Sends the specified as binary data asynchronously using + /// the WebSocket connection. + /// + /// + /// This method doesn't wait for the send to be complete. + /// + /// + /// A that represents the file to send. + /// + /// + /// An Action<bool> delegate that references the method(s) called when + /// the send is complete. A passed to this delegate is true + /// if the send is complete successfully. + /// + public void SendAsync(FileInfo file, Action completed) + { + var msg = _readyState.CheckIfAvailable(false, true, false, false) ?? + CheckSendParameter(file); + + if (msg != null) + { + _logger.Error(msg); + error("An error has occurred in sending data.", null); - if (msg != null) { - _logger.Error (msg); - error ("An error has occurred in sending data.", null); + return; + } - return; - } + sendAsync(Opcode.Binary, file.OpenRead(), completed); + } - sendAsync (Opcode.Binary, file.OpenRead (), completed); - } + /// + /// Sends text asynchronously using the WebSocket connection. + /// + /// + /// This method doesn't wait for the send to be complete. + /// + /// + /// A that represents the text data to send. + /// + /// + /// An Action<bool> delegate that references the method(s) called when + /// the send is complete. A passed to this delegate is true + /// if the send is complete successfully. + /// + public void SendAsync(string data, Action completed) + { + var msg = _readyState.CheckIfAvailable(false, true, false, false) ?? + CheckSendParameter(data); + + if (msg != null) + { + _logger.Error(msg); + error("An error has occurred in sending data.", null); - /// - /// Sends text asynchronously using the WebSocket connection. - /// - /// - /// This method doesn't wait for the send to be complete. - /// - /// - /// A that represents the text data to send. - /// - /// - /// An Action<bool> delegate that references the method(s) called when - /// the send is complete. A passed to this delegate is true - /// if the send is complete successfully. - /// - public void SendAsync (string data, Action completed) - { - var msg = _readyState.CheckIfAvailable (false, true, false, false) ?? - CheckSendParameter (data); + return; + } - if (msg != null) { - _logger.Error (msg); - error ("An error has occurred in sending data.", null); + sendAsync(Opcode.Text, new MemoryStream(data.UTF8Encode()), completed); + } - return; - } + /// + /// Sends binary data from the specified asynchronously using + /// the WebSocket connection. + /// + /// + /// This method doesn't wait for the send to be complete. + /// + /// + /// A from which contains the binary data to send. + /// + /// + /// An that represents the number of bytes to send. + /// + /// + /// An Action<bool> delegate that references the method(s) called when + /// the send is complete. A passed to this delegate is true + /// if the send is complete successfully. + /// + public void SendAsync(Stream stream, int length, Action completed) + { + var msg = _readyState.CheckIfAvailable(false, true, false, false) ?? + CheckSendParameters(stream, length); + + if (msg != null) + { + _logger.Error(msg); + error("An error has occurred in sending data.", null); - sendAsync (Opcode.Text, new MemoryStream (data.UTF8Encode ()), completed); - } + return; + } - /// - /// Sends binary data from the specified asynchronously using - /// the WebSocket connection. - /// - /// - /// This method doesn't wait for the send to be complete. - /// - /// - /// A from which contains the binary data to send. - /// - /// - /// An that represents the number of bytes to send. - /// - /// - /// An Action<bool> delegate that references the method(s) called when - /// the send is complete. A passed to this delegate is true - /// if the send is complete successfully. - /// - public void SendAsync (Stream stream, int length, Action completed) - { - var msg = _readyState.CheckIfAvailable (false, true, false, false) ?? - CheckSendParameters (stream, length); - - if (msg != null) { - _logger.Error (msg); - error ("An error has occurred in sending data.", null); - - return; - } - - stream.ReadBytesAsync ( - length, - data => { - var len = data.Length; - if (len == 0) { - _logger.Error ("The data cannot be read from 'stream'."); - error ("An error has occurred in sending data.", null); - - return; - } - - if (len < length) - _logger.Warn ( - String.Format ( - "The length of the data is less than 'length':\n expected: {0}\n actual: {1}", + stream.ReadBytesAsync( length, - len - ) + data => + { + var len = data.Length; + if (len == 0) + { + _logger.Error("The data cannot be read from 'stream'."); + error("An error has occurred in sending data.", null); + + return; + } + + if (len < length) + _logger.Warn( + String.Format( + "The length of the data is less than 'length':\n expected: {0}\n actual: {1}", + length, + len + ) + ); + + var sent = send(Opcode.Binary, new MemoryStream(data)); + if (completed != null) + completed(sent); + }, + ex => + { + _logger.Error(ex.ToString()); + error("An exception has occurred while sending data.", ex); + } ); - - var sent = send (Opcode.Binary, new MemoryStream (data)); - if (completed != null) - completed (sent); - }, - ex => { - _logger.Error (ex.ToString ()); - error ("An exception has occurred while sending data.", ex); } - ); - } - - /// - /// Sets an HTTP to send with - /// the WebSocket handshake request to the server. - /// - /// - /// This method is not available in a server. - /// - /// - /// A that represents a cookie to send. - /// - public void SetCookie (Cookie cookie) - { - string msg; - if (!checkIfAvailable (true, false, true, false, false, true, out msg)) { - _logger.Error (msg); - error ("An error has occurred in setting a cookie.", null); - - return; - } - if (cookie == null) { - _logger.Error ("'cookie' is null."); - error ("An error has occurred in setting a cookie.", null); + /// + /// Sets an HTTP to send with + /// the WebSocket handshake request to the server. + /// + /// + /// A that represents the cookie to send. + /// + public void SetCookie(Cookie cookie) + { + string msg; + if (!checkIfAvailable(true, false, true, false, false, true, out msg)) + { + _logger.Error(msg); + error("An error has occurred in setting a cookie.", null); - return; - } - - lock (_forState) { - if (!checkIfAvailable (true, false, false, true, out msg)) { - _logger.Error (msg); - error ("An error has occurred in setting a cookie.", null); - - return; - } - - lock (_cookies.SyncRoot) - _cookies.SetOrRemove (cookie); - } - } - - /// - /// Sets a pair of and for - /// the HTTP authentication (Basic/Digest). - /// - /// - /// This method is not available in a server. - /// - /// - /// - /// A that represents the user name used to authenticate. - /// - /// - /// If is or empty, - /// the credentials will be initialized and not be sent. - /// - /// - /// - /// A that represents the password for - /// used to authenticate. - /// - /// - /// true if the sends the credentials for - /// the Basic authentication with the first handshake request to the server; - /// otherwise, false. - /// - public void SetCredentials (string username, string password, bool preAuth) - { - string msg; - if (!checkIfAvailable (true, false, true, false, false, true, out msg)) { - _logger.Error (msg); - error ("An error has occurred in setting the credentials.", null); + return; + } - return; - } + lock (_forConn) + { + if (!checkIfAvailable(true, false, false, true, out msg)) + { + _logger.Error(msg); + error("An error has occurred in setting a cookie.", null); - if (!checkParametersForSetCredentials (username, password, out msg)) { - _logger.Error (msg); - error ("An error has occurred in setting the credentials.", null); + return; + } - return; - } + if (cookie == null) + { + _logger.Error("'cookie' is null."); + error("An error has occurred in setting a cookie.", null); - lock (_forState) { - if (!checkIfAvailable (true, false, false, true, out msg)) { - _logger.Error (msg); - error ("An error has occurred in setting the credentials.", null); + return; + } - return; + lock (_cookies.SyncRoot) + _cookies.SetOrRemove(cookie); + } } - if (username.IsNullOrEmpty ()) { - _logger.Warn ("The credentials are initialized."); - _credentials = null; - _preAuth = false; + /// + /// Sets a pair of and for + /// the HTTP authentication (Basic/Digest). + /// + /// + /// A that represents the user name used to authenticate. + /// + /// + /// A that represents the password for + /// used to authenticate. + /// + /// + /// true if the sends the Basic authentication credentials with + /// the first handshake request to the server; otherwise, false. + /// + public void SetCredentials(string username, string password, bool preAuth) + { + string msg; + if (!checkIfAvailable(true, false, true, false, false, true, out msg)) + { + _logger.Error(msg); + error("An error has occurred in setting the credentials.", null); - return; - } + return; + } - _credentials = new NetworkCredential (username, password, _uri.PathAndQuery); - _preAuth = preAuth; - } - } + lock (_forConn) + { + if (!checkIfAvailable(true, false, false, true, out msg)) + { + _logger.Error(msg); + error("An error has occurred in setting the credentials.", null); - /// - /// Sets the HTTP proxy server URL to connect through, and if necessary, - /// a pair of and for - /// the proxy server authentication (Basic/Digest). - /// - /// - /// This method is not available in a server. - /// - /// - /// - /// A that represents the HTTP proxy server URL to - /// connect through. The syntax must be http://<host>[:<port>]. - /// - /// - /// If is or empty, - /// the url and credentials for the proxy will be initialized, - /// and the will not use the proxy to - /// connect through. - /// - /// - /// - /// - /// A that represents the user name used to authenticate. - /// - /// - /// If is or empty, - /// the credentials for the proxy will be initialized and not be sent. - /// - /// - /// - /// A that represents the password for - /// used to authenticate. - /// - public void SetProxy (string url, string username, string password) - { - string msg; - if (!checkIfAvailable (true, false, true, false, false, true, out msg)) { - _logger.Error (msg); - error ("An error has occurred in setting the proxy.", null); + return; + } - return; - } + if (username.IsNullOrEmpty()) + { + _logger.Warn("The credentials are set back to the default."); + _credentials = null; + _preAuth = false; - if (!checkParametersForSetProxy (url, username, password, out msg)) { - _logger.Error (msg); - error ("An error has occurred in setting the proxy.", null); + return; + } - return; - } + if (username.Contains(':') || !username.IsText()) + { + _logger.Error("'username' contains an invalid character."); + error("An error has occurred in setting the credentials.", null); - lock (_forState) { - if (!checkIfAvailable (true, false, false, true, out msg)) { - _logger.Error (msg); - error ("An error has occurred in setting the proxy.", null); + return; + } - return; - } + if (!password.IsNullOrEmpty() && !password.IsText()) + { + _logger.Error("'password' contains an invalid character."); + error("An error has occurred in setting the credentials.", null); - if (url.IsNullOrEmpty ()) { - _logger.Warn ("The url and credentials for the proxy are initialized."); - _proxyUri = null; - _proxyCredentials = null; + return; + } - return; + _credentials = new NetworkCredential(username, password, _uri.PathAndQuery); + _preAuth = preAuth; + } } - _proxyUri = new Uri (url); + /// + /// Sets an HTTP proxy server URL to connect through, and if necessary, + /// a pair of and for + /// the proxy server authentication (Basic/Digest). + /// + /// + /// A that represents the proxy server URL to connect through. + /// + /// + /// A that represents the user name used to authenticate. + /// + /// + /// A that represents the password for + /// used to authenticate. + /// + public void SetProxy(string url, string username, string password) + { + string msg; + if (!checkIfAvailable(true, false, true, false, false, true, out msg)) + { + _logger.Error(msg); + error("An error has occurred in setting the proxy.", null); - if (username.IsNullOrEmpty ()) { - _logger.Warn ("The credentials for the proxy are initialized."); - _proxyCredentials = null; + return; + } - return; + lock (_forConn) + { + if (!checkIfAvailable(true, false, false, true, out msg)) + { + _logger.Error(msg); + error("An error has occurred in setting the proxy.", null); + + return; + } + + if (url.IsNullOrEmpty()) + { + _logger.Warn("The proxy url and credentials are set back to the default."); + _proxyUri = null; + _proxyCredentials = null; + + return; + } + + Uri uri; + if (!Uri.TryCreate(url, UriKind.Absolute, out uri) + || uri.Scheme != "http" + || uri.Segments.Length > 1 + ) + { + _logger.Error("The syntax of a proxy url must be 'http://[:]'."); + error("An error has occurred in setting the proxy.", null); + + return; + } + + if (username.IsNullOrEmpty()) + { + _logger.Warn("The proxy credentials are set back to the default."); + _proxyUri = uri; + _proxyCredentials = null; + + return; + } + + if (username.Contains(':') || !username.IsText()) + { + _logger.Error("'username' contains an invalid character."); + error("An error has occurred in setting the proxy.", null); + + return; + } + + if (!password.IsNullOrEmpty() && !password.IsText()) + { + _logger.Error("'password' contains an invalid character."); + error("An error has occurred in setting the proxy.", null); + + return; + } + + _proxyUri = uri; + _proxyCredentials = + new NetworkCredential( + username, password, String.Format("{0}:{1}", _uri.DnsSafeHost, _uri.Port) + ); + } } - _proxyCredentials = - new NetworkCredential ( - username, password, String.Format ("{0}:{1}", _uri.DnsSafeHost, _uri.Port) - ); - } - } + #endregion - #endregion + #region Explicit Interface Implementations - #region Explicit Interface Implementations + /// + /// Closes the WebSocket connection, and releases all associated resources. + /// + /// + /// This method closes the connection with . + /// + void IDisposable.Dispose() + { + close(new CloseEventArgs(CloseStatusCode.Away), true, true, false); + } - /// - /// Closes the WebSocket connection, and releases all associated resources. - /// - /// - /// This method closes the connection with status code 1001 (going away). - /// - void IDisposable.Dispose () - { - close (new CloseEventArgs (1001), true, true, false); + #endregion } - - #endregion - } } diff --git a/websocket-sharp/WebSocketFrame.cs b/websocket-sharp/WebSocketFrame.cs index 219242c1d..07fb82717 100644 --- a/websocket-sharp/WebSocketFrame.cs +++ b/websocket-sharp/WebSocketFrame.cs @@ -588,7 +588,7 @@ private static WebSocketFrame readPayloadData (Stream stream, WebSocketFrame fra ? stream.ReadBytes ((int) len) : stream.ReadBytes (llen, 1024); - if (bytes.LongLength != llen) + if (bytes.Length != llen) throw new WebSocketException ( "The payload data of a frame cannot be read from the stream."); @@ -615,7 +615,7 @@ private static void readPayloadDataAsync ( var llen = (long) len; Action compl = bytes => { - if (bytes.LongLength != llen) + if (bytes.Length != llen) throw new WebSocketException ( "The payload data of a frame cannot be read from the stream."); @@ -748,7 +748,7 @@ public byte[] ToArray () buff.WriteBytes (bytes, 1024); } - buff.Close (); + buff.Dispose(); return buff.ToArray (); } } diff --git a/websocket-sharp/project.json b/websocket-sharp/project.json new file mode 100644 index 000000000..b8666194f --- /dev/null +++ b/websocket-sharp/project.json @@ -0,0 +1,28 @@ +{ + "name":"websocket-sharp", + "version": "1.0.0-core", + "dependencies": { + "NETStandard.Library": "1.6.0", + "System.Security.Principal": "4.0.1", + "System.Security.Claims": "4.0.1", + "System.Security.Cryptography.Csp": "4.0.0", + "System.Collections.Specialized": "4.0.1", + "System.Threading.Thread": "4.0.0", + "System.Threading.Timer": "4.0.1", + "System.Threading": "4.0.11", + "System.Threading.ThreadPool": "4.0.10", + "System.Runtime.Serialization.Primitives": "4.1.1", + "System.Runtime.Serialization.Formatters": "4.0.0-rc3-24212-01", + "System.Diagnostics.StackTrace": "4.0.2", + "System.Net.NameResolution": "4.0.0", + "System.Net.Security": "4.0.0", + "System.Runtime": "4.1.0", + "System.Net.Sockets": "4.1.0", + "System.Net.Requests": "4.0.11" + }, + "frameworks": { + "netcoreapp1.0": { + "imports": "dnxcore50" + } + } +} \ No newline at end of file