diff --git a/Example/Program.cs b/Example/Program.cs index b60af931..ae8a2089 100644 --- a/Example/Program.cs +++ b/Example/Program.cs @@ -8,29 +8,29 @@ using System.Threading; using WebSocketSharp; using WebSocketSharp.Net; -namespace Example -{ - public struct NfMessage - { +namespace Example { + + public struct NfMessage { + public string Summary; public string Body; public string Icon; } - public class ThreadState - { + public class ThreadState { + public bool Enabled { get; set; } public AutoResetEvent Notification { get; private set; } public ThreadState() { - Enabled = true; + Enabled = true; Notification = new AutoResetEvent(false); } } - public class Program - { + public class Program { + private static Queue _msgQ = Queue.Synchronized(new Queue()); private static void enNfMessage(string summary, string body, string icon) @@ -38,8 +38,8 @@ namespace Example var msg = new NfMessage { Summary = summary, - Body = body, - Icon = icon + Body = body, + Icon = icon }; _msgQ.Enqueue(msg); @@ -47,7 +47,7 @@ namespace Example public static void Main(string[] args) { - ThreadState ts = new ThreadState(); + var ts = new ThreadState(); WaitCallback notifyMsg = state => { @@ -57,11 +57,9 @@ namespace Example if (_msgQ.Count > 0) { - NfMessage msg = (NfMessage)_msgQ.Dequeue(); + var msg = (NfMessage)_msgQ.Dequeue(); #if NOTIFY - Notification nf = new Notification(msg.Summary, - msg.Body, - msg.Icon); + var nf = new Notification(msg.Summary, msg.Body, msg.Icon); nf.AddHint("append", "allowed"); nf.Show(); #else @@ -75,15 +73,16 @@ namespace Example ThreadPool.QueueUserWorkItem(notifyMsg); - using (WebSocket ws = new WebSocket("ws://echo.websocket.org", "echo")) - //using (WebSocket ws = new WebSocket("wss://echo.websocket.org", "echo")) - //using (WebSocket ws = new WebSocket("ws://localhost:4649")) - //using (WebSocket ws = new WebSocket("ws://localhost:4649/Echo")) - //using (WebSocket ws = new WebSocket("ws://localhost:4649/Echo?name=nobita")) - //using (WebSocket ws = new WebSocket("ws://localhost:4649/エコー?name=のび太")) - //using (WebSocket ws = new WebSocket("ws://localhost:4649/Chat")) - //using (WebSocket ws = new WebSocket("ws://localhost:4649/Chat?name=nobita")) - //using (WebSocket ws = new WebSocket("ws://localhost:4649/チャット?name=のび太")) + using (var ws = new WebSocket("ws://echo.websocket.org", "echo")) + //using (var ws = new WebSocket("wss://echo.websocket.org", "echo")) + //using (var ws = new WebSocket("ws://localhost:4649")) + //using (var ws = new WebSocket("ws://localhost:4649/Echo")) + //using (var ws = new WebSocket("wss://localhost:4649/Echo")) + //using (var ws = new WebSocket("ws://localhost:4649/Echo?name=nobita")) + //using (var ws = new WebSocket("ws://localhost:4649/エコー?name=のび太")) + //using (var ws = new WebSocket("ws://localhost:4649/Chat")) + //using (var ws = new WebSocket("ws://localhost:4649/Chat?name=nobita")) + //using (var ws = new WebSocket("ws://localhost:4649/チャット?name=のび太")) { ws.OnOpen += (sender, e) => { @@ -111,11 +110,16 @@ namespace Example "notification-message-im"); }; - //ws.Origin = "http://echo.websocket.org"; - //ws.Compression = CompressionMethod.DEFLATE; #if DEBUG ws.Log.Level = LogLevel.TRACE; #endif + //ws.Compression = CompressionMethod.DEFLATE; + //ws.Origin = "http://echo.websocket.org"; + //ws.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => + //{ + // ws.Log.Debug(String.Format("\n{0}\n{1}", certificate.Issuer, certificate.Subject)); + // return true; + //}; //ws.SetCookie(new Cookie("nobita", "\"idiot, gunfighter\"")); //ws.SetCookie(new Cookie("dora", "tanuki")); ws.Connect(); diff --git a/Example2/App.config b/Example2/App.config index 84bc00fa..5a8a3e5d 100644 --- a/Example2/App.config +++ b/Example2/App.config @@ -1,6 +1,7 @@ - + + diff --git a/Example2/Example2.csproj b/Example2/Example2.csproj index 65bdb259..685a1ef6 100644 --- a/Example2/Example2.csproj +++ b/Example2/Example2.csproj @@ -49,6 +49,7 @@ + diff --git a/Example2/Program.cs b/Example2/Program.cs index 9bc5cb1a..7b56e27d 100644 --- a/Example2/Program.cs +++ b/Example2/Program.cs @@ -1,58 +1,67 @@ using System; +using System.Configuration; +using System.Security.Cryptography.X509Certificates; using WebSocketSharp; using WebSocketSharp.Server; -namespace Example2 -{ - public class Program - { - public static void Main(string[] args) +namespace Example2 { + + public class Program { + + public static void Main (string [] args) { /* Single service server - //var wssv = new WebSocketServiceHost("ws://localhost:4649"); - var wssv = new WebSocketServiceHost("ws://localhost:4649/Echo"); - //var wssv = new WebSocketServiceHost("ws://localhost:4649/エコー"); - //var wssv = new WebSocketServiceHost(4649); - //var wssv = new WebSocketServiceHost(4649, "/Echo"); - //var wssv = new WebSocketServiceHost(4649, "/エコー"); - //var wssv = new WebSocketServiceHost("ws://localhost:4649"); - //var wssv = new WebSocketServiceHost("ws://localhost:4649/Chat"); - //var wssv = new WebSocketServiceHost("ws://localhost:4649/チャット"); - //var wssv = new WebSocketServiceHost(4649); - //var wssv = new WebSocketServiceHost(4649, "/Chat"); - //var wssv = new WebSocketServiceHost(4649, "/チャット"); - //wssv.Sweeping = false; // Stop the sweep inactive session timer. + var wssv = new WebSocketServiceHost ("ws://localhost:4649"); + //var wssv = new WebSocketServiceHost ("ws://localhost:4649/Echo"); + //var wssv = new WebSocketServiceHost ("ws://localhost:4649/エコー"); + //var wssv = new WebSocketServiceHost (4649); + //var wssv = new WebSocketServiceHost (4649, "/Echo"); + //var wssv = new WebSocketServiceHost (4649, "/エコー"); + //var wssv = new WebSocketServiceHost ("ws://localhost:4649"); + //var wssv = new WebSocketServiceHost ("ws://localhost:4649/Chat"); + //var wssv = new WebSocketServiceHost ("ws://localhost:4649/チャット"); + //var wssv = new WebSocketServiceHost (4649); + //var wssv = new WebSocketServiceHost (4649, "/Chat"); + //var wssv = new WebSocketServiceHost (4649, "/チャット"); + #if DEBUG + wssv.Log.Level = LogLevel.TRACE; + #endif + //wssv.Sweeping = false; - wssv.Start(); - Console.WriteLine( + wssv.Start (); + Console.WriteLine ( "WebSocket Service Host (url: {0})\n listening on address: {1} port: {2}\n", wssv.Uri, wssv.Address, wssv.Port); */ - // Multi services server - var wssv = new WebSocketServer(4649); - //var wssv = new WebSocketServer("ws://localhost:4649"); + /* Multi services server */ + var wssv = new WebSocketServer (4649); + //var wssv = new WebSocketServer (4649, true); + //var wssv = new WebSocketServer ("ws://localhost:4649"); + //var wssv = new WebSocketServer ("wss://localhost:4649"); #if DEBUG wssv.Log.Level = LogLevel.TRACE; #endif - //wssv.Sweeping = false; // Stop the sweep inactive session timer. - wssv.AddWebSocketService("/Echo"); - wssv.AddWebSocketService("/Chat"); - //wssv.AddWebSocketService("/エコー"); - //wssv.AddWebSocketService("/チャット"); + //var file = ConfigurationManager.AppSettings ["ServerCertFile"]; + //var password = ConfigurationManager.AppSettings ["CertFilePassword"]; + //wssv.Certificate = new X509Certificate2 (file, password); + //wssv.Sweeping = false; + wssv.AddWebSocketService ("/Echo"); + wssv.AddWebSocketService ("/Chat"); + //wssv.AddWebSocketService ("/エコー"); + //wssv.AddWebSocketService ("/チャット"); - wssv.Start(); - Console.WriteLine( + wssv.Start (); + Console.WriteLine ( "WebSocket Server listening on port: {0} service path:", wssv.Port); foreach (var path in wssv.ServicePaths) - Console.WriteLine(" {0}", path); - Console.WriteLine(); - + Console.WriteLine (" {0}", path); - Console.WriteLine("Press any key to stop server..."); - Console.ReadLine(); + Console.WriteLine (); + Console.WriteLine ("Press enter key to stop server..."); + Console.ReadLine (); - wssv.Stop(); + wssv.Stop (); } } } diff --git a/README.md b/README.md index d6149d21..ce51939a 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ ws.OnOpen += (sender, e) => }; ``` -`e` has come across as `EventArgs.Empty`, so there is no operation on `e`. +`e` has come across as `EventArgs.Empty`, so you don't use `e`. ##### WebSocket.OnMessage event ##### @@ -84,7 +84,11 @@ ws.OnMessage += (sender, e) => }; ``` -`e.Type` (`WebSocketSharp.MessageEventArgs.Type`, the type of this property is `WebSocketSharp.Opcode`) indicates the **Frame type** of a WebSocket frame, so by checking this property, you determine which item you should operate. +`e.Type` (`WebSocketSharp.MessageEventArgs.Type`, the type of this property is `WebSocketSharp.Opcode`) indicates the **Frame Type** of a WebSocket frame, so by checking this property, you determine which item you should use. + +If `e.Type` equals `Opcode.TEXT`, you use `e.Data` (`WebSocketSharp.MessageEventArgs.Data`, the type of this property is `string`) that contains the received data. + +If `e.Type` equals `Opcode.BINARY`, you use `e.RawData` (`WebSocketSharp.MessageEventArgs.RawData`, the type of this property is `byte[]`) that contains the received data. ```cs if (e.Type == Opcode.TEXT) @@ -100,10 +104,6 @@ if (e.Type == Opcode.BINARY) } ``` -If `e.Type` equaled `Opcode.TEXT`, you would operate `e.Data` (`WebSocketSharp.MessageEventArgs.Data`, the type of this property is `string`). - -If `e.Type` equaled `Opcode.BINARY`, you would operate `e.RawData` (`WebSocketSharp.MessageEventArgs.RawData`, the type of this property is `byte[]`). - ##### WebSocket.OnError event ##### A `WebSocket.OnError` event occurs when the `WebSocket` gets an error. @@ -114,7 +114,7 @@ ws.OnError += (sender, e) => ... }; ``` -`e.Message` (`WebSocketSharp.ErrorEventArgs.Message`, the type of this property is `string`) contains an error message, so you operate this. +`e.Message` (`WebSocketSharp.ErrorEventArgs.Message`, the type of this property is `string`) contains an error message, so you use this. ##### WebSocket.OnClose event ##### @@ -127,7 +127,7 @@ ws.OnClose += (sender, e) => }; ``` -`e.Code` (`WebSocketSharp.CloseEventArgs.Code`, the type of this property is `ushort`) contains a status code indicating the reason for closure and `e.Reason` (`WebSocketSharp.CloseEventArgs.Reason`, the type of this property is `string`) contains the reason for closure, so you operate these. +`e.Code` (`WebSocketSharp.CloseEventArgs.Code`, the type of this property is `ushort`) contains a status code indicating the reason for closure and `e.Reason` (`WebSocketSharp.CloseEventArgs.Reason`, the type of this property is `string`) contains the reason for closure, so you use these. #### Step 4 #### @@ -157,7 +157,7 @@ Closing the WebSocket connection. ws.Close(code, reason); ``` -If you wanted to close the WebSocket connection explicitly, you would use the `Close` method. +If you want to close the WebSocket connection explicitly, you can use the `Close` method. And the `Close` method is overloaded. The types of `code` are `WebSocketSharp.CloseStatusCode` and `ushort`, the type of `reason` is `string`. @@ -176,7 +176,7 @@ namespace Example { { protected override void OnMessage(MessageEventArgs e) { - var msg = e.Data.ToLower().Equals("balus") + var msg = e.Data.ToLower() == "balus" ? "I've been balused already..." : "I'm not available now."; Send(msg); @@ -266,7 +266,7 @@ You can add any WebSocket service with a specified path to the service to your ` The type of `T` inherits `WebSocketService` class, so you can use a class that was created in **Step 2**. -If you created a instance of the `WebSocketServer` class without the port number, the `WebSocketServer` would set the port number to **80** automatically. So it is necessary to run with root permission. +If you create a instance of the `WebSocketServer` class without the port number, the `WebSocketServer` set the port number to **80** automatically. So it is necessary to run with root permission. $ sudo mono example2.exe @@ -285,7 +285,7 @@ wssv.OnError += (sender, e) => }; ``` -`e.Message` (`WebSocketSharp.ErrorEventArgs.Message`, the type of this property is `string`) contains an error message, so you operate this. +`e.Message` (`WebSocketSharp.ErrorEventArgs.Message`, the type of this property is `string`) contains an error message, so you use this. ##### WebSocketServer.OnError event ##### @@ -320,21 +320,51 @@ httpsv.AddWebSocketService("/"); For more information, could you see **[Example3]**? +### Secure Connection ### + +As a **WebSocket Client**, creating a instance of the `WebSocket` class with the WebSocket URL with the **wss** scheme. + +```cs +using (var ws = new WebSocket("wss://example.com")) +{ + ... +} +``` + +If you want to set the custom validation for the server certificate, you can use the `WebSocket.ServerCertificateValidationCallback` property. + +```cs +ws.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => +{ + // Do something to validate the server certificate. + return true; // The server certificate is valid. +}; +``` + +If you set this property to nothing, the validation does nothing with the server certificate, always returns valid. + +As a **WebSocket Server**, creating and setting a instance of the WebSocket server with some settings for the secure connection. + +```cs +var wssv = new WebSocketServer(4649, true); +wssv.Certificate = new X509Certificate2("/path/to/cert.pfx", "password for cert.pfx"); +``` + ### Logging ### The `WebSocket` class includes own logging functions. The `WebSocket.Log` property provides the logging functions. -If you wanted to change the current logging level (the default is the `LogLevel.ERROR`), you would operate the `WebSocket.Log.Level` property. +If you want to change the current logging level (the default is `LogLevel.ERROR`), you can use the `WebSocket.Log.Level` property. ```cs ws.Log.Level = LogLevel.DEBUG; ``` -This setting means that the logging outputs with a less than the `LogLevel.DEBUG` are not outputted. +This setting means that the logging outputs with a less than `LogLevel.DEBUG` are not outputted. -And if you wanted to output a log, you would use some output methods. The following outputs a log with the `LogLevel.DEBUG`. +And if you want to output a log, you can use some output methods. The following outputs a log with `LogLevel.DEBUG`. ```cs ws.Log.Debug("This is a debug message."); @@ -354,7 +384,7 @@ Examples of using **websocket-sharp**. [Example1] connects to the [Audio Data delivery server] using the WebSocket ([Example1] is only implemented the chat feature, still unfinished). -And [Example1] uses the [Json.NET]. +And [Example1] uses [Json.NET]. ### Example2 ### diff --git a/websocket-sharp/AssemblyInfo.cs b/websocket-sharp/AssemblyInfo.cs index 4db7a594..c85deaa4 100644 --- a/websocket-sharp/AssemblyInfo.cs +++ b/websocket-sharp/AssemblyInfo.cs @@ -5,7 +5,7 @@ using System.Runtime.CompilerServices; // Change them to the values specific to your project. [assembly: AssemblyTitle("websocket-sharp")] -[assembly: AssemblyDescription("A C# implementation of the WebSocket protocol client & server")] +[assembly: AssemblyDescription("A C# implementation of the WebSocket protocol client and server")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("websocket-sharp.dll")] diff --git a/websocket-sharp/Ext.cs b/websocket-sharp/Ext.cs index c67192b8..07167252 100644 --- a/websocket-sharp/Ext.cs +++ b/websocket-sharp/Ext.cs @@ -45,6 +45,7 @@ using System.IO; using System.IO.Compression; using System.Linq; using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; using System.Text; using WebSocketSharp.Net; using WebSocketSharp.Net.WebSockets; @@ -282,6 +283,12 @@ namespace WebSocketSharp { : null; } + internal static TcpListenerWebSocketContext GetWebSocketContext( + this TcpClient client, bool secure, X509Certificate cert) + { + return new TcpListenerWebSocketContext(client, secure, cert); + } + // // Determines whether the specified object is . // @@ -532,60 +539,6 @@ namespace WebSocketSharp { #region Public Methods - /// - /// Accepts a WebSocket connection by the . - /// - /// - /// A that contains a WebSocket connection. - /// - /// - /// A that provides a TCP connection to accept a WebSocket connection. - /// - /// - /// A that indicates a secure connection or not. (true indicates a secure connection.) - /// - /// - /// is . - /// - public static TcpListenerWebSocketContext AcceptWebSocket(this TcpListener listener, bool secure) - { - if (listener == null) - throw new ArgumentNullException("listener"); - - var client = listener.AcceptTcpClient(); - return new TcpListenerWebSocketContext(client, secure); - } - - /// - /// Accepts a WebSocket connection asynchronously by the . - /// - /// - /// A that provides a TCP connection to accept a WebSocket connection. - /// - /// - /// A that indicates a secure connection or not. (true indicates a secure connection.) - /// - /// - /// An Action<TcpListenerWebSocketContext> delegate that contains the method(s) that is called when an asynchronous operation completes. - /// - /// - /// is . - /// - public static void AcceptWebSocketAsync(this TcpListener listener, bool secure, Action completed) - { - if (listener == null) - throw new ArgumentNullException("listener"); - - AsyncCallback callback = (ar) => - { - var client = listener.EndAcceptTcpClient(ar); - var context = new TcpListenerWebSocketContext(client, secure); - completed(context); - }; - - listener.BeginAcceptTcpClient(callback, null); - } - /// /// Determines whether the specified contains any of characters /// in the specified array of . diff --git a/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs b/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs index 32cecfb8..7e174ca2 100644 --- a/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs +++ b/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs @@ -30,12 +30,13 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; using System.Security.Principal; namespace WebSocketSharp.Net.WebSockets { /// - /// Provides access to the WebSocket connection request objects received by the class. + /// Provides access to the WebSocket connection request objects received by the . /// /// /// @@ -44,22 +45,22 @@ namespace WebSocketSharp.Net.WebSockets { #region Private Fields private CookieCollection _cookies; - private TcpClient _tcpClient; - private bool _isSecure; + private TcpClient _client; private RequestHandshake _request; + private bool _secure; + private WsStream _stream; private WebSocket _websocket; - private WsStream _wsStream; #endregion #region Internal Constructors - internal TcpListenerWebSocketContext(TcpClient tcpClient, bool secure) + internal TcpListenerWebSocketContext(TcpClient client, bool secure, X509Certificate cert) { - _tcpClient = tcpClient; - _isSecure = secure; - _wsStream = WsStream.CreateServerStream(tcpClient, secure); - _request = RequestHandshake.Parse(_wsStream.ReadHandshake()); + _client = client; + _secure = secure; + _stream = WsStream.CreateServerStream(client, secure, cert); + _request = RequestHandshake.Parse(_stream.ReadHandshake()); _websocket = new WebSocket(this); } @@ -69,7 +70,7 @@ namespace WebSocketSharp.Net.WebSockets { internal WsStream Stream { get { - return _wsStream; + return _stream; } } @@ -85,7 +86,7 @@ namespace WebSocketSharp.Net.WebSockets { /// public override CookieCollection CookieCollection { get { - if (_cookies.IsNull()) + if (_cookies == null) _cookies = _request.Cookies; return _cookies; @@ -139,7 +140,7 @@ namespace WebSocketSharp.Net.WebSockets { /// public override bool IsSecureConnection { get { - return _isSecure; + return _secure; } } @@ -260,7 +261,7 @@ namespace WebSocketSharp.Net.WebSockets { /// public virtual System.Net.IPEndPoint ServerEndPoint { get { - return (System.Net.IPEndPoint)_tcpClient.Client.LocalEndPoint; + return (System.Net.IPEndPoint)_client.Client.LocalEndPoint; } } @@ -287,7 +288,7 @@ namespace WebSocketSharp.Net.WebSockets { /// public virtual System.Net.IPEndPoint UserEndPoint { get { - return (System.Net.IPEndPoint)_tcpClient.Client.RemoteEndPoint; + return (System.Net.IPEndPoint)_client.Client.RemoteEndPoint; } } @@ -309,8 +310,8 @@ namespace WebSocketSharp.Net.WebSockets { internal void Close() { - _wsStream.Close(); - _tcpClient.Close(); + _stream.Close(); + _client.Close(); } #endregion diff --git a/websocket-sharp/Server/WebSocketServerBase.cs b/websocket-sharp/Server/WebSocketServerBase.cs index 62fc50ee..c9f62d6d 100644 --- a/websocket-sharp/Server/WebSocketServerBase.cs +++ b/websocket-sharp/Server/WebSocketServerBase.cs @@ -30,6 +30,7 @@ using System; using System.Diagnostics; using System.Net; using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; using System.Threading; using WebSocketSharp.Net.WebSockets; @@ -45,15 +46,16 @@ namespace WebSocketSharp.Server { #region Private Fields - private IPAddress _address; - private bool _listening; - private Logger _logger; - private int _port; - private Thread _receiveRequestThread; - private bool _secure; - private bool _selfHost; - private TcpListener _tcpListener; - private Uri _uri; + private IPAddress _address; + private X509Certificate2 _cert; + private bool _listening; + private Logger _logger; + private int _port; + private Thread _receiveRequestThread; + private bool _secure; + private bool _selfHost; + private TcpListener _listener; + private Uri _uri; #endregion @@ -63,7 +65,7 @@ namespace WebSocketSharp.Server { /// Initializes a new instance of the class. /// /// - /// This constructor initializes a new instance of this class as non self host. + /// This constructor initializes a new instance of this class as non self hosted server. /// protected WebSocketServerBase() : this(new Logger()) @@ -75,7 +77,7 @@ namespace WebSocketSharp.Server { /// with the specified . /// /// - /// This constructor initializes a new instance of this class as non self host. + /// This constructor initializes a new instance of this class as non self hosted server. /// /// /// A that provides the logging functions. @@ -104,7 +106,7 @@ namespace WebSocketSharp.Server { if (url.IsNull()) throw new ArgumentNullException("url"); - Uri uri; + Uri uri; string msg; if (!tryCreateUri(url, out uri, out msg)) throw new ArgumentException(msg, "url"); @@ -208,6 +210,25 @@ namespace WebSocketSharp.Server { } } + /// + /// Gets or sets the certificate used to authenticate the server on the secure connection. + /// + /// + /// A used to authenticate the server. + /// + public X509Certificate2 Certificate { + get { + return _cert; + } + + set { + if (_listening) + return; + + _cert = value; + } + } + /// /// Gets a value indicating whether the server has been started. /// @@ -303,7 +324,7 @@ namespace WebSocketSharp.Server { _listening = false; _logger = new Logger(); _selfHost = true; - _tcpListener = new TcpListener(_address, _port); + _listener = new TcpListener(_address, _port); } private void init(Uri uri) @@ -320,13 +341,13 @@ namespace WebSocketSharp.Server { init(); } - private void processRequestAsync(TcpListenerWebSocketContext context) + private void processRequestAsync(TcpClient client) { - WaitCallback callback = (state) => + WaitCallback callback = state => { try { - AcceptWebSocket(context); + AcceptWebSocket(client.GetWebSocketContext(_secure, _cert)); } catch (Exception ex) { @@ -344,11 +365,11 @@ namespace WebSocketSharp.Server { { try { - processRequestAsync(_tcpListener.AcceptWebSocket(_secure)); + processRequestAsync(_listener.AcceptTcpClient()); } catch (SocketException) { - // TcpListener has been stopped. + _logger.Info("TcpListener has been stopped."); break; } catch (Exception ex) @@ -368,7 +389,7 @@ namespace WebSocketSharp.Server { _receiveRequestThread.Start(); } - private bool tryCreateUri(string uriString, out Uri result, out string message) + private static bool tryCreateUri(string uriString, out Uri result, out string message) { if (!uriString.TryCreateWebSocketUri(out result, out message)) return false; @@ -422,7 +443,16 @@ namespace WebSocketSharp.Server { if (!_selfHost || _listening) return; - _tcpListener.Start(); + if (_secure && _cert == null) + { + var msg = "Secure connection requires a server certificate."; + _logger.Error(msg); + error(msg); + + return; + } + + _listener.Start(); startReceiveRequestThread(); _listening = true; } @@ -435,7 +465,7 @@ namespace WebSocketSharp.Server { if (!_selfHost || !_listening) return; - _tcpListener.Stop(); + _listener.Stop(); _receiveRequestThread.Join(5 * 1000); _listening = false; } diff --git a/websocket-sharp/WebSocket.cs b/websocket-sharp/WebSocket.cs index 466590a9..4d7e1677 100644 --- a/websocket-sharp/WebSocket.cs +++ b/websocket-sharp/WebSocket.cs @@ -38,6 +38,7 @@ 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; @@ -66,6 +67,8 @@ namespace WebSocketSharp { #region Private Fields private string _base64key; + private RemoteCertificateValidationCallback + _certValidationCallback; private bool _client; private Action _closeContext; private CookieCollection _cookies; @@ -162,9 +165,8 @@ namespace WebSocketSharp { _uri = uri; _protocols = protocols.ToString(", "); _client = true; - _secure = uri.Scheme == "wss" - ? true - : false; + _secure = uri.Scheme == "wss" ? true : false; + _base64key = createBase64Key(); } /// @@ -226,6 +228,12 @@ namespace WebSocketSharp { } } + internal bool IsOpened { + get { + return _readyState == WsState.OPEN || _readyState == WsState.CLOSING; + } + } + #endregion #region Public Properties @@ -243,8 +251,14 @@ namespace WebSocketSharp { } set { - if (isOpened(true)) + if (IsOpened) + { + var msg = "The WebSocket connection has already been established."; + _logger.Error(msg); + error(msg); + return; + } _compression = value; } @@ -363,19 +377,25 @@ namespace WebSocketSharp { } set { - if (isOpened(true)) - return; - - if (value.IsNullOrEmpty()) + string msg = null; + if (IsOpened) + { + msg = "The WebSocket connection has already been established."; + } + else if (value.IsNullOrEmpty()) { _origin = String.Empty; return; } - - var origin = new Uri(value); - if (!origin.IsAbsoluteUri || origin.Segments.Length > 1) + 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) { - var msg = "The syntax of value of Origin must be '://[:]'."; _logger.Error(msg); error(msg); @@ -410,6 +430,27 @@ namespace WebSocketSharp { } } + /// + /// 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. /// @@ -777,7 +818,7 @@ namespace WebSocketSharp { // As client private bool doHandshake() { - init(); + setWsStream(); return processResponseHandshake(sendRequestHandshake()); } @@ -799,24 +840,13 @@ namespace WebSocketSharp { return CompressionMethod.NONE; } - // As client - private void init() - { - _base64key = createBase64Key(); - - var host = _uri.DnsSafeHost; - var port = _uri.Port; - _tcpClient = new TcpClient(host, port); - _wsStream = WsStream.CreateClientStream(_tcpClient, host, _secure); - } - // As server private void init(WebSocketContext context) { _context = context; - _uri = context.Path.ToUri(); - _secure = context.IsSecureConnection; - _client = false; + _uri = context.Path.ToUri(); + _secure = context.IsSecureConnection; + _client = false; } private static bool isCompressionExtension(string value) @@ -832,21 +862,6 @@ namespace WebSocketSharp { : false; } - private bool isOpened(bool errorIfOpened) - { - if (_readyState != WsState.OPEN && _readyState != WsState.CLOSING) - return false; - - if (errorIfOpened) - { - var msg = "The WebSocket connection has been established already."; - _logger.Error(msg); - error(msg); - } - - return true; - } - // As server private bool isValidHostHeader() { @@ -1351,6 +1366,15 @@ namespace WebSocketSharp { send(res); } + // As client + private void setWsStream() + { + var host = _uri.DnsSafeHost; + var port = _uri.Port; + _tcpClient = new TcpClient(host, port); + _wsStream = WsStream.CreateClientStream(_tcpClient, _secure, host, _certValidationCallback); + } + private void startReceiving() { _exitReceiving = new AutoResetEvent(false); @@ -1480,8 +1504,14 @@ namespace WebSocketSharp { /// public void Connect() { - if (isOpened(true)) + if (IsOpened) + { + var msg = "The WebSocket connection has already been established."; + _logger.Error(msg); + error(msg); + return; + } try { @@ -1690,12 +1720,14 @@ namespace WebSocketSharp { /// public void SetCookie(Cookie cookie) { - if (isOpened(true)) - return; + var msg = IsOpened + ? "The WebSocket connection has already been established." + : cookie == null + ? "'cookie' must not be null." + : null; - if (cookie == null) + if (msg != null) { - var msg = "'cookie' must not be null."; _logger.Error(msg); error(msg); @@ -1723,24 +1755,28 @@ namespace WebSocketSharp { /// public void SetCredentials(string userName, string password, bool preAuth) { - if (isOpened(true)) - return; - - if (userName == null) + 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; + } - var msg = userName.Length > 0 && (userName.Contains(':') || !userName.IsText()) - ? "'userName' contains an invalid character." - : !password.IsNullOrEmpty() && !password.IsText() - ? "'password' contains an invalid character." - : String.Empty; - - if (msg.Length > 0) + if (msg != null) { _logger.Error(msg); error(msg); diff --git a/websocket-sharp/WsStream.cs b/websocket-sharp/WsStream.cs index 585ea176..7e872fb5 100644 --- a/websocket-sharp/WsStream.cs +++ b/websocket-sharp/WsStream.cs @@ -28,7 +28,6 @@ using System; using System.Collections.Generic; -using System.Configuration; using System.IO; using System.Net.Sockets; using System.Security.Cryptography.X509Certificates; @@ -119,18 +118,20 @@ namespace WebSocketSharp { #region Internal Methods - internal static WsStream CreateClientStream(TcpClient tcpClient, string host, bool secure) + internal static WsStream CreateClientStream( + TcpClient client, + bool secure, + string host, + System.Net.Security.RemoteCertificateValidationCallback validationCallback + ) { - var netStream = tcpClient.GetStream(); + var netStream = client.GetStream(); if (secure) { - System.Net.Security.RemoteCertificateValidationCallback callback = (sender, certificate, chain, sslPolicyErrors) => - { - // FIXME: Always returns true - return true; - }; + if (validationCallback == null) + validationCallback = (sender, certificate, chain, sslPolicyErrors) => true; - var sslStream = new SslStream(netStream, false, callback); + var sslStream = new SslStream(netStream, false, validationCallback); sslStream.AuthenticateAsClient(host); return new WsStream(sslStream); @@ -139,14 +140,13 @@ namespace WebSocketSharp { return new WsStream(netStream); } - internal static WsStream CreateServerStream(TcpClient tcpClient, bool secure) + internal static WsStream CreateServerStream(TcpClient client, bool secure, X509Certificate cert) { - var netStream = tcpClient.GetStream(); + var netStream = client.GetStream(); if (secure) { var sslStream = new SslStream(netStream, false); - var certPath = ConfigurationManager.AppSettings["ServerCertPath"]; - sslStream.AuthenticateAsServer(new X509Certificate2(certPath)); + sslStream.AuthenticateAsServer(cert); return new WsStream(sslStream); }