#region License /* * WebSocket.cs * * A C# implementation of the WebSocket interface. * This code derived from WebSocket.java (http://github.com/adamac/Java-WebSocket-client). * * The MIT License * * Copyright (c) 2009 Adam MacBeth * Copyright (c) 2010-2013 sta.blockhead * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #endregion using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Sockets; using System.Net.Security; using System.Security.Cryptography; using System.Text; using System.Threading; 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 Const Fields private const int _fragmentLen = 1016; // Max value is int.MaxValue - 14. private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; private const string _version = "13"; #endregion #region Private Fields private string _base64key; private RemoteCertificateValidationCallback _certValidationCallback; private bool _client; private Action _closeContext; private CompressionMethod _compression; private WebSocketContext _context; private CookieCollection _cookies; private Func _cookiesValidation; private WsCredential _credentials; private string _extensions; private AutoResetEvent _exitReceiving; private object _forClose; private object _forFrame; private object _forSend; private volatile Logger _logger; private string _origin; private bool _preAuth; private string _protocol; private string _protocols; private volatile WsState _readyState; private AutoResetEvent _receivePong; private bool _secure; private WsStream _stream; private TcpClient _tcpClient; private Uri _uri; #endregion #region Private Constructors private WebSocket () { _compression = CompressionMethod.NONE; _cookies = new CookieCollection (); _extensions = String.Empty; _forClose = new object (); _forFrame = new object (); _forSend = new object (); _logger = new Logger (); _origin = String.Empty; _preAuth = false; _protocol = String.Empty; _readyState = WsState.CONNECTING; } #endregion #region Internal Constructors internal WebSocket (HttpListenerWebSocketContext context) : this () { _stream = context.Stream; _closeContext = () => context.Close (); init (context); } internal WebSocket (TcpListenerWebSocketContext context) : this () { _stream = context.Stream; _closeContext = () => context.Close (); init (context); } #endregion #region Public Constructors /// /// Initializes a new instance of the class with the specified WebSocket URL /// and subprotocols. /// /// /// A that contains a WebSocket URL to connect. /// /// /// An array of that contains the WebSocket subprotocols if any. /// /// /// is . /// /// /// is not valid WebSocket URL. /// public WebSocket (string url, params string[] protocols) : this () { if (url == null) throw new ArgumentNullException ("url"); Uri uri; string msg; if (!url.TryCreateWebSocketUri (out uri, out msg)) throw new ArgumentException (msg, "url"); _uri = uri; _protocols = protocols.ToString (", "); _client = true; _secure = uri.Scheme == "wss" ? true : false; _base64key = createBase64Key (); } /// /// Initializes a new instance of the class with the specified WebSocket URL, /// OnOpen, OnMessage, OnError, OnClose event handlers and subprotocols. /// /// /// This constructor initializes a new instance of the class and /// establishes a WebSocket connection. /// /// /// A that contains a WebSocket URL to connect. /// /// /// An event handler. /// /// /// An event handler. /// /// /// An event handler. /// /// /// An event handler. /// /// /// An array of that contains the WebSocket subprotocols if any. /// /// /// is . /// /// /// is not valid WebSocket URL. /// public WebSocket ( string url, EventHandler onOpen, EventHandler onMessage, EventHandler onError, EventHandler onClose, params string [] protocols) : this (url, protocols) { OnOpen = onOpen; OnMessage = onMessage; OnError = onError; OnClose = onClose; Connect (); } #endregion #region Internal Properties internal Func CookiesValidation { get { return _cookiesValidation; } set { _cookiesValidation = value; } } internal bool IsOpened { get { return _readyState == WsState.OPEN || _readyState == WsState.CLOSING; } } #endregion #region Public Properties /// /// Gets or sets the compression method used to compress the payload data of the WebSocket Data frame. /// /// /// One of the values that indicates the compression method to use. /// The default is . /// public CompressionMethod Compression { get { return _compression; } set { if (IsOpened) { var msg = "The WebSocket connection has already been established."; _logger.Error (msg); error (msg); return; } _compression = value; } } /// /// Gets the cookies used in the WebSocket opening handshake. /// /// /// An IEnumerable<Cookie> interface that provides an enumerator which supports the iteration /// over the collection of cookies. /// public IEnumerable Cookies { get { lock (_cookies.SyncRoot) { return from Cookie cookie in _cookies select cookie; } } } /// /// Gets the credentials for HTTP authentication (Basic/Digest). /// /// /// A that contains the credentials for HTTP authentication. /// public WsCredential Credentials { get { return _credentials; } } /// /// Gets the WebSocket extensions selected by the server. /// /// /// A that contains the extensions if any. The default is . /// public string Extensions { get { return _extensions; } } /// /// Gets a value indicating whether the WebSocket connection is alive. /// /// /// true if the WebSocket connection is alive; otherwise, false. /// public bool IsAlive { get { return _readyState == WsState.OPEN ? ping(new byte [] {}) : false; } } /// /// Gets a value indicating whether the WebSocket connection is secure. /// /// /// true if the connection is secure; otherwise, false. /// public bool IsSecure { get { return _secure; } } /// /// Gets the logging functions. /// /// /// The default logging level is the . /// If you want to change the current logging level, you set the Log.Level property /// to one of the values which you want. /// /// /// A that provides the logging functions. /// public Logger Log { get { return _logger; } internal set { if (value == null) return; _logger = value; } } /// /// Gets or sets the value of the Origin header used in the WebSocket opening handshake. /// /// /// A instance does not send the Origin header in the WebSocket opening handshake /// if the value of this property is . /// /// /// /// A that contains the value of the HTTP Origin header to send. /// The default is . /// /// /// The value of the Origin header has the following syntax: <scheme>://<host>[:<port>] /// /// public string Origin { get { return _origin; } set { string msg = null; if (IsOpened) { msg = "The WebSocket connection has already been established."; } else if (value.IsNullOrEmpty ()) { _origin = String.Empty; return; } else { var origin = new Uri (value); if (!origin.IsAbsoluteUri || origin.Segments.Length > 1) msg = "The syntax of value of Origin must be '://[:]'."; } if (msg != null) { _logger.Error (msg); error (msg); return; } _origin = value.TrimEnd ('/'); } } /// /// Gets the WebSocket subprotocol selected by the server. /// /// /// A that contains the subprotocol if any. The default is . /// public string Protocol { get { return _protocol; } } /// /// Gets the state of the WebSocket connection. /// /// /// One of the values. The default is . /// public WsState ReadyState { get { return _readyState; } } /// /// Gets or sets the callback used to validate the certificate supplied by the server. /// /// /// If the value of this property is , the validation does nothing /// with the server certificate, always returns valid. /// /// /// A delegate that references the method(s) /// used to validate the server certificate. The default is . /// public RemoteCertificateValidationCallback ServerCertificateValidationCallback { get { return _certValidationCallback; } set { _certValidationCallback = value; } } /// /// Gets the WebSocket URL to connect. /// /// /// A that contains the WebSocket URL to connect. /// public Uri Url { get { return _uri; } internal set { if (_readyState == WsState.CONNECTING && !_client) _uri = value; } } #endregion #region Public Events /// /// Occurs when the WebSocket connection has been closed. /// public event EventHandler OnClose; /// /// Occurs when the gets an error. /// public event EventHandler OnError; /// /// Occurs when the receives a data frame. /// public event EventHandler OnMessage; /// /// Occurs when the WebSocket connection has been established. /// public event EventHandler OnOpen; #endregion #region Private Methods // As server private bool acceptHandshake () { _logger.Debug (String.Format ("A WebSocket connection request from {0}:\n{1}", _context.UserEndPoint, _context)); if (!validateConnectionRequest (_context)) { var msg = "Invalid WebSocket connection request."; _logger.Error (msg); error (msg); Close (HttpStatusCode.BadRequest); return false; } _base64key = _context.SecWebSocketKey; if (_protocol.Length > 0 && !_context.Headers.Contains ("Sec-WebSocket-Protocol", _protocol)) _protocol = String.Empty; var extensions = _context.Headers ["Sec-WebSocket-Extensions"]; if (extensions != null && extensions.Length > 0) processRequestedExtensions (extensions); return send (createHandshakeResponse ()); } private void close (CloseEventArgs eventArgs) { if (!Thread.CurrentThread.IsBackground && _exitReceiving != null) if (!_exitReceiving.WaitOne (5 * 1000)) eventArgs.WasClean = false; if (!closeResources ()) eventArgs.WasClean = false; _readyState = WsState.CLOSED; OnClose.Emit (this, eventArgs); } private void close (PayloadData data) { _logger.Debug ("Is this thread background?: " + Thread.CurrentThread.IsBackground); CloseEventArgs args = null; lock (_forClose) { if (_readyState == WsState.CLOSING || _readyState == WsState.CLOSED) return; var state = _readyState; _readyState = WsState.CLOSING; args = new CloseEventArgs (data); if (state == WsState.CONNECTING) { if (!_client) { close (HttpStatusCode.BadRequest); return; } } else { if (!data.ContainsReservedCloseStatusCode) args.WasClean = send (createControlFrame (Opcode.CLOSE, data, _client)); } } close (args); _logger.Trace ("Exit close method."); } // As server private void close (HttpStatusCode code) { send (createHandshakeResponse (code)); closeResources (); _readyState = WsState.CLOSED; } private void close (ushort code, string reason) { var data = code.Append (reason); if (data.Length > 125) { var msg = "The payload length of a Close frame must be 125 bytes or less."; _logger.Error (String.Format ("{0}\ncode: {1}\nreason: {2}", msg, code, reason)); error (msg); return; } close (new PayloadData (data)); } // As client private void closeClientResources () { if (_stream != null) { _stream.Dispose (); _stream = null; } if (_tcpClient != null) { _tcpClient.Close (); _tcpClient = null; } } private bool closeResources () { try { if (_client) closeClientResources (); else closeServerResources (); return true; } catch (Exception ex) { _logger.Fatal (ex.Message); error ("An exception has occured."); return false; } } // As server private void closeServerResources () { if (_context != null && _closeContext != null) { _closeContext (); _stream = null; _context = null; } } private bool concatenateFragments (Stream dest) { Func processContinuation = contFrame => { if (!contFrame.IsContinuation) return false; dest.WriteBytes (contFrame.PayloadData.ApplicationData); return true; }; while (true) { var frame = _stream.ReadFrame (); if (processAbnormal (frame)) return false; if (!frame.IsFinal) { // MORE & CONT if (processContinuation (frame)) continue; } else { // FINAL & CONT if (processContinuation (frame)) break; // FINAL & PING if (processPing (frame)) continue; // FINAL & PONG if (processPong (frame)) continue; // FINAL & CLOSE if (processClose (frame)) return false; } // ? processIncorrectFrame (); return false; } return true; } private bool connect () { return _client ? doHandshake () : acceptHandshake (); } // As client private static string createBase64Key () { var src = new byte [16]; var rand = new Random (); rand.NextBytes (src); return Convert.ToBase64String (src); } private static WsFrame createControlFrame (Opcode opcode, PayloadData payloadData, bool client) { var mask = client ? Mask.MASK : Mask.UNMASK; var frame = new WsFrame (Fin.FINAL, opcode, mask, payloadData); return frame; } private static WsFrame createFrame ( Fin fin, Opcode opcode, PayloadData payloadData, bool compressed, bool client) { var mask = client ? Mask.MASK : Mask.UNMASK; var frame = new WsFrame (fin, opcode, mask, payloadData, compressed); return frame; } // As client private HandshakeRequest createHandshakeRequest () { var path = _uri.PathAndQuery; var host = _uri.Port == 80 ? _uri.DnsSafeHost : _uri.Authority; var req = new HandshakeRequest (path); req.AddHeader ("Host", host); if (_origin.Length > 0) req.AddHeader ("Origin", _origin); req.AddHeader ("Sec-WebSocket-Key", _base64key); if (!_protocols.IsNullOrEmpty ()) req.AddHeader ("Sec-WebSocket-Protocol", _protocols); var extensions = createRequestExtensions (); if (extensions.Length > 0) req.AddHeader ("Sec-WebSocket-Extensions", extensions); req.AddHeader ("Sec-WebSocket-Version", _version); if (_preAuth && _credentials != null) req.SetAuthorization (new AuthenticationResponse (_credentials)); if (_cookies.Count > 0) req.SetCookies (_cookies); return req; } // As server private HandshakeResponse createHandshakeResponse () { var res = new HandshakeResponse (); res.AddHeader ("Sec-WebSocket-Accept", createResponseKey ()); if (_protocol.Length > 0) res.AddHeader ("Sec-WebSocket-Protocol", _protocol); if (_extensions.Length > 0) res.AddHeader ("Sec-WebSocket-Extensions", _extensions); if (_cookies.Count > 0) res.SetCookies (_cookies); return res; } // As server private HandshakeResponse createHandshakeResponse (HttpStatusCode code) { var res = HandshakeResponse.CreateCloseResponse (code); res.AddHeader ("Sec-WebSocket-Version", _version); return res; } // As client private string createRequestExtensions () { var extensions = new StringBuilder (64); if (_compression != CompressionMethod.NONE) extensions.Append (_compression.ToCompressionExtension ()); return extensions.Length > 0 ? extensions.ToString () : String.Empty; } private string createResponseKey () { var buffer = new StringBuilder (_base64key, 64); buffer.Append (_guid); SHA1 sha1 = new SHA1CryptoServiceProvider (); var src = sha1.ComputeHash (Encoding.UTF8.GetBytes (buffer.ToString ())); return Convert.ToBase64String (src); } // As client private bool doHandshake () { setClientStream (); var res = sendHandshakeRequest (); var msg = res.IsUnauthorized ? String.Format ("An HTTP {0} authorization is required.", res.AuthChallenge.Scheme) : !validateConnectionResponse (res) ? "Invalid response to this WebSocket connection request." : String.Empty; if (msg.Length > 0) { _logger.Error (msg); error (msg); Close (CloseStatusCode.ABNORMAL, msg); return false; } var protocol = res.Headers ["Sec-WebSocket-Protocol"]; if (protocol != null && protocol.Length > 0) _protocol = protocol; processRespondedExtensions (res.Headers ["Sec-WebSocket-Extensions"]); var cookies = res.Cookies; if (cookies.Count > 0) _cookies.SetOrRemove (cookies); return true; } private void error (string message) { OnError.Emit (this, new ErrorEventArgs (message)); } // As server private void init (WebSocketContext context) { _context = context; _uri = context.Path.ToUri (); _secure = context.IsSecureConnection; _client = false; } private void open () { _readyState = WsState.OPEN; startReceiving (); OnOpen.Emit (this, EventArgs.Empty); } private bool ping (byte [] data) { var frame = createControlFrame (Opcode.PING, new PayloadData (data), _client); var timeOut = _client ? 5000 : 1000; return send (frame) ? _receivePong.WaitOne (timeOut) : false; } private void pong (PayloadData data) { var frame = createControlFrame (Opcode.PONG, data, _client); send (frame); } private bool processAbnormal (WsFrame frame) { if (frame != null) return false; _logger.Trace ("Start closing handshake."); var code = CloseStatusCode.ABNORMAL; Close (code, code.GetMessage ()); return true; } private bool processClose (WsFrame frame) { if (!frame.IsClose) return false; _logger.Trace ("Start closing handshake."); close (frame.PayloadData); return true; } private bool processData (WsFrame frame) { if (!frame.IsData) return false; if (frame.IsCompressed && _compression == CompressionMethod.NONE) return false; var args = frame.IsCompressed ? new MessageEventArgs ( frame.Opcode, frame.PayloadData.ApplicationData.Decompress (_compression)) : new MessageEventArgs (frame.Opcode, frame.PayloadData); OnMessage.Emit (this, args); return true; } private bool processFragmented (WsFrame frame) { // Not first fragment if (frame.IsContinuation) return true; // Not fragmented if (frame.IsFinal) return false; bool incorrect = !frame.IsData || (frame.IsCompressed && _compression == CompressionMethod.NONE); if (!incorrect) processFragments (frame); else processIncorrectFrame (); return true; } private void processFragments (WsFrame first) { using (var concatenated = new MemoryStream ()) { concatenated.WriteBytes (first.PayloadData.ApplicationData); if (!concatenateFragments (concatenated)) return; byte [] data; if (_compression != CompressionMethod.NONE) { data = concatenated.DecompressToArray (_compression); } else { concatenated.Close (); data = concatenated.ToArray (); } OnMessage.Emit (this, new MessageEventArgs (first.Opcode, data)); } } private void processFrame (WsFrame frame) { bool processed = processAbnormal (frame) || processFragmented (frame) || processData (frame) || processPing (frame) || processPong (frame) || processClose (frame); if (!processed) processIncorrectFrame (); } private void processIncorrectFrame () { _logger.Trace ("Start closing handshake."); Close (CloseStatusCode.INCORRECT_DATA); } private bool processPing (WsFrame frame) { if (!frame.IsPing) return false; _logger.Trace ("Return Pong."); pong (frame.PayloadData); return true; } private bool processPong (WsFrame frame) { if (!frame.IsPong) return false; _logger.Trace ("Receive Pong."); _receivePong.Set (); return true; } // As server private void processRequestedExtensions (string extensions) { var comp = false; var buffer = new List (); foreach (var e in extensions.SplitHeaderValue (',')) { var extension = e.Trim (); var tmp = extension.RemovePrefix ("x-webkit-"); if (!comp && tmp.IsCompressionExtension ()) { var method = tmp.ToCompressionMethod (); if (method != CompressionMethod.NONE) { _compression = method; comp = true; buffer.Add (extension); } } } if (buffer.Count > 0) _extensions = buffer.ToArray ().ToString (", "); } // As client private void processRespondedExtensions (string extensions) { var comp = _compression != CompressionMethod.NONE ? true : false; var hasComp = false; if (extensions != null && extensions.Length > 0) { foreach (var e in extensions.SplitHeaderValue (',')) { var extension = e.Trim (); if (comp && !hasComp && extension.Equals (_compression)) hasComp = true; } _extensions = extensions; } if (comp && !hasComp) _compression = CompressionMethod.NONE; } // As client private HandshakeResponse receiveHandshakeResponse () { var res = HandshakeResponse.Parse (_stream.ReadHandshake ()); _logger.Debug ("A response to this WebSocket connection request:\n" + res.ToString ()); return res; } // As client private void send (HandshakeRequest request) { _logger.Debug (String.Format ("A WebSocket connection request to {0}:\n{1}", _uri, request)); _stream.WriteHandshake (request); } // As server private bool send (HandshakeResponse response) { _logger.Debug ("A response to a WebSocket connection request:\n" + response.ToString ()); return _stream.WriteHandshake (response); } private bool send (WsFrame frame) { lock (_forFrame) { var ready = _stream == null ? false : _readyState == WsState.OPEN ? true : _readyState == WsState.CLOSING ? frame.IsClose : false; if (!ready) { var msg = "The WebSocket connection isn't established or has been closed."; _logger.Error (msg); error (msg); return false; } return _stream.WriteFrame (frame); } } private void send (Opcode opcode, Stream stream) { var data = stream; var compressed = false; try { if (_readyState != WsState.OPEN) { var msg = "The WebSocket connection isn't established or has been closed."; _logger.Error (msg); error (msg); return; } if (_compression != CompressionMethod.NONE) { data = data.Compress (_compression); compressed = true; } var length = data.Length; lock (_forSend) { if (length <= _fragmentLen) send (Fin.FINAL, opcode, data.ReadBytes ((int) length), compressed); else sendFragmented (opcode, data, compressed); } } catch (Exception ex) { _logger.Fatal (ex.Message); error ("An exception has occured."); } finally { if (compressed) data.Dispose (); stream.Dispose (); } } private bool send (Fin fin, Opcode opcode, byte [] data, bool compressed) { var frame = createFrame (fin, opcode, new PayloadData (data), compressed, _client); return send (frame); } private void sendAsync (Opcode opcode, Stream stream, Action completed) { Action sender = send; AsyncCallback callback = ar => { try { sender.EndInvoke (ar); if (completed != null) completed (); } catch (Exception ex) { _logger.Fatal (ex.Message); error ("An exception has occured."); } }; sender.BeginInvoke (opcode, stream, callback, null); } private long sendFragmented (Opcode opcode, Stream stream, bool compressed) { var length = stream.Length; var quo = length / _fragmentLen; var rem = length % _fragmentLen; var count = rem == 0 ? quo - 2 : quo - 1; long readLen = 0; var tmpLen = 0; var buffer = new byte [_fragmentLen]; // First tmpLen = stream.Read (buffer, 0, _fragmentLen); if (send (Fin.MORE, opcode, buffer, compressed)) readLen += tmpLen; else return 0; // Mid for (long i = 0; i < count; i++) { tmpLen = stream.Read (buffer, 0, _fragmentLen); if (send (Fin.MORE, Opcode.CONT, buffer, compressed)) readLen += tmpLen; else return readLen; } // Final if (rem != 0) buffer = new byte [rem]; tmpLen = stream.Read (buffer, 0, buffer.Length); if (send (Fin.FINAL, Opcode.CONT, buffer, compressed)) readLen += tmpLen; return readLen; } // As client private HandshakeResponse sendHandshakeRequest () { var req = createHandshakeRequest (); var res = sendHandshakeRequest (req); if (!_preAuth && res.IsUnauthorized && _credentials != null) { var challenge = res.AuthChallenge; req.SetAuthorization (new AuthenticationResponse (_credentials, challenge)); res = sendHandshakeRequest (req); } return res; } // As client private HandshakeResponse sendHandshakeRequest (HandshakeRequest request) { send (request); return receiveHandshakeResponse (); } // As client private void setClientStream () { var host = _uri.DnsSafeHost; var port = _uri.Port; _tcpClient = new TcpClient (host, port); _stream = WsStream.CreateClientStream (_tcpClient, _secure, host, _certValidationCallback); } private void startReceiving () { _exitReceiving = new AutoResetEvent (false); _receivePong = new AutoResetEvent (false); Action completed = null; completed = frame => { try { processFrame (frame); if (_readyState == WsState.OPEN) _stream.ReadFrameAsync (completed); else _exitReceiving.Set (); } catch (WebSocketException ex) { _logger.Fatal (ex.Message); Close (ex.Code, ex.Message); } catch (Exception ex) { _logger.Fatal (ex.Message); Close (CloseStatusCode.ABNORMAL, "An exception has occured."); } }; _stream.ReadFrameAsync (completed); } // As server private bool validateConnectionRequest (WebSocketContext context) { string version; return context.IsWebSocketRequest && validateHostHeader (context.Host) && !context.SecWebSocketKey.IsNullOrEmpty () && ((version = context.SecWebSocketVersion) != null && version == _version) && validateCookies (context.CookieCollection, _cookies); } // As client private bool validateConnectionResponse (HandshakeResponse response) { string accept, version; return response.IsWebSocketResponse && ((accept = response.Headers ["Sec-WebSocket-Accept"]) != null && accept == createResponseKey ()) && ((version = response.Headers ["Sec-WebSocket-Version"]) == null || version == _version); } // As server private bool validateCookies (CookieCollection request, CookieCollection response) { return _cookiesValidation != null ? _cookiesValidation (request, response) : true; } // As server private bool validateHostHeader (string value) { if (value == null || value.Length == 0) return false; if (!_uri.IsAbsoluteUri) return true; var i = value.IndexOf (':'); var host = i > 0 ? value.Substring (0, i) : value; var type = Uri.CheckHostName (host); return type != UriHostNameType.Dns || Uri.CheckHostName (_uri.DnsSafeHost) != UriHostNameType.Dns || host == _uri.DnsSafeHost; } #endregion #region Internal Methods // As server internal void Close (HttpStatusCode code) { _readyState = WsState.CLOSING; close (code); } #endregion #region Public Methods /// /// Closes the WebSocket connection and releases all associated resources. /// public void Close () { close (new PayloadData ()); } /// /// Closes the WebSocket connection with the specified and /// releases all associated resources. /// /// /// This Close method emits a event if is not /// in the allowable range of the WebSocket close status code. /// /// /// A that indicates the status code for closure. /// public void Close (ushort code) { Close (code, String.Empty); } /// /// Closes the WebSocket connection with the specified and /// releases all associated resources. /// /// /// One of the values that indicates the status code for closure. /// public void Close (CloseStatusCode code) { close ((ushort) code, String.Empty); } /// /// Closes the WebSocket connection with the specified and /// , and releases all associated resources. /// /// /// This Close method emits a event if is not /// in the allowable range of the WebSocket close status code. /// /// /// A that indicates the status code for closure. /// /// /// A that contains the reason for closure. /// public void Close (ushort code, string reason) { if (!code.IsCloseStatusCode ()) { var msg = "Invalid close status code."; _logger.Error (String.Format ("{0}\ncode: {1}", msg, code)); error (msg); return; } close (code, reason); } /// /// Closes the WebSocket connection with the specified and /// , and releases all associated resources. /// /// /// One of the values that indicates the status code for closure. /// /// /// A that contains the reason for closure. /// public void Close (CloseStatusCode code, string reason) { close ((ushort) code, reason); } /// /// Establishes a WebSocket connection. /// public void Connect () { if (IsOpened) { var msg = "The WebSocket connection has already been established."; _logger.Error (msg); error (msg); return; } try { if (connect ()) open (); } catch (Exception ex) { _logger.Fatal (ex.Message); var msg = "An exception has occured."; error (msg); Close (CloseStatusCode.ABNORMAL, msg); } } /// /// Closes the WebSocket connection and releases all associated resources. /// /// /// This method closes the WebSocket connection with the . /// public void Dispose () { Close (CloseStatusCode.AWAY); } /// /// Sends a Ping using the WebSocket connection. /// /// /// true if a instance receives a Pong in a time; otherwise, false. /// public bool Ping () { return ping (new byte [] {}); } /// /// Sends a Ping with the specified using the WebSocket connection. /// /// /// A that contains a message to send with a Ping. /// /// /// true if a instance receives a Pong in a time; otherwise, false. /// public bool Ping (string message) { if (message.IsNullOrEmpty ()) return ping (new byte [] {}); var data = Encoding.UTF8.GetBytes (message); if (data.Length > 125) { var msg = "The payload length of a Ping frame must be 125 bytes or less."; _logger.Error (msg); error (msg); return false; } return ping (data); } /// /// Sends a binary using the WebSocket connection. /// /// /// An array of that contains a binary data to send. /// public void Send (byte[] data) { if (data == null) { var msg = "'data' must not be null."; _logger.Error (msg); error (msg); return; } var stream = new MemoryStream (data); send (Opcode.BINARY, stream); } /// /// Sends a text using the WebSocket connection. /// /// /// A that contains a text data to send. /// public void Send (string data) { if (data == null) { var msg = "'data' must not be null."; _logger.Error (msg); error (msg); return; } var stream = new MemoryStream (Encoding.UTF8.GetBytes (data)); send (Opcode.TEXT, stream); } /// /// Sends a binary data using the WebSocket connection. /// /// /// A that contains a binary data to send. /// public void Send (FileInfo file) { if (file == null) { var msg = "'file' must not be null."; _logger.Error (msg); error (msg); return; } send (Opcode.BINARY, file.OpenRead ()); } /// /// Sends a binary asynchronously using the WebSocket connection. /// /// /// An array of that contains a binary data to send. /// /// /// An delegate that references the method(s) called when /// the asynchronous operation completes. /// public void SendAsync (byte [] data, Action completed) { if (data == null) { var msg = "'data' must not be null."; _logger.Error (msg); error (msg); return; } var stream = new MemoryStream (data); sendAsync (Opcode.BINARY, stream, completed); } /// /// Sends a text asynchronously using the WebSocket connection. /// /// /// A that contains a text data to send. /// /// /// An delegate that references the method(s) called when /// the asynchronous operation completes. /// public void SendAsync (string data, Action completed) { if (data == null) { var msg = "'data' must not be null."; _logger.Error (msg); error (msg); return; } var stream = new MemoryStream (Encoding.UTF8.GetBytes (data)); sendAsync (Opcode.TEXT, stream, completed); } /// /// Sends a binary data asynchronously using the WebSocket connection. /// /// /// A that contains a binary data to send. /// /// /// An delegate that references the method(s) called when /// the asynchronous operation completes. /// public void SendAsync (FileInfo file, Action completed) { if (file == null) { var msg = "'file' must not be null."; _logger.Error (msg); error (msg); return; } sendAsync (Opcode.BINARY, file.OpenRead (), completed); } /// /// Sets a used in the WebSocket opening handshake. /// /// /// A that contains an HTTP Cookie to set. /// public void SetCookie (Cookie cookie) { var msg = IsOpened ? "The WebSocket connection has already been established." : cookie == null ? "'cookie' must not be null." : null; if (msg != null) { _logger.Error (msg); error (msg); return; } lock (_cookies.SyncRoot) { _cookies.SetOrRemove (cookie); } } /// /// Sets the credentials for HTTP authentication (Basic/Digest). /// /// /// A that contains a user name associated with the credentials. /// /// /// A that contains a password for associated with the credentials. /// /// /// true if sends the credentials as a Basic authorization with the first request handshake; /// otherwise, false. /// public void SetCredentials (string userName, string password, bool preAuth) { string msg = null; if (IsOpened) { msg = "The WebSocket connection has already been established."; } else if (userName == null) { _credentials = null; _preAuth = false; return; } else { msg = userName.Length > 0 && (userName.Contains (':') || !userName.IsText ()) ? "'userName' contains an invalid character." : !password.IsNullOrEmpty () && !password.IsText () ? "'password' contains an invalid character." : null; } if (msg != null) { _logger.Error (msg); error (msg); return; } _credentials = new WsCredential (userName, password, _uri.PathAndQuery); _preAuth = preAuth; } #endregion } }