diff --git a/Example/Program.cs b/Example/Program.cs index b7313a9d4..8d16c0794 100644 --- a/Example/Program.cs +++ b/Example/Program.cs @@ -18,47 +18,50 @@ public static void Main (string[] args) // If you would like to connect to the server with the secure connection, // you should create a new instance with a wss scheme WebSocket URL. - using (var ws = new WebSocket ("ws://localhost:4649/Echo")) + //using (var ws = new WebSocket ("ws://localhost:4649/Echo")) //using (var ws = new WebSocket ("wss://localhost:5963/Echo")) - //using (var ws = new WebSocket ("ws://localhost:4649/Chat")) + using (var ws = new WebSocket ("ws://localhost:4649/Chat")) //using (var ws = new WebSocket ("wss://localhost:5963/Chat")) //using (var ws = new WebSocket ("ws://localhost:4649/Chat?name=nobita")) //using (var ws = new WebSocket ("wss://localhost:5963/Chat?name=nobita")) { - // Set the WebSocket events. +#if DEBUG + // To change the logging level. + ws.Log.Level = LogLevel.Trace; - ws.OnOpen += (sender, e) => ws.Send ("Hi, there!"); + // To enable the Per-message Compression extension. + //ws.Compression = CompressionMethod.Deflate; - ws.OnMessage += (sender, e) => { - var fmt = "[WebSocket Message] {0}"; - var body = !e.IsPing ? e.Data : "A ping was received."; + // To emit a WebSocket.OnMessage event when receives a ping. + //ws.EmitOnPing = true; - Console.WriteLine (fmt, body); - }; + // To enable the redirection. + //ws.EnableRedirection = true; - ws.OnError += (sender, e) => { - var fmt = "[WebSocket Error] {0}"; + // To disable a delay when send or receive buffer of the underlying + // TCP socket is not full. + ws.NoDelay = true; - Console.WriteLine (fmt, e.Message); - }; + // To send the Origin header. + //ws.Origin = "http://localhost:4649/"; - ws.OnClose += (sender, e) => { - var fmt = "[WebSocket Close ({0})] {1}"; + // To send the cookies. + //ws.SetCookie (new Cookie ("name", "nobita")); + //ws.SetCookie (new Cookie ("roles", "\"idiot, gunfighter\"")); - Console.WriteLine (fmt, e.Code, e.Reason); - }; -#if DEBUG - // To change the logging level. - ws.Log.Level = LogLevel.Trace; + // To send the credentials for the HTTP Authentication (Basic/Digest). + //ws.SetCredentials ("nobita", "password", false); - // To change the wait time for the response to the Ping or Close. - //ws.WaitTime = TimeSpan.FromSeconds (10); + // To connect through the HTTP Proxy server. + //ws.SetProxy ("http://localhost:3128/", "nobita", "password"); + + // To send a user header. + + var reqHeader = "RequestForID"; + var resHeader = "ID"; + + ws.SetUserHeader (reqHeader, resHeader); - // To emit a WebSocket.OnMessage event when receives a ping. - //ws.EmitOnPing = true; -#endif - // To enable the Per-message Compression extension. - //ws.Compression = CompressionMethod.Deflate; // To validate the server certificate. /* @@ -66,7 +69,9 @@ public static void Main (string[] args) (sender, certificate, chain, sslPolicyErrors) => { var fmt = "Certificate:\n- Issuer: {0}\n- Subject: {1}"; var msg = String.Format ( - fmt, certificate.Issuer, certificate.Subject + fmt, + certificate.Issuer, + certificate.Subject ); ws.Log.Debug (msg); @@ -75,21 +80,47 @@ public static void Main (string[] args) }; */ - // To send the credentials for the HTTP Authentication (Basic/Digest). - //ws.SetCredentials ("nobita", "password", false); + // To change the wait time for the response to the Ping or Close. + //ws.WaitTime = TimeSpan.FromSeconds (10); +#endif + // Set the WebSocket events. - // To send the Origin header. - //ws.Origin = "http://localhost:4649/"; + ws.OnClose += + (sender, e) => { + var fmt = "[WebSocket Close ({0})] {1}"; - // To send the cookies. - //ws.SetCookie (new Cookie ("name", "nobita")); - //ws.SetCookie (new Cookie ("roles", "\"idiot, gunfighter\"")); + Console.WriteLine (fmt, e.Code, e.Reason); + }; - // To connect through the HTTP Proxy server. - //ws.SetProxy ("http://localhost:3128/", "nobita", "password"); + ws.OnError += + (sender, e) => { + var fmt = "[WebSocket Error] {0}"; - // To enable the redirection. - //ws.EnableRedirection = true; + Console.WriteLine (fmt, e.Message); + }; + + ws.OnMessage += + (sender, e) => { + var fmt = e.IsPing + ? "[WebSocket Ping] {0}" + : "[WebSocket Message] {0}"; + + Console.WriteLine (fmt, e.Data); + }; + + ws.OnOpen += + (sender, e) => { +#if DEBUG + var val = ws.HandshakeResponseHeaders[resHeader]; + + if (!val.IsNullOrEmpty ()) { + var fmt = "[WebSocket Open] {0}: {1}"; + + Console.WriteLine (fmt, resHeader, val); + } +#endif + ws.Send ("Hi, there!"); + }; // Connect to the server. ws.Connect (); @@ -97,10 +128,11 @@ public static void Main (string[] args) // Connect to the server asynchronously. //ws.ConnectAsync (); - Console.WriteLine ("\nType 'exit' to exit.\n"); + Console.WriteLine ("\nType \"exit\" to exit.\n"); while (true) { Thread.Sleep (1000); + Console.Write ("> "); var msg = Console.ReadLine (); diff --git a/Example2/Program.cs b/Example2/Program.cs index 7b024a0ee..4a2e8c0bf 100644 --- a/Example2/Program.cs +++ b/Example2/Program.cs @@ -14,7 +14,7 @@ public static void Main (string[] args) // Create a new instance of the WebSocketServer class. // // If you would like to provide the secure connection, you should - // create a new instance with the 'secure' parameter set to true or + // create a new instance with the "secure" parameter set to true or // with a wss scheme WebSocket URL. var wssv = new WebSocketServer (4649); @@ -50,24 +50,12 @@ public static void Main (string[] args) // To change the logging level. wssv.Log.Level = LogLevel.Trace; - // To change the wait time for the response to the WebSocket Ping or Close. - //wssv.WaitTime = TimeSpan.FromSeconds (2); - - // Not to remove the inactive sessions periodically. - //wssv.KeepClean = false; -#endif - // To provide the secure connection. - /* - var cert = ConfigurationManager.AppSettings["ServerCertFile"]; - var passwd = ConfigurationManager.AppSettings["CertFilePassword"]; - wssv.SslConfiguration.ServerCertificate = new X509Certificate2 (cert, passwd); - */ - // To provide the HTTP Authentication (Basic/Digest). /* wssv.AuthenticationSchemes = AuthenticationSchemes.Basic; wssv.Realm = "WebSocket Test"; - wssv.UserCredentialsFinder = id => { + wssv.UserCredentialsFinder = + id => { var name = id.Name; // Return user name, password, and roles. @@ -77,66 +65,107 @@ public static void Main (string[] args) }; */ + // To remove the inactive sessions periodically. + //wssv.KeepClean = true; + // To resolve to wait for socket in TIME_WAIT state. //wssv.ReuseAddress = true; + // To provide the secure connection. + /* + var cert = ConfigurationManager.AppSettings["ServerCertFile"]; + var passwd = ConfigurationManager.AppSettings["CertFilePassword"]; + + wssv.SslConfiguration.ServerCertificate = new X509Certificate2 ( + cert, + passwd + ); + */ + + // To change the wait time for the response to the WebSocket Ping or Close. + //wssv.WaitTime = TimeSpan.FromSeconds (2); +#endif // Add the WebSocket services. + wssv.AddWebSocketService ("/Echo"); - wssv.AddWebSocketService ("/Chat"); - // Add the WebSocket service with initializing. - /* + // With initializing. wssv.AddWebSocketService ( "/Chat", s => { s.Prefix = "Anon#"; +#if DEBUG + // To respond to the cookies. + /* + s.CookiesResponder = + (reqCookies, resCookies) => { + foreach (var cookie in reqCookies) { + cookie.Expired = true; - // To send the Sec-WebSocket-Protocol header that has a subprotocol name. - s.Protocol = "chat"; + resCookies.Add (cookie); + } + }; + */ + + // To emit a WebSocket.OnMessage event when receives a ping. + //s.EmitOnPing = true; // To ignore the Sec-WebSocket-Extensions header. - s.IgnoreExtensions = true; + //s.IgnoreExtensions = true; - // To emit a WebSocket.OnMessage event when receives a ping. - s.EmitOnPing = true; + // To disable a delay when send or receive buffer of the underlying + // TCP socket is not full. + s.NoDelay = true; // To validate the Origin header. - s.OriginValidator = val => { + /* + s.OriginValidator = + val => { // Check the value of the Origin header, and return true if valid. + Uri origin; return !val.IsNullOrEmpty () && Uri.TryCreate (val, UriKind.Absolute, out origin) && origin.Host == "localhost"; }; + */ - // To validate the cookies. - s.CookiesValidator = (req, res) => { - // Check the cookies in 'req', and set the cookies to send to - // the client with 'res' if necessary. - foreach (var cookie in req) { - cookie.Expired = true; - res.Add (cookie); - } + // To send the Sec-WebSocket-Protocol header that has a subprotocol + // name. + //s.Protocol = "chat"; + + // To respond to the user headers. + + s.UserHeadersResponder = + (reqHeaders, userHeaders) => { + var val = reqHeaders["RequestForID"]; - return true; // If valid. + if (!val.IsNullOrEmpty ()) + userHeaders[val] = s.ID; }; + +#endif } ); - */ + // Start the server. wssv.Start (); if (wssv.IsListening) { - Console.WriteLine ("Listening on port {0}, and providing WebSocket services:", wssv.Port); + var fmt = "Listening on port {0}, and providing WebSocket services:"; + + Console.WriteLine (fmt, wssv.Port); foreach (var path in wssv.WebSocketServices.Paths) Console.WriteLine ("- {0}", path); } Console.WriteLine ("\nPress Enter key to stop the server..."); + Console.ReadLine (); + // Stop the server. wssv.Stop (); } } diff --git a/Example3/Program.cs b/Example3/Program.cs index b338e10b3..259b5164b 100644 --- a/Example3/Program.cs +++ b/Example3/Program.cs @@ -15,7 +15,7 @@ public static void Main (string[] args) // Create a new instance of the HttpServer class. // // If you would like to provide the secure connection, you should - // create a new instance with the 'secure' parameter set to true or + // create a new instance with the "secure" parameter set to true or // with an https scheme HTTP URL. var httpsv = new HttpServer (4649); @@ -51,24 +51,12 @@ public static void Main (string[] args) // To change the logging level. httpsv.Log.Level = LogLevel.Trace; - // To change the wait time for the response to the WebSocket Ping or Close. - //httpsv.WaitTime = TimeSpan.FromSeconds (2); - - // Not to remove the inactive WebSocket sessions periodically. - //httpsv.KeepClean = false; -#endif - // To provide the secure connection. - /* - var cert = ConfigurationManager.AppSettings["ServerCertFile"]; - var passwd = ConfigurationManager.AppSettings["CertFilePassword"]; - httpsv.SslConfiguration.ServerCertificate = new X509Certificate2 (cert, passwd); - */ - // To provide the HTTP Authentication (Basic/Digest). /* httpsv.AuthenticationSchemes = AuthenticationSchemes.Basic; httpsv.Realm = "WebSocket Test"; - httpsv.UserCredentialsFinder = id => { + httpsv.UserCredentialsFinder = + id => { var name = id.Name; // Return user name, password, and roles. @@ -78,14 +66,33 @@ public static void Main (string[] args) }; */ + // To remove the inactive WebSocket sessions periodically. + //httpsv.KeepClean = true; + // To resolve to wait for socket in TIME_WAIT state. //httpsv.ReuseAddress = true; + // To provide the secure connection. + /* + var cert = ConfigurationManager.AppSettings["ServerCertFile"]; + var passwd = ConfigurationManager.AppSettings["CertFilePassword"]; + + httpsv.SslConfiguration.ServerCertificate = new X509Certificate2 ( + cert, + passwd + ); + */ + + // To change the wait time for the response to the WebSocket Ping or Close. + //httpsv.WaitTime = TimeSpan.FromSeconds (2); +#endif // Set the document root path. - httpsv.DocumentRootPath = ConfigurationManager.AppSettings["DocumentRootPath"]; + httpsv.DocumentRootPath = ConfigurationManager + .AppSettings["DocumentRootPath"]; // Set the HTTP GET request event. - httpsv.OnGet += (sender, e) => { + httpsv.OnGet += + (sender, e) => { var req = e.Request; var res = e.Response; @@ -117,62 +124,86 @@ public static void Main (string[] args) }; // Add the WebSocket services. + httpsv.AddWebSocketService ("/Echo"); - httpsv.AddWebSocketService ("/Chat"); - // Add the WebSocket service with initializing. - /* + // With initializing. httpsv.AddWebSocketService ( "/Chat", s => { s.Prefix = "Anon#"; +#if DEBUG + // To respond to the cookies. + /* + s.CookiesResponder = + (reqCookies, resCookies) => { + foreach (var cookie in reqCookies) { + cookie.Expired = true; + + resCookies.Add (cookie); + } + }; + */ - // To send the Sec-WebSocket-Protocol header that has a subprotocol name. - s.Protocol = "chat"; + // To emit a WebSocket.OnMessage event when receives a ping. + //s.EmitOnPing = true; // To ignore the Sec-WebSocket-Extensions header. - s.IgnoreExtensions = true; + //s.IgnoreExtensions = true; - // To emit a WebSocket.OnMessage event when receives a ping. - s.EmitOnPing = true; + // To disable a delay when send or receive buffer of the underlying + // TCP socket is not full. + s.NoDelay = true; // To validate the Origin header. - s.OriginValidator = val => { + /* + s.OriginValidator = + val => { // Check the value of the Origin header, and return true if valid. + Uri origin; return !val.IsNullOrEmpty () && Uri.TryCreate (val, UriKind.Absolute, out origin) && origin.Host == "localhost"; }; + */ + + // To send the Sec-WebSocket-Protocol header that has a subprotocol + // name. + //s.Protocol = "chat"; - // To validate the cookies. - s.CookiesValidator = (req, res) => { - // Check the cookies in 'req', and set the cookies to send to - // the client with 'res' if necessary. - foreach (var cookie in req) { - cookie.Expired = true; - res.Add (cookie); - } + // To respond to the user headers. + + s.UserHeadersResponder = + (reqHeaders, userHeaders) => { + var val = reqHeaders["RequestForID"]; - return true; // If valid. + if (!val.IsNullOrEmpty ()) + userHeaders[val] = s.ID; }; + +#endif } ); - */ + // Start the server. httpsv.Start (); if (httpsv.IsListening) { - Console.WriteLine ("Listening on port {0}, and providing WebSocket services:", httpsv.Port); + var fmt = "Listening on port {0}, and providing WebSocket services:"; + + Console.WriteLine (fmt, httpsv.Port); foreach (var path in httpsv.WebSocketServices.Paths) Console.WriteLine ("- {0}", path); } Console.WriteLine ("\nPress Enter key to stop the server..."); + Console.ReadLine (); + // Stop the server. httpsv.Stop (); } } diff --git a/LICENSE.txt b/LICENSE.txt index f7473a9b6..b8fa987a2 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2010-2022 sta.blockhead +Copyright (c) 2010-2025 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 diff --git a/README.md b/README.md index 27fb489d3..35f94d454 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ websocket-sharp supports: - [Per-message Compression](#per-message-compression) extension - [Secure Connection](#secure-connection) - [HTTP Authentication](#http-authentication) -- [Query string, Origin header, and Cookies](#query-string-origin-header-and-cookies) +- [Query string, Origin header, Cookies, and User headers](#query-string-origin-header-cookies-and-user-headers) - [Connecting through the HTTP proxy server](#connecting-through-the-http-proxy-server) -- .NET Framework **3.5** or later (includes compatible environment such as [Mono]) +- .NET Framework **3.5** or later versions of .NET Framework (includes compatible environment such as [Mono]) ## Branches ## @@ -43,25 +43,6 @@ You can add websocket-sharp to your project with the NuGet Package Manager, by u PM> Install-Package WebSocketSharp -Pre -### Unity Asset Store ### - -websocket-sharp is available on the Unity Asset Store (Sorry, Not available now). - -- [WebSocket-Sharp for Unity] - -It works with **Unity Free**, but there are some limitations: - -- [Security Sandbox of the Webplayer] (The server is not available in Web Player) -- [WebGL Networking] (Not available in WebGL) -- Incompatible platform (Not available for such UWP) -- Lack of dll for the System.IO.Compression (The compression extension is not available on Windows) -- .NET Socket Support for iOS/Android (iOS/Android Pro is required if your Unity is earlier than Unity 5) -- .NET API 2.0 compatibility level for iOS/Android - -.NET API 2.0 compatibility level for iOS/Android may require to fix lack of some features for later than .NET Framework 2.0, such as the `System.Func<...>` delegates (so i have added them in the asset package). - -And it is priced at **US$15**. I believe your $15 makes this project more better, **Thank you!** - ## Usage ## ### WebSocket Client ### @@ -78,7 +59,7 @@ namespace Example { using (var ws = new WebSocket ("ws://dragonsnest.far/Laputa")) { ws.OnMessage += (sender, e) => - Console.WriteLine ("Laputa says: " + e.Data); + Console.WriteLine ("Laputa says: " + e.Data); ws.Connect (); ws.Send ("BALUS"); @@ -127,20 +108,20 @@ This event occurs when the WebSocket connection has been established. ```csharp ws.OnOpen += (sender, e) => { - ... - }; + ... + }; ``` `System.EventArgs.Empty` is passed as `e`, so you do not need to use it. ##### WebSocket.OnMessage Event ##### -This event occurs when the `WebSocket` receives a message. +This event occurs when the `WebSocket` instance receives a message. ```csharp ws.OnMessage += (sender, e) => { - ... - }; + ... + }; ``` A `WebSocketSharp.MessageEventArgs` instance is passed as `e`. @@ -172,23 +153,23 @@ And if you would like to notify that a **ping** has been received, via this even ```csharp ws.EmitOnPing = true; ws.OnMessage += (sender, e) => { - if (e.IsPing) { - // Do something to notify that a ping has been received. - ... + if (e.IsPing) { + // Do something to notify that a ping has been received. + ... - return; - } - }; + return; + } + }; ``` ##### WebSocket.OnError Event ##### -This event occurs when the `WebSocket` gets an error. +This event occurs when the `WebSocket` instance gets an error. ```csharp ws.OnError += (sender, e) => { - ... - }; + ... + }; ``` A `WebSocketSharp.ErrorEventArgs` instance is passed as `e`. @@ -205,8 +186,8 @@ This event occurs when the WebSocket connection has been closed. ```csharp ws.OnClose += (sender, e) => { - ... - }; + ... + }; ``` A `WebSocketSharp.CloseEventArgs` instance is passed as `e`. @@ -237,7 +218,7 @@ ws.Send (data); The `WebSocket.Send` method is overloaded. -You can use the `WebSocket.Send (string)`, `WebSocket.Send (byte[])`, or `WebSocket.Send (System.IO.FileInfo)` method to send the data. +You can use the `WebSocket.Send (string)`, `WebSocket.Send (byte[])`, `WebSocket.Send (System.IO.FileInfo)`, or `WebSocket.Send (System.IO.Stream, int)` method to send the data. If you would like to send the data asynchronously, you should use the `WebSocket.SendAsync` method. @@ -533,7 +514,9 @@ If you would like to provide the Digest authentication, you should set such as t wssv.AuthenticationSchemes = AuthenticationSchemes.Digest; ``` -### Query string, Origin header, and Cookies ### +### Query string, Origin header, Cookies, and User headers ### + +#### Query string #### As a WebSocket client, if you would like to send the query string in the handshake request, you should create a new instance of the `WebSocket` class with a WebSocket URL that includes the [Query] string parameters. @@ -541,18 +524,6 @@ As a WebSocket client, if you would like to send the query string in the handsha var ws = new WebSocket ("ws://example.com/?name=nobita"); ``` -If you would like to send the Origin header in the handshake request, you should set the `WebSocket.Origin` property to an allowable value as the [Origin] header before calling the connect method. - -```csharp -ws.Origin = "http://example.com/"; -``` - -And if you would like to send the cookies in the handshake request, you should set any cookie by using the `WebSocket.SetCookie (WebSocketSharp.Net.Cookie)` method before calling the connect method. - -```csharp -ws.SetCookie (new Cookie ("name", "nobita")); -``` - As a WebSocket server, if you would like to get the query string included in a handshake request, you should access the `WebSocketBehavior.QueryString` property, such as the following. ```csharp @@ -570,34 +541,86 @@ public class Chat : WebSocketBehavior } ``` -If you would like to get the value of the Origin header included in a handshake request, you should access the `WebSocketBehavior.Context.Origin` property. +#### Origin header #### -If you would like to get the cookies included in a handshake request, you should access the `WebSocketBehavior.Context.CookieCollection` property. +As a WebSocket client, if you would like to send the Origin header in the handshake request, you should set the `WebSocket.Origin` property to an allowable value as the [Origin] header before calling the connect method. + +```csharp +ws.Origin = "http://example.com/"; +``` -And if you would like to validate the Origin header, cookies, or both, you should set each validation for it with your `WebSocketBehavior`, for example, by using the `WebSocketServer.AddWebSocketService (string, Action)` method with initializing, such as the following. +As a WebSocket server, if you would like to validate the Origin header, you should set a validation for it with your `WebSocketBehavior`, for example, by using the `WebSocketServer.AddWebSocketService (string, Action)` method with initializing, such as the following. ```csharp wssv.AddWebSocketService ( "/Chat", s => { - s.OriginValidator = val => { + s.OriginValidator = + val => { // Check the value of the Origin header, and return true if valid. + Uri origin; return !val.IsNullOrEmpty () && Uri.TryCreate (val, UriKind.Absolute, out origin) && origin.Host == "example.com"; }; + } +); +``` + +#### Cookies #### + +As a WebSocket client, if you would like to send the cookies in the handshake request, you should set any cookie by using the `WebSocket.SetCookie (WebSocketSharp.Net.Cookie)` method before calling the connect method. + +```csharp +ws.SetCookie (new Cookie ("name", "nobita")); +``` + +As a WebSocket server, if you would like to respond to the cookies, you should set a response action for it with your `WebSocketBehavior`, for example, by using the `WebSocketServer.AddWebSocketService (string, Action)` method with initializing, such as the following. - s.CookiesValidator = (req, res) => { - // Check the cookies in 'req', and set the cookies to send to - // the client with 'res' if necessary. - foreach (var cookie in req) { +```csharp +wssv.AddWebSocketService ( + "/Chat", + s => { + s.CookiesResponder = + (reqCookies, resCookies) => { + foreach (var cookie in reqCookies) { cookie.Expired = true; - res.Add (cookie); + + resCookies.Add (cookie); } + }; + } +); +``` + +#### User headers #### + +As a WebSocket client, if you would like to send the user headers in the handshake request, you should set any user defined header by using the `WebSocket.SetUserHeader (string, string)` method before calling the connect method. + +```csharp +ws.SetUserHeader ("RequestForID", "ID"); +``` + +And if you would like to get the user headers included in the handshake response, you should access the `WebSocket.HandshakeResponseHeaders` property after the handshake is done. + +```csharp +var id = ws.HandshakeResponseHeaders["ID"]; +``` + +As a WebSocket server, if you would like to respond to the user headers, you should set a response action for it with your `WebSocketBehavior`, for example, by using the `WebSocketServer.AddWebSocketService (string, Action)` method with initializing, such as the following. + +```csharp +wssv.AddWebSocketService ( + "/Chat", + s => { + s.UserHeadersResponder = + (reqHeaders, userHeaders) => { + var val = reqHeaders["RequestForID"]; - return true; // If valid. + if (!val.IsNullOrEmpty ()) + userHeaders[val] = s.ID; }; } ); @@ -688,12 +711,9 @@ websocket-sharp is provided under [The MIT License]. [NuGet Gallery: websocket-sharp]: http://www.nuget.org/packages/WebSocketSharp [Origin]: http://tools.ietf.org/html/rfc6454#section-7 [Query]: http://tools.ietf.org/html/rfc3986#section-3.4 -[Security Sandbox of the Webplayer]: http://docs.unity3d.com/Manual/SecuritySandbox.html [Squid]: http://www.squid-cache.org [The MIT License]: https://raw.github.com/sta/websocket-sharp/master/LICENSE.txt [Unity]: http://unity3d.com -[WebGL Networking]: http://docs.unity3d.com/Manual/webgl-networking.html -[WebSocket-Sharp for Unity]: http://u3d.as/content/sta-blockhead/websocket-sharp-for-unity [api]: http://www.w3.org/TR/websockets [api_ja]: http://www.hcn.zaq.ne.jp/___/WEB/WebSocket-ja.html [context take over]: https://datatracker.ietf.org/doc/html/rfc7692#section-7.1.1 diff --git a/websocket-sharp/CloseEventArgs.cs b/websocket-sharp/CloseEventArgs.cs index 8127ce418..8aa46e2d2 100644 --- a/websocket-sharp/CloseEventArgs.cs +++ b/websocket-sharp/CloseEventArgs.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2019 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -35,19 +35,20 @@ namespace WebSocketSharp /// /// /// - /// That event occurs when the WebSocket connection has been closed. + /// The close event occurs when the WebSocket connection has been closed. /// /// - /// If you would like to get the reason for the connection close, you should - /// access the or property. + /// If you would like to get the reason for the connection close, + /// you should access the or + /// property. /// /// public class CloseEventArgs : EventArgs { #region Private Fields - private bool _clean; private PayloadData _payloadData; + private bool _wasClean; #endregion @@ -56,13 +57,7 @@ public class CloseEventArgs : EventArgs internal CloseEventArgs (PayloadData payloadData, bool clean) { _payloadData = payloadData; - _clean = clean; - } - - internal CloseEventArgs (ushort code, string reason, bool clean) - { - _payloadData = new PayloadData (code, reason); - _clean = clean; + _wasClean = clean; } #endregion @@ -73,8 +68,13 @@ internal CloseEventArgs (ushort code, string reason, bool clean) /// Gets the status code for the connection close. /// /// - /// A that represents the status code for - /// the connection close if present. + /// + /// A that represents the status code for + /// the connection close. + /// + /// + /// 1005 (no status) if not present. + /// /// public ushort Code { get { @@ -86,8 +86,13 @@ public ushort Code { /// Gets the reason for the connection close. /// /// - /// A that represents the reason for - /// the connection close if present. + /// + /// A that represents the reason for + /// the connection close. + /// + /// + /// An empty string if not present. + /// /// public string Reason { get { @@ -104,7 +109,7 @@ public string Reason { /// public bool WasClean { get { - return _clean; + return _wasClean; } } diff --git a/websocket-sharp/ErrorEventArgs.cs b/websocket-sharp/ErrorEventArgs.cs index 41502ab08..c02d0e000 100644 --- a/websocket-sharp/ErrorEventArgs.cs +++ b/websocket-sharp/ErrorEventArgs.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2016 sta.blockhead + * Copyright (c) 2012-2022 sta.blockhead * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -42,14 +42,15 @@ namespace WebSocketSharp /// /// /// - /// That event occurs when the gets an error. + /// The error event occurs when the interface + /// gets an error. /// /// /// If you would like to get the error message, you should access /// the property. /// /// - /// And if the error is due to an exception, you can get it by accessing + /// If the error is due to an exception, you can get it by accessing /// the property. /// /// @@ -83,8 +84,13 @@ internal ErrorEventArgs (string message, Exception exception) /// Gets the exception that caused the error. /// /// - /// An instance that represents the cause of - /// the error if it is due to an exception; otherwise, . + /// + /// An instance that represents + /// the cause of the error. + /// + /// + /// if not present. + /// /// public Exception Exception { get { diff --git a/websocket-sharp/Ext.cs b/websocket-sharp/Ext.cs index 6d39410a0..2540f803e 100644 --- a/websocket-sharp/Ext.cs +++ b/websocket-sharp/Ext.cs @@ -3,9 +3,9 @@ * Ext.cs * * Some parts of this code are derived from Mono (http://www.mono-project.com): - * - GetStatusDescription is derived from HttpListenerResponse.cs (System.Net) - * - MaybeUri is derived from Uri.cs (System) - * - isPredefinedScheme is derived from Uri.cs (System) + * - The GetStatusDescription method is derived from HttpListenerResponse.cs (System.Net) + * - The MaybeUri method is derived from Uri.cs (System) + * - The isPredefinedScheme method is derived from Uri.cs (System) * * The MIT License * @@ -14,7 +14,7 @@ * Copyright (c) 2003 Ben Maurer * Copyright (c) 2003, 2005, 2009 Novell, Inc. (http://www.novell.com) * Copyright (c) 2009 Stephane Delcroix - * Copyright (c) 2010-2022 sta.blockhead + * Copyright (c) 2010-2025 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 @@ -202,21 +202,19 @@ internal static byte[] Append (this ushort code, string reason) } internal static byte[] Compress ( - this byte[] data, CompressionMethod method + this byte[] data, + CompressionMethod method ) { - return method == CompressionMethod.Deflate - ? data.compress () - : data; + return method == CompressionMethod.Deflate ? data.compress () : data; } internal static Stream Compress ( - this Stream stream, CompressionMethod method + this Stream stream, + CompressionMethod method ) { - return method == CompressionMethod.Deflate - ? stream.compress () - : stream; + return method == CompressionMethod.Deflate ? stream.compress () : stream; } internal static bool Contains (this string value, params char[] anyOf) @@ -227,7 +225,8 @@ internal static bool Contains (this string value, params char[] anyOf) } internal static bool Contains ( - this NameValueCollection collection, string name + this NameValueCollection collection, + string name ) { return collection[name] != null; @@ -254,7 +253,8 @@ StringComparison comparisonTypeForValue } internal static bool Contains ( - this IEnumerable source, Func condition + this IEnumerable source, + Func condition ) { foreach (T elm in source) { @@ -307,7 +307,9 @@ internal static T[] Copy (this T[] sourceArray, long length) } internal static void CopyTo ( - this Stream sourceStream, Stream destinationStream, int bufferLength + this Stream sourceStream, + Stream destinationStream, + int bufferLength ) { var buff = new byte[bufferLength]; @@ -365,16 +367,16 @@ Action error } internal static byte[] Decompress ( - this byte[] data, CompressionMethod method + this byte[] data, + CompressionMethod method ) { - return method == CompressionMethod.Deflate - ? data.decompress () - : data; + return method == CompressionMethod.Deflate ? data.decompress () : data; } internal static Stream Decompress ( - this Stream stream, CompressionMethod method + this Stream stream, + CompressionMethod method ) { return method == CompressionMethod.Deflate @@ -383,7 +385,8 @@ internal static Stream Decompress ( } internal static byte[] DecompressToArray ( - this Stream stream, CompressionMethod method + this Stream stream, + CompressionMethod method ) { return method == CompressionMethod.Deflate @@ -392,7 +395,9 @@ internal static byte[] DecompressToArray ( } internal static void Emit ( - this EventHandler eventHandler, object sender, EventArgs e + this EventHandler eventHandler, + object sender, + EventArgs e ) { if (eventHandler == null) @@ -402,7 +407,9 @@ internal static void Emit ( } internal static void Emit ( - this EventHandler eventHandler, object sender, TEventArgs e + this EventHandler eventHandler, + object sender, + TEventArgs e ) where TEventArgs : EventArgs { @@ -428,10 +435,12 @@ internal static string GetAbsolutePath (this Uri uri) } internal static CookieCollection GetCookies ( - this NameValueCollection headers, bool response + this NameValueCollection headers, + bool response ) { - var val = headers[response ? "Set-Cookie" : "Cookie"]; + var name = response ? "Set-Cookie" : "Cookie"; + var val = headers[name]; return val != null ? CookieCollection.Parse (val, response) @@ -445,32 +454,37 @@ internal static string GetDnsSafeHost (this Uri uri, bool bracketIPv6) : uri.DnsSafeHost; } - internal static string GetMessage (this CloseStatusCode code) + internal static string GetErrorMessage (this ushort code) { switch (code) { - case CloseStatusCode.ProtocolError: + case 1002: return "A protocol error has occurred."; - case CloseStatusCode.UnsupportedData: + case 1003: return "Unsupported data has been received."; - case CloseStatusCode.Abnormal: + case 1006: return "An abnormal error has occurred."; - case CloseStatusCode.InvalidData: + case 1007: return "Invalid data has been received."; - case CloseStatusCode.PolicyViolation: + case 1008: return "A policy violation has occurred."; - case CloseStatusCode.TooBig: + case 1009: return "A too big message has been received."; - case CloseStatusCode.MandatoryExtension: + case 1010: return "The client did not receive expected extension(s)."; - case CloseStatusCode.ServerError: + case 1011: return "The server got an internal error."; - case CloseStatusCode.TlsHandshakeFailure: + case 1015: return "An error has occurred during a TLS handshake."; default: return String.Empty; } } + internal static string GetErrorMessage (this CloseStatusCode code) + { + return ((ushort) code).GetErrorMessage (); + } + internal static string GetName (this string nameAndValue, char separator) { var idx = nameAndValue.IndexOf (separator); @@ -480,12 +494,22 @@ internal static string GetName (this string nameAndValue, char separator) internal static string GetUTF8DecodedString (this byte[] bytes) { - return Encoding.UTF8.GetString (bytes); + try { + return Encoding.UTF8.GetString (bytes); + } + catch { + return null; + } } internal static byte[] GetUTF8EncodedBytes (this string s) { - return Encoding.UTF8.GetBytes (s); + try { + return Encoding.UTF8.GetBytes (s); + } + catch { + return null; + } } internal static string GetValue (this string nameAndValue, char separator) @@ -494,7 +518,9 @@ internal static string GetValue (this string nameAndValue, char separator) } internal static string GetValue ( - this string nameAndValue, char separator, bool unquote + this string nameAndValue, + char separator, + bool unquote ) { var idx = nameAndValue.IndexOf (separator); @@ -508,37 +534,25 @@ internal static string GetValue ( } internal static bool IsCompressionExtension ( - this string value, CompressionMethod method + this string value, + CompressionMethod method ) { - var val = method.ToExtensionString (); + var extStr = method.ToExtensionString (); var compType = StringComparison.Ordinal; - return value.StartsWith (val, compType); - } - - internal static bool IsControl (this byte opcode) - { - return opcode > 0x7 && opcode < 0x10; - } - - internal static bool IsControl (this Opcode opcode) - { - return opcode >= Opcode.Close; - } - - internal static bool IsData (this byte opcode) - { - return opcode == 0x1 || opcode == 0x2; + return value.StartsWith (extStr, compType); } - internal static bool IsData (this Opcode opcode) + internal static bool IsDefined (this CloseStatusCode code) { - return opcode == Opcode.Text || opcode == Opcode.Binary; + return Enum.IsDefined (typeof (CloseStatusCode), code); } internal static bool IsEqualTo ( - this int value, char c, Action beforeComparing + this int value, + char c, + Action beforeComparing ) { beforeComparing (value); @@ -563,7 +577,12 @@ internal static bool IsPortNumber (this int value) return value > 0 && value < 65536; } - internal static bool IsReserved (this ushort code) + internal static bool IsReserved (this CloseStatusCode code) + { + return ((ushort) code).IsReservedStatusCode (); + } + + internal static bool IsReservedStatusCode (this ushort code) { return code == 1004 || code == 1005 @@ -571,15 +590,7 @@ internal static bool IsReserved (this ushort code) || code == 1015; } - internal static bool IsReserved (this CloseStatusCode code) - { - return code == CloseStatusCode.Undefined - || code == CloseStatusCode.NoStatus - || code == CloseStatusCode.Abnormal - || code == CloseStatusCode.TlsHandshakeFailure; - } - - internal static bool IsSupported (this byte opcode) + internal static bool IsSupportedOpcode (this int opcode) { return Enum.IsDefined (typeof (Opcode), opcode); } @@ -634,14 +645,15 @@ internal static bool IsToken (this string value) } internal static bool KeepsAlive ( - this NameValueCollection headers, Version version + this NameValueCollection headers, + Version version ) { var compType = StringComparison.OrdinalIgnoreCase; - return version < HttpVersion.Version11 - ? headers.Contains ("Connection", "keep-alive", compType) - : !headers.Contains ("Connection", "close", compType); + return version > HttpVersion.Version10 + ? !headers.Contains ("Connection", "close", compType) + : headers.Contains ("Connection", "keep-alive", compType); } internal static bool MaybeUri (this string value) @@ -694,7 +706,9 @@ internal static byte[] ReadBytes (this Stream stream, int length) } internal static byte[] ReadBytes ( - this Stream stream, long length, int bufferLength + this Stream stream, + long length, + int bufferLength ) { using (var dest = new MemoryStream ()) { @@ -832,6 +846,7 @@ Action error dest.Close (); var ret = dest.ToArray (); + completed (ret); } @@ -845,8 +860,9 @@ Action error if (nread == len) { if (completed != null) { dest.Close (); - + var ret = dest.ToArray (); + completed (ret); } @@ -895,7 +911,8 @@ internal static T[] Reverse (this T[] array) } internal static IEnumerable SplitHeaderValue ( - this string value, params char[] separators + this string value, + params char[] separators ) { var len = value.Length; @@ -907,6 +924,7 @@ internal static IEnumerable SplitHeaderValue ( for (var i = 0; i <= end; i++) { var c = value[i]; + buff.Append (c); if (c == '"') { @@ -993,7 +1011,8 @@ internal static CompressionMethod ToCompressionMethod (this string value) } internal static string ToExtensionString ( - this CompressionMethod method, params string[] parameters + this CompressionMethod method, + params string[] parameters ) { if (method == CompressionMethod.None) @@ -1043,7 +1062,8 @@ this IEnumerable source } internal static string ToString ( - this System.Net.IPAddress address, bool bracketIPv6 + this System.Net.IPAddress address, + bool bracketIPv6 ) { return bracketIPv6 @@ -1094,7 +1114,8 @@ internal static string TrimSlashOrBackslashFromEnd (this string value) } internal static bool TryCreateVersion ( - this string versionString, out Version result + this string versionString, + out Version result ) { result = null; @@ -1110,7 +1131,9 @@ internal static bool TryCreateVersion ( } internal static bool TryCreateWebSocketUri ( - this string uriString, out Uri result, out string message + this string uriString, + out Uri result, + out string message ) { result = null; @@ -1134,7 +1157,7 @@ internal static bool TryCreateWebSocketUri ( var valid = schm == "ws" || schm == "wss"; if (!valid) { - message = "The scheme part is not 'ws' or 'wss'."; + message = "The scheme part is not \"ws\" or \"wss\"."; return false; } @@ -1173,7 +1196,8 @@ internal static bool TryCreateWebSocketUri ( } internal static bool TryGetUTF8DecodedString ( - this byte[] bytes, out string s + this byte[] bytes, + out string s ) { s = null; @@ -1189,7 +1213,8 @@ internal static bool TryGetUTF8DecodedString ( } internal static bool TryGetUTF8EncodedBytes ( - this string s, out byte[] bytes + this string s, + out byte[] bytes ) { bytes = null; @@ -1205,7 +1230,8 @@ internal static bool TryGetUTF8EncodedBytes ( } internal static bool TryOpenRead ( - this FileInfo fileInfo, out FileStream fileStream + this FileInfo fileInfo, + out FileStream fileStream ) { fileStream = null; @@ -1240,7 +1266,8 @@ internal static string Unquote (this string value) } internal static bool Upgrades ( - this NameValueCollection headers, string protocol + this NameValueCollection headers, + string protocol ) { var compType = StringComparison.OrdinalIgnoreCase; @@ -1262,7 +1289,9 @@ internal static string UrlEncode (this string value, Encoding encoding) } internal static void WriteBytes ( - this Stream stream, byte[] bytes, int bufferLength + this Stream stream, + byte[] bytes, + int bufferLength ) { using (var src = new MemoryStream (bytes)) diff --git a/websocket-sharp/Fin.cs b/websocket-sharp/Fin.cs index 8965c378e..36622d7e5 100644 --- a/websocket-sharp/Fin.cs +++ b/websocket-sharp/Fin.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2015 sta.blockhead + * Copyright (c) 2012-2025 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 @@ -35,9 +35,10 @@ namespace WebSocketSharp /// /// /// The values of this enumeration are defined in - /// Section 5.2 of RFC 6455. + /// + /// Section 5.2 of RFC 6455. /// - internal enum Fin : byte + internal enum Fin { /// /// Equivalent to numeric value 0. Indicates more frames of a message follow. diff --git a/websocket-sharp/HttpBase.cs b/websocket-sharp/HttpBase.cs index e386ecc84..c4a244f45 100644 --- a/websocket-sharp/HttpBase.cs +++ b/websocket-sharp/HttpBase.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2022 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -96,8 +96,10 @@ protected string HeaderSection { get { var buff = new StringBuilder (64); + var fmt = "{0}: {1}{2}"; + foreach (var key in _headers.AllKeys) - buff.AppendFormat ("{0}: {1}{2}", key, _headers[key], CrLf); + buff.AppendFormat (fmt, key, _headers[key], CrLf); buff.Append (CrLf); @@ -161,13 +163,13 @@ private static byte[] readMessageBodyFrom (Stream stream, string length) long len; if (!Int64.TryParse (length, out len)) { - var msg = "It cannot be parsed."; + var msg = "It could not be parsed."; throw new ArgumentException (msg, "length"); } if (len < 0) { - var msg = "It is less than zero."; + var msg = "Less than zero."; throw new ArgumentOutOfRangeException ("length", msg); } @@ -222,10 +224,23 @@ private static string[] readMessageHeaderFrom (Stream stream) #endregion + #region Internal Methods + + internal void WriteTo (Stream stream) + { + var bytes = ToByteArray (); + + stream.Write (bytes, 0, bytes.Length); + } + + #endregion + #region Protected Methods protected static T Read ( - Stream stream, Func parser, int millisecondsTimeout + Stream stream, + Func parser, + int millisecondsTimeout ) where T : HttpBase { @@ -235,6 +250,7 @@ protected static T Read ( var timer = new Timer ( state => { timeout = true; + stream.Close (); }, null, diff --git a/websocket-sharp/HttpRequest.cs b/websocket-sharp/HttpRequest.cs index 8e68b144f..946024d6a 100644 --- a/websocket-sharp/HttpRequest.cs +++ b/websocket-sharp/HttpRequest.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2022 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -81,9 +81,9 @@ internal HttpRequest (string method, string target) internal string RequestLine { get { - return String.Format ( - "{0} {1} HTTP/{2}{3}", _method, _target, ProtocolVersion, CrLf - ); + var fmt = "{0} {1} HTTP/{2}{3}"; + + return String.Format (fmt, _method, _target, ProtocolVersion, CrLf); } } @@ -142,9 +142,10 @@ public string RequestTarget { internal static HttpRequest CreateConnectRequest (Uri targetUri) { + var fmt = "{0}:{1}"; var host = targetUri.DnsSafeHost; var port = targetUri.Port; - var authority = String.Format ("{0}:{1}", host, port); + var authority = String.Format (fmt, host, port); var ret = new HttpRequest ("CONNECT", authority); @@ -161,10 +162,10 @@ internal static HttpRequest CreateWebSocketHandshakeRequest (Uri targetUri) var port = targetUri.Port; var schm = targetUri.Scheme; - var defaultPort = (port == 80 && schm == "ws") - || (port == 443 && schm == "wss"); + var isDefaultPort = (port == 80 && schm == "ws") + || (port == 443 && schm == "wss"); - headers["Host"] = !defaultPort + headers["Host"] = !isDefaultPort ? targetUri.Authority : targetUri.DnsSafeHost; @@ -176,8 +177,7 @@ internal static HttpRequest CreateWebSocketHandshakeRequest (Uri targetUri) internal HttpResponse GetResponse (Stream stream, int millisecondsTimeout) { - var bytes = ToByteArray (); - stream.Write (bytes, 0, bytes.Length); + WriteTo (stream); return HttpResponse.ReadResponse (stream, millisecondsTimeout); } @@ -213,7 +213,8 @@ internal static HttpRequest Parse (string[] messageHeader) } internal static HttpRequest ReadRequest ( - Stream stream, int millisecondsTimeout + Stream stream, + int millisecondsTimeout ) { return Read (stream, Parse, millisecondsTimeout); @@ -230,7 +231,7 @@ public void SetCookies (CookieCollection cookies) var buff = new StringBuilder (64); - foreach (var cookie in cookies.Sorted) { + foreach (var cookie in cookies.SortedList) { if (cookie.Expired) continue; diff --git a/websocket-sharp/HttpResponse.cs b/websocket-sharp/HttpResponse.cs index 00dccf53f..d511c94fe 100644 --- a/websocket-sharp/HttpResponse.cs +++ b/websocket-sharp/HttpResponse.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2022 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -46,7 +46,10 @@ internal class HttpResponse : HttpBase #region Private Constructors private HttpResponse ( - int code, string reason, Version version, NameValueCollection headers + int code, + string reason, + Version version, + NameValueCollection headers ) : base (version, headers) { @@ -92,10 +95,17 @@ internal string StatusLine { get { return _reason != null ? String.Format ( - "HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf + "HTTP/{0} {1} {2}{3}", + ProtocolVersion, + _code, + _reason, + CrLf ) : String.Format ( - "HTTP/{0} {1}{2}", ProtocolVersion, _code, CrLf + "HTTP/{0} {1}{2}", + ProtocolVersion, + _code, + CrLf ); } } @@ -234,7 +244,8 @@ internal static HttpResponse Parse (string[] messageHeader) } internal static HttpResponse ReadResponse ( - Stream stream, int millisecondsTimeout + Stream stream, + int millisecondsTimeout ) { return Read (stream, Parse, millisecondsTimeout); @@ -251,7 +262,7 @@ public void SetCookies (CookieCollection cookies) var headers = Headers; - foreach (var cookie in cookies.Sorted) { + foreach (var cookie in cookies.SortedList) { var val = cookie.ToResponseString (); headers.Add ("Set-Cookie", val); diff --git a/websocket-sharp/LogData.cs b/websocket-sharp/LogData.cs index 9c0843093..bb3492a9b 100644 --- a/websocket-sharp/LogData.cs +++ b/websocket-sharp/LogData.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2013-2015 sta.blockhead + * Copyright (c) 2013-2024 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 @@ -53,6 +53,7 @@ internal LogData (LogLevel level, StackFrame caller, string message) _level = level; _caller = caller; _message = message ?? String.Empty; + _date = DateTime.Now; } @@ -64,7 +65,8 @@ internal LogData (LogLevel level, StackFrame caller, string message) /// Gets the information of the logging method caller. /// /// - /// A that provides the information of the logging method caller. + /// A that provides the information of + /// the logging method caller. /// public StackFrame Caller { get { @@ -76,7 +78,8 @@ public StackFrame Caller { /// Gets the date and time when the log data was created. /// /// - /// A that represents the date and time when the log data was created. + /// A that represents the date and time when + /// the log data was created. /// public DateTime Date { get { @@ -88,7 +91,12 @@ public DateTime Date { /// Gets the logging level of the log data. /// /// - /// One of the enum values, indicates the logging level of the log data. + /// + /// One of the enum values. + /// + /// + /// It represents the logging level of the log data. + /// /// public LogLevel Level { get { @@ -113,34 +121,36 @@ public string Message { #region Public Methods /// - /// Returns a that represents the current . + /// Returns a string that represents the current instance. /// /// - /// A that represents the current . + /// A that represents the current instance. /// public override string ToString () { - var header = String.Format ("{0}|{1,-5}|", _date, _level); + var date = String.Format ("[{0}]", _date); + var level = String.Format ("{0,-5}", _level.ToString ().ToUpper ()); + var method = _caller.GetMethod (); var type = method.DeclaringType; #if DEBUG - var lineNum = _caller.GetFileLineNumber (); - var headerAndCaller = - String.Format ("{0}{1}.{2}:{3}|", header, type.Name, method.Name, lineNum); + var num = _caller.GetFileLineNumber (); + var caller = String.Format ("{0}.{1}:{2}", type.Name, method.Name, num); #else - var headerAndCaller = String.Format ("{0}{1}.{2}|", header, type.Name, method.Name); + var caller = String.Format ("{0}.{1}", type.Name, method.Name); #endif var msgs = _message.Replace ("\r\n", "\n").TrimEnd ('\n').Split ('\n'); + if (msgs.Length <= 1) - return String.Format ("{0}{1}", headerAndCaller, _message); + return String.Format ("{0} {1} {2} {3}", date, level, caller, _message); + + var buff = new StringBuilder (64); - var buff = new StringBuilder (String.Format ("{0}{1}\n", headerAndCaller, msgs[0]), 64); + buff.AppendFormat ("{0} {1} {2}\n\n", date, level, caller); - var fmt = String.Format ("{{0,{0}}}{{1}}\n", header.Length); - for (var i = 1; i < msgs.Length; i++) - buff.AppendFormat (fmt, "", msgs[i]); + foreach (var msg in msgs) + buff.AppendFormat (" {0}\n", msg); - buff.Length--; return buff.ToString (); } diff --git a/websocket-sharp/LogLevel.cs b/websocket-sharp/LogLevel.cs index ef9967728..5ff1d8fed 100644 --- a/websocket-sharp/LogLevel.cs +++ b/websocket-sharp/LogLevel.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2013-2015 sta.blockhead + * Copyright (c) 2013-2022 sta.blockhead * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -58,6 +58,10 @@ public enum LogLevel /// /// Specifies the top logging level. /// - Fatal + Fatal, + /// + /// Specifies not to output logs. + /// + None } } diff --git a/websocket-sharp/Logger.cs b/websocket-sharp/Logger.cs index 17850e67e..a280ce43f 100644 --- a/websocket-sharp/Logger.cs +++ b/websocket-sharp/Logger.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2013-2015 sta.blockhead + * Copyright (c) 2013-2024 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 @@ -37,17 +37,17 @@ namespace WebSocketSharp /// /// /// - /// If you output a log with lower than the value of the property, + /// If you output a log with lower than the current logging level, /// it cannot be outputted. /// /// - /// The default output action writes a log to the standard output stream and the log file - /// if the property has a valid path to it. + /// The default output method writes a log to the standard output + /// stream and the text file if it has a valid path. /// /// - /// If you would like to use the custom output action, you should set - /// the property to any Action<LogData, string> - /// delegate. + /// If you would like to use the custom output method, you should + /// specify it with the constructor or the + /// property. /// /// public class Logger @@ -67,7 +67,7 @@ public class Logger /// Initializes a new instance of the class. /// /// - /// This constructor initializes the current logging level with . + /// This constructor initializes the logging level with the Error level. /// public Logger () : this (LogLevel.Error, null, null) @@ -76,10 +76,11 @@ public Logger () /// /// Initializes a new instance of the class with - /// the specified logging . + /// the specified logging level. /// /// - /// One of the enum values. + /// One of the enum values that specifies + /// the logging level. /// public Logger (LogLevel level) : this (level, null, null) @@ -88,25 +89,26 @@ public Logger (LogLevel level) /// /// Initializes a new instance of the class with - /// the specified logging , path to the log , - /// and action. + /// the specified logging level, path to the log file, and delegate + /// used to output a log. /// /// - /// One of the enum values. + /// One of the enum values that specifies + /// the logging level. /// /// - /// A that represents the path to the log file. + /// A that specifies the path to the log file. /// /// - /// An Action<LogData, string> delegate that references the method(s) used to - /// output a log. A parameter passed to this delegate is - /// . + /// An that specifies + /// the delegate used to output a log. /// public Logger (LogLevel level, string file, Action output) { _level = level; _file = file; _output = output ?? defaultOutput; + _sync = new object (); } @@ -115,10 +117,10 @@ public Logger (LogLevel level, string file, Action output) #region Public Properties /// - /// Gets or sets the current path to the log file. + /// Gets or sets the path to the log file. /// /// - /// A that represents the current path to the log file if any. + /// A that represents the path to the log file if any. /// public string File { get { @@ -126,11 +128,8 @@ public string File { } set { - lock (_sync) { + lock (_sync) _file = value; - Warn ( - String.Format ("The current path to the log file has been changed to {0}.", _file)); - } } } @@ -141,7 +140,12 @@ public string File { /// A log with lower than the value of this property cannot be outputted. /// /// - /// One of the enum values, specifies the current logging level. + /// + /// One of the enum values. + /// + /// + /// It represents the current logging level. + /// /// public LogLevel Level { get { @@ -149,25 +153,28 @@ public LogLevel Level { } set { - lock (_sync) { + lock (_sync) _level = value; - Warn (String.Format ("The current logging level has been changed to {0}.", _level)); - } } } /// - /// Gets or sets the current output action used to output a log. + /// Gets or sets the delegate used to output a log. /// /// /// - /// An Action<LogData, string> delegate that references the method(s) used to - /// output a log. A parameter passed to this delegate is the value of + /// An delegate. + /// + /// + /// It represents the delegate called when the logger outputs a log. + /// + /// + /// The string parameter passed to the delegate is the value of /// the property. /// /// - /// If the value to set is , the current output action is changed to - /// the default output action. + /// If the value to set is , the default + /// output method is set. /// /// public Action Output { @@ -176,10 +183,8 @@ public Action Output { } set { - lock (_sync) { + lock (_sync) _output = value ?? defaultOutput; - Warn ("The current output action has been changed."); - } } } @@ -189,10 +194,12 @@ public Action Output { private static void defaultOutput (LogData data, string path) { - var log = data.ToString (); - Console.WriteLine (log); + var val = data.ToString (); + + Console.WriteLine (val); + if (path != null && path.Length > 0) - writeToFile (log, path); + writeToFile (val, path); } private void output (string message, LogLevel level) @@ -201,13 +208,18 @@ private void output (string message, LogLevel level) if (_level > level) return; - LogData data = null; try { - data = new LogData (level, new StackFrame (2, true), message); + var 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); + var data = new LogData ( + LogLevel.Fatal, + new StackFrame (0, true), + ex.Message + ); + Console.WriteLine (data.ToString ()); } } @@ -225,14 +237,14 @@ private static void writeToFile (string value, string path) #region Public Methods /// - /// Outputs as a log with . + /// Outputs the specified message as a log with the Debug level. /// /// - /// If the current logging level is higher than , - /// this method doesn't output as a log. + /// If the current logging level is higher than the Debug level, + /// this method does not output the message as a log. /// /// - /// A that represents the message to output as a log. + /// A that specifies the message to output. /// public void Debug (string message) { @@ -243,14 +255,14 @@ public void Debug (string message) } /// - /// Outputs as a log with . + /// Outputs the specified message as a log with the Error level. /// /// - /// If the current logging level is higher than , - /// this method doesn't output as a log. + /// If the current logging level is higher than the Error level, + /// this method does not output the message as a log. /// /// - /// A that represents the message to output as a log. + /// A that specifies the message to output. /// public void Error (string message) { @@ -261,25 +273,28 @@ public void Error (string message) } /// - /// Outputs as a log with . + /// Outputs the specified message as a log with the Fatal level. /// /// - /// A that represents the message to output as a log. + /// A that specifies the message to output. /// public void Fatal (string message) { + if (_level > LogLevel.Fatal) + return; + output (message, LogLevel.Fatal); } /// - /// Outputs as a log with . + /// Outputs the specified message as a log with the Info level. /// /// - /// If the current logging level is higher than , - /// this method doesn't output as a log. + /// If the current logging level is higher than the Info level, + /// this method does not output the message as a log. /// /// - /// A that represents the message to output as a log. + /// A that specifies the message to output. /// public void Info (string message) { @@ -290,14 +305,14 @@ public void Info (string message) } /// - /// Outputs as a log with . + /// Outputs the specified message as a log with the Trace level. /// /// - /// If the current logging level is higher than , - /// this method doesn't output as a log. + /// If the current logging level is higher than the Trace level, + /// this method does not output the message as a log. /// /// - /// A that represents the message to output as a log. + /// A that specifies the message to output. /// public void Trace (string message) { @@ -308,14 +323,14 @@ public void Trace (string message) } /// - /// Outputs as a log with . + /// Outputs the specified message as a log with the Warn level. /// /// - /// If the current logging level is higher than , - /// this method doesn't output as a log. + /// If the current logging level is higher than the Warn level, + /// this method does not output the message as a log. /// /// - /// A that represents the message to output as a log. + /// A that specifies the message to output. /// public void Warn (string message) { diff --git a/websocket-sharp/Mask.cs b/websocket-sharp/Mask.cs index fcafac80c..2958f5a83 100644 --- a/websocket-sharp/Mask.cs +++ b/websocket-sharp/Mask.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2015 sta.blockhead + * Copyright (c) 2012-2025 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 @@ -35,9 +35,10 @@ namespace WebSocketSharp /// /// /// The values of this enumeration are defined in - /// Section 5.2 of RFC 6455. + /// + /// Section 5.2 of RFC 6455. /// - internal enum Mask : byte + internal enum Mask { /// /// Equivalent to numeric value 0. Indicates not masked. diff --git a/websocket-sharp/MessageEventArgs.cs b/websocket-sharp/MessageEventArgs.cs index 7940f98b7..63add90f7 100644 --- a/websocket-sharp/MessageEventArgs.cs +++ b/websocket-sharp/MessageEventArgs.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2016 sta.blockhead + * Copyright (c) 2012-2022 sta.blockhead * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -35,8 +35,8 @@ namespace WebSocketSharp /// /// /// - /// That event occurs when the receives - /// a message or a ping if the + /// The message event occurs when the interface + /// receives a message or a ping if the /// property is set to true. /// /// @@ -97,13 +97,19 @@ internal Opcode Opcode { /// Gets the message data as a . /// /// - /// A that represents the message data if its type is - /// text or ping and if decoding it to a string has successfully done; - /// otherwise, . + /// + /// A that represents the message data + /// if the message type is text or ping. + /// + /// + /// if the message type is binary or + /// the message data could not be UTF-8-decoded. + /// /// public string Data { get { setData (); + return _data; } } @@ -153,6 +159,7 @@ public bool IsText { public byte[] RawData { get { setData (); + return _rawData; } } @@ -168,10 +175,12 @@ private void setData () if (_opcode == Opcode.Binary) { _dataSet = true; + return; } string data; + if (_rawData.TryGetUTF8DecodedString (out data)) _data = data; diff --git a/websocket-sharp/Net/AuthenticationBase.cs b/websocket-sharp/Net/AuthenticationBase.cs deleted file mode 100644 index 107750499..000000000 --- a/websocket-sharp/Net/AuthenticationBase.cs +++ /dev/null @@ -1,151 +0,0 @@ -#region License -/* - * AuthenticationBase.cs - * - * The MIT License - * - * Copyright (c) 2014 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.Specialized; -using System.Text; - -namespace WebSocketSharp.Net -{ - internal abstract class AuthenticationBase - { - #region Private Fields - - private AuthenticationSchemes _scheme; - - #endregion - - #region Internal Fields - - internal NameValueCollection Parameters; - - #endregion - - #region Protected Constructors - - protected AuthenticationBase (AuthenticationSchemes scheme, NameValueCollection parameters) - { - _scheme = scheme; - Parameters = parameters; - } - - #endregion - - #region Public Properties - - public string Algorithm { - get { - return Parameters["algorithm"]; - } - } - - public string Nonce { - get { - return Parameters["nonce"]; - } - } - - public string Opaque { - get { - return Parameters["opaque"]; - } - } - - public string Qop { - get { - return Parameters["qop"]; - } - } - - public string Realm { - get { - return Parameters["realm"]; - } - } - - public AuthenticationSchemes Scheme { - get { - return _scheme; - } - } - - #endregion - - #region Internal Methods - - internal static string CreateNonceValue () - { - var src = new byte[16]; - var rand = new Random (); - rand.NextBytes (src); - - var res = new StringBuilder (32); - foreach (var b in src) - res.Append (b.ToString ("x2")); - - return res.ToString (); - } - - internal static NameValueCollection ParseParameters (string value) - { - var res = new NameValueCollection (); - foreach (var param in value.SplitHeaderValue (',')) { - var i = param.IndexOf ('='); - var name = i > 0 ? param.Substring (0, i).Trim () : null; - var val = i < 0 - ? param.Trim ().Trim ('"') - : i < param.Length - 1 - ? param.Substring (i + 1).Trim ().Trim ('"') - : String.Empty; - - res.Add (name, val); - } - - return res; - } - - internal abstract string ToBasicString (); - - internal abstract string ToDigestString (); - - #endregion - - #region Public Methods - - public override string ToString () - { - return _scheme == AuthenticationSchemes.Basic - ? ToBasicString () - : _scheme == AuthenticationSchemes.Digest - ? ToDigestString () - : String.Empty; - } - - #endregion - } -} diff --git a/websocket-sharp/Net/AuthenticationChallenge.cs b/websocket-sharp/Net/AuthenticationChallenge.cs index 3472204b9..726740801 100644 --- a/websocket-sharp/Net/AuthenticationChallenge.cs +++ b/websocket-sharp/Net/AuthenticationChallenge.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2013-2014 sta.blockhead + * Copyright (c) 2013-2024 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 @@ -32,27 +32,52 @@ namespace WebSocketSharp.Net { - internal class AuthenticationChallenge : AuthenticationBase + internal class AuthenticationChallenge { + #region Private Fields + + private NameValueCollection _parameters; + private AuthenticationSchemes _scheme; + + #endregion + #region Private Constructors - private AuthenticationChallenge (AuthenticationSchemes scheme, NameValueCollection parameters) - : base (scheme, parameters) + private AuthenticationChallenge ( + AuthenticationSchemes scheme, + NameValueCollection parameters + ) { + _scheme = scheme; + _parameters = parameters; } #endregion #region Internal Constructors - internal AuthenticationChallenge (AuthenticationSchemes scheme, string realm) - : base (scheme, new NameValueCollection ()) + internal AuthenticationChallenge ( + AuthenticationSchemes scheme, + string realm + ) + : this (scheme, new NameValueCollection ()) { - Parameters["realm"] = realm; + _parameters["realm"] = realm; + if (scheme == AuthenticationSchemes.Digest) { - Parameters["nonce"] = CreateNonceValue (); - Parameters["algorithm"] = "MD5"; - Parameters["qop"] = "auth"; + _parameters["nonce"] = CreateNonceValue (); + _parameters["algorithm"] = "MD5"; + _parameters["qop"] = "auth"; + } + } + + #endregion + + #region Internal Properties + + internal NameValueCollection Parameters { + get { + return _parameters; } } @@ -60,15 +85,51 @@ internal AuthenticationChallenge (AuthenticationSchemes scheme, string realm) #region Public Properties + public string Algorithm { + get { + return _parameters["algorithm"]; + } + } + public string Domain { get { - return Parameters["domain"]; + return _parameters["domain"]; + } + } + + public string Nonce { + get { + return _parameters["nonce"]; + } + } + + public string Opaque { + get { + return _parameters["opaque"]; + } + } + + public string Qop { + get { + return _parameters["qop"]; + } + } + + public string Realm { + get { + return _parameters["realm"]; + } + } + + public AuthenticationSchemes Scheme { + get { + return _scheme; } } public string Stale { get { - return Parameters["stale"]; + return _parameters["stale"]; } } @@ -86,59 +147,132 @@ internal static AuthenticationChallenge CreateDigestChallenge (string realm) return new AuthenticationChallenge (AuthenticationSchemes.Digest, realm); } + internal static string CreateNonceValue () + { + var rand = new Random (); + var bytes = new byte[16]; + + rand.NextBytes (bytes); + + var buff = new StringBuilder (32); + + foreach (var b in bytes) + buff.Append (b.ToString ("x2")); + + return buff.ToString (); + } + internal static AuthenticationChallenge Parse (string value) { var chal = value.Split (new[] { ' ' }, 2); + if (chal.Length != 2) return null; var schm = chal[0].ToLower (); - return schm == "basic" - ? new AuthenticationChallenge ( - AuthenticationSchemes.Basic, ParseParameters (chal[1])) - : schm == "digest" - ? new AuthenticationChallenge ( - AuthenticationSchemes.Digest, ParseParameters (chal[1])) - : null; + + if (schm == "basic") { + var parameters = ParseParameters (chal[1]); + + return new AuthenticationChallenge ( + AuthenticationSchemes.Basic, + parameters + ); + } + + if (schm == "digest") { + var parameters = ParseParameters (chal[1]); + + return new AuthenticationChallenge ( + AuthenticationSchemes.Digest, + parameters + ); + } + + return null; } - internal override string ToBasicString () + internal static NameValueCollection ParseParameters (string value) { - return String.Format ("Basic realm=\"{0}\"", Parameters["realm"]); + var ret = new NameValueCollection (); + + foreach (var param in value.SplitHeaderValue (',')) { + var i = param.IndexOf ('='); + + var name = i > 0 ? param.Substring (0, i).Trim () : null; + var val = i < 0 + ? param.Trim ().Trim ('"') + : i < param.Length - 1 + ? param.Substring (i + 1).Trim ().Trim ('"') + : String.Empty; + + ret.Add (name, val); + } + + return ret; } - internal override string ToDigestString () + internal string ToBasicString () { - var output = new StringBuilder (128); + return String.Format ("Basic realm=\"{0}\"", _parameters["realm"]); + } + + internal string ToDigestString () + { + var buff = new StringBuilder (128); - var domain = Parameters["domain"]; - if (domain != null) - output.AppendFormat ( + var domain = _parameters["domain"]; + var realm = _parameters["realm"]; + var nonce = _parameters["nonce"]; + + if (domain != null) { + buff.AppendFormat ( "Digest realm=\"{0}\", domain=\"{1}\", nonce=\"{2}\"", - Parameters["realm"], + realm, domain, - Parameters["nonce"]); - else - output.AppendFormat ( - "Digest realm=\"{0}\", nonce=\"{1}\"", Parameters["realm"], Parameters["nonce"]); + nonce + ); + } + else { + buff.AppendFormat ("Digest realm=\"{0}\", nonce=\"{1}\"", realm, nonce); + } + + var opaque = _parameters["opaque"]; - var opaque = Parameters["opaque"]; if (opaque != null) - output.AppendFormat (", opaque=\"{0}\"", opaque); + buff.AppendFormat (", opaque=\"{0}\"", opaque); + + var stale = _parameters["stale"]; - var stale = Parameters["stale"]; if (stale != null) - output.AppendFormat (", stale={0}", stale); + buff.AppendFormat (", stale={0}", stale); + + var algo = _parameters["algorithm"]; - var algo = Parameters["algorithm"]; if (algo != null) - output.AppendFormat (", algorithm={0}", algo); + buff.AppendFormat (", algorithm={0}", algo); + + var qop = _parameters["qop"]; - var qop = Parameters["qop"]; if (qop != null) - output.AppendFormat (", qop=\"{0}\"", qop); + buff.AppendFormat (", qop=\"{0}\"", qop); + + return buff.ToString (); + } + + #endregion + + #region Public Methods + + public override string ToString () + { + if (_scheme == AuthenticationSchemes.Basic) + return ToBasicString (); + + if (_scheme == AuthenticationSchemes.Digest) + return ToDigestString (); - return output.ToString (); + return String.Empty; } #endregion diff --git a/websocket-sharp/Net/AuthenticationResponse.cs b/websocket-sharp/Net/AuthenticationResponse.cs index 0257d85b2..28fbd2236 100644 --- a/websocket-sharp/Net/AuthenticationResponse.cs +++ b/websocket-sharp/Net/AuthenticationResponse.cs @@ -2,13 +2,13 @@ /* * AuthenticationResponse.cs * - * ParseBasicCredentials is derived from System.Net.HttpListenerContext.cs of Mono - * (http://www.mono-project.com). + * The ParseBasicCredentials method is derived from HttpListenerContext.cs + * (System.Net) of Mono (http://www.mono-project.com). * * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2013-2014 sta.blockhead + * Copyright (c) 2013-2024 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 @@ -38,19 +38,25 @@ namespace WebSocketSharp.Net { - internal class AuthenticationResponse : AuthenticationBase + internal class AuthenticationResponse { #region Private Fields - private uint _nonceCount; + private uint _nonceCount; + private NameValueCollection _parameters; + private AuthenticationSchemes _scheme; #endregion #region Private Constructors - private AuthenticationResponse (AuthenticationSchemes scheme, NameValueCollection parameters) - : base (scheme, parameters) + private AuthenticationResponse ( + AuthenticationSchemes scheme, + NameValueCollection parameters + ) { + _scheme = scheme; + _parameters = parameters; } #endregion @@ -58,12 +64,20 @@ private AuthenticationResponse (AuthenticationSchemes scheme, NameValueCollectio #region Internal Constructors internal AuthenticationResponse (NetworkCredential credentials) - : this (AuthenticationSchemes.Basic, new NameValueCollection (), credentials, 0) + : this ( + AuthenticationSchemes.Basic, + new NameValueCollection (), + credentials, + 0 + ) { } internal AuthenticationResponse ( - AuthenticationChallenge challenge, NetworkCredential credentials, uint nonceCount) + AuthenticationChallenge challenge, + NetworkCredential credentials, + uint nonceCount + ) : this (challenge.Scheme, challenge.Parameters, credentials, nonceCount) { } @@ -72,13 +86,15 @@ internal AuthenticationResponse ( AuthenticationSchemes scheme, NameValueCollection parameters, NetworkCredential credentials, - uint nonceCount) - : base (scheme, parameters) + uint nonceCount + ) + : this (scheme, parameters) { - Parameters["username"] = credentials.Username; - Parameters["password"] = credentials.Password; - Parameters["uri"] = credentials.Domain; + _parameters["username"] = credentials.Username; + _parameters["password"] = credentials.Password; + _parameters["uri"] = credentials.Domain; _nonceCount = nonceCount; + if (scheme == AuthenticationSchemes.Digest) initAsDigest (); } @@ -89,9 +105,13 @@ internal AuthenticationResponse ( internal uint NonceCount { get { - return _nonceCount < UInt32.MaxValue - ? _nonceCount - : 0; + return _nonceCount < UInt32.MaxValue ? _nonceCount : 0; + } + } + + internal NameValueCollection Parameters { + get { + return _parameters; } } @@ -99,39 +119,75 @@ internal uint NonceCount { #region Public Properties + public string Algorithm { + get { + return _parameters["algorithm"]; + } + } + public string Cnonce { get { - return Parameters["cnonce"]; + return _parameters["cnonce"]; } } public string Nc { get { - return Parameters["nc"]; + return _parameters["nc"]; + } + } + + public string Nonce { + get { + return _parameters["nonce"]; + } + } + + public string Opaque { + get { + return _parameters["opaque"]; } } public string Password { get { - return Parameters["password"]; + return _parameters["password"]; + } + } + + public string Qop { + get { + return _parameters["qop"]; + } + } + + public string Realm { + get { + return _parameters["realm"]; } } public string Response { get { - return Parameters["response"]; + return _parameters["response"]; + } + } + + public AuthenticationSchemes Scheme { + get { + return _scheme; } } public string Uri { get { - return Parameters["uri"]; + return _parameters["uri"]; } } public string UserName { get { - return Parameters["username"]; + return _parameters["username"]; } } @@ -139,16 +195,26 @@ public string UserName { #region Private Methods - private static string createA1 (string username, string password, string realm) + private static string createA1 ( + string username, + string password, + string realm + ) { return String.Format ("{0}:{1}:{2}", username, realm, password); } private static string createA1 ( - string username, string password, string realm, string nonce, string cnonce) + string username, + string password, + string realm, + string nonce, + string cnonce + ) { - return String.Format ( - "{0}:{1}:{2}", hash (createA1 (username, password, realm)), nonce, cnonce); + var a1 = createA1 (username, password, realm); + + return String.Format ("{0}:{1}:{2}", hash (a1), nonce, cnonce); } private static string createA2 (string method, string uri) @@ -163,33 +229,39 @@ private static string createA2 (string method, string uri, string entity) private static string hash (string value) { - var src = Encoding.UTF8.GetBytes (value); + var buff = new StringBuilder (64); + var md5 = MD5.Create (); - var hashed = md5.ComputeHash (src); + var bytes = Encoding.UTF8.GetBytes (value); + var res = md5.ComputeHash (bytes); - var res = new StringBuilder (64); - foreach (var b in hashed) - res.Append (b.ToString ("x2")); + foreach (var b in res) + buff.Append (b.ToString ("x2")); - return res.ToString (); + return buff.ToString (); } private void initAsDigest () { - var qops = Parameters["qop"]; + var qops = _parameters["qop"]; + if (qops != null) { - if (qops.Split (',').Contains (qop => qop.Trim ().ToLower () == "auth")) { - Parameters["qop"] = "auth"; - Parameters["cnonce"] = CreateNonceValue (); - Parameters["nc"] = String.Format ("{0:x8}", ++_nonceCount); + var hasAuth = qops.Split (',').Contains ( + qop => qop.Trim ().ToLower () == "auth" + ); + + if (hasAuth) { + _parameters["qop"] = "auth"; + _parameters["cnonce"] = AuthenticationChallenge.CreateNonceValue (); + _parameters["nc"] = String.Format ("{0:x8}", ++_nonceCount); } else { - Parameters["qop"] = null; + _parameters["qop"] = null; } } - Parameters["method"] = "GET"; - Parameters["response"] = CreateRequestDigest (Parameters); + _parameters["method"] = "GET"; + _parameters["response"] = CreateRequestDigest (_parameters); } #endregion @@ -198,8 +270,8 @@ private void initAsDigest () internal static string CreateRequestDigest (NameValueCollection parameters) { - var user = parameters["username"]; - var pass = parameters["password"]; + var uname = parameters["username"]; + var passwd = parameters["password"]; var realm = parameters["realm"]; var nonce = parameters["nonce"]; var uri = parameters["uri"]; @@ -210,8 +282,8 @@ internal static string CreateRequestDigest (NameValueCollection parameters) var method = parameters["method"]; var a1 = algo != null && algo.ToLower () == "md5-sess" - ? createA1 (user, pass, realm, nonce, cnonce) - : createA1 (user, pass, realm); + ? createA1 (uname, passwd, realm, nonce, cnonce) + : createA1 (uname, passwd, realm); var a2 = qop != null && qop.ToLower () == "auth-int" ? createA2 (method, uri, parameters["entity"]) @@ -219,89 +291,142 @@ internal static string CreateRequestDigest (NameValueCollection parameters) var secret = hash (a1); var data = qop != null - ? String.Format ("{0}:{1}:{2}:{3}:{4}", nonce, nc, cnonce, qop, hash (a2)) + ? String.Format ( + "{0}:{1}:{2}:{3}:{4}", + nonce, + nc, + cnonce, + qop, + hash (a2) + ) : String.Format ("{0}:{1}", nonce, hash (a2)); - return hash (String.Format ("{0}:{1}", secret, data)); + var keyed = String.Format ("{0}:{1}", secret, data); + + return hash (keyed); } internal static AuthenticationResponse Parse (string value) { try { var cred = value.Split (new[] { ' ' }, 2); + if (cred.Length != 2) return null; var schm = cred[0].ToLower (); - return schm == "basic" - ? new AuthenticationResponse ( - AuthenticationSchemes.Basic, ParseBasicCredentials (cred[1])) - : schm == "digest" - ? new AuthenticationResponse ( - AuthenticationSchemes.Digest, ParseParameters (cred[1])) - : null; + + if (schm == "basic") { + var parameters = ParseBasicCredentials (cred[1]); + + return new AuthenticationResponse ( + AuthenticationSchemes.Basic, + parameters + ); + } + + if (schm == "digest") { + var parameters = AuthenticationChallenge.ParseParameters (cred[1]); + + return new AuthenticationResponse ( + AuthenticationSchemes.Digest, + parameters + ); + } + + return null; } catch { + return null; } - - return null; } internal static NameValueCollection ParseBasicCredentials (string value) { + var ret = new NameValueCollection (); + // Decode the basic-credentials (a Base64 encoded string). - var userPass = Encoding.Default.GetString (Convert.FromBase64String (value)); + + var bytes = Convert.FromBase64String (value); + var userPass = Encoding.UTF8.GetString (bytes); // The format is [\]:. - var i = userPass.IndexOf (':'); - var user = userPass.Substring (0, i); - var pass = i < userPass.Length - 1 ? userPass.Substring (i + 1) : String.Empty; - // Check if 'domain' exists. - i = user.IndexOf ('\\'); - if (i > -1) - user = user.Substring (i + 1); + var idx = userPass.IndexOf (':'); + var uname = userPass.Substring (0, idx); + var passwd = idx < userPass.Length - 1 + ? userPass.Substring (idx + 1) + : String.Empty; - var res = new NameValueCollection (); - res["username"] = user; - res["password"] = pass; + // Check if exists. - return res; + idx = uname.IndexOf ('\\'); + + if (idx > -1) + uname = uname.Substring (idx + 1); + + ret["username"] = uname; + ret["password"] = passwd; + + return ret; } - internal override string ToBasicString () + internal string ToBasicString () { - var userPass = String.Format ("{0}:{1}", Parameters["username"], Parameters["password"]); - var cred = Convert.ToBase64String (Encoding.UTF8.GetBytes (userPass)); + var uname = _parameters["username"]; + var passwd = _parameters["password"]; + var userPass = String.Format ("{0}:{1}", uname, passwd); + + var bytes = Encoding.UTF8.GetBytes (userPass); + var cred = Convert.ToBase64String (bytes); return "Basic " + cred; } - internal override string ToDigestString () + internal string ToDigestString () { - var output = new StringBuilder (256); - output.AppendFormat ( + var buff = new StringBuilder (256); + + var uname = _parameters["username"]; + var realm = _parameters["realm"]; + var nonce = _parameters["nonce"]; + var uri = _parameters["uri"]; + var res = _parameters["response"]; + + buff.AppendFormat ( "Digest username=\"{0}\", realm=\"{1}\", nonce=\"{2}\", uri=\"{3}\", response=\"{4}\"", - Parameters["username"], - Parameters["realm"], - Parameters["nonce"], - Parameters["uri"], - Parameters["response"]); + uname, + realm, + nonce, + uri, + res + ); + + var opaque = _parameters["opaque"]; - var opaque = Parameters["opaque"]; if (opaque != null) - output.AppendFormat (", opaque=\"{0}\"", opaque); + buff.AppendFormat (", opaque=\"{0}\"", opaque); + + var algo = _parameters["algorithm"]; - var algo = Parameters["algorithm"]; if (algo != null) - output.AppendFormat (", algorithm={0}", algo); + buff.AppendFormat (", algorithm={0}", algo); + + var qop = _parameters["qop"]; - var qop = Parameters["qop"]; - if (qop != null) - output.AppendFormat ( - ", qop={0}, cnonce=\"{1}\", nc={2}", qop, Parameters["cnonce"], Parameters["nc"]); + if (qop != null) { + var cnonce = _parameters["cnonce"]; + var nc = _parameters["nc"]; - return output.ToString (); + buff.AppendFormat ( + ", qop={0}, cnonce=\"{1}\", nc={2}", + qop, + cnonce, + nc + ); + } + + return buff.ToString (); } #endregion @@ -310,12 +435,28 @@ internal override string ToDigestString () public IIdentity ToIdentity () { - var schm = Scheme; - return schm == AuthenticationSchemes.Basic - ? new HttpBasicIdentity (Parameters["username"], Parameters["password"]) as IIdentity - : schm == AuthenticationSchemes.Digest - ? new HttpDigestIdentity (Parameters) - : null; + if (_scheme == AuthenticationSchemes.Basic) { + var uname = _parameters["username"]; + var passwd = _parameters["password"]; + + return new HttpBasicIdentity (uname, passwd); + } + + if (_scheme == AuthenticationSchemes.Digest) + return new HttpDigestIdentity (_parameters); + + return null; + } + + public override string ToString () + { + if (_scheme == AuthenticationSchemes.Basic) + return ToBasicString (); + + if (_scheme == AuthenticationSchemes.Digest) + return ToDigestString (); + + return String.Empty; } #endregion diff --git a/websocket-sharp/Net/ChunkStream.cs b/websocket-sharp/Net/ChunkStream.cs index bcd87f9bf..3de4374db 100644 --- a/websocket-sharp/Net/ChunkStream.cs +++ b/websocket-sharp/Net/ChunkStream.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2003 Ximian, Inc (http://www.ximian.com) - * Copyright (c) 2012-2022 sta.blockhead + * Copyright (c) 2012-2023 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 @@ -163,7 +163,9 @@ private InputChunkState seekCrLf (byte[] buffer, ref int offset, int length) } private InputChunkState setChunkSize ( - byte[] buffer, ref int offset, int length + byte[] buffer, + ref int offset, + int length ) { byte b = 0; @@ -226,7 +228,9 @@ private InputChunkState setChunkSize ( } private InputChunkState setTrailer ( - byte[] buffer, ref int offset, int length + byte[] buffer, + ref int offset, + int length ) { while (offset < length) { @@ -289,7 +293,10 @@ private InputChunkState setTrailer ( private static void throwProtocolViolation (string message) { throw new WebException ( - message, null, WebExceptionStatus.ServerProtocolViolation, null + message, + null, + WebExceptionStatus.ServerProtocolViolation, + null ); } @@ -358,7 +365,9 @@ private void write (byte[] buffer, int offset, int length) } private InputChunkState writeData ( - byte[] buffer, ref int offset, int length + byte[] buffer, + ref int offset, + int length ) { var cnt = length - offset; @@ -368,9 +377,11 @@ private InputChunkState writeData ( cnt = left; var data = new byte[cnt]; + Buffer.BlockCopy (buffer, offset, data, 0, cnt); var chunk = new Chunk (data); + _chunks.Add (chunk); offset += cnt; diff --git a/websocket-sharp/Net/ChunkedRequestStream.cs b/websocket-sharp/Net/ChunkedRequestStream.cs index d88c47b18..f4a583925 100644 --- a/websocket-sharp/Net/ChunkedRequestStream.cs +++ b/websocket-sharp/Net/ChunkedRequestStream.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2012-2022 sta.blockhead + * Copyright (c) 2012-2023 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 @@ -124,6 +124,7 @@ private void onRead (IAsyncResult asyncResult) var nread = base.EndRead (asyncResult); _decoder.Write (ares.Buffer, ares.Offset, nread); + nread = _decoder.Read (rstate.Buffer, rstate.Offset, rstate.Count); rstate.Offset += nread; @@ -133,6 +134,7 @@ private void onRead (IAsyncResult asyncResult) _noMoreData = !_decoder.WantsMore && nread == 0; ares.Count = rstate.InitialCount - rstate.Count; + ares.Complete (); return; @@ -142,6 +144,7 @@ private void onRead (IAsyncResult asyncResult) } catch (Exception ex) { _context.ErrorMessage = "I/O operation aborted"; + _context.SendError (); ares.Complete (ex); @@ -153,14 +156,15 @@ private void onRead (IAsyncResult asyncResult) #region Public Methods public override IAsyncResult BeginRead ( - byte[] buffer, int offset, int count, AsyncCallback callback, object state + byte[] buffer, + int offset, + int count, + AsyncCallback callback, + object state ) { - if (_disposed) { - var name = GetType ().ToString (); - - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); if (buffer == null) throw new ArgumentNullException ("buffer"); @@ -180,7 +184,7 @@ public override IAsyncResult BeginRead ( var len = buffer.Length; if (offset + count > len) { - var msg = "The sum of 'offset' and 'count' is greater than the length of 'buffer'."; + var msg = "The sum of offset and count is greater than the length of buffer."; throw new ArgumentException (msg); } @@ -200,6 +204,7 @@ public override IAsyncResult BeginRead ( if (count == 0) { ares.Count = nread; + ares.Complete (); return ares; @@ -209,6 +214,7 @@ public override IAsyncResult BeginRead ( _noMoreData = nread == 0; ares.Count = nread; + ares.Complete (); return ares; @@ -219,6 +225,7 @@ public override IAsyncResult BeginRead ( ares.Count = _bufferLength; var rstate = new ReadBufferState (buffer, offset, count, ares); + rstate.InitialCount += nread; base.BeginRead (ares.Buffer, ares.Offset, ares.Count, onRead, rstate); @@ -238,11 +245,8 @@ public override void Close () public override int EndRead (IAsyncResult asyncResult) { - if (_disposed) { - var name = GetType ().ToString (); - - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); if (asyncResult == null) throw new ArgumentNullException ("asyncResult"); diff --git a/websocket-sharp/Net/ClientSslConfiguration.cs b/websocket-sharp/Net/ClientSslConfiguration.cs index 7816e0301..33438a93c 100644 --- a/websocket-sharp/Net/ClientSslConfiguration.cs +++ b/websocket-sharp/Net/ClientSslConfiguration.cs @@ -5,7 +5,7 @@ * The MIT License * * Copyright (c) 2014 liryna - * Copyright (c) 2014-2020 sta.blockhead + * Copyright (c) 2014-2024 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 @@ -42,7 +42,8 @@ namespace WebSocketSharp.Net { /// - /// Stores the parameters for the used by clients. + /// Stores the parameters for an instance used by + /// a client. /// public class ClientSslConfiguration { @@ -61,17 +62,18 @@ public class ClientSslConfiguration /// /// Initializes a new instance of the - /// class with the specified target host server name. + /// class with the specified target host name. /// /// - /// A that specifies the target host server name. + /// A that specifies the name of the server that + /// will share a secure connection with the client. /// - /// - /// is . - /// /// /// is an empty string. /// + /// + /// is . + /// public ClientSslConfiguration (string targetHost) { if (targetHost == null) @@ -87,7 +89,7 @@ public ClientSslConfiguration (string targetHost) /// /// Initializes a new instance of the - /// class that stores the parameters copied from the specified configuration. + /// class copying from the specified configuration. /// /// /// A from which to copy. @@ -136,15 +138,16 @@ public bool CheckCertificateRevocation { } /// - /// Gets or sets the collection of client certificates from which to select + /// Gets or sets the collection of the certificates from which to select /// one to supply to the server. /// /// /// - /// A or . + /// A that contains + /// the certificates from which to select. /// /// - /// The collection contains client certificates from which to select. + /// if not present. /// /// /// The default value is . @@ -169,12 +172,15 @@ public X509CertificateCollection ClientCertificates { /// /// /// - /// A delegate that - /// invokes the method called for selecting the certificate. + /// A delegate. + /// + /// + /// It represents the delegate called when the client selects + /// the certificate. /// /// - /// The default value is a delegate that invokes a method that only - /// returns . + /// The default value invokes a method that only returns + /// . /// /// public LocalCertificateSelectionCallback ClientCertificateSelectionCallback { @@ -191,14 +197,14 @@ public LocalCertificateSelectionCallback ClientCertificateSelectionCallback { } /// - /// Gets or sets the protocols used for authentication. + /// Gets or sets the enabled versions of the SSL/TLS protocols. /// /// /// /// Any of the enum values. /// /// - /// It represents the protocols used for authentication. + /// It represents the enabled versions of the SSL/TLS protocols. /// /// /// The default value is . @@ -223,12 +229,14 @@ public SslProtocols EnabledSslProtocols { /// /// /// - /// A delegate that - /// invokes the method called for validating the certificate. + /// A delegate. /// /// - /// The default value is a delegate that invokes a method that only - /// returns true. + /// It represents the delegate called when the client validates + /// the certificate. + /// + /// + /// The default value invokes a method that only returns true. /// /// public RemoteCertificateValidationCallback ServerCertificateValidationCallback { @@ -245,18 +253,18 @@ public RemoteCertificateValidationCallback ServerCertificateValidationCallback { } /// - /// Gets or sets the target host server name. + /// Gets or sets the target host name. /// /// /// A that represents the name of the server that - /// will share a secure connection with a client. + /// will share a secure connection with the client. /// - /// - /// The value specified for a set operation is . - /// /// /// The value specified for a set operation is an empty string. /// + /// + /// The value specified for a set operation is . + /// public string TargetHost { get { return _targetHost; diff --git a/websocket-sharp/Net/Cookie.cs b/websocket-sharp/Net/Cookie.cs index 1c5a4bf2d..149b5041e 100644 --- a/websocket-sharp/Net/Cookie.cs +++ b/websocket-sharp/Net/Cookie.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2004,2009 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2012-2019 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -56,7 +56,8 @@ namespace WebSocketSharp.Net /// /// /// - /// Netscape specification + /// + /// Netscape specification /// /// /// @@ -116,9 +117,6 @@ static Cookie () #region Internal Constructors - /// - /// Initializes a new instance of the class. - /// internal Cookie () { init (String.Empty, String.Empty, String.Empty, String.Empty); @@ -145,33 +143,33 @@ internal Cookie () /// /// A that specifies the value of the cookie. /// - /// - /// is . - /// /// /// /// is an empty string. /// /// - /// - or - + /// -or- /// /// /// starts with a dollar sign. /// /// - /// - or - + /// -or- /// /// /// contains an invalid character. /// /// - /// - or - + /// -or- /// /// /// is a string not enclosed in double quotes - /// that contains an invalid character. + /// although it contains a reserved character. /// /// + /// + /// is . + /// public Cookie (string name, string value) : this (name, value, String.Empty, String.Empty) { @@ -198,33 +196,33 @@ public Cookie (string name, string value) /// A that specifies the value of the Path /// attribute of the cookie. /// - /// - /// is . - /// /// /// /// is an empty string. /// /// - /// - or - + /// -or- /// /// /// starts with a dollar sign. /// /// - /// - or - + /// -or- /// /// /// contains an invalid character. /// /// - /// - or - + /// -or- /// /// /// is a string not enclosed in double quotes - /// that contains an invalid character. + /// although it contains a reserved character. /// /// + /// + /// is . + /// public Cookie (string name, string value, string path) : this (name, value, path, String.Empty) { @@ -255,33 +253,33 @@ public Cookie (string name, string value, string path) /// A that specifies the value of the Domain /// attribute of the cookie. /// - /// - /// is . - /// /// /// /// is an empty string. /// /// - /// - or - + /// -or- /// /// /// starts with a dollar sign. /// /// - /// - or - + /// -or- /// /// /// contains an invalid character. /// /// - /// - or - + /// -or- /// /// /// is a string not enclosed in double quotes - /// that contains an invalid character. + /// although it contains a reserved character. /// /// + /// + /// is . + /// public Cookie (string name, string value, string path, string domain) { if (name == null) @@ -292,11 +290,13 @@ public Cookie (string name, string value, string path, string domain) if (name[0] == '$') { var msg = "It starts with a dollar sign."; + throw new ArgumentException (msg, "name"); } if (!name.IsToken ()) { var msg = "It contains an invalid character."; + throw new ArgumentException (msg, "name"); } @@ -306,6 +306,7 @@ public Cookie (string name, string value, string path, string domain) if (value.Contains (_reservedCharsForValue)) { if (!value.IsEnclosedIn ('"')) { var msg = "A string not enclosed in double quotes."; + throw new ArgumentException (msg, "value"); } } @@ -333,6 +334,7 @@ internal int MaxAge { : _expires; var span = expires - DateTime.Now; + return span > TimeSpan.Zero ? (int) span.TotalSeconds : 0; @@ -447,7 +449,7 @@ internal set { /// the cookie is valid for. /// /// - /// An empty string if this attribute is not needed. + /// An empty string if not necessary. /// /// public string Domain { @@ -490,7 +492,7 @@ public bool Expired { /// the cookie expires on. /// /// - /// if this attribute is not needed. + /// if not necessary. /// /// /// The default value is . @@ -542,26 +544,26 @@ public bool HttpOnly { /// RFC 2616. /// /// - /// - /// The value specified for a set operation is . - /// /// /// /// The value specified for a set operation is an empty string. /// /// - /// - or - + /// -or- /// /// /// The value specified for a set operation starts with a dollar sign. /// /// - /// - or - + /// -or- /// /// /// The value specified for a set operation contains an invalid character. /// /// + /// + /// The value specified for a set operation is . + /// public string Name { get { return _name; @@ -576,11 +578,13 @@ public string Name { if (value[0] == '$') { var msg = "It starts with a dollar sign."; + throw new ArgumentException (msg, "value"); } if (!value.IsToken ()) { var msg = "It contains an invalid character."; + throw new ArgumentException (msg, "value"); } @@ -627,11 +631,12 @@ public string Port { internal set { int[] ports; + if (!tryCreatePorts (value, out ports)) return; - _port = value; _ports = ports; + _port = value; } } @@ -683,7 +688,7 @@ public DateTime TimeStamp { /// /// /// The value specified for a set operation is a string not enclosed in - /// double quotes that contains an invalid character. + /// double quotes although it contains a reserved character. /// public string Value { get { @@ -697,6 +702,7 @@ public string Value { if (value.Contains (_reservedCharsForValue)) { if (!value.IsEnclosedIn ('"')) { var msg = "A string not enclosed in double quotes."; + throw new ArgumentException (msg, "value"); } } @@ -714,7 +720,10 @@ public string Value { /// management that the cookie conforms to. /// /// - /// 0 or 1. 0 if not present. + /// 0 or 1. + /// + /// + /// 0 if not present. /// /// /// The default value is 0. @@ -764,13 +773,14 @@ private string toResponseStringVersion0 () buff.AppendFormat ("{0}={1}", _name, _value); if (_expires != DateTime.MinValue) { - buff.AppendFormat ( - "; Expires={0}", - _expires.ToUniversalTime ().ToString ( - "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", - CultureInfo.CreateSpecificCulture ("en-US") - ) - ); + var expires = _expires + .ToUniversalTime () + .ToString ( + "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", + CultureInfo.CreateSpecificCulture ("en-US") + ); + + buff.AppendFormat ("; Expires={0}", expires); } if (!_path.IsNullOrEmpty ()) @@ -813,13 +823,18 @@ private string toResponseStringVersion1 () buff.Append ("; Port"); } - if (_comment != null) - buff.AppendFormat ("; Comment={0}", HttpUtility.UrlEncode (_comment)); + if (_comment != null) { + var comment = HttpUtility.UrlEncode (_comment); + + buff.AppendFormat ("; Comment={0}", comment); + } if (_commentUri != null) { var url = _commentUri.OriginalString; + buff.AppendFormat ( - "; CommentURL={0}", !url.IsToken () ? url.Quote () : url + "; CommentURL={0}", + !url.IsToken () ? url.Quote () : url ); } @@ -842,8 +857,10 @@ private static bool tryCreatePorts (string value, out int[] result) for (var i = 0; i < len; i++) { var s = arr[i].Trim (); + if (s.Length == 0) { res[i] = Int32.MinValue; + continue; } @@ -852,6 +869,7 @@ private static bool tryCreatePorts (string value, out int[] result) } result = res; + return true; } @@ -914,24 +932,21 @@ internal string ToRequestString (Uri uri) return buff.ToString (); } - /// - /// Returns a string that represents the current cookie instance. - /// - /// - /// A that is suitable for the Set-Cookie response - /// header. - /// internal string ToResponseString () { - return _name.Length == 0 - ? String.Empty - : _version == 0 - ? toResponseStringVersion0 () - : toResponseStringVersion1 (); + if (_name.Length == 0) + return String.Empty; + + if (_version == 0) + return toResponseStringVersion0 (); + + return toResponseStringVersion1 (); } internal static bool TryCreate ( - string name, string value, out Cookie result + string name, + string value, + out Cookie result ) { result = null; @@ -970,6 +985,7 @@ internal static bool TryCreate ( public override bool Equals (object comparand) { var cookie = comparand as Cookie; + if (cookie == null) return false; @@ -991,13 +1007,13 @@ public override bool Equals (object comparand) /// public override int GetHashCode () { - return hash ( - StringComparer.InvariantCultureIgnoreCase.GetHashCode (_name), - _value.GetHashCode (), - _path.GetHashCode (), - StringComparer.InvariantCultureIgnoreCase.GetHashCode (_domain), - _version - ); + var i = StringComparer.InvariantCultureIgnoreCase.GetHashCode (_name); + var j = _value.GetHashCode (); + var k = _path.GetHashCode (); + var l = StringComparer.InvariantCultureIgnoreCase.GetHashCode (_domain); + var m = _version; + + return hash (i, j, k, l, m); } /// diff --git a/websocket-sharp/Net/CookieCollection.cs b/websocket-sharp/Net/CookieCollection.cs index 8c0322bda..02e065580 100644 --- a/websocket-sharp/Net/CookieCollection.cs +++ b/websocket-sharp/Net/CookieCollection.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2004,2009 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2012-2019 sta.blockhead + * Copyright (c) 2012-2025 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 @@ -57,7 +57,7 @@ public class CookieCollection : ICollection private List _list; private bool _readOnly; - private object _sync; + private object _syncRoot; #endregion @@ -69,7 +69,7 @@ public class CookieCollection : ICollection public CookieCollection () { _list = new List (); - _sync = ((ICollection) _list).SyncRoot; + _syncRoot = ((ICollection) _list).SyncRoot; } #endregion @@ -82,11 +82,22 @@ internal IList List { } } - internal IEnumerable Sorted { + internal bool ReadOnly { + get { + return _readOnly; + } + + set { + _readOnly = value; + } + } + + internal ICollection SortedList { get { var list = new List (_list); + if (list.Count > 1) - list.Sort (compareForSorted); + list.Sort (compareForSortedList); return list; } @@ -124,10 +135,6 @@ public bool IsReadOnly { get { return _readOnly; } - - internal set { - _readOnly = value; - } } /// @@ -195,7 +202,7 @@ public Cookie this[string name] { var caseInsensitive = StringComparison.InvariantCultureIgnoreCase; - foreach (var cookie in Sorted) { + foreach (var cookie in SortedList) { if (cookie.Name.Equals (name, caseInsensitive)) return cookie; } @@ -212,7 +219,7 @@ public Cookie this[string name] { /// public object SyncRoot { get { - return _sync; + return _syncRoot; } } @@ -223,8 +230,10 @@ public object SyncRoot { private void add (Cookie cookie) { var idx = search (cookie); + if (idx == -1) { _list.Add (cookie); + return; } @@ -237,14 +246,19 @@ private static int compareForSort (Cookie x, Cookie y) - (y.Name.Length + y.Value.Length); } - private static int compareForSorted (Cookie x, Cookie y) + private static int compareForSortedList (Cookie x, Cookie y) { var ret = x.Version - y.Version; - return ret != 0 - ? ret - : (ret = x.Name.CompareTo (y.Name)) != 0 - ? ret - : y.Path.Length - x.Path.Length; + + if (ret != 0) + return ret; + + ret = x.Name.CompareTo (y.Name); + + if (ret != 0) + return ret; + + return y.Path.Length - x.Path.Length; } private static CookieCollection parseRequest (string value) @@ -253,22 +267,25 @@ private static CookieCollection parseRequest (string value) Cookie cookie = null; var ver = 0; - var caseInsensitive = StringComparison.InvariantCultureIgnoreCase; + var pairs = value.SplitHeaderValue (',', ';').ToList (); for (var i = 0; i < pairs.Count; i++) { var pair = pairs[i].Trim (); + if (pair.Length == 0) continue; var idx = pair.IndexOf ('='); + if (idx == -1) { if (cookie == null) continue; if (pair.Equals ("$port", caseInsensitive)) { cookie.Port = "\"\""; + continue; } @@ -278,6 +295,7 @@ private static CookieCollection parseRequest (string value) if (idx == 0) { if (cookie != null) { ret.add (cookie); + cookie = null; } @@ -293,11 +311,15 @@ private static CookieCollection parseRequest (string value) if (val.Length == 0) continue; + var s = val.Unquote (); + int num; - if (!Int32.TryParse (val.Unquote (), out num)) + + if (!Int32.TryParse (s, out num)) continue; ver = num; + continue; } @@ -309,6 +331,7 @@ private static CookieCollection parseRequest (string value) continue; cookie.Path = val; + continue; } @@ -320,6 +343,7 @@ private static CookieCollection parseRequest (string value) continue; cookie.Domain = val; + continue; } @@ -331,6 +355,7 @@ private static CookieCollection parseRequest (string value) continue; cookie.Port = val; + continue; } @@ -355,37 +380,43 @@ private static CookieCollection parseResponse (string value) var ret = new CookieCollection (); Cookie cookie = null; - var caseInsensitive = StringComparison.InvariantCultureIgnoreCase; + var pairs = value.SplitHeaderValue (',', ';').ToList (); for (var i = 0; i < pairs.Count; i++) { var pair = pairs[i].Trim (); + if (pair.Length == 0) continue; var idx = pair.IndexOf ('='); + if (idx == -1) { if (cookie == null) continue; if (pair.Equals ("port", caseInsensitive)) { cookie.Port = "\"\""; + continue; } if (pair.Equals ("discard", caseInsensitive)) { cookie.Discard = true; + continue; } if (pair.Equals ("secure", caseInsensitive)) { cookie.Secure = true; + continue; } if (pair.Equals ("httponly", caseInsensitive)) { cookie.HttpOnly = true; + continue; } @@ -395,6 +426,7 @@ private static CookieCollection parseResponse (string value) if (idx == 0) { if (cookie != null) { ret.add (cookie); + cookie = null; } @@ -413,11 +445,15 @@ private static CookieCollection parseResponse (string value) if (val.Length == 0) continue; + var s = val.Unquote (); + int num; - if (!Int32.TryParse (val.Unquote (), out num)) + + if (!Int32.TryParse (s, out num)) continue; cookie.Version = num; + continue; } @@ -437,22 +473,30 @@ private static CookieCollection parseResponse (string value) continue; var buff = new StringBuilder (val, 32); + buff.AppendFormat (", {0}", pairs[i].Trim ()); + var s = buff.ToString (); + var fmts = new[] { "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", "r" }; + var provider = CultureInfo.CreateSpecificCulture ("en-US"); + var style = DateTimeStyles.AdjustToUniversal + | DateTimeStyles.AssumeUniversal; + DateTime expires; - if ( - !DateTime.TryParseExact ( - buff.ToString (), - new[] { "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", "r" }, - CultureInfo.CreateSpecificCulture ("en-US"), - DateTimeStyles.AdjustToUniversal - | DateTimeStyles.AssumeUniversal, - out expires - ) - ) + + var done = DateTime.TryParseExact ( + s, + fmts, + provider, + style, + out expires + ); + + if (!done) continue; cookie.Expires = expires.ToLocalTime (); + continue; } @@ -463,11 +507,15 @@ out expires if (val.Length == 0) continue; - int num; - if (!Int32.TryParse (val.Unquote (), out num)) + var s = val.Unquote (); + + int maxAge; + + if (!Int32.TryParse (s, out maxAge)) continue; - cookie.MaxAge = num; + cookie.MaxAge = maxAge; + continue; } @@ -479,6 +527,7 @@ out expires continue; cookie.Path = val; + continue; } @@ -490,6 +539,7 @@ out expires continue; cookie.Domain = val; + continue; } @@ -501,6 +551,7 @@ out expires continue; cookie.Port = val; + continue; } @@ -512,6 +563,7 @@ out expires continue; cookie.Comment = urlDecode (val, Encoding.UTF8); + continue; } @@ -523,6 +575,7 @@ out expires continue; cookie.CommentUri = val.Unquote ().ToUri (); + continue; } @@ -534,6 +587,7 @@ out expires continue; cookie.SameSite = val.Unquote (); + continue; } @@ -579,9 +633,7 @@ private static string urlDecode (string s, Encoding encoding) internal static CookieCollection Parse (string value, bool response) { try { - return response - ? parseResponse (value) - : parseRequest (value); + return response ? parseResponse (value) : parseRequest (value); } catch (Exception ex) { throw new CookieException ("It could not be parsed.", ex); @@ -591,16 +643,19 @@ internal static CookieCollection Parse (string value, bool response) internal void SetOrRemove (Cookie cookie) { var idx = search (cookie); + if (idx == -1) { if (cookie.Expired) return; _list.Add (cookie); + return; } if (cookie.Expired) { _list.RemoveAt (idx); + return; } @@ -615,8 +670,10 @@ internal void SetOrRemove (CookieCollection cookies) internal void Sort () { - if (_list.Count > 1) - _list.Sort (compareForSort); + if (_list.Count < 2) + return; + + _list.Sort (compareForSort); } #endregion @@ -629,16 +686,17 @@ internal void Sort () /// /// A to add. /// - /// - /// The collection is read-only. - /// /// /// is . /// + /// + /// This method is not available if the collection is read-only. + /// public void Add (Cookie cookie) { if (_readOnly) { var msg = "The collection is read-only."; + throw new InvalidOperationException (msg); } @@ -654,16 +712,17 @@ public void Add (Cookie cookie) /// /// A that contains the cookies to add. /// - /// - /// The collection is read-only. - /// /// /// is . /// + /// + /// This method is not available if the collection is read-only. + /// public void Add (CookieCollection cookies) { if (_readOnly) { var msg = "The collection is read-only."; + throw new InvalidOperationException (msg); } @@ -678,12 +737,13 @@ public void Add (CookieCollection cookies) /// Removes all cookies from the collection. /// /// - /// The collection is read-only. + /// This method is not available if the collection is read-only. /// public void Clear () { if (_readOnly) { var msg = "The collection is read-only."; + throw new InvalidOperationException (msg); } @@ -723,26 +783,30 @@ public bool Contains (Cookie cookie) /// An that specifies the zero-based index in /// the array at which copying starts. /// + /// + /// The space from to the end of + /// is not enough to copy to. + /// /// /// is . /// /// /// is less than zero. /// - /// - /// The space from to the end of - /// is not enough to copy to. - /// public void CopyTo (Cookie[] array, int index) { if (array == null) throw new ArgumentNullException ("array"); - if (index < 0) - throw new ArgumentOutOfRangeException ("index", "Less than zero."); + if (index < 0) { + var msg = "Less than zero."; + + throw new ArgumentOutOfRangeException ("index", msg); + } if (array.Length - index < _list.Count) { var msg = "The available space of the array is not enough to copy to."; + throw new ArgumentException (msg); } @@ -765,27 +829,23 @@ public IEnumerator GetEnumerator () /// Removes the specified cookie from the collection. /// /// - /// - /// true if the cookie is successfully removed; otherwise, - /// false. - /// - /// - /// false if the cookie is not found in the collection. - /// + /// true if the cookie is successfully found and removed; + /// otherwise, false. /// /// /// A to remove. /// - /// - /// The collection is read-only. - /// /// /// is . /// + /// + /// This method is not available if the collection is read-only. + /// public bool Remove (Cookie cookie) { if (_readOnly) { var msg = "The collection is read-only."; + throw new InvalidOperationException (msg); } @@ -793,10 +853,12 @@ public bool Remove (Cookie cookie) throw new ArgumentNullException ("cookie"); var idx = search (cookie); + if (idx == -1) return false; _list.RemoveAt (idx); + return true; } diff --git a/websocket-sharp/Net/CookieException.cs b/websocket-sharp/Net/CookieException.cs index 2a5abe98a..3c9ab3f9e 100644 --- a/websocket-sharp/Net/CookieException.cs +++ b/websocket-sharp/Net/CookieException.cs @@ -7,7 +7,7 @@ * * The MIT License * - * Copyright (c) 2012-2019 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -66,10 +66,11 @@ internal CookieException (string message, Exception innerException) /// /// Initializes a new instance of the class - /// with the serialized data. + /// with the specified serialized data. /// /// - /// A that holds the serialized object data. + /// A that contains the serialized + /// object data. /// /// /// A that specifies the source for @@ -79,7 +80,8 @@ internal CookieException (string message, Exception innerException) /// is . /// protected CookieException ( - SerializationInfo serializationInfo, StreamingContext streamingContext + SerializationInfo serializationInfo, + StreamingContext streamingContext ) : base (serializationInfo, streamingContext) { @@ -122,7 +124,8 @@ public CookieException () ) ] public override void GetObjectData ( - SerializationInfo serializationInfo, StreamingContext streamingContext + SerializationInfo serializationInfo, + StreamingContext streamingContext ) { base.GetObjectData (serializationInfo, streamingContext); @@ -154,7 +157,8 @@ public override void GetObjectData ( ) ] void ISerializable.GetObjectData ( - SerializationInfo serializationInfo, StreamingContext streamingContext + SerializationInfo serializationInfo, + StreamingContext streamingContext ) { base.GetObjectData (serializationInfo, streamingContext); diff --git a/websocket-sharp/Net/EndPointListener.cs b/websocket-sharp/Net/EndPointListener.cs index b372a2524..3c8b3653a 100644 --- a/websocket-sharp/Net/EndPointListener.cs +++ b/websocket-sharp/Net/EndPointListener.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2012-2020 sta.blockhead + * Copyright (c) 2012-2023 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 @@ -171,7 +171,8 @@ public ServerSslConfiguration SslConfiguration { #region Private Methods private static void addSpecial ( - List prefixes, HttpListenerPrefix prefix + List prefixes, + HttpListenerPrefix prefix ) { var path = prefix.Path; @@ -199,9 +200,7 @@ private void clearConnections () conns = new HttpConnection[cnt]; - var vals = _connections.Values; - vals.CopyTo (conns, 0); - + _connections.Values.CopyTo (conns, 0); _connections.Clear (); } @@ -214,13 +213,16 @@ private static RSACryptoServiceProvider createRSAFromFile (string path) var rsa = new RSACryptoServiceProvider (); var key = File.ReadAllBytes (path); + rsa.ImportCspBlob (key); return rsa; } private static X509Certificate2 getCertificate ( - int port, string folderPath, X509Certificate2 defaultCertificate + int port, + string folderPath, + X509Certificate2 defaultCertificate ) { if (folderPath == null || folderPath.Length == 0) @@ -230,17 +232,20 @@ private static X509Certificate2 getCertificate ( 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); + var exists = File.Exists (cer) && File.Exists (key); - return cert; - } + if (!exists) + return defaultCertificate; + + var cert = new X509Certificate2 (cer); + + cert.PrivateKey = createRSAFromFile (key); + + return cert; } catch { + return defaultCertificate; } - - return defaultCertificate; } private void leaveIfNoPrefix () @@ -296,7 +301,8 @@ private static void onAccept (IAsyncResult asyncResult) } private static void processAccepted ( - Socket socket, EndPointListener listener + Socket socket, + EndPointListener listener ) { HttpConnection conn = null; @@ -319,7 +325,8 @@ private static void processAccepted ( } private static bool removeSpecial ( - List prefixes, HttpListenerPrefix prefix + List prefixes, + HttpListenerPrefix prefix ) { var path = prefix.Path; @@ -337,7 +344,8 @@ private static bool removeSpecial ( } private static HttpListener searchHttpListenerFromSpecial ( - string path, List prefixes + string path, + List prefixes ) { if (prefixes == null) @@ -354,10 +362,13 @@ private static HttpListener searchHttpListenerFromSpecial ( if (len < bestLen) continue; - if (path.StartsWith (prefPath, StringComparison.Ordinal)) { - bestLen = len; - ret = pref.Listener; - } + var match = path.StartsWith (prefPath, StringComparison.Ordinal); + + if (!match) + continue; + + bestLen = len; + ret = pref.Listener; } return ret; @@ -423,10 +434,13 @@ internal bool TrySearchHttpListener (Uri uri, out HttpListener listener) if (len < bestLen) continue; - if (path.StartsWith (prefPath, StringComparison.Ordinal)) { - bestLen = len; - listener = pref.Listener; - } + var match = path.StartsWith (prefPath, StringComparison.Ordinal); + + if (!match) + continue; + + bestLen = len; + listener = pref.Listener; } if (bestLen != -1) @@ -461,7 +475,8 @@ public void AddPrefix (HttpListenerPrefix prefix) addSpecial (future, prefix); } while ( - Interlocked.CompareExchange (ref _unhandled, future, current) != current + Interlocked.CompareExchange (ref _unhandled, future, current) + != current ); return; @@ -477,7 +492,8 @@ public void AddPrefix (HttpListenerPrefix prefix) addSpecial (future, prefix); } while ( - Interlocked.CompareExchange (ref _all, future, current) != current + Interlocked.CompareExchange (ref _all, future, current) + != current ); return; @@ -485,13 +501,13 @@ public void AddPrefix (HttpListenerPrefix prefix) do { current = _prefixes; + var idx = current.IndexOf (prefix); if (idx > -1) { if (current[idx].Listener != prefix.Listener) { - var msg = String.Format ( - "There is another listener for {0}.", prefix - ); + var fmt = "There is another listener for {0}."; + var msg = String.Format (fmt, prefix); throw new HttpListenerException (87, msg); } @@ -500,10 +516,12 @@ public void AddPrefix (HttpListenerPrefix prefix) } future = new List (current); + future.Add (prefix); } while ( - Interlocked.CompareExchange (ref _prefixes, future, current) != current + Interlocked.CompareExchange (ref _prefixes, future, current) + != current ); } @@ -532,7 +550,8 @@ public void RemovePrefix (HttpListenerPrefix prefix) break; } while ( - Interlocked.CompareExchange (ref _unhandled, future, current) != current + Interlocked.CompareExchange (ref _unhandled, future, current) + != current ); leaveIfNoPrefix (); @@ -553,7 +572,8 @@ public void RemovePrefix (HttpListenerPrefix prefix) break; } while ( - Interlocked.CompareExchange (ref _all, future, current) != current + Interlocked.CompareExchange (ref _all, future, current) + != current ); leaveIfNoPrefix (); @@ -568,10 +588,12 @@ public void RemovePrefix (HttpListenerPrefix prefix) break; future = new List (current); + future.Remove (prefix); } while ( - Interlocked.CompareExchange (ref _prefixes, future, current) != current + Interlocked.CompareExchange (ref _prefixes, future, current) + != current ); leaveIfNoPrefix (); diff --git a/websocket-sharp/Net/HttpConnection.cs b/websocket-sharp/Net/HttpConnection.cs index 30ca852ab..e51efb11c 100644 --- a/websocket-sharp/Net/HttpConnection.cs +++ b/websocket-sharp/Net/HttpConnection.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2012-2022 sta.blockhead + * Copyright (c) 2012-2025 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 @@ -68,6 +68,7 @@ internal sealed class HttpConnection private EndPointListener _endPointListener; private InputState _inputState; private RequestStream _inputStream; + private bool _isSecure; private LineState _lineState; private EndPoint _localEndPoint; private static readonly int _maxInputLength; @@ -76,7 +77,6 @@ internal sealed class HttpConnection private EndPoint _remoteEndPoint; private MemoryStream _requestBuffer; private int _reuses; - private bool _secure; private Socket _socket; private Stream _stream; private object _sync; @@ -120,7 +120,7 @@ internal HttpConnection (Socket socket, EndPointListener listener) sslConf.CheckCertificateRevocation ); - _secure = true; + _isSecure = true; _stream = sslStream; } else { @@ -156,7 +156,7 @@ public bool IsLocal { public bool IsSecure { get { - return _secure; + return _isSecure; } } @@ -178,6 +178,12 @@ public int Reuses { } } + public Socket Socket { + get { + return _socket; + } + } + public Stream Stream { get { return _stream; @@ -451,7 +457,10 @@ private bool processRequestBuffer () } private string readLineFrom ( - byte[] buffer, int offset, int length, out int nread + byte[] buffer, + int offset, + int length, + out int nread ) { nread = 0; @@ -601,10 +610,18 @@ public RequestStream GetRequestStream (long contentLength, bool chunked) _inputStream = chunked ? new ChunkedRequestStream ( - _stream, buff, _position, cnt, _context + _stream, + buff, + _position, + cnt, + _context ) : new RequestStream ( - _stream, buff, _position, cnt, contentLength + _stream, + buff, + _position, + cnt, + contentLength ); disposeRequestBuffer (); diff --git a/websocket-sharp/Net/HttpDigestIdentity.cs b/websocket-sharp/Net/HttpDigestIdentity.cs index 68ec86d9f..e2863aa9c 100644 --- a/websocket-sharp/Net/HttpDigestIdentity.cs +++ b/websocket-sharp/Net/HttpDigestIdentity.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2014-2017 sta.blockhead + * Copyright (c) 2014-2024 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 @@ -33,8 +33,8 @@ namespace WebSocketSharp.Net { /// - /// Holds the username and other parameters from - /// an HTTP Digest authentication attempt. + /// Holds the username and other parameters from an HTTP Digest + /// authentication attempt. /// public class HttpDigestIdentity : GenericIdentity { @@ -169,17 +169,22 @@ public string Uri { #region Internal Methods internal bool IsValid ( - string password, string realm, string method, string entity + string password, + string realm, + string method, + string entity ) { - var copied = new NameValueCollection (_parameters); - copied["password"] = password; - copied["realm"] = realm; - copied["method"] = method; - copied["entity"] = entity; - - var expected = AuthenticationResponse.CreateRequestDigest (copied); - return _parameters["response"] == expected; + var parameters = new NameValueCollection (_parameters); + + parameters["password"] = password; + parameters["realm"] = realm; + parameters["method"] = method; + parameters["entity"] = entity; + + var expectedDigest = AuthenticationResponse.CreateRequestDigest (parameters); + + return _parameters["response"] == expectedDigest; } #endregion diff --git a/websocket-sharp/Net/HttpListener.cs b/websocket-sharp/Net/HttpListener.cs index cfc915297..44b964c0c 100644 --- a/websocket-sharp/Net/HttpListener.cs +++ b/websocket-sharp/Net/HttpListener.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2012-2022 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -81,13 +81,13 @@ public sealed class HttpListener : IDisposable private static readonly string _defaultRealm; private bool _disposed; private bool _ignoreWriteExceptions; - private volatile bool _listening; + private volatile bool _isListening; private Logger _log; - private string _objectName; private HttpListenerPrefixCollection _prefixes; private string _realm; private bool _reuseAddress; private ServerSslConfiguration _sslConfig; + private object _sync; private Func _userCredFinder; private Queue _waitQueue; @@ -111,13 +111,11 @@ public HttpListener () { _authSchemes = AuthenticationSchemes.Anonymous; _contextQueue = new Queue (); - _contextRegistry = new LinkedList (); _contextRegistrySync = ((ICollection) _contextRegistry).SyncRoot; - _log = new Logger (); - _objectName = GetType ().ToString (); _prefixes = new HttpListenerPrefixCollection (this); + _sync = new object (); _waitQueue = new Queue (); } @@ -125,6 +123,12 @@ public HttpListener () #region Internal Properties + internal string ObjectName { + get { + return GetType ().ToString (); + } + } + internal bool ReuseAddress { get { return _reuseAddress; @@ -161,21 +165,21 @@ internal bool ReuseAddress { public AuthenticationSchemes AuthenticationSchemes { get { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); return _authSchemes; } set { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); _authSchemes = value; } } /// - /// Gets or sets the delegate called to select the scheme used to + /// Gets or sets the delegate called to determine the scheme used to /// authenticate the clients. /// /// @@ -191,15 +195,17 @@ public AuthenticationSchemes AuthenticationSchemes { /// /// /// - /// A Func<, - /// > delegate or - /// if not needed. + /// A + /// delegate. /// /// - /// The delegate references the method used to select + /// It represents the delegate called when the listener selects /// an authentication scheme. /// /// + /// if not necessary. + /// + /// /// The default value is . /// /// @@ -209,14 +215,14 @@ public AuthenticationSchemes AuthenticationSchemes { public Func AuthenticationSchemeSelector { get { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); return _authSchemeSelector; } set { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); _authSchemeSelector = value; } @@ -238,9 +244,9 @@ public Func AuthenticationSchemeSele /// /// /// If this property is or an empty string, - /// the result of System.Environment.GetFolderPath () - /// is used as the default path. + /// the result of the + /// with the method is used as + /// the default path. /// /// /// @@ -258,14 +264,14 @@ public Func AuthenticationSchemeSele public string CertificateFolderPath { get { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); return _certFolderPath; } set { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); _certFolderPath = value; } @@ -290,14 +296,14 @@ public string CertificateFolderPath { public bool IgnoreWriteExceptions { get { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); return _ignoreWriteExceptions; } set { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); _ignoreWriteExceptions = value; } @@ -311,7 +317,7 @@ public bool IgnoreWriteExceptions { /// public bool IsListening { get { - return _listening; + return _isListening; } } @@ -343,8 +349,14 @@ public static bool IsSupported { /// /// A that provides the logging functions. /// + /// + /// This listener has been closed. + /// public Logger Log { get { + if (_disposed) + throw new ObjectDisposedException (ObjectName); + return _log; } } @@ -362,7 +374,7 @@ public Logger Log { public HttpListenerPrefixCollection Prefixes { get { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); return _prefixes; } @@ -373,7 +385,7 @@ public HttpListenerPrefixCollection Prefixes { /// /// /// If this property is or an empty string, - /// "SECRET AREA" will be used as the name of the realm. + /// "SECRET AREA" is used as the name of the realm. /// /// /// @@ -389,26 +401,25 @@ public HttpListenerPrefixCollection Prefixes { public string Realm { get { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); return _realm; } set { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); _realm = value; } } /// - /// Gets the SSL configuration used to authenticate the server and - /// optionally the client for secure connection. + /// Gets the configuration for secure connection. /// /// - /// A that represents the SSL - /// configuration for secure connection. + /// A that represents the + /// configuration used to provide secure connections. /// /// /// This listener has been closed. @@ -416,7 +427,7 @@ public string Realm { public ServerSslConfiguration SslConfiguration { get { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); if (_sslConfig == null) _sslConfig = new ServerSslConfiguration (); @@ -457,12 +468,19 @@ public bool UnsafeConnectionNtlmAuthentication { /// /// /// - /// A Func<, - /// > delegate or - /// if not needed. + /// A + /// delegate. + /// + /// + /// It represents the delegate called when the listener finds + /// the credentials used to authenticate a client. /// /// - /// It references the method used to find the credentials. + /// It must return if the credentials + /// are not found. + /// + /// + /// if not necessary. /// /// /// The default value is . @@ -474,14 +492,14 @@ public bool UnsafeConnectionNtlmAuthentication { public Func UserCredentialsFinder { get { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); return _userCredFinder; } set { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); _userCredFinder = value; } @@ -491,57 +509,45 @@ public Func UserCredentialsFinder { #region Private Methods - private bool authenticateContext (HttpListenerContext context) + private bool authenticateClient (HttpListenerContext context) { - var req = context.Request; - var schm = selectAuthenticationScheme (req); + var schm = selectAuthenticationScheme (context.Request); if (schm == AuthenticationSchemes.Anonymous) return true; if (schm == AuthenticationSchemes.None) { var msg = "Authentication not allowed"; + context.SendError (403, msg); return false; } var realm = getRealm (); - var user = HttpUtility.CreateUser ( - req.Headers["Authorization"], - schm, - realm, - req.HttpMethod, - _userCredFinder - ); - var authenticated = user != null && user.Identity.IsAuthenticated; - - if (!authenticated) { + if (!context.SetUser (schm, realm, _userCredFinder)) { context.SendAuthenticationChallenge (schm, realm); return false; } - context.User = user; - return true; } private HttpListenerAsyncResult beginGetContext ( - AsyncCallback callback, object state + AsyncCallback callback, + object state ) { lock (_contextRegistrySync) { - if (!_listening) { - var msg = _disposed - ? "The listener is closed." - : "The listener is stopped."; + if (!_isListening) { + var msg = "The method is canceled."; throw new HttpListenerException (995, msg); } - var ares = new HttpListenerAsyncResult (callback, state); + var ares = new HttpListenerAsyncResult (callback, state, _log); if (_contextQueue.Count == 0) { _waitQueue.Enqueue (ares); @@ -550,6 +556,7 @@ private HttpListenerAsyncResult beginGetContext ( } var ctx = _contextQueue.Dequeue (); + ares.Complete (ctx, true); return ares; @@ -583,9 +590,11 @@ private void cleanupContextRegistry () return; var ctxs = new HttpListenerContext[cnt]; - _contextRegistry.CopyTo (ctxs, 0); - _contextRegistry.Clear (); + lock (_contextRegistrySync) { + _contextRegistry.CopyTo (ctxs, 0); + _contextRegistry.Clear (); + } foreach (var ctx in ctxs) ctx.Connection.Close (true); @@ -602,29 +611,38 @@ private void cleanupWaitQueue (string message) foreach (var ares in aress) { var ex = new HttpListenerException (995, message); + ares.Complete (ex); } } private void close (bool force) { - if (!_listening) { - _disposed = true; + lock (_sync) { + if (_disposed) + return; - return; - } + lock (_contextRegistrySync) { + if (!_isListening) { + _disposed = true; - _listening = false; + return; + } - cleanupContextQueue (force); - cleanupContextRegistry (); + _isListening = false; + } + + cleanupContextQueue (force); + cleanupContextRegistry (); - var msg = "The listener is closed."; - cleanupWaitQueue (msg); + var msg = "The listener is closed."; + + cleanupWaitQueue (msg); - EndPointManager.RemoveListener (this); + EndPointManager.RemoveListener (this); - _disposed = true; + _disposed = true; + } } private string getRealm () @@ -636,8 +654,11 @@ private string getRealm () private bool registerContext (HttpListenerContext context) { + if (!_isListening) + return false; + lock (_contextRegistrySync) { - if (!_listening) + if (!_isListening) return false; context.Listener = this; @@ -651,6 +672,7 @@ private bool registerContext (HttpListenerContext context) } var ares = _waitQueue.Dequeue (); + ares.Complete (context, false); return true; @@ -681,12 +703,12 @@ HttpListenerRequest request internal void CheckDisposed () { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); } internal bool RegisterContext (HttpListenerContext context) { - if (!authenticateContext (context)) + if (!authenticateClient (context)) return false; if (!registerContext (context)) { @@ -716,12 +738,7 @@ public void Abort () if (_disposed) return; - lock (_contextRegistrySync) { - if (_disposed) - return; - - close (true); - } + close (true); } /// @@ -729,26 +746,34 @@ public void Abort () /// /// /// - /// This asynchronous operation must be completed by calling - /// the EndGetContext method. + /// This asynchronous operation must be ended by calling + /// the method. /// /// - /// Typically, the EndGetContext method is called by + /// Typically, the method is called by /// . /// /// /// - /// An that represents the status of + /// An instance that represents the status of /// the asynchronous operation. /// /// - /// An delegate that references the method - /// to invoke when the asynchronous operation completes. + /// + /// An delegate. + /// + /// + /// It specifies the delegate called when the asynchronous operation is + /// complete. + /// /// /// - /// An that specifies a user defined object to - /// pass to . + /// An that specifies a user defined object to pass to + /// . /// + /// + /// This method is canceled. + /// /// /// /// This listener has not been started or is currently stopped. @@ -760,18 +785,15 @@ public void Abort () /// This listener has no URI prefix on which listens. /// /// - /// - /// This method is canceled. - /// /// /// This listener has been closed. /// public IAsyncResult BeginGetContext (AsyncCallback callback, object state) { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); - if (!_listening) { + if (!_isListening) { var msg = "The listener has not been started."; throw new InvalidOperationException (msg); @@ -794,34 +816,32 @@ public void Close () if (_disposed) return; - lock (_contextRegistrySync) { - if (_disposed) - return; - - close (false); - } + close (false); } /// /// Ends an asynchronous operation to get an incoming request. /// /// - /// This method completes an asynchronous operation started by - /// calling the BeginGetContext method. + /// This method ends an asynchronous operation started by calling + /// the method. /// /// /// A that represents a request. /// /// /// An instance obtained by calling - /// the BeginGetContext method. + /// the method. /// + /// + /// was not obtained by calling + /// the method. + /// /// /// is . /// - /// - /// was not obtained by calling - /// the BeginGetContext method. + /// + /// This method is canceled. /// /// /// @@ -834,18 +854,15 @@ public void Close () /// This method was already called for . /// /// - /// - /// This method is canceled. - /// /// /// This listener has been closed. /// public HttpListenerContext EndGetContext (IAsyncResult asyncResult) { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); - if (!_listening) { + if (!_isListening) { var msg = "The listener has not been started."; throw new InvalidOperationException (msg); @@ -888,6 +905,9 @@ public HttpListenerContext EndGetContext (IAsyncResult asyncResult) /// /// A that represents a request. /// + /// + /// This method is canceled. + /// /// /// /// This listener has not been started or is currently stopped. @@ -899,18 +919,15 @@ public HttpListenerContext EndGetContext (IAsyncResult asyncResult) /// This listener has no URI prefix on which listens. /// /// - /// - /// This method is canceled. - /// /// /// This listener has been closed. /// public HttpListenerContext GetContext () { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); - if (!_listening) { + if (!_isListening) { var msg = "The listener has not been started."; throw new InvalidOperationException (msg); @@ -923,6 +940,7 @@ public HttpListenerContext GetContext () } var ares = beginGetContext (null, null); + ares.EndCalled = true; if (!ares.IsCompleted) @@ -940,18 +958,20 @@ public HttpListenerContext GetContext () public void Start () { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); - lock (_contextRegistrySync) { + lock (_sync) { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); - if (_listening) - return; + lock (_contextRegistrySync) { + if (_isListening) + return; - EndPointManager.AddListener (this); + EndPointManager.AddListener (this); - _listening = true; + _isListening = true; + } } } @@ -964,21 +984,24 @@ public void Start () public void Stop () { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); - lock (_contextRegistrySync) { + lock (_sync) { if (_disposed) - throw new ObjectDisposedException (_objectName); + throw new ObjectDisposedException (ObjectName); - if (!_listening) - return; + lock (_contextRegistrySync) { + if (!_isListening) + return; - _listening = false; + _isListening = false; + } cleanupContextQueue (false); cleanupContextRegistry (); var msg = "The listener is stopped."; + cleanupWaitQueue (msg); EndPointManager.RemoveListener (this); @@ -997,12 +1020,7 @@ void IDisposable.Dispose () if (_disposed) return; - lock (_contextRegistrySync) { - if (_disposed) - return; - - close (true); - } + close (true); } #endregion diff --git a/websocket-sharp/Net/HttpListenerAsyncResult.cs b/websocket-sharp/Net/HttpListenerAsyncResult.cs index abd9e1aea..e4742d77b 100644 --- a/websocket-sharp/Net/HttpListenerAsyncResult.cs +++ b/websocket-sharp/Net/HttpListenerAsyncResult.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2005 Ximian, Inc. (http://www.ximian.com) - * Copyright (c) 2012-2021 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -59,6 +59,7 @@ internal class HttpListenerAsyncResult : IAsyncResult private HttpListenerContext _context; private bool _endCalled; private Exception _exception; + private Logger _log; private object _state; private object _sync; private ManualResetEvent _waitHandle; @@ -67,10 +68,15 @@ internal class HttpListenerAsyncResult : IAsyncResult #region Internal Constructors - internal HttpListenerAsyncResult (AsyncCallback callback, object state) + internal HttpListenerAsyncResult ( + AsyncCallback callback, + object state, + Logger log + ) { _callback = callback; _state = state; + _log = log; _sync = new object (); } @@ -160,7 +166,9 @@ private void complete () try { _callback (this); } - catch { + catch (Exception ex) { + _log.Error (ex.Message); + _log.Debug (ex.ToString ()); } }, null @@ -179,7 +187,8 @@ internal void Complete (Exception exception) } internal void Complete ( - HttpListenerContext context, bool completedSynchronously + HttpListenerContext context, + bool completedSynchronously ) { _context = context; diff --git a/websocket-sharp/Net/HttpListenerContext.cs b/websocket-sharp/Net/HttpListenerContext.cs index cbd85631c..82ea0176c 100644 --- a/websocket-sharp/Net/HttpListenerContext.cs +++ b/websocket-sharp/Net/HttpListenerContext.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2012-2022 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -153,26 +153,21 @@ public HttpListenerResponse Response { } /// - /// Gets the client information (identity, authentication, and security - /// roles). + /// Gets the client information. /// /// /// - /// A instance or - /// if not authenticated. + /// A instance that represents identity, + /// authentication, and security roles for the client. /// /// - /// The instance describes the client. + /// if the client is not authenticated. /// /// public IPrincipal User { get { return _user; } - - internal set { - _user = value; - } } #endregion @@ -180,7 +175,9 @@ internal set { #region Private Methods private static string createErrorContent ( - int statusCode, string statusDescription, string message + int statusCode, + string statusDescription, + string message ) { return message != null && message.Length > 0 @@ -209,13 +206,15 @@ internal HttpListenerWebSocketContext GetWebSocketContext (string protocol) } internal void SendAuthenticationChallenge ( - AuthenticationSchemes scheme, string realm + AuthenticationSchemes scheme, + string realm ) { _response.StatusCode = 401; - var chal = new AuthenticationChallenge (scheme, realm).ToString (); - _response.Headers.InternalSet ("WWW-Authenticate", chal, true); + var val = new AuthenticationChallenge (scheme, realm).ToString (); + + _response.Headers.InternalSet ("WWW-Authenticate", val, true); _response.Close (); } @@ -260,6 +259,31 @@ internal void SendError (int statusCode, string message) SendError (); } + internal bool SetUser ( + AuthenticationSchemes scheme, + string realm, + Func credentialsFinder + ) + { + var user = HttpUtility.CreateUser ( + _request.Headers["Authorization"], + scheme, + realm, + _request.HttpMethod, + credentialsFinder + ); + + if (user == null) + return false; + + if (!user.Identity.IsAuthenticated) + return false; + + _user = user; + + return true; + } + internal void Unregister () { if (_listener == null) @@ -273,19 +297,24 @@ internal void Unregister () #region Public Methods /// - /// Accepts a WebSocket handshake request. + /// Accepts a WebSocket connection. /// /// /// A that represents /// the WebSocket handshake request. /// /// - /// A that specifies the subprotocol supported on - /// the WebSocket connection. + /// + /// A that specifies the name of the subprotocol + /// supported on the WebSocket connection. + /// + /// + /// if not necessary. + /// /// /// /// - /// is empty. + /// is an empty string. /// /// /// -or- @@ -295,12 +324,88 @@ internal void Unregister () /// /// /// - /// This method has already been called. + /// + /// This method has already been done. + /// + /// + /// -or- + /// + /// + /// The client request is not a WebSocket handshake request. + /// /// public HttpListenerWebSocketContext AcceptWebSocket (string protocol) + { + return AcceptWebSocket (protocol, null); + } + + /// + /// Accepts a WebSocket connection with initializing the WebSocket + /// interface. + /// + /// + /// A that represents + /// the WebSocket handshake request. + /// + /// + /// + /// A that specifies the name of the subprotocol + /// supported on the WebSocket connection. + /// + /// + /// if not necessary. + /// + /// + /// + /// + /// An delegate. + /// + /// + /// It specifies the delegate called when a new WebSocket instance is + /// initialized. + /// + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. + /// + /// + /// -or- + /// + /// + /// caused an exception. + /// + /// + /// + /// + /// This method has already been done. + /// + /// + /// -or- + /// + /// + /// The client request is not a WebSocket handshake request. + /// + /// + public HttpListenerWebSocketContext AcceptWebSocket ( + string protocol, + Action initializer + ) { if (_websocketContext != null) { - var msg = "The accepting is already in progress."; + var msg = "The method has already been done."; + + throw new InvalidOperationException (msg); + } + + if (!_request.IsWebSocketRequest) { + var msg = "The request is not a WebSocket handshake request."; throw new InvalidOperationException (msg); } @@ -319,7 +424,27 @@ public HttpListenerWebSocketContext AcceptWebSocket (string protocol) } } - return GetWebSocketContext (protocol); + var ret = GetWebSocketContext (protocol); + + var ws = ret.WebSocket; + + if (initializer != null) { + try { + initializer (ws); + } + catch (Exception ex) { + if (ws.ReadyState == WebSocketState.New) + _websocketContext = null; + + var msg = "It caused an exception."; + + throw new ArgumentException (msg, "initializer", ex); + } + } + + ws.Accept (); + + return ret; } #endregion diff --git a/websocket-sharp/Net/HttpListenerException.cs b/websocket-sharp/Net/HttpListenerException.cs index 64b7c1d82..dec858d53 100644 --- a/websocket-sharp/Net/HttpListenerException.cs +++ b/websocket-sharp/Net/HttpListenerException.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2012-2021 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -54,8 +54,7 @@ public class HttpListenerException : Win32Exception /// /// Initializes a new instance of the - /// class from the specified instances of the - /// and classes. + /// class with the specified serialized data. /// /// /// A that contains the serialized @@ -65,8 +64,12 @@ public class HttpListenerException : Win32Exception /// A that specifies the source for /// the deserialization. /// + /// + /// is . + /// protected HttpListenerException ( - SerializationInfo serializationInfo, StreamingContext streamingContext + SerializationInfo serializationInfo, + StreamingContext streamingContext ) : base (serializationInfo, streamingContext) { diff --git a/websocket-sharp/Net/HttpListenerPrefix.cs b/websocket-sharp/Net/HttpListenerPrefix.cs index 3c9b0f8ae..aba9d1bfd 100644 --- a/websocket-sharp/Net/HttpListenerPrefix.cs +++ b/websocket-sharp/Net/HttpListenerPrefix.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2012-2020 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -47,30 +47,18 @@ internal sealed class HttpListenerPrefix #region Private Fields private string _host; + private bool _isSecure; private HttpListener _listener; private string _original; private string _path; private string _port; private string _prefix; - private bool _secure; + private string _scheme; #endregion #region Internal Constructors - /// - /// Initializes a new instance of the class - /// with the specified URI prefix and HTTP listener. - /// - /// - /// This constructor must be called after calling the CheckPrefix method. - /// - /// - /// A that specifies the URI prefix. - /// - /// - /// A that specifies the HTTP listener. - /// internal HttpListenerPrefix (string uriPrefix, HttpListener listener) { _original = uriPrefix; @@ -91,7 +79,7 @@ public string Host { public bool IsSecure { get { - return _secure; + return _isSecure; } } @@ -119,39 +107,48 @@ public string Port { } } + public string Scheme { + get { + return _scheme; + } + } + #endregion #region Private Methods private void parse (string uriPrefix) { - if (uriPrefix.StartsWith ("https")) - _secure = true; + var compType = StringComparison.Ordinal; + + _isSecure = uriPrefix.StartsWith ("https", compType); + _scheme = _isSecure ? "https" : "http"; + + var hostStartIdx = uriPrefix.IndexOf (':') + 3; var len = uriPrefix.Length; - var host = uriPrefix.IndexOf (':') + 3; - var root = uriPrefix.IndexOf ('/', host + 1, len - host - 1); + var rootIdx = uriPrefix + .IndexOf ('/', hostStartIdx + 1, len - hostStartIdx - 1); - var colon = uriPrefix.LastIndexOf (':', root - 1, root - host - 1); + var colonIdx = uriPrefix + .LastIndexOf (':', rootIdx - 1, rootIdx - hostStartIdx - 1); - if (uriPrefix[root - 1] != ']' && colon > host) { - _host = uriPrefix.Substring (host, colon - host); - _port = uriPrefix.Substring (colon + 1, root - colon - 1); + var hasPort = uriPrefix[rootIdx - 1] != ']' && colonIdx > hostStartIdx; + + if (hasPort) { + _host = uriPrefix.Substring (hostStartIdx, colonIdx - hostStartIdx); + _port = uriPrefix.Substring (colonIdx + 1, rootIdx - colonIdx - 1); } else { - _host = uriPrefix.Substring (host, root - host); - _port = _secure ? "443" : "80"; + _host = uriPrefix.Substring (hostStartIdx, rootIdx - hostStartIdx); + _port = _isSecure ? "443" : "80"; } - _path = uriPrefix.Substring (root); + _path = uriPrefix.Substring (rootIdx); + + var fmt = "{0}://{1}:{2}{3}"; - _prefix = String.Format ( - "{0}://{1}:{2}{3}", - _secure ? "https" : "http", - _host, - _port, - _path - ); + _prefix = String.Format (fmt, _scheme, _host, _port, _path); } #endregion @@ -171,77 +168,59 @@ public static void CheckPrefix (string uriPrefix) throw new ArgumentException (msg, "uriPrefix"); } - var schm = uriPrefix.StartsWith ("http://") - || uriPrefix.StartsWith ("https://"); + var compType = StringComparison.Ordinal; + var isHttpSchm = uriPrefix.StartsWith ("http://", compType) + || uriPrefix.StartsWith ("https://", compType); - if (!schm) { - var msg = "The scheme is not 'http' or 'https'."; + if (!isHttpSchm) { + var msg = "The scheme is not http or https."; throw new ArgumentException (msg, "uriPrefix"); } - var end = len - 1; + var endIdx = len - 1; - if (uriPrefix[end] != '/') { - var msg = "It ends without '/'."; + if (uriPrefix[endIdx] != '/') { + var msg = "It ends without a forward slash."; throw new ArgumentException (msg, "uriPrefix"); } - var host = uriPrefix.IndexOf (':') + 3; + var hostStartIdx = uriPrefix.IndexOf (':') + 3; - if (host >= end) { + if (hostStartIdx >= endIdx) { var msg = "No host is specified."; throw new ArgumentException (msg, "uriPrefix"); } - if (uriPrefix[host] == ':') { + if (uriPrefix[hostStartIdx] == ':') { var msg = "No host is specified."; throw new ArgumentException (msg, "uriPrefix"); } - var root = uriPrefix.IndexOf ('/', host, len - host); + var rootIdx = uriPrefix.IndexOf ('/', hostStartIdx, len - hostStartIdx); - if (root == host) { + if (rootIdx == hostStartIdx) { var msg = "No host is specified."; throw new ArgumentException (msg, "uriPrefix"); } - if (uriPrefix[root - 1] == ':') { + if (uriPrefix[rootIdx - 1] == ':') { var msg = "No port is specified."; throw new ArgumentException (msg, "uriPrefix"); } - if (root == end - 1) { + if (rootIdx == endIdx - 1) { var msg = "No path is specified."; throw new ArgumentException (msg, "uriPrefix"); } } - /// - /// Determines whether the current instance is equal to the specified - /// instance. - /// - /// - /// This method will be required to detect duplicates in any collection. - /// - /// - /// - /// An instance to compare to the current instance. - /// - /// - /// An reference to a instance. - /// - /// - /// - /// true if the current instance and have - /// the same URI prefix; otherwise, false. - /// public override bool Equals (object obj) { var pref = obj as HttpListenerPrefix; @@ -249,15 +228,6 @@ public override bool Equals (object obj) return pref != null && _prefix.Equals (pref._prefix); } - /// - /// Gets the hash code for the current instance. - /// - /// - /// This method will be required to detect duplicates in any collection. - /// - /// - /// An that represents the hash code. - /// public override int GetHashCode () { return _prefix.GetHashCode (); diff --git a/websocket-sharp/Net/HttpListenerPrefixCollection.cs b/websocket-sharp/Net/HttpListenerPrefixCollection.cs index cbe062ee1..3f5c44a2c 100644 --- a/websocket-sharp/Net/HttpListenerPrefixCollection.cs +++ b/websocket-sharp/Net/HttpListenerPrefixCollection.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2012-2020 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -48,8 +48,8 @@ namespace WebSocketSharp.Net /// the class. /// /// - /// The instance responds to the request which has - /// a requested URI that the prefixes most closely match. + /// The instance responds to the request which + /// has a requested URI that the prefixes most closely match. /// public class HttpListenerPrefixCollection : ICollection { @@ -65,6 +65,7 @@ public class HttpListenerPrefixCollection : ICollection internal HttpListenerPrefixCollection (HttpListener listener) { _listener = listener; + _prefixes = new List (); } @@ -123,15 +124,15 @@ public bool IsSynchronized { /// /// /// It must be a well-formed URI prefix with http or https scheme, - /// and must end with a '/'. + /// and must end with a forward slash (/). /// /// - /// - /// is . - /// /// /// is invalid. /// + /// + /// is . + /// /// /// The instance associated with this /// collection is closed. @@ -207,16 +208,16 @@ public bool Contains (string uriPrefix) /// An that specifies the zero-based index in /// the array at which copying begins. /// + /// + /// The space from to the end of + /// is not enough to copy to. + /// /// /// is . /// /// /// is less than zero. /// - /// - /// The space from to the end of - /// is not enough to copy to. - /// /// /// The instance associated with this /// collection is closed. diff --git a/websocket-sharp/Net/HttpListenerRequest.cs b/websocket-sharp/Net/HttpListenerRequest.cs index f9428d950..9c7b1a16d 100644 --- a/websocket-sharp/Net/HttpListenerRequest.cs +++ b/websocket-sharp/Net/HttpListenerRequest.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2012-2022 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -113,11 +113,8 @@ internal HttpListenerRequest (HttpListenerContext context) /// /// /// - /// An array of or . - /// - /// - /// The array contains the names of the media types specified in - /// the value of the Accept header. + /// An array of that contains the names of + /// the media types specified in the value of the Accept header. /// /// /// if the header is not present. @@ -203,10 +200,8 @@ public long ContentLength64 { /// /// /// - /// A or . - /// - /// - /// The string represents the value of the Content-Type header. + /// A that represents the value of the Content-Type + /// header. /// /// /// if the header is not present. @@ -219,7 +214,7 @@ public string ContentType { } /// - /// Gets the cookies included in the request. + /// Gets the HTTP cookies included in the request. /// /// /// @@ -252,7 +247,7 @@ public bool HasEntityBody { } /// - /// Gets the headers included in the request. + /// Gets the HTTP headers included in the request. /// /// /// A that contains the headers. @@ -464,10 +459,7 @@ public Guid RequestTraceIdentifier { /// /// /// - /// A or . - /// - /// - /// The Uri represents the URL parsed from the request. + /// A that represents the URL parsed from the request. /// /// /// if the URL cannot be parsed. @@ -496,10 +488,7 @@ public Uri Url { /// /// /// - /// A or . - /// - /// - /// The Uri represents the value of the Referer header. + /// A that represents the value of the Referer header. /// /// /// if the header value is not available. @@ -524,10 +513,8 @@ public Uri UrlReferrer { /// /// /// - /// A or . - /// - /// - /// The string represents the value of the User-Agent header. + /// A that represents the value of the User-Agent + /// header. /// /// /// if the header is not present. @@ -574,11 +561,9 @@ public string UserHostName { /// /// /// - /// An array of or . - /// - /// - /// The array contains the names of the natural languages specified in - /// the value of the Accept-Language header. + /// An array of that contains the names of the + /// natural languages specified in the value of the Accept-Language + /// header. /// /// /// if the header is not present. @@ -870,22 +855,28 @@ internal void SetRequestLine (string requestLine) /// Begins getting the certificate provided by the client asynchronously. /// /// - /// An instance that indicates the status of - /// the operation. + /// An instance that represents the status of + /// the asynchronous operation. /// /// - /// An delegate that invokes the method called - /// when the operation is complete. + /// + /// An delegate. + /// + /// + /// It specifies the delegate called when the asynchronous operation is + /// complete. + /// /// /// - /// An that specifies a user defined object to pass - /// to the callback delegate. + /// An that specifies a user defined object to pass to + /// . /// /// /// This method is not supported. /// public IAsyncResult BeginGetClientCertificate ( - AsyncCallback requestCallback, object state + AsyncCallback requestCallback, + object state ) { throw new NotSupportedException (); @@ -900,8 +891,8 @@ public IAsyncResult BeginGetClientCertificate ( /// provided by the client. /// /// - /// An instance returned when the operation - /// started. + /// An instance obtained by calling + /// the method. /// /// /// This method is not supported. diff --git a/websocket-sharp/Net/HttpListenerResponse.cs b/websocket-sharp/Net/HttpListenerResponse.cs index 492f51021..500d42827 100644 --- a/websocket-sharp/Net/HttpListenerResponse.cs +++ b/websocket-sharp/Net/HttpListenerResponse.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2012-2021 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -63,22 +63,32 @@ public sealed class HttpListenerResponse : IDisposable { #region Private Fields - private bool _closeConnection; - private Encoding _contentEncoding; - private long _contentLength; - private string _contentType; - private HttpListenerContext _context; - private CookieCollection _cookies; - private bool _disposed; - private WebHeaderCollection _headers; - private bool _headersSent; - private bool _keepAlive; - private ResponseStream _outputStream; - private Uri _redirectLocation; - private bool _sendChunked; - private int _statusCode; - private string _statusDescription; - private Version _version; + private bool _closeConnection; + private Encoding _contentEncoding; + private long _contentLength; + private string _contentType; + private HttpListenerContext _context; + private CookieCollection _cookies; + private static readonly string _defaultProductName; + private bool _disposed; + private WebHeaderCollection _headers; + private bool _headersSent; + private bool _keepAlive; + private ResponseStream _outputStream; + private Uri _redirectLocation; + private bool _sendChunked; + private int _statusCode; + private string _statusDescription; + private Version _version; + + #endregion + + #region Static Constructor + + static HttpListenerResponse () + { + _defaultProductName = "websocket-sharp/1.0"; + } #endregion @@ -87,6 +97,7 @@ public sealed class HttpListenerResponse : IDisposable internal HttpListenerResponse (HttpListenerContext context) { _context = context; + _keepAlive = true; _statusCode = 200; _statusDescription = "OK"; @@ -115,33 +126,27 @@ internal WebHeaderCollection FullHeaders { headers.Add (_headers); if (_contentType != null) { - headers.InternalSet ( - "Content-Type", - createContentTypeHeaderText (_contentType, _contentEncoding), - true - ); + var val = createContentTypeHeaderText (_contentType, _contentEncoding); + + headers.InternalSet ("Content-Type", val, true); } if (headers["Server"] == null) - headers.InternalSet ("Server", "websocket-sharp/1.0", true); + headers.InternalSet ("Server", _defaultProductName, true); if (headers["Date"] == null) { - headers.InternalSet ( - "Date", - DateTime.UtcNow.ToString ("r", CultureInfo.InvariantCulture), - true - ); + var val = DateTime.UtcNow.ToString ("r", CultureInfo.InvariantCulture); + + headers.InternalSet ("Date", val, true); } if (_sendChunked) { headers.InternalSet ("Transfer-Encoding", "chunked", true); } else { - headers.InternalSet ( - "Content-Length", - _contentLength.ToString (CultureInfo.InvariantCulture), - true - ); + var val = _contentLength.ToString (CultureInfo.InvariantCulture); + + headers.InternalSet ("Content-Length", val, true); } /* @@ -154,8 +159,11 @@ internal WebHeaderCollection FullHeaders { * - 500 Internal Server Error * - 503 Service Unavailable */ + + var reuses = _context.Connection.Reuses; var closeConn = !_context.Request.KeepAlive || !_keepAlive + || reuses >= 100 || _statusCode == 400 || _statusCode == 408 || _statusCode == 411 @@ -164,20 +172,15 @@ internal WebHeaderCollection FullHeaders { || _statusCode == 500 || _statusCode == 503; - var reuses = _context.Connection.Reuses; - - if (closeConn || reuses >= 100) { + if (closeConn) { headers.InternalSet ("Connection", "close", true); } else { - headers.InternalSet ( - "Keep-Alive", - String.Format ("timeout=15,max={0}", 100 - reuses), - true - ); - - if (_context.Request.ProtocolVersion < HttpVersion.Version11) - headers.InternalSet ("Connection", "keep-alive", true); + var fmt = "timeout=15,max={0}"; + var max = 100 - reuses; + var val = String.Format (fmt, max); + + headers.InternalSet ("Keep-Alive", val, true); } if (_redirectLocation != null) @@ -185,11 +188,9 @@ internal WebHeaderCollection FullHeaders { if (_cookies != null) { foreach (var cookie in _cookies) { - headers.InternalSet ( - "Set-Cookie", - cookie.ToResponseString (), - true - ); + var val = cookie.ToResponseString (); + + headers.InternalSet ("Set-Cookie", val, true); } } @@ -207,14 +208,17 @@ internal bool HeadersSent { } } + internal string ObjectName { + get { + return GetType ().ToString (); + } + } + internal string StatusLine { get { - return String.Format ( - "HTTP/{0} {1} {2}\r\n", - _version, - _statusCode, - _statusDescription - ); + var fmt = "HTTP/{0} {1} {2}\r\n"; + + return String.Format (fmt, _version, _statusCode, _statusDescription); } } @@ -250,13 +254,12 @@ public Encoding ContentEncoding { } set { - if (_disposed) { - var name = GetType ().ToString (); - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); if (_headersSent) { var msg = "The response is already being sent."; + throw new InvalidOperationException (msg); } @@ -295,18 +298,18 @@ public long ContentLength64 { } set { - if (_disposed) { - var name = GetType ().ToString (); - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); if (_headersSent) { var msg = "The response is already being sent."; + throw new InvalidOperationException (msg); } if (value < 0) { var msg = "Less than zero."; + throw new ArgumentOutOfRangeException (msg, "value"); } @@ -357,28 +360,27 @@ public string ContentType { } set { - if (_disposed) { - var name = GetType ().ToString (); - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); if (_headersSent) { var msg = "The response is already being sent."; + throw new InvalidOperationException (msg); } if (value == null) { _contentType = null; + return; } - if (value.Length == 0) { - var msg = "An empty string."; - throw new ArgumentException (msg, "value"); - } + if (value.Length == 0) + throw new ArgumentException ("An empty string.", "value"); if (!isValidForContentType (value)) { var msg = "It contains an invalid character."; + throw new ArgumentException (msg, "value"); } @@ -387,7 +389,7 @@ public string ContentType { } /// - /// Gets or sets the collection of cookies sent with the response. + /// Gets or sets the collection of the HTTP cookies sent with the response. /// /// /// A that contains the cookies sent with @@ -427,11 +429,13 @@ public WebHeaderCollection Headers { set { if (value == null) { _headers = null; + return; } if (value.State != HttpHeaderType.Response) { var msg = "The value is not valid for a response."; + throw new InvalidOperationException (msg); } @@ -464,13 +468,12 @@ public bool KeepAlive { } set { - if (_disposed) { - var name = GetType ().ToString (); - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); if (_headersSent) { var msg = "The response is already being sent."; + throw new InvalidOperationException (msg); } @@ -490,10 +493,8 @@ public bool KeepAlive { /// public Stream OutputStream { get { - if (_disposed) { - var name = GetType ().ToString (); - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); if (_outputStream == null) _outputStream = _context.Connection.GetResponseStream (); @@ -564,29 +565,29 @@ public string RedirectLocation { } set { - if (_disposed) { - var name = GetType ().ToString (); - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); if (_headersSent) { var msg = "The response is already being sent."; + throw new InvalidOperationException (msg); } if (value == null) { _redirectLocation = null; + return; } - if (value.Length == 0) { - var msg = "An empty string."; - throw new ArgumentException (msg, "value"); - } + if (value.Length == 0) + throw new ArgumentException ("An empty string.", "value"); Uri uri; + if (!Uri.TryCreate (value, UriKind.Absolute, out uri)) { var msg = "Not an absolute URL."; + throw new ArgumentException (msg, "value"); } @@ -619,13 +620,12 @@ public bool SendChunked { } set { - if (_disposed) { - var name = GetType ().ToString (); - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); if (_headersSent) { var msg = "The response is already being sent."; + throw new InvalidOperationException (msg); } @@ -666,18 +666,18 @@ public int StatusCode { } set { - if (_disposed) { - var name = GetType ().ToString (); - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); if (_headersSent) { var msg = "The response is already being sent."; + throw new InvalidOperationException (msg); } if (value < 100 || value > 999) { var msg = "A value is not between 100 and 999 inclusive."; + throw new System.Net.ProtocolViolationException (msg); } @@ -705,12 +705,12 @@ public int StatusCode { /// An empty string if an RFC 2616 description does not exist. /// /// - /// - /// The value specified for a set operation is . - /// /// /// The value specified for a set operation contains an invalid character. /// + /// + /// The value specified for a set operation is . + /// /// /// The response is already being sent. /// @@ -723,13 +723,12 @@ public string StatusDescription { } set { - if (_disposed) { - var name = GetType ().ToString (); - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); if (_headersSent) { var msg = "The response is already being sent."; + throw new InvalidOperationException (msg); } @@ -738,11 +737,13 @@ public string StatusDescription { if (value.Length == 0) { _statusDescription = _statusCode.GetStatusDescription (); + return; } if (!isValidForStatusDescription (value)) { var msg = "It contains an invalid character."; + throw new ArgumentException (msg, "value"); } @@ -756,14 +757,14 @@ public string StatusDescription { private bool canSetCookie (Cookie cookie) { - var found = findCookie (cookie).ToList (); + var res = findCookie (cookie).ToList (); - if (found.Count == 0) + if (res.Count == 0) return true; var ver = cookie.Version; - foreach (var c in found) { + foreach (var c in res) { if (c.Version == ver) return true; } @@ -774,21 +775,20 @@ private bool canSetCookie (Cookie cookie) private void close (bool force) { _disposed = true; + _context.Connection.Close (force); } private void close (byte[] responseEntity, int bufferLength, bool willBlock) { - var stream = OutputStream; - if (willBlock) { - stream.WriteBytes (responseEntity, bufferLength); + OutputStream.WriteBytes (responseEntity, bufferLength); close (false); return; } - stream.WriteBytesAsync ( + OutputStream.WriteBytesAsync ( responseEntity, bufferLength, () => close (false), @@ -797,16 +797,19 @@ private void close (byte[] responseEntity, int bufferLength, bool willBlock) } private static string createContentTypeHeaderText ( - string value, Encoding encoding + string value, + Encoding encoding ) { - if (value.IndexOf ("charset=", StringComparison.Ordinal) > -1) + if (value.Contains ("charset=")) return value; if (encoding == null) return value; - return String.Format ("{0}; charset={1}", value, encoding.WebName); + var fmt = "{0}; charset={1}"; + + return String.Format (fmt, value, encoding.WebName); } private IEnumerable findCookie (Cookie cookie) @@ -865,7 +868,7 @@ public void Abort () } /// - /// Appends the specified cookie to the cookies sent with the response. + /// Appends an HTTP cookie to the cookies sent with the response. /// /// /// A to append. @@ -890,9 +893,6 @@ public void AppendCookie (Cookie cookie) /// A that specifies the value of the header to /// append. /// - /// - /// is . - /// /// /// /// is an empty string. @@ -922,6 +922,9 @@ public void AppendCookie (Cookie cookie) /// is a restricted header name. /// /// + /// + /// is . + /// /// /// The length of is greater than 65,535 /// characters. @@ -965,10 +968,8 @@ public void Close () /// public void Close (byte[] responseEntity, bool willBlock) { - if (_disposed) { - var name = GetType ().ToString (); - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); if (responseEntity == null) throw new ArgumentNullException ("responseEntity"); @@ -977,6 +978,7 @@ public void Close (byte[] responseEntity, bool willBlock) if (len > Int32.MaxValue) { close (responseEntity, 1024, willBlock); + return; } @@ -1048,9 +1050,6 @@ public void CopyFrom (HttpListenerResponse templateResponse) /// A that specifies the absolute URL to which /// the client is redirected to locate a requested resource. /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1062,6 +1061,9 @@ public void CopyFrom (HttpListenerResponse templateResponse) /// is not an absolute URL. /// /// + /// + /// is . + /// /// /// The response is already being sent. /// @@ -1070,27 +1072,26 @@ public void CopyFrom (HttpListenerResponse templateResponse) /// public void Redirect (string url) { - if (_disposed) { - var name = GetType ().ToString (); - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); if (_headersSent) { var msg = "The response is already being sent."; + throw new InvalidOperationException (msg); } if (url == null) throw new ArgumentNullException ("url"); - if (url.Length == 0) { - var msg = "An empty string."; - throw new ArgumentException (msg, "url"); - } + if (url.Length == 0) + throw new ArgumentException ("An empty string.", "url"); Uri uri; + if (!Uri.TryCreate (url, UriKind.Absolute, out uri)) { var msg = "Not an absolute URL."; + throw new ArgumentException (msg, "url"); } @@ -1100,18 +1101,18 @@ public void Redirect (string url) } /// - /// Adds or updates a cookie in the cookies sent with the response. + /// Adds or updates an HTTP cookie in the cookies sent with the response. /// /// /// A to set. /// - /// - /// is . - /// /// /// already exists in the cookies but /// it cannot be updated. /// + /// + /// is . + /// public void SetCookie (Cookie cookie) { if (cookie == null) @@ -1119,6 +1120,7 @@ public void SetCookie (Cookie cookie) if (!canSetCookie (cookie)) { var msg = "It cannot be updated."; + throw new ArgumentException (msg, "cookie"); } @@ -1135,9 +1137,6 @@ public void SetCookie (Cookie cookie) /// /// A that specifies the value of the header to set. /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1167,6 +1166,9 @@ public void SetCookie (Cookie cookie) /// is a restricted header name. /// /// + /// + /// is . + /// /// /// The length of is greater than 65,535 /// characters. diff --git a/websocket-sharp/Net/HttpUtility.cs b/websocket-sharp/Net/HttpUtility.cs index 90461d073..b7db7b922 100644 --- a/websocket-sharp/Net/HttpUtility.cs +++ b/websocket-sharp/Net/HttpUtility.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2005-2009 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2012-2019 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -85,13 +85,16 @@ private static Dictionary getEntities () private static int getNumber (char c) { - return c >= '0' && c <= '9' - ? c - '0' - : c >= 'A' && c <= 'F' - ? c - 'A' + 10 - : c >= 'a' && c <= 'f' - ? c - 'a' + 10 - : -1; + if (c >= '0' && c <= '9') + return c - '0'; + + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + + return -1; } private static int getNumber (byte[] bytes, int offset, int count) @@ -99,12 +102,15 @@ private static int getNumber (byte[] bytes, int offset, int count) var ret = 0; var end = offset + count - 1; + for (var i = offset; i <= end; i++) { - var num = getNumber ((char) bytes[i]); - if (num == -1) + var c = (char) bytes[i]; + var n = getNumber (c); + + if (n == -1) return -1; - ret = (ret << 4) + num; + ret = (ret << 4) + n; } return ret; @@ -115,12 +121,15 @@ private static int getNumber (string s, int offset, int count) var ret = 0; var end = offset + count - 1; + for (var i = offset; i <= end; i++) { - var num = getNumber (s[i]); - if (num == -1) + var c = s[i]; + var n = getNumber (c); + + if (n == -1) return -1; - ret = (ret << 4) + num; + ret = (ret << 4) + n; } return ret; @@ -144,12 +153,14 @@ private static string htmlDecode (string s) if (state == 0) { if (c == '&') { reference.Append ('&'); + state = 1; continue; } buff.Append (c); + continue; } @@ -157,7 +168,9 @@ private static string htmlDecode (string s) buff.Append (reference.ToString ()); reference.Length = 0; + reference.Append ('&'); + state = 1; continue; @@ -187,6 +200,7 @@ private static string htmlDecode (string s) var name = entity.Substring (1, entity.Length - 2); var entities = getEntities (); + if (entities.ContainsKey (name)) buff.Append (entities[name]); else @@ -216,15 +230,18 @@ private static string htmlDecode (string s) if (c == 'x') { state = reference.Length == 3 ? 4 : 2; + continue; } if (!isNumeric (c)) { state = 2; + continue; } num = num * 10 + (c - '0'); + continue; } @@ -242,8 +259,10 @@ private static string htmlDecode (string s) } var n = getNumber (c); + if (n == -1) { state = 2; + continue; } @@ -285,19 +304,41 @@ private static string htmlEncode (string s, bool minimal) var buff = new StringBuilder (); foreach (var c in s) { - buff.Append ( - c == '"' - ? """ - : c == '&' - ? "&" - : c == '<' - ? "<" - : c == '>' - ? ">" - : !minimal && c > 159 - ? String.Format ("&#{0};", (int) c) - : c.ToString () - ); + if (c == '"') { + buff.Append ("""); + + continue; + } + + if (c == '&') { + buff.Append ("&"); + + continue; + } + + if (c == '<') { + buff.Append ("<"); + + continue; + } + + if (c == '>') { + buff.Append (">"); + + continue; + } + + if (c > 159) { + if (!minimal) { + var val = String.Format ("&#{0};", (int) c); + + buff.Append (val); + + continue; + } + } + + buff.Append (c); } return buff.ToString (); @@ -313,6 +354,7 @@ private static string htmlEncode (string s, bool minimal) private static void initEntities () { _entities = new Dictionary (); + _entities.Add ("nbsp", '\u00A0'); _entities.Add ("iexcl", '\u00A1'); _entities.Add ("cent", '\u00A2'); @@ -569,8 +611,7 @@ private static void initEntities () private static bool isAlphabet (char c) { - return (c >= 'A' && c <= 'Z') - || (c >= 'a' && c <= 'z'); + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); } private static bool isNumeric (char c) @@ -611,19 +652,22 @@ private static byte[] urlDecodeToBytes (byte[] bytes, int offset, int count) { using (var buff = new MemoryStream ()) { var end = offset + count - 1; + for (var i = offset; i <= end; i++) { var b = bytes[i]; - var c = (char) b; + if (c == '%') { if (i > end - 2) break; var num = getNumber (bytes, i + 1, 2); + if (num == -1) break; buff.WriteByte ((byte) num); + i += 2; continue; @@ -631,6 +675,7 @@ private static byte[] urlDecodeToBytes (byte[] bytes, int offset, int count) if (c == '+') { buff.WriteByte ((byte) ' '); + continue; } @@ -638,6 +683,7 @@ private static byte[] urlDecodeToBytes (byte[] bytes, int offset, int count) } buff.Close (); + return buff.ToArray (); } } @@ -646,23 +692,28 @@ private static void urlEncode (byte b, Stream output) { if (b > 31 && b < 127) { var c = (char) b; + if (c == ' ') { output.WriteByte ((byte) '+'); + return; } if (isNumeric (c)) { output.WriteByte (b); + return; } if (isAlphabet (c)) { output.WriteByte (b); + return; } if (isUnreserved (c)) { output.WriteByte (b); + return; } } @@ -681,10 +732,12 @@ private static byte[] urlEncodeToBytes (byte[] bytes, int offset, int count) { using (var buff = new MemoryStream ()) { var end = offset + count - 1; + for (var i = offset; i <= end; i++) urlEncode (bytes[i], buff); buff.Close (); + return buff.ToArray (); } } @@ -694,7 +747,10 @@ private static byte[] urlEncodeToBytes (byte[] bytes, int offset, int count) #region Internal Methods internal static Uri CreateRequestUrl ( - string requestUri, string host, bool websocketRequest, bool secure + string requestUri, + string host, + bool websocketRequest, + bool secure ) { if (requestUri == null || requestUri.Length == 0) @@ -776,18 +832,22 @@ Func credentialsFinder return null; var compType = StringComparison.OrdinalIgnoreCase; - if (response.IndexOf (scheme.ToString (), compType) != 0) + + if (!response.StartsWith (scheme.ToString (), compType)) return null; var res = AuthenticationResponse.Parse (response); + if (res == null) return null; var id = res.ToIdentity (); + if (id == null) return null; NetworkCredential cred = null; + try { cred = credentialsFinder (id); } @@ -799,12 +859,14 @@ Func credentialsFinder if (scheme == AuthenticationSchemes.Basic) { var basicId = (HttpBasicIdentity) id; + return basicId.Password == cred.Password ? new GenericPrincipal (id, cred.Roles) : null; } var digestId = (HttpDigestIdentity) id; + return digestId.IsValid (cred.Password, realm, method, null) ? new GenericPrincipal (id, cred.Roles) : null; @@ -833,7 +895,8 @@ internal static Encoding GetEncoding (string contentType) } internal static bool TryGetEncoding ( - string contentType, out Encoding result + string contentType, + out Encoding result ) { result = null; @@ -871,7 +934,9 @@ public static void HtmlAttributeEncode (string s, TextWriter output) if (s.Length == 0) return; - output.Write (htmlEncode (s, true)); + var encodedS = htmlEncode (s, true); + + output.Write (encodedS); } public static string HtmlDecode (string s) @@ -893,7 +958,9 @@ public static void HtmlDecode (string s, TextWriter output) if (s.Length == 0) return; - output.Write (htmlDecode (s)); + var decodedS = htmlDecode (s); + + output.Write (decodedS); } public static string HtmlEncode (string s) @@ -915,7 +982,9 @@ public static void HtmlEncode (string s, TextWriter output) if (s.Length == 0) return; - output.Write (htmlEncode (s, false)); + var encodedS = htmlEncode (s, false); + + output.Write (encodedS); } public static string UrlDecode (string s) @@ -929,11 +998,13 @@ public static string UrlDecode (byte[] bytes, Encoding encoding) throw new ArgumentNullException ("bytes"); var len = bytes.Length; - return len > 0 - ? (encoding ?? Encoding.UTF8).GetString ( - urlDecodeToBytes (bytes, 0, len) - ) - : String.Empty; + + if (len == 0) + return String.Empty; + + var decodedBytes = urlDecodeToBytes (bytes, 0, len); + + return (encoding ?? Encoding.UTF8).GetString (decodedBytes); } public static string UrlDecode (string s, Encoding encoding) @@ -945,19 +1016,23 @@ public static string UrlDecode (string s, Encoding encoding) return s; var bytes = Encoding.ASCII.GetBytes (s); - return (encoding ?? Encoding.UTF8).GetString ( - urlDecodeToBytes (bytes, 0, bytes.Length) - ); + var decodedBytes = urlDecodeToBytes (bytes, 0, bytes.Length); + + return (encoding ?? Encoding.UTF8).GetString (decodedBytes); } public static string UrlDecode ( - byte[] bytes, int offset, int count, Encoding encoding + byte[] bytes, + int offset, + int count, + Encoding encoding ) { if (bytes == null) throw new ArgumentNullException ("bytes"); var len = bytes.Length; + if (len == 0) { if (offset != 0) throw new ArgumentOutOfRangeException ("offset"); @@ -974,11 +1049,12 @@ public static string UrlDecode ( if (count < 0 || count > len - offset) throw new ArgumentOutOfRangeException ("count"); - return count > 0 - ? (encoding ?? Encoding.UTF8).GetString ( - urlDecodeToBytes (bytes, offset, count) - ) - : String.Empty; + if (count == 0) + return String.Empty; + + var decodedBytes = urlDecodeToBytes (bytes, offset, count); + + return (encoding ?? Encoding.UTF8).GetString (decodedBytes); } public static byte[] UrlDecodeToBytes (byte[] bytes) @@ -987,9 +1063,8 @@ public static byte[] UrlDecodeToBytes (byte[] bytes) throw new ArgumentNullException ("bytes"); var len = bytes.Length; - return len > 0 - ? urlDecodeToBytes (bytes, 0, len) - : bytes; + + return len > 0 ? urlDecodeToBytes (bytes, 0, len) : bytes; } public static byte[] UrlDecodeToBytes (string s) @@ -1001,6 +1076,7 @@ public static byte[] UrlDecodeToBytes (string s) return new byte[0]; var bytes = Encoding.ASCII.GetBytes (s); + return urlDecodeToBytes (bytes, 0, bytes.Length); } @@ -1010,6 +1086,7 @@ public static byte[] UrlDecodeToBytes (byte[] bytes, int offset, int count) throw new ArgumentNullException ("bytes"); var len = bytes.Length; + if (len == 0) { if (offset != 0) throw new ArgumentOutOfRangeException ("offset"); @@ -1026,9 +1103,7 @@ public static byte[] UrlDecodeToBytes (byte[] bytes, int offset, int count) if (count < 0 || count > len - offset) throw new ArgumentOutOfRangeException ("count"); - return count > 0 - ? urlDecodeToBytes (bytes, offset, count) - : new byte[0]; + return count > 0 ? urlDecodeToBytes (bytes, offset, count) : new byte[0]; } public static string UrlEncode (byte[] bytes) @@ -1037,9 +1112,13 @@ public static string UrlEncode (byte[] bytes) throw new ArgumentNullException ("bytes"); var len = bytes.Length; - return len > 0 - ? Encoding.ASCII.GetString (urlEncodeToBytes (bytes, 0, len)) - : String.Empty; + + if (len == 0) + return String.Empty; + + var encodedBytes = urlEncodeToBytes (bytes, 0, len); + + return Encoding.ASCII.GetString (encodedBytes); } public static string UrlEncode (string s) @@ -1053,16 +1132,19 @@ public static string UrlEncode (string s, Encoding encoding) throw new ArgumentNullException ("s"); var len = s.Length; + if (len == 0) return s; if (encoding == null) encoding = Encoding.UTF8; - var bytes = new byte[encoding.GetMaxByteCount (len)]; - var realLen = encoding.GetBytes (s, 0, len, bytes, 0); + var maxCnt = encoding.GetMaxByteCount (len); + var bytes = new byte[maxCnt]; + var cnt = encoding.GetBytes (s, 0, len, bytes, 0); + var encodedBytes = urlEncodeToBytes (bytes, 0, cnt); - return Encoding.ASCII.GetString (urlEncodeToBytes (bytes, 0, realLen)); + return Encoding.ASCII.GetString (encodedBytes); } public static string UrlEncode (byte[] bytes, int offset, int count) @@ -1071,6 +1153,7 @@ public static string UrlEncode (byte[] bytes, int offset, int count) throw new ArgumentNullException ("bytes"); var len = bytes.Length; + if (len == 0) { if (offset != 0) throw new ArgumentOutOfRangeException ("offset"); @@ -1087,11 +1170,12 @@ public static string UrlEncode (byte[] bytes, int offset, int count) if (count < 0 || count > len - offset) throw new ArgumentOutOfRangeException ("count"); - return count > 0 - ? Encoding.ASCII.GetString ( - urlEncodeToBytes (bytes, offset, count) - ) - : String.Empty; + if (count == 0) + return String.Empty; + + var encodedBytes = urlEncodeToBytes (bytes, offset, count); + + return Encoding.ASCII.GetString (encodedBytes); } public static byte[] UrlEncodeToBytes (byte[] bytes) @@ -1100,6 +1184,7 @@ public static byte[] UrlEncodeToBytes (byte[] bytes) throw new ArgumentNullException ("bytes"); var len = bytes.Length; + return len > 0 ? urlEncodeToBytes (bytes, 0, len) : bytes; } @@ -1117,6 +1202,7 @@ public static byte[] UrlEncodeToBytes (string s, Encoding encoding) return new byte[0]; var bytes = (encoding ?? Encoding.UTF8).GetBytes (s); + return urlEncodeToBytes (bytes, 0, bytes.Length); } @@ -1126,6 +1212,7 @@ public static byte[] UrlEncodeToBytes (byte[] bytes, int offset, int count) throw new ArgumentNullException ("bytes"); var len = bytes.Length; + if (len == 0) { if (offset != 0) throw new ArgumentOutOfRangeException ("offset"); diff --git a/websocket-sharp/Net/HttpVersion.cs b/websocket-sharp/Net/HttpVersion.cs index d20061e0b..95f8f0a38 100644 --- a/websocket-sharp/Net/HttpVersion.cs +++ b/websocket-sharp/Net/HttpVersion.cs @@ -2,12 +2,12 @@ /* * HttpVersion.cs * - * This code is derived from System.Net.HttpVersion.cs of Mono + * This code is derived from HttpVersion.cs (System.Net) of Mono * (http://www.mono-project.com). * * The MIT License * - * Copyright (c) 2012-2014 sta.blockhead + * Copyright (c) 2012-2024 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 diff --git a/websocket-sharp/Net/NetworkCredential.cs b/websocket-sharp/Net/NetworkCredential.cs index 3ee52f402..f97df971f 100644 --- a/websocket-sharp/Net/NetworkCredential.cs +++ b/websocket-sharp/Net/NetworkCredential.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2014-2017 sta.blockhead + * Copyright (c) 2014-2024 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 @@ -57,57 +57,59 @@ static NetworkCredential () #region Public Constructors /// - /// Initializes a new instance of the class with - /// the specified and . + /// Initializes a new instance of the class + /// with the specified username and password. /// /// - /// A that represents the username associated with + /// A that specifies the username associated with /// the credentials. /// /// - /// A that represents the password for the username + /// A that specifies the password for the username /// associated with the credentials. /// + /// + /// is an empty string. + /// /// /// is . /// - /// - /// is empty. - /// public NetworkCredential (string username, string password) : this (username, password, null, null) { } /// - /// Initializes a new instance of the class with - /// the specified , , - /// and . + /// Initializes a new instance of the class + /// with the specified username, password, domain and roles. /// /// - /// A that represents the username associated with + /// A that specifies the username associated with /// the credentials. /// /// - /// A that represents the password for the username + /// A that specifies the password for the username /// associated with the credentials. /// /// - /// A that represents the domain associated with + /// A that specifies the domain associated with /// the credentials. /// /// - /// An array of that represents the roles - /// associated with the credentials if any. + /// An array of that specifies the roles associated + /// with the credentials if any. /// + /// + /// is an empty string. + /// /// /// is . /// - /// - /// is empty. - /// public NetworkCredential ( - string username, string password, string domain, params string[] roles + string username, + string password, + string domain, + params string[] roles ) { if (username == null) @@ -129,13 +131,15 @@ public NetworkCredential ( /// /// Gets the domain associated with the credentials. /// - /// - /// This property returns an empty string if the domain was - /// initialized with . - /// /// - /// A that represents the domain name - /// to which the username belongs. + /// + /// A that represents the domain name + /// to which the username belongs. + /// + /// + /// An empty string if the value was initialized with + /// . + /// /// public string Domain { get { @@ -150,12 +154,14 @@ internal set { /// /// Gets the password for the username associated with the credentials. /// - /// - /// This property returns an empty string if the password was - /// initialized with . - /// /// - /// A that represents the password. + /// + /// A that represents the password. + /// + /// + /// An empty string if the value was initialized with + /// . + /// /// public string Password { get { @@ -170,13 +176,15 @@ internal set { /// /// Gets the roles associated with the credentials. /// - /// - /// This property returns an empty array if the roles were - /// initialized with . - /// /// - /// An array of that represents the role names - /// to which the username belongs. + /// + /// An array of that represents the role names + /// to which the username belongs. + /// + /// + /// An empty array if the value was initialized with + /// . + /// /// public string[] Roles { get { diff --git a/websocket-sharp/Net/QueryStringCollection.cs b/websocket-sharp/Net/QueryStringCollection.cs index f07f34f40..d5b7a8d9a 100644 --- a/websocket-sharp/Net/QueryStringCollection.cs +++ b/websocket-sharp/Net/QueryStringCollection.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2005-2009 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2018-2022 sta.blockhead + * Copyright (c) 2018-2023 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 @@ -124,13 +124,17 @@ public static QueryStringCollection Parse (string query, Encoding encoding) public override string ToString () { + if (Count == 0) + return String.Empty; + var buff = new StringBuilder (); + var fmt = "{0}={1}&"; + foreach (var key in AllKeys) - buff.AppendFormat ("{0}={1}&", key, this[key]); + buff.AppendFormat (fmt, key, this[key]); - if (buff.Length > 0) - buff.Length--; + buff.Length--; return buff.ToString (); } diff --git a/websocket-sharp/Net/ReadBufferState.cs b/websocket-sharp/Net/ReadBufferState.cs index 1d8280f00..bf0de8848 100644 --- a/websocket-sharp/Net/ReadBufferState.cs +++ b/websocket-sharp/Net/ReadBufferState.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2014-2021 sta.blockhead + * Copyright (c) 2014-2023 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 @@ -56,7 +56,10 @@ internal class ReadBufferState #region Public Constructors public ReadBufferState ( - byte[] buffer, int offset, int count, HttpStreamAsyncResult asyncResult + byte[] buffer, + int offset, + int count, + HttpStreamAsyncResult asyncResult ) { _buffer = buffer; diff --git a/websocket-sharp/Net/RequestStream.cs b/websocket-sharp/Net/RequestStream.cs index a10096ee7..dd40f920a 100644 --- a/websocket-sharp/Net/RequestStream.cs +++ b/websocket-sharp/Net/RequestStream.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2012-2022 sta.blockhead + * Copyright (c) 2012-2023 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 @@ -88,6 +88,12 @@ internal byte[] InitialBuffer { } } + internal string ObjectName { + get { + return GetType ().ToString (); + } + } + internal int Offset { get { return _offset; @@ -171,14 +177,15 @@ private int fillFromInitialBuffer (byte[] buffer, int offset, int count) #region Public Methods public override IAsyncResult BeginRead ( - byte[] buffer, int offset, int count, AsyncCallback callback, object state + byte[] buffer, + int offset, + int count, + AsyncCallback callback, + object state ) { - if (_disposed) { - var name = GetType ().ToString (); - - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); if (buffer == null) throw new ArgumentNullException ("buffer"); @@ -198,7 +205,7 @@ public override IAsyncResult BeginRead ( var len = buffer.Length; if (offset + count > len) { - var msg = "The sum of 'offset' and 'count' is greater than the length of 'buffer'."; + var msg = "The sum of offset and count is greater than the length of buffer."; throw new ArgumentException (msg); } @@ -228,7 +235,11 @@ public override IAsyncResult BeginRead ( } public override IAsyncResult BeginWrite ( - byte[] buffer, int offset, int count, AsyncCallback callback, object state + byte[] buffer, + int offset, + int count, + AsyncCallback callback, + object state ) { throw new NotSupportedException (); @@ -241,11 +252,8 @@ public override void Close () public override int EndRead (IAsyncResult asyncResult) { - if (_disposed) { - var name = GetType ().ToString (); - - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); if (asyncResult == null) throw new ArgumentNullException ("asyncResult"); @@ -278,11 +286,8 @@ public override void Flush () public override int Read (byte[] buffer, int offset, int count) { - if (_disposed) { - var name = GetType ().ToString (); - - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); if (buffer == null) throw new ArgumentNullException ("buffer"); @@ -302,7 +307,7 @@ public override int Read (byte[] buffer, int offset, int count) var len = buffer.Length; if (offset + count > len) { - var msg = "The sum of 'offset' and 'count' is greater than the length of 'buffer'."; + var msg = "The sum of offset and count is greater than the length of buffer."; throw new ArgumentException (msg); } diff --git a/websocket-sharp/Net/ResponseStream.cs b/websocket-sharp/Net/ResponseStream.cs index c91b6e295..456d1e470 100644 --- a/websocket-sharp/Net/ResponseStream.cs +++ b/websocket-sharp/Net/ResponseStream.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2012-2020 sta.blockhead + * Copyright (c) 2012-2023 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 @@ -97,6 +97,16 @@ bool ignoreWriteExceptions #endregion + #region Internal Properties + + internal string ObjectName { + get { + return GetType ().ToString (); + } + } + + #endregion + #region Public Properties public override bool CanRead { @@ -176,12 +186,15 @@ private void flushBody (bool closing) } } else if (len > 0) { - _writeBody (_bodyBuffer.GetBuffer (), 0, (int) len); + var buff = _bodyBuffer.GetBuffer (); + + _writeBody (buff, 0, (int) len); } } if (!closing) { _bodyBuffer = new MemoryStream (); + return; } @@ -198,24 +211,28 @@ private bool flushHeaders () return false; } - var statusLine = _response.StatusLine; var headers = _response.FullHeaders; - var buff = new MemoryStream (); + var stream = new MemoryStream (); var enc = Encoding.UTF8; - using (var writer = new StreamWriter (buff, enc, 256)) { - writer.Write (statusLine); - writer.Write (headers.ToStringMultiValue (true)); + using (var writer = new StreamWriter (stream, enc, 256)) { + writer.Write (_response.StatusLine); + + var s = headers.ToStringMultiValue (true); + + writer.Write (s); writer.Flush (); var start = enc.GetPreamble ().Length; - var len = buff.Length - start; + var len = stream.Length - start; if (len > _maxHeadersLength) return false; - _write (buff.GetBuffer (), start, (int) len); + var buff = stream.GetBuffer (); + + _write (buff, start, (int) len); } _response.CloseConnection = headers["Connection"] == "close"; @@ -223,16 +240,17 @@ private bool flushHeaders () return true; } - private static byte[] getChunkSizeBytes (int size) + private static byte[] getChunkSizeStringAsBytes (int size) { - var chunkSize = String.Format ("{0:x}\r\n", size); + var fmt = "{0:x}\r\n"; + var s = String.Format (fmt, size); - return Encoding.ASCII.GetBytes (chunkSize); + return Encoding.ASCII.GetBytes (s); } private void writeChunked (byte[] buffer, int offset, int count) { - var size = getChunkSizeBytes (count); + var size = getChunkSizeStringAsBytes (count); _innerStream.Write (size, 0, size.Length); _innerStream.Write (buffer, offset, count); @@ -240,7 +258,9 @@ private void writeChunked (byte[] buffer, int offset, int count) } private void writeChunkedWithoutThrowingException ( - byte[] buffer, int offset, int count + byte[] buffer, + int offset, + int count ) { try { @@ -251,7 +271,9 @@ private void writeChunkedWithoutThrowingException ( } private void writeWithoutThrowingException ( - byte[] buffer, int offset, int count + byte[] buffer, + int offset, + int count ) { try { @@ -324,11 +346,8 @@ public override IAsyncResult BeginWrite ( object state ) { - if (_disposed) { - var name = GetType ().ToString (); - - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); return _bodyBuffer.BeginWrite (buffer, offset, count, callback, state); } @@ -350,11 +369,8 @@ public override int EndRead (IAsyncResult asyncResult) public override void EndWrite (IAsyncResult asyncResult) { - if (_disposed) { - var name = GetType ().ToString (); - - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); _bodyBuffer.EndWrite (asyncResult); } @@ -389,11 +405,8 @@ public override void SetLength (long value) public override void Write (byte[] buffer, int offset, int count) { - if (_disposed) { - var name = GetType ().ToString (); - - throw new ObjectDisposedException (name); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); _bodyBuffer.Write (buffer, offset, count); } diff --git a/websocket-sharp/Net/ServerSslConfiguration.cs b/websocket-sharp/Net/ServerSslConfiguration.cs index 47541f435..b4de5d64c 100644 --- a/websocket-sharp/Net/ServerSslConfiguration.cs +++ b/websocket-sharp/Net/ServerSslConfiguration.cs @@ -5,7 +5,7 @@ * The MIT License * * Copyright (c) 2014 liryna - * Copyright (c) 2014-2020 sta.blockhead + * Copyright (c) 2014-2024 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 @@ -42,7 +42,8 @@ namespace WebSocketSharp.Net { /// - /// Stores the parameters for the used by servers. + /// Stores the parameters for instances used by + /// a server. /// public class ServerSslConfiguration { @@ -69,7 +70,7 @@ public ServerSslConfiguration () /// /// Initializes a new instance of the - /// class that stores the parameters copied from the specified configuration. + /// class copying from the specified configuration. /// /// /// A from which to copy. @@ -117,12 +118,12 @@ public bool CheckCertificateRevocation { } /// - /// Gets or sets a value indicating whether the client is asked for + /// Gets or sets a value indicating whether each client is asked for /// a certificate for authentication. /// /// /// - /// true if the client is asked for a certificate for + /// true if each client is asked for a certificate for /// authentication; otherwise, false. /// /// @@ -141,19 +142,21 @@ public bool ClientCertificateRequired { /// /// Gets or sets the callback used to validate the certificate supplied by - /// the client. + /// each client. /// /// /// The certificate is valid if the callback returns true. /// /// /// - /// A delegate that - /// invokes the method called for validating the certificate. + /// A delegate. /// /// - /// The default value is a delegate that invokes a method that only - /// returns true. + /// It represents the delegate called when the server validates + /// the certificate. + /// + /// + /// The default value invokes a method that only returns true. /// /// public RemoteCertificateValidationCallback ClientCertificateValidationCallback { @@ -170,14 +173,14 @@ public RemoteCertificateValidationCallback ClientCertificateValidationCallback { } /// - /// Gets or sets the protocols used for authentication. + /// Gets or sets the enabled versions of the SSL/TLS protocols. /// /// /// /// Any of the enum values. /// /// - /// It represents the protocols used for authentication. + /// It represents the enabled versions of the SSL/TLS protocols. /// /// /// The default value is . @@ -198,10 +201,10 @@ public SslProtocols EnabledSslProtocols { /// /// /// - /// A or . + /// A that represents an X.509 certificate. /// /// - /// The certificate represents an X.509 certificate. + /// if not present. /// /// /// The default value is . diff --git a/websocket-sharp/Net/WebHeaderCollection.cs b/websocket-sharp/Net/WebHeaderCollection.cs index f92fc2915..da4a79d94 100644 --- a/websocket-sharp/Net/WebHeaderCollection.cs +++ b/websocket-sharp/Net/WebHeaderCollection.cs @@ -9,7 +9,7 @@ * * Copyright (c) 2003 Ximian, Inc. (http://www.ximian.com) * Copyright (c) 2007 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2012-2020 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -133,7 +133,9 @@ static WebHeaderCollection () "Authorization", new HttpHeaderInfo ( "Authorization", - HttpHeaderType.Request | HttpHeaderType.MultiValue + HttpHeaderType.Request + | HttpHeaderType.Restricted + | HttpHeaderType.MultiValue ) }, { @@ -216,14 +218,14 @@ static WebHeaderCollection () "Cookie", new HttpHeaderInfo ( "Cookie", - HttpHeaderType.Request + HttpHeaderType.Request | HttpHeaderType.Restricted ) }, { "Cookie2", new HttpHeaderInfo ( "Cookie2", - HttpHeaderType.Request + HttpHeaderType.Request | HttpHeaderType.Restricted ) }, { @@ -327,7 +329,7 @@ static WebHeaderCollection () "Location", new HttpHeaderInfo ( "Location", - HttpHeaderType.Response + HttpHeaderType.Response | HttpHeaderType.Restricted ) }, { @@ -348,14 +350,16 @@ static WebHeaderCollection () "ProxyAuthenticate", new HttpHeaderInfo ( "Proxy-Authenticate", - HttpHeaderType.Response | HttpHeaderType.MultiValue + HttpHeaderType.Response + | HttpHeaderType.Restricted + | HttpHeaderType.MultiValue ) }, { "ProxyAuthorization", new HttpHeaderInfo ( "Proxy-Authorization", - HttpHeaderType.Request + HttpHeaderType.Request | HttpHeaderType.Restricted ) }, { @@ -427,6 +431,7 @@ static WebHeaderCollection () "Sec-WebSocket-Protocol", HttpHeaderType.Request | HttpHeaderType.Response + | HttpHeaderType.Restricted | HttpHeaderType.MultiValueInRequest ) }, @@ -451,14 +456,18 @@ static WebHeaderCollection () "SetCookie", new HttpHeaderInfo ( "Set-Cookie", - HttpHeaderType.Response | HttpHeaderType.MultiValue + HttpHeaderType.Response + | HttpHeaderType.Restricted + | HttpHeaderType.MultiValue ) }, { "SetCookie2", new HttpHeaderInfo ( "Set-Cookie2", - HttpHeaderType.Response | HttpHeaderType.MultiValue + HttpHeaderType.Response + | HttpHeaderType.Restricted + | HttpHeaderType.MultiValue ) }, { @@ -498,6 +507,7 @@ static WebHeaderCollection () "Upgrade", HttpHeaderType.Request | HttpHeaderType.Response + | HttpHeaderType.Restricted | HttpHeaderType.MultiValue ) }, @@ -560,9 +570,8 @@ internal WebHeaderCollection (HttpHeaderType state, bool internallyUsed) #region Protected Constructors /// - /// Initializes a new instance of the class - /// from the specified instances of the and - /// classes. + /// Initializes a new instance of the + /// class with the specified serialized data. /// /// /// A that contains the serialized @@ -572,15 +581,16 @@ internal WebHeaderCollection (HttpHeaderType state, bool internallyUsed) /// A that specifies the source for /// the deserialization. /// - /// - /// is . - /// /// /// An element with the specified name is not found in /// . /// + /// + /// is . + /// protected WebHeaderCollection ( - SerializationInfo serializationInfo, StreamingContext streamingContext + SerializationInfo serializationInfo, + StreamingContext streamingContext ) { if (serializationInfo == null) @@ -865,10 +875,10 @@ private static string checkValue (string value, string paramName) private static HttpHeaderInfo getHeaderInfo (string name) { - var comparison = StringComparison.InvariantCultureIgnoreCase; + var compType = StringComparison.InvariantCultureIgnoreCase; foreach (var headerInfo in _headers.Values) { - if (headerInfo.HeaderName.Equals (name, comparison)) + if (headerInfo.HeaderName.Equals (name, compType)) return headerInfo; } @@ -987,17 +997,19 @@ internal string ToStringMultiValue (bool response) var buff = new StringBuilder (); + var fmt = "{0}: {1}\r\n"; + for (var i = 0; i < cnt; i++) { var name = GetKey (i); if (isMultiValue (name, response)) { foreach (var val in GetValues (i)) - buff.AppendFormat ("{0}: {1}\r\n", name, val); + buff.AppendFormat (fmt, name, val); continue; } - buff.AppendFormat ("{0}: {1}\r\n", name, Get (i)); + buff.AppendFormat (fmt, name, Get (i)); } buff.Append ("\r\n"); @@ -1019,9 +1031,6 @@ internal string ToStringMultiValue (bool response) /// /// A that specifies the value of the header to add. /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1045,6 +1054,9 @@ internal string ToStringMultiValue (bool response) /// contains an invalid character. /// /// + /// + /// is . + /// /// /// The length of is greater than 65,535 /// characters. @@ -1060,6 +1072,7 @@ protected void AddWithoutValidate (string headerName, string headerValue) var headerType = getHeaderType (headerName); checkAllowed (headerType); + add (headerName, headerValue, headerType); } @@ -1074,9 +1087,6 @@ protected void AddWithoutValidate (string headerName, string headerValue) /// A that specifies the header to add, /// with the name and value separated by a colon character (':'). /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1120,6 +1130,9 @@ protected void AddWithoutValidate (string headerName, string headerValue) /// is a restricted header. /// /// + /// + /// is . + /// /// /// The length of the value part of is greater /// than 65,535 characters. @@ -1149,9 +1162,7 @@ public void Add (string header) } var name = header.Substring (0, idx); - var val = idx < len - 1 - ? header.Substring (idx + 1) - : String.Empty; + var val = idx < len - 1 ? header.Substring (idx + 1) : String.Empty; name = checkName (name, "header"); val = checkValue (val, "header"); @@ -1160,6 +1171,7 @@ public void Add (string header) checkRestricted (name, headerType); checkAllowed (headerType); + add (name, val, headerType); } @@ -1205,6 +1217,7 @@ public void Add (HttpRequestHeader header, string value) checkRestricted (name, HttpHeaderType.Request); checkAllowed (HttpHeaderType.Request); + add (name, value, HttpHeaderType.Request); } @@ -1250,6 +1263,7 @@ public void Add (HttpResponseHeader header, string value) checkRestricted (name, HttpHeaderType.Response); checkAllowed (HttpHeaderType.Response); + add (name, value, HttpHeaderType.Response); } @@ -1262,9 +1276,6 @@ public void Add (HttpResponseHeader header, string value) /// /// A that specifies the value of the header to add. /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1294,6 +1305,9 @@ public void Add (HttpResponseHeader header, string value) /// is a restricted header name. /// /// + /// + /// is . + /// /// /// The length of is greater than 65,535 /// characters. @@ -1310,6 +1324,7 @@ public override void Add (string name, string value) checkRestricted (name, headerType); checkAllowed (headerType); + add (name, value, headerType); } @@ -1319,6 +1334,7 @@ public override void Add (string name, string value) public override void Clear () { base.Clear (); + _state = HttpHeaderType.Unspecified; } @@ -1330,7 +1346,7 @@ public override void Clear () /// /// /// An that specifies the zero-based index of the header - /// to find. + /// to get. /// /// /// is out of allowable range of indexes for @@ -1353,7 +1369,7 @@ public override string Get (int index) /// /// /// - /// A that specifies the name of the header to find. + /// A that specifies the name of the header to get. /// public override string Get (string name) { @@ -1380,7 +1396,7 @@ public override IEnumerator GetEnumerator () /// /// /// An that specifies the zero-based index of the header - /// to find. + /// to get. /// /// /// is out of allowable range of indexes for @@ -1405,7 +1421,7 @@ public override string GetKey (int index) /// /// /// An that specifies the zero-based index of the header - /// to find. + /// to get. /// /// /// is out of allowable range of indexes for @@ -1431,7 +1447,7 @@ public override string[] GetValues (int index) /// /// /// - /// A that specifies the name of the header to find. + /// A that specifies the name of the header to get. /// public override string[] GetValues (string name) { @@ -1441,11 +1457,11 @@ public override string[] GetValues (string name) } /// - /// Populates a instance with the data - /// needed to serialize this instance. + /// Populates the specified instance with + /// the data needed to serialize the current instance. /// /// - /// A to populate with the data. + /// A that holds the serialized object data. /// /// /// A that specifies the destination for @@ -1461,7 +1477,8 @@ public override string[] GetValues (string name) ) ] public override void GetObjectData ( - SerializationInfo serializationInfo, StreamingContext streamingContext + SerializationInfo serializationInfo, + StreamingContext streamingContext ) { if (serializationInfo == null) @@ -1481,7 +1498,7 @@ public override void GetObjectData ( } /// - /// Determines whether the specified HTTP header can be set for the request. + /// Determines whether the specified header can be set for the request. /// /// /// true if the header cannot be set; otherwise, false. @@ -1489,9 +1506,6 @@ public override void GetObjectData ( /// /// A that specifies the name of the header to test. /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1509,14 +1523,17 @@ public override void GetObjectData ( /// contains an invalid character. /// /// + /// + /// is . + /// public static bool IsRestricted (string headerName) { return IsRestricted (headerName, false); } /// - /// Determines whether the specified HTTP header can be set for the request - /// or the response. + /// Determines whether the specified header can be set for the request or + /// the response. /// /// /// true if the header cannot be set; otherwise, false. @@ -1528,9 +1545,6 @@ public static bool IsRestricted (string headerName) /// A : true if the test is for the response; /// otherwise, false. /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1548,6 +1562,9 @@ public static bool IsRestricted (string headerName) /// contains an invalid character. /// /// + /// + /// is . + /// public static bool IsRestricted (string headerName, bool response) { headerName = checkName (headerName, "headerName"); @@ -1591,6 +1608,7 @@ public void Remove (HttpRequestHeader header) checkRestricted (name, HttpHeaderType.Request); checkAllowed (HttpHeaderType.Request); + base.Remove (name); } @@ -1618,6 +1636,7 @@ public void Remove (HttpResponseHeader header) checkRestricted (name, HttpHeaderType.Response); checkAllowed (HttpHeaderType.Response); + base.Remove (name); } @@ -1627,9 +1646,6 @@ public void Remove (HttpResponseHeader header) /// /// A that specifies the name of the header to remove. /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1653,6 +1669,9 @@ public void Remove (HttpResponseHeader header) /// is a restricted header name. /// /// + /// + /// is . + /// /// /// This instance does not allow the header. /// @@ -1664,6 +1683,7 @@ public override void Remove (string name) checkRestricted (name, headerType); checkAllowed (headerType); + base.Remove (name); } @@ -1709,6 +1729,7 @@ public void Set (HttpRequestHeader header, string value) checkRestricted (name, HttpHeaderType.Request); checkAllowed (HttpHeaderType.Request); + set (name, value, HttpHeaderType.Request); } @@ -1754,6 +1775,7 @@ public void Set (HttpResponseHeader header, string value) checkRestricted (name, HttpHeaderType.Response); checkAllowed (HttpHeaderType.Response); + set (name, value, HttpHeaderType.Response); } @@ -1766,9 +1788,6 @@ public void Set (HttpResponseHeader header, string value) /// /// A that specifies the value of the header to set. /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1798,6 +1817,9 @@ public void Set (HttpResponseHeader header, string value) /// is a restricted header name. /// /// + /// + /// is . + /// /// /// The length of is greater than 65,535 /// characters. @@ -1814,6 +1836,7 @@ public override void Set (string name, string value) checkRestricted (name, headerType); checkAllowed (headerType); + set (name, value, headerType); } @@ -1826,7 +1849,9 @@ public override void Set (string name, string value) /// public byte[] ToByteArray () { - return Encoding.UTF8.GetBytes (ToString ()); + var s = ToString (); + + return Encoding.UTF8.GetBytes (s); } /// @@ -1844,8 +1869,10 @@ public override string ToString () var buff = new StringBuilder (); + var fmt = "{0}: {1}\r\n"; + for (var i = 0; i < cnt; i++) - buff.AppendFormat ("{0}: {1}\r\n", GetKey (i), Get (i)); + buff.AppendFormat (fmt, GetKey (i), Get (i)); buff.Append ("\r\n"); @@ -1857,11 +1884,11 @@ public override string ToString () #region Explicit Interface Implementations /// - /// Populates a instance with the data - /// needed to serialize this instance. + /// Populates the specified instance with + /// the data needed to serialize the current instance. /// /// - /// A to populate with the data. + /// A that holds the serialized object data. /// /// /// A that specifies the destination for @@ -1878,7 +1905,8 @@ public override string ToString () ) ] void ISerializable.GetObjectData ( - SerializationInfo serializationInfo, StreamingContext streamingContext + SerializationInfo serializationInfo, + StreamingContext streamingContext ) { GetObjectData (serializationInfo, streamingContext); diff --git a/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs b/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs index ac963c785..7e0358d1f 100644 --- a/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs +++ b/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2018 sta.blockhead + * Copyright (c) 2012-2025 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 @@ -30,13 +30,14 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.IO; +using System.Net.Sockets; using System.Security.Principal; namespace WebSocketSharp.Net.WebSockets { /// - /// Provides the access to the information in a WebSocket handshake request to - /// a instance. + /// Provides the access to the information in a WebSocket handshake request + /// to a instance. /// public class HttpListenerWebSocketContext : WebSocketContext { @@ -50,7 +51,8 @@ public class HttpListenerWebSocketContext : WebSocketContext #region Internal Constructors internal HttpListenerWebSocketContext ( - HttpListenerContext context, string protocol + HttpListenerContext context, + string protocol ) { _context = context; @@ -67,6 +69,12 @@ internal Logger Log { } } + internal Socket Socket { + get { + return _context.Connection.Socket; + } + } + internal Stream Stream { get { return _context.Connection.Stream; @@ -186,7 +194,7 @@ public override bool IsWebSocketRequest { /// A that represents the value of the Origin header. /// /// - /// if the header is not present. + /// if not included. /// /// public override string Origin { @@ -244,7 +252,7 @@ public override Uri RequestUri { /// a valid WebSocket handshake request. /// /// - /// if the header is not present. + /// if not included. /// /// public override string SecWebSocketKey { @@ -270,11 +278,13 @@ public override string SecWebSocketKey { public override IEnumerable SecWebSocketProtocols { get { var val = _context.Request.Headers["Sec-WebSocket-Protocol"]; + if (val == null || val.Length == 0) yield break; foreach (var elm in val.Split (',')) { var protocol = elm.Trim (); + if (protocol.Length == 0) continue; @@ -293,7 +303,7 @@ public override IEnumerable SecWebSocketProtocols { /// version specified by the client. /// /// - /// if the header is not present. + /// if not included. /// /// public override string SecWebSocketVersion { @@ -306,8 +316,8 @@ public override string SecWebSocketVersion { /// Gets the endpoint to which the handshake request is sent. /// /// - /// A that represents the server IP - /// address and port number. + /// A that represents the server + /// IP address and port number. /// public override System.Net.IPEndPoint ServerEndPoint { get { @@ -337,8 +347,8 @@ public override IPrincipal User { /// Gets the endpoint from which the handshake request is sent. /// /// - /// A that represents the client IP - /// address and port number. + /// A that represents the client + /// IP address and port number. /// public override System.Net.IPEndPoint UserEndPoint { get { @@ -347,11 +357,11 @@ public override System.Net.IPEndPoint UserEndPoint { } /// - /// Gets the WebSocket instance used for two-way communication between + /// Gets the WebSocket interface used for two-way communication between /// the client and server. /// /// - /// A . + /// A that represents the interface. /// public override WebSocket WebSocket { get { @@ -371,6 +381,7 @@ internal void Close () internal void Close (HttpStatusCode code) { _context.Response.StatusCode = (int) code; + _context.Response.Close (); } diff --git a/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs b/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs index 8f70dfcde..3e8d12777 100644 --- a/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs +++ b/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2018 sta.blockhead + * Copyright (c) 2012-2025 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 @@ -45,23 +45,21 @@ namespace WebSocketSharp.Net.WebSockets { /// - /// Provides the access to the information in a WebSocket handshake request to - /// a instance. + /// Provides the access to the information in a WebSocket handshake request + /// to a instance. /// internal class TcpListenerWebSocketContext : WebSocketContext { #region Private Fields + private bool _isSecureConnection; private Logger _log; private NameValueCollection _queryString; private HttpRequest _request; private Uri _requestUri; - private bool _secure; - private System.Net.EndPoint _serverEndPoint; private Stream _stream; private TcpClient _tcpClient; private IPrincipal _user; - private System.Net.EndPoint _userEndPoint; private WebSocket _websocket; #endregion @@ -77,10 +75,10 @@ Logger log ) { _tcpClient = tcpClient; - _secure = secure; _log = log; var netStream = tcpClient.GetStream (); + if (secure) { var sslStream = new SslStream ( netStream, @@ -95,16 +93,13 @@ Logger log sslConfig.CheckCertificateRevocation ); + _isSecureConnection = true; _stream = sslStream; } else { _stream = netStream; } - var sock = tcpClient.Client; - _serverEndPoint = sock.LocalEndPoint; - _userEndPoint = sock.RemoteEndPoint; - _request = HttpRequest.ReadRequest (_stream, 90000); _websocket = new WebSocket (this, protocol); } @@ -119,6 +114,12 @@ internal Logger Log { } } + internal Socket Socket { + get { + return _tcpClient.Client; + } + } + internal Stream Stream { get { return _stream; @@ -212,7 +213,7 @@ public override bool IsLocal { /// public override bool IsSecureConnection { get { - return _secure; + return _isSecureConnection; } } @@ -238,7 +239,7 @@ public override bool IsWebSocketRequest { /// A that represents the value of the Origin header. /// /// - /// if the header is not present. + /// if not included. /// /// public override string Origin { @@ -263,10 +264,9 @@ public override NameValueCollection QueryString { get { if (_queryString == null) { var uri = RequestUri; - _queryString = QueryStringCollection.Parse ( - uri != null ? uri.Query : null, - Encoding.UTF8 - ); + var query = uri != null ? uri.Query : null; + + _queryString = QueryStringCollection.Parse (query, Encoding.UTF8); } return _queryString; @@ -291,7 +291,7 @@ public override Uri RequestUri { _request.RequestTarget, _request.Headers["Host"], _request.IsWebSocketRequest, - _secure + _isSecureConnection ); } @@ -313,7 +313,7 @@ public override Uri RequestUri { /// a valid WebSocket handshake request. /// /// - /// if the header is not present. + /// if not included. /// /// public override string SecWebSocketKey { @@ -339,11 +339,13 @@ public override string SecWebSocketKey { public override IEnumerable SecWebSocketProtocols { get { var val = _request.Headers["Sec-WebSocket-Protocol"]; + if (val == null || val.Length == 0) yield break; foreach (var elm in val.Split (',')) { var protocol = elm.Trim (); + if (protocol.Length == 0) continue; @@ -362,7 +364,7 @@ public override IEnumerable SecWebSocketProtocols { /// version specified by the client. /// /// - /// if the header is not present. + /// if not included. /// /// public override string SecWebSocketVersion { @@ -375,12 +377,12 @@ public override string SecWebSocketVersion { /// Gets the endpoint to which the handshake request is sent. /// /// - /// A that represents the server IP - /// address and port number. + /// A that represents the server + /// IP address and port number. /// public override System.Net.IPEndPoint ServerEndPoint { get { - return (System.Net.IPEndPoint) _serverEndPoint; + return (System.Net.IPEndPoint) _tcpClient.Client.LocalEndPoint; } } @@ -406,21 +408,21 @@ public override IPrincipal User { /// Gets the endpoint from which the handshake request is sent. /// /// - /// A that represents the client IP - /// address and port number. + /// A that represents the client + /// IP address and port number. /// public override System.Net.IPEndPoint UserEndPoint { get { - return (System.Net.IPEndPoint) _userEndPoint; + return (System.Net.IPEndPoint) _tcpClient.Client.RemoteEndPoint; } } /// - /// Gets the WebSocket instance used for two-way communication between + /// Gets the WebSocket interface used for two-way communication between /// the client and server. /// /// - /// A . + /// A that represents the interface. /// public override WebSocket WebSocket { get { @@ -430,72 +432,52 @@ public override WebSocket WebSocket { #endregion - #region Private Methods + #region Internal Methods - private HttpRequest sendAuthenticationChallenge (string challenge) + internal void Close () { - var res = HttpResponse.CreateUnauthorizedResponse (challenge); - var bytes = res.ToByteArray (); + _stream.Close (); + _tcpClient.Close (); + } - _stream.Write (bytes, 0, bytes.Length); + internal void Close (HttpStatusCode code) + { + HttpResponse.CreateCloseResponse (code).WriteTo (_stream); - return HttpRequest.ReadRequest (_stream, 15000); + _stream.Close (); + _tcpClient.Close (); } - #endregion + internal void SendAuthenticationChallenge (string challenge) + { + HttpResponse.CreateUnauthorizedResponse (challenge).WriteTo (_stream); - #region Internal Methods + _request = HttpRequest.ReadRequest (_stream, 15000); + } - internal bool Authenticate ( + internal bool SetUser ( AuthenticationSchemes scheme, string realm, Func credentialsFinder ) { - var chal = new AuthenticationChallenge (scheme, realm).ToString (); - - var retry = -1; - Func auth = null; - auth = - () => { - retry++; - if (retry > 99) - return false; - - var user = HttpUtility.CreateUser ( - _request.Headers["Authorization"], - scheme, - realm, - _request.HttpMethod, - credentialsFinder - ); - - if (user != null && user.Identity.IsAuthenticated) { - _user = user; - return true; - } - - _request = sendAuthenticationChallenge (chal); - return auth (); - }; - - return auth (); - } + var user = HttpUtility.CreateUser ( + _request.Headers["Authorization"], + scheme, + realm, + _request.HttpMethod, + credentialsFinder + ); - internal void Close () - { - _stream.Close (); - _tcpClient.Close (); - } + if (user == null) + return false; - internal void Close (HttpStatusCode code) - { - var res = HttpResponse.CreateCloseResponse (code); - var bytes = res.ToByteArray (); - _stream.Write (bytes, 0, bytes.Length); + if (!user.Identity.IsAuthenticated) + return false; - _stream.Close (); - _tcpClient.Close (); + _user = user; + + return true; } #endregion diff --git a/websocket-sharp/Net/WebSockets/WebSocketContext.cs b/websocket-sharp/Net/WebSockets/WebSocketContext.cs index 6921891f7..84841f5bd 100644 --- a/websocket-sharp/Net/WebSockets/WebSocketContext.cs +++ b/websocket-sharp/Net/WebSockets/WebSocketContext.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2018 sta.blockhead + * Copyright (c) 2012-2022 sta.blockhead * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -187,8 +187,8 @@ protected WebSocketContext () /// Gets the endpoint to which the handshake request is sent. /// /// - /// A that represents the server IP - /// address and port number. + /// A that represents the server + /// IP address and port number. /// public abstract System.Net.IPEndPoint ServerEndPoint { get; } @@ -205,17 +205,17 @@ protected WebSocketContext () /// Gets the endpoint from which the handshake request is sent. /// /// - /// A that represents the client IP - /// address and port number. + /// A that represents the client + /// IP address and port number. /// public abstract System.Net.IPEndPoint UserEndPoint { get; } /// - /// Gets the WebSocket instance used for two-way communication between + /// Gets the WebSocket interface used for two-way communication between /// the client and server. /// /// - /// A . + /// A that represents the interface. /// public abstract WebSocket WebSocket { get; } diff --git a/websocket-sharp/Opcode.cs b/websocket-sharp/Opcode.cs index 5a8c632e0..06da1b5e1 100644 --- a/websocket-sharp/Opcode.cs +++ b/websocket-sharp/Opcode.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2016 sta.blockhead + * Copyright (c) 2012-2025 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 @@ -38,7 +38,7 @@ namespace WebSocketSharp /// /// Section 5.2 of RFC 6455. /// - internal enum Opcode : byte + internal enum Opcode { /// /// Equivalent to numeric value 0. Indicates continuation frame. diff --git a/websocket-sharp/PayloadData.cs b/websocket-sharp/PayloadData.cs index 9e40b9404..e01fcd27e 100644 --- a/websocket-sharp/PayloadData.cs +++ b/websocket-sharp/PayloadData.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2019 sta.blockhead + * Copyright (c) 2012-2025 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 @@ -36,9 +36,10 @@ internal class PayloadData : IEnumerable { #region Private Fields - private byte[] _data; - private long _extDataLength; - private long _length; + private byte[] _data; + private static readonly byte[] _emptyBytes; + private long _extDataLength; + private long _length; #endregion @@ -54,7 +55,7 @@ internal class PayloadData : IEnumerable /// /// /// - /// A will occur when the length of + /// A is thrown when the length of /// incoming payload data is greater than the value of this field. /// /// @@ -71,7 +72,9 @@ internal class PayloadData : IEnumerable static PayloadData () { - Empty = new PayloadData (WebSocket.EmptyBytes, 0); + _emptyBytes = new byte[0]; + + Empty = new PayloadData (_emptyBytes, 0); MaxLength = Int64.MaxValue; } @@ -120,7 +123,7 @@ internal long ExtensionDataLength { internal bool HasReservedCode { get { - return _length >= 2 && Code.IsReserved (); + return _length >= 2 && Code.IsReservedStatusCode (); } } @@ -129,10 +132,11 @@ internal string Reason { if (_length <= 2) return String.Empty; - var raw = _data.SubArray (2, _length - 2); + var bytes = _data.SubArray (2, _length - 2); string reason; - return raw.TryGetUTF8DecodedString (out reason) + + return bytes.TryGetUTF8DecodedString (out reason) ? reason : String.Empty; } @@ -154,7 +158,7 @@ public byte[] ExtensionData { get { return _extDataLength > 0 ? _data.SubArray (0, _extDataLength) - : WebSocket.EmptyBytes; + : _emptyBytes; } } diff --git a/websocket-sharp/Rsv.cs b/websocket-sharp/Rsv.cs index 8a10567c5..c2a4dea7a 100644 --- a/websocket-sharp/Rsv.cs +++ b/websocket-sharp/Rsv.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2015 sta.blockhead + * Copyright (c) 2012-2025 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 @@ -31,13 +31,15 @@ namespace WebSocketSharp { /// - /// Indicates whether each RSV (RSV1, RSV2, and RSV3) of a WebSocket frame is non-zero. + /// Indicates whether each RSV (RSV1, RSV2, and RSV3) of a WebSocket + /// frame is non-zero. /// /// /// The values of this enumeration are defined in - /// Section 5.2 of RFC 6455. + /// + /// Section 5.2 of RFC 6455. /// - internal enum Rsv : byte + internal enum Rsv { /// /// Equivalent to numeric value 0. Indicates zero. diff --git a/websocket-sharp/Server/HttpRequestEventArgs.cs b/websocket-sharp/Server/HttpRequestEventArgs.cs index 722fe8b93..45af3c1ee 100644 --- a/websocket-sharp/Server/HttpRequestEventArgs.cs +++ b/websocket-sharp/Server/HttpRequestEventArgs.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2021 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -64,7 +64,8 @@ public class HttpRequestEventArgs : EventArgs #region Internal Constructors internal HttpRequestEventArgs ( - HttpListenerContext context, string documentRootPath + HttpListenerContext context, + string documentRootPath ) { _context = context; @@ -106,12 +107,11 @@ public HttpListenerResponse Response { /// /// /// - /// A instance or - /// if not authenticated. + /// A instance that represents identity, + /// authentication scheme, and security roles for the client. /// /// - /// That instance describes the identity, authentication scheme, - /// and security roles for the client. + /// if the client is not authenticated. /// /// public IPrincipal User { @@ -161,20 +161,17 @@ private static bool tryReadFile (string path, out byte[] contents) /// /// /// - /// An array of or - /// if it fails. + /// An array of that receives the contents of + /// the file. /// /// - /// That array receives the contents of the file. + /// if the read has failed. /// /// /// - /// A that specifies a virtual path to - /// find the file from the document folder. + /// A that specifies a virtual path to find + /// the file from the document folder. /// - /// - /// is . - /// /// /// /// is an empty string. @@ -186,6 +183,9 @@ private static bool tryReadFile (string path, out byte[] contents) /// contains "..". /// /// + /// + /// is . + /// public byte[] ReadFile (string path) { if (path == null) @@ -194,8 +194,11 @@ public byte[] ReadFile (string path) if (path.Length == 0) throw new ArgumentException ("An empty string.", "path"); - if (path.IndexOf ("..") > -1) - throw new ArgumentException ("It contains '..'.", "path"); + if (path.Contains ("..")) { + var msg = "It contains \"..\"."; + + throw new ArgumentException (msg, "path"); + } path = createFilePath (path); byte[] contents; @@ -210,7 +213,7 @@ public byte[] ReadFile (string path) /// the class. /// /// - /// true if it succeeds to read; otherwise, false. + /// true if the try has succeeded; otherwise, false. /// /// /// A that specifies a virtual path to find @@ -218,16 +221,13 @@ public byte[] ReadFile (string path) /// /// /// - /// When this method returns, an array of or - /// if it fails. + /// When this method returns, an array of that + /// receives the contents of the file. /// /// - /// That array receives the contents of the file. + /// if the read has failed. /// /// - /// - /// is . - /// /// /// /// is an empty string. @@ -239,6 +239,9 @@ public byte[] ReadFile (string path) /// contains "..". /// /// + /// + /// is . + /// public bool TryReadFile (string path, out byte[] contents) { if (path == null) @@ -247,8 +250,11 @@ public bool TryReadFile (string path, out byte[] contents) if (path.Length == 0) throw new ArgumentException ("An empty string.", "path"); - if (path.IndexOf ("..") > -1) - throw new ArgumentException ("It contains '..'.", "path"); + if (path.Contains ("..")) { + var msg = "It contains \"..\"."; + + throw new ArgumentException (msg, "path"); + } path = createFilePath (path); diff --git a/websocket-sharp/Server/HttpServer.cs b/websocket-sharp/Server/HttpServer.cs index 82284f8f3..209109984 100644 --- a/websocket-sharp/Server/HttpServer.cs +++ b/websocket-sharp/Server/HttpServer.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2022 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -57,7 +57,7 @@ namespace WebSocketSharp.Server /// The server supports HTTP/1.1 version request and response. /// /// - /// And the server allows to accept WebSocket handshake requests. + /// Also the server allows to accept WebSocket handshake requests. /// /// /// This class can provide multiple WebSocket services. @@ -69,12 +69,11 @@ public class HttpServer private System.Net.IPAddress _address; private string _docRootPath; - private string _hostname; + private bool _isSecure; private HttpListener _listener; private Logger _log; private int _port; private Thread _receiveThread; - private bool _secure; private WebSocketServiceManager _services; private volatile ServerState _state; private object _sync; @@ -142,9 +141,6 @@ public HttpServer (int port) /// /// A that specifies the HTTP URL of the server. /// - /// - /// is . - /// /// /// /// is an empty string. @@ -156,6 +152,9 @@ public HttpServer (int port) /// is invalid. /// /// + /// + /// is . + /// public HttpServer (string url) { if (url == null) @@ -210,7 +209,7 @@ public HttpServer (string url) public HttpServer (int port, bool secure) { if (!port.IsPortNumber ()) { - var msg = "It is less than 1 or greater than 65535."; + var msg = "Less than 1 or greater than 65535."; throw new ArgumentOutOfRangeException ("port", msg); } @@ -239,12 +238,12 @@ public HttpServer (int port, bool secure) /// An that specifies the number of the port on which /// to listen. /// - /// - /// is . - /// /// /// is not a local IP address. /// + /// + /// is . + /// /// /// is less than 1 or greater than 65535. /// @@ -273,12 +272,12 @@ public HttpServer (System.Net.IPAddress address, int port) /// A : true if the new instance provides /// secure connections; otherwise, false. /// - /// - /// is . - /// /// /// is not a local IP address. /// + /// + /// is . + /// /// /// is less than 1 or greater than 65535. /// @@ -288,13 +287,13 @@ public HttpServer (System.Net.IPAddress address, int port, bool secure) throw new ArgumentNullException ("address"); if (!address.IsLocal ()) { - var msg = "It is not a local IP address."; + var msg = "Not a local IP address."; throw new ArgumentException (msg, "address"); } if (!port.IsPortNumber ()) { - var msg = "It is less than 1 or greater than 65535."; + var msg = "Less than 1 or greater than 65535."; throw new ArgumentOutOfRangeException ("port", msg); } @@ -323,8 +322,8 @@ public System.Net.IPAddress Address { /// Gets or sets the scheme used to authenticate the clients. /// /// - /// The set operation does nothing if the server has already started or - /// it is shutting down. + /// The set operation works if the current state of the server is + /// Ready or Stop. /// /// /// @@ -358,13 +357,8 @@ public AuthenticationSchemes AuthenticationSchemes { /// Gets or sets the path to the document folder of the server. /// /// - /// - /// '/' or '\' is trimmed from the end of the value if any. - /// - /// - /// The set operation does nothing if the server has already - /// started or it is shutting down. - /// + /// The set operation works if the current state of the server is + /// Ready or Stop. /// /// /// @@ -372,12 +366,12 @@ public AuthenticationSchemes AuthenticationSchemes { /// from which to find the requested file. /// /// + /// / or \ is trimmed from the end of the value if present. + /// + /// /// The default value is "./Public". /// /// - /// - /// The value specified for a set operation is . - /// /// /// /// The value specified for a set operation is an empty string. @@ -395,6 +389,9 @@ public AuthenticationSchemes AuthenticationSchemes { /// The value specified for a set operation is an invalid path string. /// /// + /// + /// The value specified for a set operation is . + /// public string DocumentRootPath { get { return _docRootPath; @@ -457,33 +454,33 @@ public bool IsListening { } /// - /// Gets a value indicating whether secure connections are provided. + /// Gets a value indicating whether the server provides secure connections. /// /// - /// true if this instance provides secure connections; otherwise, + /// true if the server provides secure connections; otherwise, /// false. /// public bool IsSecure { get { - return _secure; + return _isSecure; } } /// - /// Gets or sets a value indicating whether the server cleans up the - /// inactive sessions periodically. + /// Gets or sets a value indicating whether the server cleans up + /// the inactive sessions periodically. /// /// - /// The set operation does nothing if the server has already started or - /// it is shutting down. + /// The set operation works if the current state of the server is + /// Ready or Stop. /// /// /// - /// true if the server cleans up the inactive sessions every - /// 60 seconds; otherwise, false. + /// true if the server cleans up the inactive sessions + /// every 60 seconds; otherwise, false. /// /// - /// The default value is true. + /// The default value is false. /// /// public bool KeepClean { @@ -528,19 +525,16 @@ public int Port { /// Gets or sets the name of the realm associated with the server. /// /// - /// - /// "SECRET AREA" is used as the name of the realm if the value is - /// or an empty string. - /// - /// - /// The set operation does nothing if the server has already started - /// or it is shutting down. - /// + /// The set operation works if the current state of the server is + /// Ready or Stop. /// /// /// - /// A that represents the name of the realm or - /// . + /// A that represents the name of the realm. + /// + /// + /// "SECRET AREA" is used as the name of the realm if the value is + /// or an empty string. /// /// /// The default value is . @@ -571,8 +565,8 @@ public string Realm { /// resolve to wait for socket in TIME_WAIT state. /// /// - /// The set operation does nothing if the server has already started - /// or it is shutting down. + /// The set operation works if the current state of the server is + /// Ready or Stop. /// /// /// @@ -603,19 +597,19 @@ public bool ReuseAddress { /// Gets the configuration for secure connection. /// /// - /// The configuration will be referenced when attempts to start, + /// The configuration is used when the server attempts to start, /// so it must be configured before the start method is called. /// /// - /// A that represents - /// the configuration used to provide secure connections. + /// A that represents the + /// configuration used to provide secure connections. /// /// - /// This server does not provide secure connections. + /// The server does not provide secure connections. /// public ServerSslConfiguration SslConfiguration { get { - if (!_secure) { + if (!_isSecure) { var msg = "The server does not provide secure connections."; throw new InvalidOperationException (msg); @@ -626,29 +620,28 @@ public ServerSslConfiguration SslConfiguration { } /// - /// Gets or sets the delegate used to find the credentials for - /// an identity. + /// Gets or sets the delegate called to find the credentials for + /// an identity used to authenticate a client. /// /// + /// The set operation works if the current state of the server is + /// Ready or Stop. + /// + /// /// - /// No credentials are found if the method invoked by - /// the delegate returns or - /// the value is . + /// A + /// delegate. /// /// - /// The set operation does nothing if the server has - /// already started or it is shutting down. + /// It represents the delegate called when the server finds + /// the credentials used to authenticate a client. /// - /// - /// /// - /// A Func<, - /// > delegate or - /// if not needed. + /// It must return if the credentials + /// are not found. /// /// - /// The delegate invokes the method called for finding - /// the credentials used to authenticate a client. + /// if not necessary. /// /// /// The default value is . @@ -674,12 +667,13 @@ public Func UserCredentialsFinder { /// Ping or Close. /// /// - /// The set operation does nothing if the server has already started or - /// it is shutting down. + /// The set operation works if the current state of the server is + /// Ready or Stop. /// /// /// - /// A to wait for the response. + /// A that represents the time to wait for + /// the response. /// /// /// The default value is the same as 1 second. @@ -822,30 +816,35 @@ private bool checkCertificate (out string message) } private static HttpListener createListener ( - string hostname, int port, bool secure + string hostname, + int port, + bool secure ) { - var lsnr = new HttpListener (); + var ret = new HttpListener (); + var fmt = "{0}://{1}:{2}/"; var schm = secure ? "https" : "http"; - var pref = String.Format ("{0}://{1}:{2}/", schm, hostname, port); + var pref = String.Format (fmt, schm, hostname, port); - lsnr.Prefixes.Add (pref); + ret.Prefixes.Add (pref); - return lsnr; + return ret; } private void init ( - string hostname, System.Net.IPAddress address, int port, bool secure + string hostname, + System.Net.IPAddress address, + int port, + bool secure ) { - _hostname = hostname; _address = address; _port = port; - _secure = secure; + _isSecure = secure; _docRootPath = "./Public"; - _listener = createListener (_hostname, _port, _secure); + _listener = createListener (hostname, port, secure); _log = _listener.Log; _services = new WebSocketServiceManager (_log); _sync = new object (); @@ -874,12 +873,14 @@ private void processRequest (HttpListenerContext context) if (evt == null) { context.ErrorStatusCode = 501; + context.SendError (); return; } var e = new HttpRequestEventArgs (context, _docRootPath); + evt (this, e); context.Response.Close (); @@ -940,11 +941,8 @@ private void receiveRequest () ); } catch (HttpListenerException ex) { - if (_state == ServerState.ShuttingDown) { - _log.Info ("The underlying listener is stopped."); - + if (_state == ServerState.ShuttingDown) return; - } _log.Fatal (ex.Message); _log.Debug (ex.ToString ()); @@ -952,11 +950,8 @@ private void receiveRequest () break; } catch (InvalidOperationException ex) { - if (_state == ServerState.ShuttingDown) { - _log.Info ("The underlying listener is stopped."); - + if (_state == ServerState.ShuttingDown) return; - } _log.Fatal (ex.Message); _log.Debug (ex.ToString ()); @@ -986,7 +981,7 @@ private void start () if (_state == ServerState.Start || _state == ServerState.ShuttingDown) return; - if (_secure) { + if (_isSecure) { string msg; if (!checkCertificate (out msg)) @@ -1044,7 +1039,9 @@ private void stop (ushort code, string reason) } try { - stopReceiving (5000); + var timeout = 5000; + + stopReceiving (timeout); } catch (Exception ex) { _log.Fatal (ex.Message); @@ -1061,7 +1058,9 @@ private void stopReceiving (int millisecondsTimeout) } private static bool tryCreateUri ( - string uriString, out Uri result, out string message + string uriString, + out Uri result, + out string message ) { result = null; @@ -1082,9 +1081,9 @@ private static bool tryCreateUri ( } var schm = uri.Scheme; - var http = schm == "http" || schm == "https"; + var isHttpSchm = schm == "http" || schm == "https"; - if (!http) { + if (!isHttpSchm) { message = "The scheme part is not 'http' or 'https'."; return false; @@ -1137,12 +1136,9 @@ private static bool tryCreateUri ( /// It must inherit the class. /// /// - /// And also, it must have a public parameterless constructor. + /// Also it must have a public parameterless constructor. /// /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1167,6 +1163,9 @@ private static bool tryCreateUri ( /// is already in use. /// /// + /// + /// is . + /// public void AddWebSocketService (string path) where TBehavior : WebSocketBehavior, new () { @@ -1175,7 +1174,7 @@ public void AddWebSocketService (string path) /// /// Adds a WebSocket service with the specified behavior, path, - /// and delegate. + /// and initializer. /// /// /// @@ -1188,12 +1187,14 @@ public void AddWebSocketService (string path) /// /// /// - /// An Action<TBehavior> delegate or - /// if not needed. + /// An delegate. /// /// - /// The delegate invokes the method called when initializing - /// a new session instance for the service. + /// It specifies the delegate called when the service initializes + /// a new session instance. + /// + /// + /// if not necessary. /// /// /// @@ -1204,12 +1205,9 @@ public void AddWebSocketService (string path) /// It must inherit the class. /// /// - /// And also, it must have a public parameterless constructor. + /// Also it must have a public parameterless constructor. /// /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1234,8 +1232,12 @@ public void AddWebSocketService (string path) /// is already in use. /// /// + /// + /// is . + /// public void AddWebSocketService ( - string path, Action initializer + string path, + Action initializer ) where TBehavior : WebSocketBehavior, new () { @@ -1247,7 +1249,7 @@ public void AddWebSocketService ( /// /// /// The service is stopped with close status 1001 (going away) - /// if it has already started. + /// if the current state of the service is Start. /// /// /// true if the service is successfully found and removed; @@ -1262,9 +1264,6 @@ public void AddWebSocketService ( /// / is trimmed from the end of the string if present. /// /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1283,6 +1282,9 @@ public void AddWebSocketService ( /// query and fragment components. /// /// + /// + /// is . + /// public bool RemoveWebSocketService (string path) { return _services.RemoveService (path); @@ -1292,8 +1294,7 @@ public bool RemoveWebSocketService (string path) /// Starts receiving incoming requests. /// /// - /// This method does nothing if the server has already started or - /// it is shutting down. + /// This method works if the current state of the server is Ready or Stop. /// /// /// @@ -1318,8 +1319,7 @@ public void Start () /// Stops receiving incoming requests. /// /// - /// This method does nothing if the server is not started, - /// it is shutting down, or it has already stopped. + /// This method works if the current state of the server is Start. /// public void Stop () { diff --git a/websocket-sharp/Server/IWebSocketSession.cs b/websocket-sharp/Server/IWebSocketSession.cs index 296b5bf5a..4ddc84aca 100644 --- a/websocket-sharp/Server/IWebSocketSession.cs +++ b/websocket-sharp/Server/IWebSocketSession.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2013-2018 sta.blockhead + * Copyright (c) 2013-2022 sta.blockhead * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -27,7 +27,6 @@ #endregion using System; -using WebSocketSharp.Net.WebSockets; namespace WebSocketSharp.Server { @@ -38,28 +37,6 @@ public interface IWebSocketSession { #region Properties - /// - /// Gets the current state of the WebSocket connection for the session. - /// - /// - /// - /// One of the enum values. - /// - /// - /// It indicates the current state of the connection. - /// - /// - WebSocketState ConnectionState { get; } - - /// - /// Gets the information in the WebSocket handshake request. - /// - /// - /// A instance that provides the access to - /// the information in the handshake request. - /// - WebSocketContext Context { get; } - /// /// Gets the unique ID of the session. /// @@ -69,22 +46,21 @@ public interface IWebSocketSession string ID { get; } /// - /// Gets the name of the WebSocket subprotocol for the session. + /// Gets the time that the session has started. /// /// - /// A that represents the name of the subprotocol - /// if present. + /// A that represents the time that the session + /// has started. /// - string Protocol { get; } + DateTime StartTime { get; } /// - /// Gets the time that the session has started. + /// Gets the WebSocket interface for the session. /// /// - /// A that represents the time that the session - /// has started. + /// A that represents the interface. /// - DateTime StartTime { get; } + WebSocket WebSocket { get; } #endregion } diff --git a/websocket-sharp/Server/WebSocketBehavior.cs b/websocket-sharp/Server/WebSocketBehavior.cs index b2a5dd217..a0befe74d 100644 --- a/websocket-sharp/Server/WebSocketBehavior.cs +++ b/websocket-sharp/Server/WebSocketBehavior.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2021 sta.blockhead + * Copyright (c) 2012-2025 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 @@ -29,6 +29,7 @@ using System; using System.Collections.Specialized; using System.IO; +using System.Security.Principal; using WebSocketSharp.Net; using WebSocketSharp.Net.WebSockets; @@ -46,16 +47,20 @@ public abstract class WebSocketBehavior : IWebSocketSession { #region Private Fields - private WebSocketContext _context; - private Func _cookiesValidator; - private bool _emitOnPing; - private string _id; - private bool _ignoreExtensions; - private Func _originValidator; - private string _protocol; - private WebSocketSessionManager _sessions; - private DateTime _startTime; - private WebSocket _websocket; + private WebSocketContext _context; + private Action _cookiesResponder; + private bool _emitOnPing; + private Func _hostValidator; + private string _id; + private bool _ignoreExtensions; + private bool _noDelay; + private Func _originValidator; + private string _protocol; + private bool _registered; + private WebSocketSessionManager _sessions; + private DateTime _startTime; + private Action _userHeadersResponder; + private WebSocket _websocket; #endregion @@ -74,167 +79,249 @@ protected WebSocketBehavior () #region Protected Properties /// - /// Gets the HTTP headers included in a WebSocket handshake request. + /// Gets the HTTP headers for a session. /// /// - /// - /// A that contains the headers. - /// - /// - /// if the session has not started yet. - /// + /// A that contains the headers + /// included in the WebSocket handshake request. /// + /// + /// The get operation is not available when the session has not started yet. + /// protected NameValueCollection Headers { get { - return _context != null ? _context.Headers : null; + if (!_registered) { + var msg = "The session has not started yet."; + + throw new InvalidOperationException (msg); + } + + return _context.Headers; + } + } + + /// + /// Gets a value indicating whether the communication is possible for + /// a session. + /// + /// + /// true if the communication is possible; otherwise, false. + /// + /// + /// The get operation is not available when the session has not started yet. + /// + protected bool IsAlive { + get { + if (!_registered) { + var msg = "The session has not started yet."; + + throw new InvalidOperationException (msg); + } + + return _websocket.IsAlive; } } /// - /// Gets the query string included in a WebSocket handshake request. + /// Gets the query string for a session. /// /// /// /// A that contains the query - /// parameters. + /// parameters included in the WebSocket handshake request. /// /// /// An empty collection if not included. /// - /// - /// if the session has not started yet. - /// /// + /// + /// The get operation is not available when the session has not started yet. + /// protected NameValueCollection QueryString { get { - return _context != null ? _context.QueryString : null; + if (!_registered) { + var msg = "The session has not started yet."; + + throw new InvalidOperationException (msg); + } + + return _context.QueryString; } } /// - /// Gets the management function for the sessions in the service. + /// Gets the current state of the WebSocket interface for a session. /// /// /// - /// A that manages the sessions in - /// the service. + /// One of the enum values. /// /// - /// if the session has not started yet. + /// It indicates the current state of the interface. /// /// - protected WebSocketSessionManager Sessions { + /// + /// The get operation is not available when the session has not started yet. + /// + protected WebSocketState ReadyState { get { - return _sessions; + if (!_registered) { + var msg = "The session has not started yet."; + + throw new InvalidOperationException (msg); + } + + return _websocket.ReadyState; } } - #endregion + /// + /// Gets the management function for the sessions in the service. + /// + /// + /// A that manages the sessions in + /// the service. + /// + /// + /// The get operation is not available when the session has not started yet. + /// + protected WebSocketSessionManager Sessions { + get { + if (!_registered) { + var msg = "The session has not started yet."; - #region Public Properties + throw new InvalidOperationException (msg); + } + + return _sessions; + } + } /// - /// Gets the current state of the WebSocket connection for a session. + /// Gets the client information for a session. /// /// /// - /// One of the enum values. - /// - /// - /// It indicates the current state of the connection. + /// A instance that represents identity, + /// authentication, and security roles for the client. /// /// - /// if the session has not - /// started yet. + /// if the client is not authenticated. /// /// - public WebSocketState ConnectionState { + /// + /// The get operation is not available when the session has not started yet. + /// + protected IPrincipal User { get { - return _websocket != null - ? _websocket.ReadyState - : WebSocketState.Connecting; + if (!_registered) { + var msg = "The session has not started yet."; + + throw new InvalidOperationException (msg); + } + + return _context.User; } } /// - /// Gets the information in a WebSocket handshake request to the service. + /// Gets the client endpoint for a session. /// /// - /// - /// A instance that provides the access to - /// the information in the handshake request. - /// - /// - /// if the session has not started yet. - /// + /// A that represents the client + /// IP address and port number. /// - public WebSocketContext Context { + /// + /// The get operation is not available when the session has not started yet. + /// + protected System.Net.IPEndPoint UserEndPoint { get { - return _context; + if (!_registered) { + var msg = "The session has not started yet."; + + throw new InvalidOperationException (msg); + } + + return _context.UserEndPoint; } } + #endregion + + #region Public Properties + /// - /// Gets or sets the delegate used to validate the HTTP cookies included in - /// a WebSocket handshake request to the service. + /// Gets or sets the delegate used to respond to the HTTP cookies. /// /// /// - /// A Func<CookieCollection, CookieCollection, bool> delegate - /// or if not needed. + /// A + /// delegate. /// /// - /// The delegate invokes the method called when the WebSocket instance - /// for a session validates the handshake request. + /// It represents the delegate called when the WebSocket interface + /// for a session respond to the handshake request. /// /// - /// 1st parameter passed to the method - /// contains the cookies to validate if present. + /// 1st parameter passed to the delegate + /// contains the cookies included in the handshake request if any. /// /// - /// 2nd parameter passed to the method - /// receives the cookies to send to the client. + /// 2nd parameter passed to the delegate + /// holds the cookies to send to the client. /// /// - /// The method must return true if the cookies are valid. + /// if not necessary. /// /// /// The default value is . /// /// - public Func CookiesValidator { + /// + /// The set operation is not available when the session has already started. + /// + public Action CookiesResponder { get { - return _cookiesValidator; + return _cookiesResponder; } set { - _cookiesValidator = value; + if (_registered) { + var msg = "The session has already started."; + + throw new InvalidOperationException (msg); + } + + _cookiesResponder = value; } } /// - /// Gets or sets a value indicating whether the WebSocket instance for - /// a session emits the message event when receives a ping. + /// Gets or sets a value indicating whether the WebSocket interface for + /// a session emits the message event when it receives a ping. /// /// /// - /// true if the WebSocket instance emits the message event - /// when receives a ping; otherwise, false. + /// true if the interface emits the message event when it receives + /// a ping; otherwise, false. /// /// /// The default value is false. /// /// + /// + /// The set operation is not available when the session has already started. + /// public bool EmitOnPing { get { - return _websocket != null ? _websocket.EmitOnPing : _emitOnPing; + return _emitOnPing; } set { - if (_websocket != null) { - _websocket.EmitOnPing = value; + if (_registered) { + var msg = "The session has already started."; - return; + throw new InvalidOperationException (msg); } _emitOnPing = value; @@ -242,125 +329,223 @@ public bool EmitOnPing { } /// - /// Gets the unique ID of a session. + /// Gets or sets the delegate used to validate the Host header. /// /// /// - /// A that represents the unique ID of the session. + /// A delegate. /// /// - /// if the session has not started yet. + /// It represents the delegate called when the WebSocket interface + /// for a session validates the handshake request. + /// + /// + /// The parameter passed to the delegate is + /// the value of the Host header. + /// + /// + /// The method invoked by the delegate must return true + /// if the header value is valid. + /// + /// + /// if not necessary. /// + /// + /// The default value is . + /// + /// + /// + /// The set operation is not available when the session has already started. + /// + public Func HostValidator { + get { + return _hostValidator; + } + + set { + if (_registered) { + var msg = "The session has already started."; + + throw new InvalidOperationException (msg); + } + + _hostValidator = value; + } + } + + /// + /// Gets the unique ID of a session. + /// + /// + /// A that represents the unique ID of the session. /// public string ID { get { + if (_id == null) + _id = WebSocketSessionManager.CreateID (); + return _id; } } /// - /// Gets or sets a value indicating whether the service ignores - /// the Sec-WebSocket-Extensions header included in a WebSocket - /// handshake request. + /// Gets or sets a value indicating whether the WebSocket interface for + /// a session ignores the Sec-WebSocket-Extensions header. /// /// /// - /// true if the service ignores the extensions requested - /// from a client; otherwise, false. + /// true if the interface ignores the extensions requested + /// from the client; otherwise, false. /// /// /// The default value is false. /// /// + /// + /// The set operation is not available when the session has already started. + /// public bool IgnoreExtensions { get { return _ignoreExtensions; } set { + if (_registered) { + var msg = "The session has already started."; + + throw new InvalidOperationException (msg); + } + _ignoreExtensions = value; } } /// - /// Gets or sets the delegate used to validate the Origin header included in - /// a WebSocket handshake request to the service. + /// Gets or sets a value indicating whether the underlying TCP socket of + /// the WebSocket interface for a session disables a delay when send or + /// receive buffer is not full. /// /// /// - /// A Func<string, bool> delegate or - /// if not needed. + /// true if the delay is disabled; otherwise, false. /// /// - /// The delegate invokes the method called when the WebSocket instance + /// The default value is false. + /// + /// + /// + /// + /// The set operation is not available when the session has already started. + /// + public bool NoDelay { + get { + return _noDelay; + } + + set { + if (_registered) { + var msg = "The session has already started."; + + throw new InvalidOperationException (msg); + } + + _noDelay = value; + } + } + + /// + /// Gets or sets the delegate used to validate the Origin header. + /// + /// + /// + /// A delegate. + /// + /// + /// It represents the delegate called when the WebSocket interface /// for a session validates the handshake request. /// /// - /// The parameter passed to the method is the value - /// of the Origin header or if the header is not - /// present. + /// The parameter passed to the delegate is + /// the value of the Origin header or if + /// the header is not present. + /// + /// + /// The method invoked by the delegate must return true + /// if the header value is valid. /// /// - /// The method must return true if the header value is valid. + /// if not necessary. /// /// /// The default value is . /// /// + /// + /// The set operation is not available when the session has already started. + /// public Func OriginValidator { get { return _originValidator; } set { + if (_registered) { + var msg = "The session has already started."; + + throw new InvalidOperationException (msg); + } + _originValidator = value; } } /// - /// Gets or sets the name of the WebSocket subprotocol for the service. + /// Gets or sets the name of the WebSocket subprotocol for a session. /// /// /// /// A that represents the name of the subprotocol. /// /// - /// The value specified for a set must be a token defined in + /// The value specified for a set operation must be a token defined in /// /// RFC 2616. /// /// + /// The value is initialized if not requested. + /// + /// /// The default value is an empty string. /// /// - /// - /// The set operation is not available if the session has already started. - /// /// /// The value specified for a set operation is not a token. /// + /// + /// The set operation is not available when the session has already started. + /// public string Protocol { get { - return _websocket != null + return _registered ? _websocket.Protocol : (_protocol ?? String.Empty); } set { - if (_websocket != null) { + if (_registered) { var msg = "The session has already started."; throw new InvalidOperationException (msg); } - if (value == null || value.Length == 0) { + if (value.IsNullOrEmpty ()) { _protocol = null; return; } if (!value.IsToken ()) { - var msg = "It is not a token."; + var msg = "Not a token."; throw new ArgumentException (msg, "value"); } @@ -378,7 +563,7 @@ public string Protocol { /// has started. /// /// - /// if the session has not started yet. + /// when the session has not started yet. /// /// public DateTime StartTime { @@ -387,23 +572,72 @@ public DateTime StartTime { } } + /// + /// Gets or sets the delegate used to respond to the user headers. + /// + /// + /// + /// A + /// delegate. + /// + /// + /// It represents the delegate called when the WebSocket interface + /// for a session respond to the handshake request. + /// + /// + /// 1st parameter passed to the delegate + /// contains the HTTP headers included in the handshake request. + /// + /// + /// 2nd parameter passed to the delegate + /// holds the user headers to send to the client. + /// + /// + /// if not necessary. + /// + /// + /// The default value is . + /// + /// + /// + /// The set operation is not available when the session has already started. + /// + public Action UserHeadersResponder { + get { + return _userHeadersResponder; + } + + set { + if (_registered) { + var msg = "The session has already started."; + + throw new InvalidOperationException (msg); + } + + _userHeadersResponder = value; + } + } + #endregion #region Private Methods private string checkHandshakeRequest (WebSocketContext context) { - if (_originValidator != null) { - if (!_originValidator (context.Origin)) - return "It includes no Origin header or an invalid one."; + if (_hostValidator != null) { + if (!_hostValidator (context.Host)) { + var msg = "The Host header is invalid."; + + return msg; + } } - if (_cookiesValidator != null) { - var req = context.CookieCollection; - var res = context.WebSocket.CookieCollection; + if (_originValidator != null) { + if (!_originValidator (context.Origin)) { + var msg = "The Origin header is non-existent or invalid."; - if (!_cookiesValidator (req, res)) - return "It includes no cookie or an invalid one."; + return msg; + } } return null; @@ -411,7 +645,7 @@ private string checkHandshakeRequest (WebSocketContext context) private void onClose (object sender, CloseEventArgs e) { - if (_id == null) + if (!_registered) return; _sessions.Remove (_id); @@ -431,9 +665,9 @@ private void onMessage (object sender, MessageEventArgs e) private void onOpen (object sender, EventArgs e) { - _id = _sessions.Add (this); + _registered = _sessions.Add (this); - if (_id == null) { + if (!_registered) { _websocket.Close (CloseStatusCode.Away); return; @@ -444,12 +678,30 @@ private void onOpen (object sender, EventArgs e) OnOpen (); } + private void respondToHandshakeRequest (WebSocketContext context) + { + if (_cookiesResponder != null) { + var reqCookies = context.CookieCollection; + var resCookies = context.WebSocket.Cookies; + + _cookiesResponder (reqCookies, resCookies); + } + + if (_userHeadersResponder != null) { + var reqHeaders = context.Headers; + var userHeaders = context.WebSocket.UserHeaders; + + _userHeadersResponder (reqHeaders, userHeaders); + } + } + #endregion #region Internal Methods internal void Start ( - WebSocketContext context, WebSocketSessionManager sessions + WebSocketContext context, + WebSocketSessionManager sessions ) { _context = context; @@ -457,21 +709,31 @@ internal void Start ( _websocket = context.WebSocket; _websocket.CustomHandshakeRequestChecker = checkHandshakeRequest; - _websocket.EmitOnPing = _emitOnPing; - _websocket.IgnoreExtensions = _ignoreExtensions; - _websocket.Protocol = _protocol; + _websocket.CustomHandshakeRequestResponder = respondToHandshakeRequest; + + if (_emitOnPing) + _websocket.EmitOnPing = true; + + if (_ignoreExtensions) + _websocket.IgnoreExtensions = true; + + if (_noDelay) + _websocket.NoDelay = true; + + if (_protocol != null) + _websocket.Protocol = _protocol; var waitTime = sessions.WaitTime; if (waitTime != _websocket.WaitTime) _websocket.WaitTime = waitTime; - _websocket.OnOpen += onOpen; - _websocket.OnMessage += onMessage; - _websocket.OnError += onError; _websocket.OnClose += onClose; + _websocket.OnError += onError; + _websocket.OnMessage += onMessage; + _websocket.OnOpen += onOpen; - _websocket.InternalAccept (); + _websocket.Accept (); } #endregion @@ -482,15 +744,15 @@ internal void Start ( /// Closes the WebSocket connection for a session. /// /// - /// This method does nothing if the current state of the connection is - /// Closing or Closed. + /// This method does nothing when the current state of the WebSocket + /// interface is Closing or Closed. /// /// - /// The session has not started yet. + /// This method is not available when the session has not started yet. /// protected void Close () { - if (_websocket == null) { + if (!_registered) { var msg = "The session has not started yet."; throw new InvalidOperationException (msg); @@ -501,11 +763,11 @@ protected void Close () /// /// Closes the WebSocket connection for a session with the specified - /// code and reason. + /// status code and reason. /// /// - /// This method does nothing if the current state of the connection is - /// Closing or Closed. + /// This method does nothing when the current state of the WebSocket + /// interface is Closing or Closed. /// /// /// @@ -523,43 +785,44 @@ protected void Close () /// A that specifies the reason for the close. /// /// - /// The size must be 123 bytes or less in UTF-8. + /// Its size must be 123 bytes or less in UTF-8. /// /// - /// - /// The session has not started yet. - /// - /// + /// /// - /// is less than 1000 or greater than 4999. + /// is 1010 (mandatory extension). /// /// /// -or- /// /// - /// The size of is greater than 123 bytes. + /// is 1005 (no status) and + /// is specified. /// - /// - /// /// - /// is 1010 (mandatory extension). + /// -or- /// /// - /// -or- + /// could not be UTF-8-encoded. /// + /// + /// /// - /// is 1005 (no status) and there is reason. + /// is less than 1000 or greater than 4999. /// /// /// -or- /// /// - /// could not be UTF-8-encoded. + /// The size of is greater than 123 bytes. /// /// + /// + /// This method is not available when the session has not started yet. + /// protected void Close (ushort code, string reason) { - if (_websocket == null) { + if (!_registered) { var msg = "The session has not started yet."; throw new InvalidOperationException (msg); @@ -570,11 +833,11 @@ protected void Close (ushort code, string reason) /// /// Closes the WebSocket connection for a session with the specified - /// code and reason. + /// status code and reason. /// /// - /// This method does nothing if the current state of the connection is - /// Closing or Closed. + /// This method does nothing when the current state of the WebSocket + /// interface is Closing or Closed. /// /// /// @@ -589,26 +852,25 @@ protected void Close (ushort code, string reason) /// A that specifies the reason for the close. /// /// - /// The size must be 123 bytes or less in UTF-8. + /// Its size must be 123 bytes or less in UTF-8. /// /// - /// - /// The session has not started yet. - /// - /// - /// The size of is greater than 123 bytes. - /// /// /// - /// is - /// . + /// is an undefined enum value. + /// + /// + /// -or- + /// + /// + /// is . /// /// /// -or- /// /// - /// is - /// and there is reason. + /// is and + /// is specified. /// /// /// -or- @@ -617,9 +879,15 @@ protected void Close (ushort code, string reason) /// could not be UTF-8-encoded. /// /// + /// + /// The size of is greater than 123 bytes. + /// + /// + /// This method is not available when the session has not started yet. + /// protected void Close (CloseStatusCode code, string reason) { - if (_websocket == null) { + if (!_registered) { var msg = "The session has not started yet."; throw new InvalidOperationException (msg); @@ -636,16 +904,16 @@ protected void Close (CloseStatusCode code, string reason) /// This method does not wait for the close to be complete. /// /// - /// This method does nothing if the current state of the connection is - /// Closing or Closed. + /// This method does nothing when the current state of the WebSocket + /// interface is Closing or Closed. /// /// /// - /// The session has not started yet. + /// This method is not available when the session has not started yet. /// protected void CloseAsync () { - if (_websocket == null) { + if (!_registered) { var msg = "The session has not started yet."; throw new InvalidOperationException (msg); @@ -656,15 +924,15 @@ protected void CloseAsync () /// /// Closes the WebSocket connection for a session asynchronously with - /// the specified code and reason. + /// the specified status code and reason. /// /// /// /// This method does not wait for the close to be complete. /// /// - /// This method does nothing if the current state of the connection is - /// Closing or Closed. + /// This method does nothing when the current state of the WebSocket + /// interface is Closing or Closed. /// /// /// @@ -683,43 +951,44 @@ protected void CloseAsync () /// A that specifies the reason for the close. /// /// - /// The size must be 123 bytes or less in UTF-8. + /// Its size must be 123 bytes or less in UTF-8. /// /// - /// - /// The session has not started yet. - /// - /// + /// /// - /// is less than 1000 or greater than 4999. + /// is 1010 (mandatory extension). /// /// /// -or- /// /// - /// The size of is greater than 123 bytes. + /// is 1005 (no status) and + /// is specified. /// - /// - /// /// - /// is 1010 (mandatory extension). + /// -or- /// /// - /// -or- + /// could not be UTF-8-encoded. /// + /// + /// /// - /// is 1005 (no status) and there is reason. + /// is less than 1000 or greater than 4999. /// /// /// -or- /// /// - /// could not be UTF-8-encoded. + /// The size of is greater than 123 bytes. /// /// + /// + /// This method is not available when the session has not started yet. + /// protected void CloseAsync (ushort code, string reason) { - if (_websocket == null) { + if (!_registered) { var msg = "The session has not started yet."; throw new InvalidOperationException (msg); @@ -730,15 +999,15 @@ protected void CloseAsync (ushort code, string reason) /// /// Closes the WebSocket connection for a session asynchronously with - /// the specified code and reason. + /// the specified status code and reason. /// /// /// /// This method does not wait for the close to be complete. /// /// - /// This method does nothing if the current state of the connection is - /// Closing or Closed. + /// This method does nothing when the current state of the WebSocket + /// interface is Closing or Closed. /// /// /// @@ -754,23 +1023,25 @@ protected void CloseAsync (ushort code, string reason) /// A that specifies the reason for the close. /// /// - /// The size must be 123 bytes or less in UTF-8. + /// Its size must be 123 bytes or less in UTF-8. /// /// - /// - /// The session has not started yet. - /// /// /// - /// is - /// . + /// is an undefined enum value. + /// + /// + /// -or- + /// + /// + /// is . /// /// /// -or- /// /// - /// is - /// and there is reason. + /// is and + /// is specified. /// /// /// -or- @@ -782,9 +1053,12 @@ protected void CloseAsync (ushort code, string reason) /// /// The size of is greater than 123 bytes. /// + /// + /// This method is not available when the session has not started yet. + /// protected void CloseAsync (CloseStatusCode code, string reason) { - if (_websocket == null) { + if (!_registered) { var msg = "The session has not started yet."; throw new InvalidOperationException (msg); @@ -797,30 +1071,30 @@ protected void CloseAsync (CloseStatusCode code, string reason) /// Called when the WebSocket connection for a session has been closed. /// /// - /// A that represents the event data passed - /// from a event. + /// A that represents the close event data + /// passed from the WebSocket interface. /// protected virtual void OnClose (CloseEventArgs e) { } /// - /// Called when the WebSocket instance for a session gets an error. + /// Called when the WebSocket interface for a session gets an error. /// /// - /// A that represents the event data passed - /// from a event. + /// A that represents the error event data + /// passed from the interface. /// protected virtual void OnError (ErrorEventArgs e) { } /// - /// Called when the WebSocket instance for a session receives a message. + /// Called when the WebSocket interface for a session receives a message. /// /// - /// A that represents the event data passed - /// from a event. + /// A that represents the message event data + /// passed from the interface. /// protected virtual void OnMessage (MessageEventArgs e) { @@ -834,18 +1108,18 @@ protected virtual void OnOpen () } /// - /// Sends a ping to a client using the WebSocket connection. + /// Sends a ping to the client for a session. /// /// - /// true if the send has done with no error and a pong has been + /// true if the send has successfully done and a pong has been /// received within a time; otherwise, false. /// /// - /// The session has not started yet. + /// This method is not available when the session has not started yet. /// protected bool Ping () { - if (_websocket == null) { + if (!_registered) { var msg = "The session has not started yet."; throw new InvalidOperationException (msg); @@ -855,11 +1129,10 @@ protected bool Ping () } /// - /// Sends a ping with the specified message to a client using - /// the WebSocket connection. + /// Sends a ping with the specified message to the client for a session. /// /// - /// true if the send has done with no error and a pong has been + /// true if the send has successfully done and a pong has been /// received within a time; otherwise, false. /// /// @@ -867,21 +1140,21 @@ protected bool Ping () /// A that specifies the message to send. /// /// - /// The size must be 125 bytes or less in UTF-8. + /// Its size must be 125 bytes or less in UTF-8. /// /// - /// - /// The session has not started yet. - /// /// /// could not be UTF-8-encoded. /// /// /// The size of is greater than 125 bytes. /// + /// + /// This method is not available when the session has not started yet. + /// protected bool Ping (string message) { - if (_websocket == null) { + if (!_registered) { var msg = "The session has not started yet."; throw new InvalidOperationException (msg); @@ -891,21 +1164,30 @@ protected bool Ping (string message) } /// - /// Sends the specified data to a client using the WebSocket connection. + /// Sends the specified data to the client for a session. /// /// /// An array of that specifies the binary data to send. /// - /// - /// The current state of the connection is not Open. - /// /// /// is . /// + /// + /// + /// This method is not available when the session has not started yet. + /// + /// + /// -or- + /// + /// + /// This method is not available when the current state of the WebSocket + /// interface is not Open. + /// + /// protected void Send (byte[] data) { - if (_websocket == null) { - var msg = "The current state of the connection is not Open."; + if (!_registered) { + var msg = "The session has not started yet."; throw new InvalidOperationException (msg); } @@ -914,7 +1196,7 @@ protected void Send (byte[] data) } /// - /// Sends the specified file to a client using the WebSocket connection. + /// Sends the specified file to the client for a session. /// /// /// @@ -924,27 +1206,36 @@ protected void Send (byte[] data) /// The file is sent as the binary data. /// /// - /// - /// The current state of the connection is not Open. + /// + /// + /// The file does not exist. + /// + /// + /// -or- + /// + /// + /// The file could not be opened. + /// /// /// /// is . /// - /// + /// /// - /// The file does not exist. + /// This method is not available when the session has not started yet. /// /// /// -or- /// /// - /// The file could not be opened. + /// This method is not available when the current state of the WebSocket + /// interface is not Open. /// /// protected void Send (FileInfo fileInfo) { - if (_websocket == null) { - var msg = "The current state of the connection is not Open."; + if (!_registered) { + var msg = "The session has not started yet."; throw new InvalidOperationException (msg); } @@ -953,24 +1244,33 @@ protected void Send (FileInfo fileInfo) } /// - /// Sends the specified data to a client using the WebSocket connection. + /// Sends the specified data to the client for a session. /// /// /// A that specifies the text data to send. /// - /// - /// The current state of the connection is not Open. + /// + /// could not be UTF-8-encoded. /// /// /// is . /// - /// - /// could not be UTF-8-encoded. + /// + /// + /// This method is not available when the session has not started yet. + /// + /// + /// -or- + /// + /// + /// This method is not available when the current state of the WebSocket + /// interface is not Open. + /// /// protected void Send (string data) { - if (_websocket == null) { - var msg = "The current state of the connection is not Open."; + if (!_registered) { + var msg = "The session has not started yet."; throw new InvalidOperationException (msg); } @@ -979,8 +1279,8 @@ protected void Send (string data) } /// - /// Sends the data from the specified stream instance to a client using - /// the WebSocket connection. + /// Sends the data from the specified stream instance to the client for + /// a session. /// /// /// @@ -993,12 +1293,6 @@ protected void Send (string data) /// /// An that specifies the number of bytes to send. /// - /// - /// The current state of the connection is not Open. - /// - /// - /// is . - /// /// /// /// cannot be read. @@ -1016,10 +1310,25 @@ protected void Send (string data) /// No data could be read from . /// /// + /// + /// is . + /// + /// + /// + /// This method is not available when the session has not started yet. + /// + /// + /// -or- + /// + /// + /// This method is not available when the current state of the WebSocket + /// interface is not Open. + /// + /// protected void Send (Stream stream, int length) { - if (_websocket == null) { - var msg = "The current state of the connection is not Open."; + if (!_registered) { + var msg = "The session has not started yet."; throw new InvalidOperationException (msg); } @@ -1028,8 +1337,7 @@ protected void Send (Stream stream, int length) } /// - /// Sends the specified data to a client asynchronously using - /// the WebSocket connection. + /// Sends the specified data to the client for a session asynchronously. /// /// /// This method does not wait for the send to be complete. @@ -1039,27 +1347,38 @@ protected void Send (Stream stream, int length) /// /// /// - /// An Action<bool> delegate or - /// if not needed. + /// An delegate. + /// + /// + /// It specifies the delegate called when the send is complete. /// /// - /// The delegate invokes the method called when the send is complete. + /// The parameter passed to the delegate is true + /// if the send has successfully done; otherwise, false. /// /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. + /// if not necessary. /// /// - /// - /// The current state of the connection is not Open. - /// /// /// is . /// + /// + /// + /// This method is not available when the session has not started yet. + /// + /// + /// -or- + /// + /// + /// This method is not available when the current state of the WebSocket + /// interface is not Open. + /// + /// protected void SendAsync (byte[] data, Action completed) { - if (_websocket == null) { - var msg = "The current state of the connection is not Open."; + if (!_registered) { + var msg = "The session has not started yet."; throw new InvalidOperationException (msg); } @@ -1068,8 +1387,7 @@ protected void SendAsync (byte[] data, Action completed) } /// - /// Sends the specified file to a client asynchronously using - /// the WebSocket connection. + /// Sends the specified file to the client for a session asynchronously. /// /// /// This method does not wait for the send to be complete. @@ -1084,38 +1402,49 @@ protected void SendAsync (byte[] data, Action completed) /// /// /// - /// An Action<bool> delegate or - /// if not needed. + /// An delegate. + /// + /// + /// It specifies the delegate called when the send is complete. /// /// - /// The delegate invokes the method called when the send is complete. + /// The parameter passed to the delegate is true + /// if the send has successfully done; otherwise, false. /// /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. + /// if not necessary. /// /// - /// - /// The current state of the connection is not Open. + /// + /// + /// The file does not exist. + /// + /// + /// -or- + /// + /// + /// The file could not be opened. + /// /// /// /// is . /// - /// + /// /// - /// The file does not exist. + /// This method is not available when the session has not started yet. /// /// /// -or- /// /// - /// The file could not be opened. + /// This method is not available when the current state of the WebSocket + /// interface is not Open. /// /// protected void SendAsync (FileInfo fileInfo, Action completed) { - if (_websocket == null) { - var msg = "The current state of the connection is not Open."; + if (!_registered) { + var msg = "The session has not started yet."; throw new InvalidOperationException (msg); } @@ -1124,8 +1453,7 @@ protected void SendAsync (FileInfo fileInfo, Action completed) } /// - /// Sends the specified data to a client asynchronously using - /// the WebSocket connection. + /// Sends the specified data to the client for a session asynchronously. /// /// /// This method does not wait for the send to be complete. @@ -1135,30 +1463,41 @@ protected void SendAsync (FileInfo fileInfo, Action completed) /// /// /// - /// An Action<bool> delegate or - /// if not needed. + /// An delegate. /// /// - /// The delegate invokes the method called when the send is complete. + /// It specifies the delegate called when the send is complete. /// /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. + /// The parameter passed to the delegate is true + /// if the send has successfully done; otherwise, false. + /// + /// + /// if not necessary. /// /// - /// - /// The current state of the connection is not Open. + /// + /// could not be UTF-8-encoded. /// /// /// is . /// - /// - /// could not be UTF-8-encoded. + /// + /// + /// This method is not available when the session has not started yet. + /// + /// + /// -or- + /// + /// + /// This method is not available when the current state of the WebSocket + /// interface is not Open. + /// /// protected void SendAsync (string data, Action completed) { - if (_websocket == null) { - var msg = "The current state of the connection is not Open."; + if (!_registered) { + var msg = "The session has not started yet."; throw new InvalidOperationException (msg); } @@ -1167,8 +1506,8 @@ protected void SendAsync (string data, Action completed) } /// - /// Sends the data from the specified stream instance to a client - /// asynchronously using the WebSocket connection. + /// Sends the data from the specified stream instance to the client for + /// a session asynchronously. /// /// /// This method does not wait for the send to be complete. @@ -1186,23 +1525,19 @@ protected void SendAsync (string data, Action completed) /// /// /// - /// An Action<bool> delegate or - /// if not needed. + /// An delegate. + /// + /// + /// It specifies the delegate called when the send is complete. /// /// - /// The delegate invokes the method called when the send is complete. + /// The parameter passed to the delegate is true + /// if the send has successfully done; otherwise, false. /// /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. + /// if not necessary. /// /// - /// - /// The current state of the connection is not Open. - /// - /// - /// is . - /// /// /// /// cannot be read. @@ -1220,10 +1555,25 @@ protected void SendAsync (string data, Action completed) /// No data could be read from . /// /// + /// + /// is . + /// + /// + /// + /// This method is not available when the session has not started yet. + /// + /// + /// -or- + /// + /// + /// This method is not available when the current state of the WebSocket + /// interface is not Open. + /// + /// protected void SendAsync (Stream stream, int length, Action completed) { - if (_websocket == null) { - var msg = "The current state of the connection is not Open."; + if (!_registered) { + var msg = "The session has not started yet."; throw new InvalidOperationException (msg); } @@ -1232,5 +1582,27 @@ protected void SendAsync (Stream stream, int length, Action completed) } #endregion + + #region Explicit Interface Implementations + + /// + /// Gets the WebSocket interface for a session. + /// + /// + /// + /// A that represents + /// the WebSocket interface. + /// + /// + /// when the session has not started yet. + /// + /// + WebSocket IWebSocketSession.WebSocket { + get { + return _websocket; + } + } + + #endregion } } diff --git a/websocket-sharp/Server/WebSocketServer.cs b/websocket-sharp/Server/WebSocketServer.cs index e1a775afb..50f03f020 100644 --- a/websocket-sharp/Server/WebSocketServer.cs +++ b/websocket-sharp/Server/WebSocketServer.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2022 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -59,11 +59,11 @@ public class WebSocketServer #region Private Fields private System.Net.IPAddress _address; - private bool _allowForwardedRequest; private AuthenticationSchemes _authSchemes; private static readonly string _defaultRealm; - private bool _dnsStyle; private string _hostname; + private bool _isDnsStyle; + private bool _isSecure; private TcpListener _listener; private Logger _log; private int _port; @@ -71,7 +71,6 @@ public class WebSocketServer private string _realmInUse; private Thread _receiveThread; private bool _reuseAddress; - private bool _secure; private WebSocketServiceManager _services; private ServerSslConfiguration _sslConfig; private ServerSslConfiguration _sslConfigInUse; @@ -153,9 +152,6 @@ public WebSocketServer (int port) /// /// A that specifies the WebSocket URL of the server. /// - /// - /// is . - /// /// /// /// is an empty string. @@ -167,6 +163,9 @@ public WebSocketServer (int port) /// is invalid. /// /// + /// + /// is . + /// public WebSocketServer (string url) { if (url == null) @@ -221,7 +220,7 @@ public WebSocketServer (string url) public WebSocketServer (int port, bool secure) { if (!port.IsPortNumber ()) { - var msg = "It is less than 1 or greater than 65535."; + var msg = "Less than 1 or greater than 65535."; throw new ArgumentOutOfRangeException ("port", msg); } @@ -252,12 +251,12 @@ public WebSocketServer (int port, bool secure) /// An that specifies the number of the port on which /// to listen. /// - /// - /// is . - /// /// /// is not a local IP address. /// + /// + /// is . + /// /// /// is less than 1 or greater than 65535. /// @@ -286,12 +285,12 @@ public WebSocketServer (System.Net.IPAddress address, int port) /// A : true if the new instance provides /// secure connections; otherwise, false. /// - /// - /// is . - /// /// /// is not a local IP address. /// + /// + /// is . + /// /// /// is less than 1 or greater than 65535. /// @@ -301,13 +300,13 @@ public WebSocketServer (System.Net.IPAddress address, int port, bool secure) throw new ArgumentNullException ("address"); if (!address.IsLocal ()) { - var msg = "It is not a local IP address."; + var msg = "Not a local IP address."; throw new ArgumentException (msg, "address"); } if (!port.IsPortNumber ()) { - var msg = "It is less than 1 or greater than 65535."; + var msg = "Less than 1 or greater than 65535."; throw new ArgumentOutOfRangeException ("port", msg); } @@ -332,44 +331,12 @@ public System.Net.IPAddress Address { } } - /// - /// Gets or sets a value indicating whether the server accepts every - /// handshake request without checking the request URI. - /// - /// - /// The set operation does nothing if the server has already started or - /// it is shutting down. - /// - /// - /// - /// true if the server accepts every handshake request without - /// checking the request URI; otherwise, false. - /// - /// - /// The default value is false. - /// - /// - public bool AllowForwardedRequest { - get { - return _allowForwardedRequest; - } - - set { - lock (_sync) { - if (!canSet ()) - return; - - _allowForwardedRequest = value; - } - } - } - /// /// Gets or sets the scheme used to authenticate the clients. /// /// - /// The set operation does nothing if the server has already started or - /// it is shutting down. + /// The set operation works if the current state of the server is + /// Ready or Stop. /// /// /// @@ -412,7 +379,7 @@ public bool IsListening { } /// - /// Gets a value indicating whether secure connections are provided. + /// Gets a value indicating whether the server provides secure connections. /// /// /// true if the server provides secure connections; otherwise, @@ -420,25 +387,25 @@ public bool IsListening { /// public bool IsSecure { get { - return _secure; + return _isSecure; } } /// - /// Gets or sets a value indicating whether the server cleans up the - /// inactive sessions periodically. + /// Gets or sets a value indicating whether the server cleans up + /// the inactive sessions periodically. /// /// - /// The set operation does nothing if the server has already started or - /// it is shutting down. + /// The set operation works if the current state of the server is + /// Ready or Stop. /// /// /// - /// true if the server cleans up the inactive sessions every - /// 60 seconds; otherwise, false. + /// true if the server cleans up the inactive sessions + /// every 60 seconds; otherwise, false. /// /// - /// The default value is true. + /// The default value is false. /// /// public bool KeepClean { @@ -483,19 +450,16 @@ public int Port { /// Gets or sets the name of the realm associated with the server. /// /// - /// - /// "SECRET AREA" is used as the name of the realm if the value is - /// or an empty string. - /// - /// - /// The set operation does nothing if the server has already started - /// or it is shutting down. - /// + /// The set operation works if the current state of the server is + /// Ready or Stop. /// /// /// - /// A that represents the name of the realm or - /// . + /// A that represents the name of the realm. + /// + /// + /// "SECRET AREA" is used as the name of the realm if the value is + /// or an empty string. /// /// /// The default value is . @@ -526,8 +490,8 @@ public string Realm { /// resolve to wait for socket in TIME_WAIT state. /// /// - /// The set operation does nothing if the server has already started - /// or it is shutting down. + /// The set operation works if the current state of the server is + /// Ready or Stop. /// /// /// @@ -558,19 +522,19 @@ public bool ReuseAddress { /// Gets the configuration for secure connection. /// /// - /// The configuration will be referenced when attempts to start, + /// The configuration is used when the server attempts to start, /// so it must be configured before the start method is called. /// /// - /// A that represents - /// the configuration used to provide secure connections. + /// A that represents the + /// configuration used to provide secure connections. /// /// /// The server does not provide secure connections. /// public ServerSslConfiguration SslConfiguration { get { - if (!_secure) { + if (!_isSecure) { var msg = "The server does not provide secure connections."; throw new InvalidOperationException (msg); @@ -581,29 +545,28 @@ public ServerSslConfiguration SslConfiguration { } /// - /// Gets or sets the delegate used to find the credentials for - /// an identity. + /// Gets or sets the delegate called to find the credentials for + /// an identity used to authenticate a client. /// /// + /// The set operation works if the current state of the server is + /// Ready or Stop. + /// + /// /// - /// No credentials are found if the method invoked by - /// the delegate returns or - /// the value is . + /// A + /// delegate. /// /// - /// The set operation does nothing if the server has - /// already started or it is shutting down. + /// It represents the delegate called when the server finds + /// the credentials used to authenticate a client. /// - /// - /// /// - /// A Func<, - /// > delegate or - /// if not needed. + /// It must return if the credentials + /// are not found. /// /// - /// The delegate invokes the method called for finding - /// the credentials used to authenticate a client. + /// if not necessary. /// /// /// The default value is . @@ -629,12 +592,13 @@ public Func UserCredentialsFinder { /// Ping or Close. /// /// - /// The set operation does nothing if the server has already started or - /// it is shutting down. + /// The set operation works if the current state of the server is + /// Ready or Stop. /// /// /// - /// A to wait for the response. + /// A that represents the time to wait for + /// the response. /// /// /// The default value is the same as 1 second. @@ -707,7 +671,27 @@ private bool authenticateClient (TcpListenerWebSocketContext context) if (_authSchemes == AuthenticationSchemes.None) return false; - return context.Authenticate (_authSchemes, _realmInUse, _userCredFinder); + var chal = new AuthenticationChallenge (_authSchemes, _realmInUse) + .ToString (); + + var retry = -1; + Func auth = null; + auth = + () => { + retry++; + + if (retry > 99) + return false; + + if (context.SetUser (_authSchemes, _realmInUse, _userCredFinder)) + return true; + + context.SendAuthenticationChallenge (chal); + + return auth (); + }; + + return auth (); } private bool canSet () @@ -717,7 +701,7 @@ private bool canSet () private bool checkHostNameForRequest (string name) { - return !_dnsStyle + return !_isDnsStyle || Uri.CheckHostName (name) != UriHostNameType.Dns || name == _hostname; } @@ -738,16 +722,19 @@ private ServerSslConfiguration getSslConfiguration () } private void init ( - string hostname, System.Net.IPAddress address, int port, bool secure + string hostname, + System.Net.IPAddress address, + int port, + bool secure ) { _hostname = hostname; _address = address; _port = port; - _secure = secure; + _isSecure = secure; _authSchemes = AuthenticationSchemes.Anonymous; - _dnsStyle = Uri.CheckHostName (hostname) == UriHostNameType.Dns; + _isDnsStyle = Uri.CheckHostName (hostname) == UriHostNameType.Dns; _listener = new TcpListener (address, port); _log = new Logger (); _services = new WebSocketServiceManager (_log); @@ -770,18 +757,12 @@ private void processRequest (TcpListenerWebSocketContext context) return; } - if (!_allowForwardedRequest) { - if (uri.Port != _port) { - context.Close (HttpStatusCode.BadRequest); - - return; - } + var name = uri.DnsSafeHost; - if (!checkHostNameForRequest (uri.DnsSafeHost)) { - context.Close (HttpStatusCode.NotFound); + if (!checkHostNameForRequest (name)) { + context.Close (HttpStatusCode.NotFound); - return; - } + return; } var path = uri.AbsolutePath; @@ -812,7 +793,11 @@ private void receiveRequest () state => { try { var ctx = new TcpListenerWebSocketContext ( - cl, null, _secure, _sslConfigInUse, _log + cl, + null, + _isSecure, + _sslConfigInUse, + _log ); processRequest (ctx); @@ -827,11 +812,8 @@ private void receiveRequest () ); } catch (SocketException ex) { - if (_state == ServerState.ShuttingDown) { - _log.Info ("The underlying listener is stopped."); - + if (_state == ServerState.ShuttingDown) return; - } _log.Fatal (ex.Message); _log.Debug (ex.ToString ()); @@ -839,11 +821,8 @@ private void receiveRequest () break; } catch (InvalidOperationException ex) { - if (_state == ServerState.ShuttingDown) { - _log.Info ("The underlying listener is stopped."); - + if (_state == ServerState.ShuttingDown) return; - } _log.Fatal (ex.Message); _log.Debug (ex.ToString ()); @@ -873,7 +852,7 @@ private void start () if (_state == ServerState.Start || _state == ServerState.ShuttingDown) return; - if (_secure) { + if (_isSecure) { var src = getSslConfiguration (); var conf = new ServerSslConfiguration (src); @@ -907,7 +886,9 @@ private void startReceiving () { if (_reuseAddress) { _listener.Server.SetSocketOption ( - SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true + SocketOptionLevel.Socket, + SocketOptionName.ReuseAddress, + true ); } @@ -937,7 +918,9 @@ private void stop (ushort code, string reason) } try { - stopReceiving (5000); + var timeout = 5000; + + stopReceiving (timeout); } catch (Exception ex) { _log.Fatal (ex.Message); @@ -962,7 +945,9 @@ private void stopReceiving (int millisecondsTimeout) } private static bool tryCreateUri ( - string uriString, out Uri result, out string message + string uriString, + out Uri result, + out string message ) { if (!uriString.TryCreateWebSocketUri (out result, out message)) @@ -1002,12 +987,9 @@ private static bool tryCreateUri ( /// It must inherit the class. /// /// - /// And also, it must have a public parameterless constructor. + /// Also it must have a public parameterless constructor. /// /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1032,6 +1014,9 @@ private static bool tryCreateUri ( /// is already in use. /// /// + /// + /// is . + /// public void AddWebSocketService (string path) where TBehavior : WebSocketBehavior, new () { @@ -1040,7 +1025,7 @@ public void AddWebSocketService (string path) /// /// Adds a WebSocket service with the specified behavior, path, - /// and delegate. + /// and initializer. /// /// /// @@ -1053,12 +1038,14 @@ public void AddWebSocketService (string path) /// /// /// - /// An Action<TBehavior> delegate or - /// if not needed. + /// An delegate. + /// + /// + /// It specifies the delegate called when the service initializes + /// a new session instance. /// /// - /// The delegate invokes the method called when initializing - /// a new session instance for the service. + /// if not necessary. /// /// /// @@ -1069,12 +1056,9 @@ public void AddWebSocketService (string path) /// It must inherit the class. /// /// - /// And also, it must have a public parameterless constructor. + /// Also it must have a public parameterless constructor. /// /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1099,8 +1083,12 @@ public void AddWebSocketService (string path) /// is already in use. /// /// + /// + /// is . + /// public void AddWebSocketService ( - string path, Action initializer + string path, + Action initializer ) where TBehavior : WebSocketBehavior, new () { @@ -1112,7 +1100,7 @@ public void AddWebSocketService ( /// /// /// The service is stopped with close status 1001 (going away) - /// if it has already started. + /// if the current state of the service is Start. /// /// /// true if the service is successfully found and removed; @@ -1127,9 +1115,6 @@ public void AddWebSocketService ( /// / is trimmed from the end of the string if present. /// /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1148,6 +1133,9 @@ public void AddWebSocketService ( /// query and fragment components. /// /// + /// + /// is . + /// public bool RemoveWebSocketService (string path) { return _services.RemoveService (path); @@ -1157,8 +1145,7 @@ public bool RemoveWebSocketService (string path) /// Starts receiving incoming handshake requests. /// /// - /// This method does nothing if the server has already started or - /// it is shutting down. + /// This method works if the current state of the server is Ready or Stop. /// /// /// @@ -1183,8 +1170,7 @@ public void Start () /// Stops receiving incoming handshake requests. /// /// - /// This method does nothing if the server is not started, - /// it is shutting down, or it has already stopped. + /// This method works if the current state of the server is Start. /// public void Stop () { diff --git a/websocket-sharp/Server/WebSocketServiceHost.cs b/websocket-sharp/Server/WebSocketServiceHost.cs index dd138bc3f..ee20d92a9 100644 --- a/websocket-sharp/Server/WebSocketServiceHost.cs +++ b/websocket-sharp/Server/WebSocketServiceHost.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2021 sta.blockhead + * Copyright (c) 2012-2023 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 @@ -109,12 +109,12 @@ protected Logger Log { #region Public Properties /// - /// Gets or sets a value indicating whether the service cleans up the - /// inactive sessions periodically. + /// Gets or sets a value indicating whether the service cleans up + /// the inactive sessions periodically. /// /// - /// The set operation does nothing if the service has already started or - /// it is shutting down. + /// The set operation works if the current state of the service is + /// Ready or Stop. /// /// /// true if the service cleans up the inactive sessions every @@ -166,15 +166,16 @@ public WebSocketSessionManager Sessions { public abstract Type BehaviorType { get; } /// - /// Gets or sets the time to wait for the response to the WebSocket Ping - /// or Close. + /// Gets or sets the time to wait for the response to the WebSocket + /// Ping or Close. /// /// - /// The set operation does nothing if the service has already started or - /// it is shutting down. + /// The set operation works if the current state of the service is + /// Ready or Stop. /// /// - /// A to wait for the response. + /// A that represents the time to wait for + /// the response. /// /// /// The value specified for a set operation is zero or less. diff --git a/websocket-sharp/Server/WebSocketServiceHost`1.cs b/websocket-sharp/Server/WebSocketServiceHost`1.cs index f311251c0..8aac424e3 100644 --- a/websocket-sharp/Server/WebSocketServiceHost`1.cs +++ b/websocket-sharp/Server/WebSocketServiceHost`1.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2015-2021 sta.blockhead + * Copyright (c) 2015-2024 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 @@ -42,7 +42,9 @@ internal class WebSocketServiceHost : WebSocketServiceHost #region Internal Constructors internal WebSocketServiceHost ( - string path, Action initializer, Logger log + string path, + Action initializer, + Logger log ) : base (path, log) { diff --git a/websocket-sharp/Server/WebSocketServiceManager.cs b/websocket-sharp/Server/WebSocketServiceManager.cs index 787873d1c..29021062a 100644 --- a/websocket-sharp/Server/WebSocketServiceManager.cs +++ b/websocket-sharp/Server/WebSocketServiceManager.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2021 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -29,10 +29,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Threading; -using WebSocketSharp.Net; namespace WebSocketSharp.Server { @@ -40,8 +36,8 @@ namespace WebSocketSharp.Server /// Provides the management function for the WebSocket services. /// /// - /// This class manages the WebSocket services provided by - /// the or class. + /// This class manages the WebSocket services provided by the + /// or class. /// public class WebSocketServiceManager { @@ -63,7 +59,6 @@ internal WebSocketServiceManager (Logger log) _log = log; _hosts = new Dictionary (); - _keepClean = true; _state = ServerState.Ready; _sync = ((ICollection) _hosts).SyncRoot; _waitTime = TimeSpan.FromSeconds (1); @@ -91,7 +86,8 @@ public int Count { /// /// /// - /// An IEnumerable<WebSocketServiceHost> instance. + /// An + /// instance. /// /// /// It provides an enumerator which supports the iteration over @@ -111,26 +107,25 @@ public IEnumerable Hosts { /// /// /// - /// A instance or - /// if not found. + /// A instance that represents + /// the service host instance. /// /// - /// The service host instance provides the function to access - /// the information in the service. + /// It provides the function to access the information in the service. + /// + /// + /// if not found. /// /// /// /// /// A that specifies an absolute path to - /// the service to find. + /// the service to get. /// /// /// / is trimmed from the end of the string if present. /// /// - /// - /// is . - /// /// /// /// is an empty string. @@ -149,6 +144,9 @@ public IEnumerable Hosts { /// query and fragment components. /// /// + /// + /// is . + /// public WebSocketServiceHost this[string path] { get { if (path == null) @@ -158,7 +156,7 @@ public WebSocketServiceHost this[string path] { throw new ArgumentException ("An empty string.", "path"); if (path[0] != '/') { - var msg = "It is not an absolute path."; + var msg = "Not an absolute path."; throw new ArgumentException (msg, "path"); } @@ -182,8 +180,8 @@ public WebSocketServiceHost this[string path] { /// the WebSocket services are cleaned up periodically. /// /// - /// The set operation does nothing if the server has already started or - /// it is shutting down. + /// The set operation works if the current state of the server is + /// Ready or Stop. /// /// /// @@ -191,7 +189,7 @@ public WebSocketServiceHost this[string path] { /// seconds; otherwise, false. /// /// - /// The default value is true. + /// The default value is false. /// /// public bool KeepClean { @@ -217,7 +215,8 @@ public bool KeepClean { /// /// /// - /// An IEnumerable<string> instance. + /// An + /// instance. /// /// /// It provides an enumerator which supports the iteration over @@ -232,16 +231,17 @@ public IEnumerable Paths { } /// - /// Gets or sets the time to wait for the response to the WebSocket Ping - /// or Close. + /// Gets or sets the time to wait for the response to the WebSocket + /// Ping or Close. /// /// - /// The set operation does nothing if the server has already started or - /// it is shutting down. + /// The set operation works if the current state of the server is + /// Ready or Stop. /// /// /// - /// A to wait for the response. + /// A that represents the time to wait for + /// the response. /// /// /// The default value is the same as 1 second. @@ -257,7 +257,7 @@ public TimeSpan WaitTime { set { if (value <= TimeSpan.Zero) { - var msg = "It is zero or less."; + var msg = "Zero or less."; throw new ArgumentOutOfRangeException ("value", msg); } @@ -288,7 +288,8 @@ private bool canSet () #region Internal Methods internal bool InternalTryGetServiceHost ( - string path, out WebSocketServiceHost host + string path, + out WebSocketServiceHost host ) { path = path.TrimSlashFromEnd (); @@ -325,7 +326,7 @@ internal void Stop (ushort code, string reason) /// /// Adds a WebSocket service with the specified behavior, path, - /// and delegate. + /// and initializer. /// /// /// @@ -338,12 +339,14 @@ internal void Stop (ushort code, string reason) /// /// /// - /// An Action<TBehavior> delegate or - /// if not needed. + /// An delegate. + /// + /// + /// It specifies the delegate called when the service initializes + /// a new session instance. /// /// - /// The delegate invokes the method called when initializing - /// a new session instance for the service. + /// if not necessary. /// /// /// @@ -354,12 +357,9 @@ internal void Stop (ushort code, string reason) /// It must inherit the class. /// /// - /// And also, it must have a public parameterless constructor. + /// Also it must have a public parameterless constructor. /// /// - /// - /// is . - /// /// /// /// is an empty string. @@ -384,8 +384,12 @@ internal void Stop (ushort code, string reason) /// is already in use. /// /// + /// + /// is . + /// public void AddService ( - string path, Action initializer + string path, + Action initializer ) where TBehavior : WebSocketBehavior, new () { @@ -396,7 +400,7 @@ public void AddService ( throw new ArgumentException ("An empty string.", "path"); if (path[0] != '/') { - var msg = "It is not an absolute path."; + var msg = "Not an absolute path."; throw new ArgumentException (msg, "path"); } @@ -420,8 +424,8 @@ public void AddService ( host = new WebSocketServiceHost (path, initializer, _log); - if (!_keepClean) - host.KeepClean = false; + if (_keepClean) + host.KeepClean = true; if (_waitTime != host.WaitTime) host.WaitTime = _waitTime; @@ -437,8 +441,8 @@ public void AddService ( /// Removes all WebSocket services managed by the manager. /// /// - /// A service is stopped with close status 1001 (going away) - /// if it has already started. + /// Each service is stopped with close status 1001 (going away) + /// if the current state of the service is Start. /// public void Clear () { @@ -461,7 +465,7 @@ public void Clear () /// /// /// The service is stopped with close status 1001 (going away) - /// if it has already started. + /// if the current state of the service is Start. /// /// /// true if the service is successfully found and removed; @@ -476,9 +480,6 @@ public void Clear () /// / is trimmed from the end of the string if present. /// /// - /// - /// is . - /// /// /// /// is an empty string. @@ -497,6 +498,9 @@ public void Clear () /// query and fragment components. /// /// + /// + /// is . + /// public bool RemoveService (string path) { if (path == null) @@ -506,7 +510,7 @@ public bool RemoveService (string path) throw new ArgumentException ("An empty string.", "path"); if (path[0] != '/') { - var msg = "It is not an absolute path."; + var msg = "Not an absolute path."; throw new ArgumentException (msg, "path"); } @@ -538,13 +542,12 @@ public bool RemoveService (string path) /// the specified path. /// /// - /// true if the service is successfully found; otherwise, - /// false. + /// true if the try has succeeded; otherwise, false. /// /// /// /// A that specifies an absolute path to - /// the service to find. + /// the service to get. /// /// /// / is trimmed from the end of the string if present. @@ -553,16 +556,15 @@ public bool RemoveService (string path) /// /// /// When this method returns, a - /// instance or if not found. + /// instance that receives the service host instance. + /// + /// + /// It provides the function to access the information in the service. /// /// - /// The service host instance provides the function to access - /// the information in the service. + /// if not found. /// /// - /// - /// is . - /// /// /// /// is an empty string. @@ -581,6 +583,9 @@ public bool RemoveService (string path) /// query and fragment components. /// /// + /// + /// is . + /// public bool TryGetServiceHost (string path, out WebSocketServiceHost host) { if (path == null) @@ -590,7 +595,7 @@ public bool TryGetServiceHost (string path, out WebSocketServiceHost host) throw new ArgumentException ("An empty string.", "path"); if (path[0] != '/') { - var msg = "It is not an absolute path."; + var msg = "Not an absolute path."; throw new ArgumentException (msg, "path"); } diff --git a/websocket-sharp/Server/WebSocketSessionManager.cs b/websocket-sharp/Server/WebSocketSessionManager.cs index 6229abaff..e1d1a2001 100644 --- a/websocket-sharp/Server/WebSocketSessionManager.cs +++ b/websocket-sharp/Server/WebSocketSessionManager.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2021 sta.blockhead + * Copyright (c) 2012-2025 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 @@ -48,10 +48,10 @@ public class WebSocketSessionManager { #region Private Fields - private static readonly byte[] _emptyPingFrameAsBytes; private object _forSweep; private volatile bool _keepClean; private Logger _log; + private static readonly byte[] _rawEmptyPingFrame; private Dictionary _sessions; private volatile ServerState _state; private volatile bool _sweeping; @@ -65,9 +65,7 @@ public class WebSocketSessionManager static WebSocketSessionManager () { - _emptyPingFrameAsBytes = WebSocketFrame - .CreatePingFrame (false) - .ToArray (); + _rawEmptyPingFrame = WebSocketFrame.CreatePingFrame (false).ToArray (); } #endregion @@ -79,7 +77,6 @@ internal WebSocketSessionManager (Logger log) _log = log; _forSweep = new object (); - _keepClean = true; _sessions = new Dictionary (); _state = ServerState.Ready; _sync = ((ICollection) _sessions).SyncRoot; @@ -107,7 +104,8 @@ internal ServerState State { /// /// /// - /// An IEnumerable<string> instance. + /// An + /// instance. /// /// /// It provides an enumerator which supports the iteration over @@ -116,7 +114,7 @@ internal ServerState State { /// public IEnumerable ActiveIDs { get { - foreach (var res in broadping (_emptyPingFrameAsBytes)) { + foreach (var res in broadping (_rawEmptyPingFrame)) { if (res.Value) yield return res.Key; } @@ -141,7 +139,8 @@ public int Count { /// /// /// - /// An IEnumerable<string> instance. + /// An + /// instance. /// /// /// It provides an enumerator which supports the iteration over @@ -167,7 +166,8 @@ public IEnumerable IDs { /// /// /// - /// An IEnumerable<string> instance. + /// An + /// instance. /// /// /// It provides an enumerator which supports the iteration over @@ -176,7 +176,7 @@ public IEnumerable IDs { /// public IEnumerable InactiveIDs { get { - foreach (var res in broadping (_emptyPingFrameAsBytes)) { + foreach (var res in broadping (_rawEmptyPingFrame)) { if (!res.Value) yield return res.Key; } @@ -188,23 +188,22 @@ public IEnumerable InactiveIDs { /// /// /// - /// A instance or - /// if not found. + /// A instance that provides + /// the function to access the information in the session. /// /// - /// The session instance provides the function to access the information - /// in the session. + /// if not found. /// /// /// - /// A that specifies the ID of the session to find. + /// A that specifies the ID of the session to get. /// - /// - /// is . - /// /// /// is an empty string. /// + /// + /// is . + /// public IWebSocketSession this[string id] { get { if (id == null) @@ -226,8 +225,8 @@ public IWebSocketSession this[string id] { /// the WebSocket service are cleaned up periodically. /// /// - /// The set operation does nothing if the service has already started or - /// it is shutting down. + /// The set operation works if the current state of the service is + /// Ready or Stop. /// /// /// true if the inactive sessions are cleaned up every 60 seconds; @@ -253,7 +252,8 @@ public bool KeepClean { /// /// /// - /// An IEnumerable<IWebSocketSession> instance. + /// An + /// instance. /// /// /// It provides an enumerator which supports the iteration over @@ -275,15 +275,16 @@ public IEnumerable Sessions { } /// - /// Gets or sets the time to wait for the response to the WebSocket Ping - /// or Close. + /// Gets or sets the time to wait for the response to the WebSocket + /// Ping or Close. /// /// - /// The set operation does nothing if the service has already started or - /// it is shutting down. + /// The set operation works if the current state of the service is + /// Ready or Stop. /// /// - /// A to wait for the response. + /// A that represents the time to wait for + /// the response. /// /// /// The value specified for a set operation is zero or less. @@ -295,7 +296,7 @@ public TimeSpan WaitTime { set { if (value <= TimeSpan.Zero) { - var msg = "It is zero or less."; + var msg = "Zero or less."; throw new ArgumentOutOfRangeException ("value", msg); } @@ -320,12 +321,12 @@ private void broadcast (Opcode opcode, byte[] data, Action completed) try { foreach (var session in Sessions) { if (_state != ServerState.Start) { - _log.Error ("The service is shutting down."); + _log.Error ("The send is cancelled."); break; } - session.Context.WebSocket.Send (opcode, data, cache); + session.WebSocket.Send (opcode, data, cache); } if (completed != null) @@ -340,19 +341,23 @@ private void broadcast (Opcode opcode, byte[] data, Action completed) } } - private void broadcast (Opcode opcode, Stream stream, Action completed) + private void broadcast ( + Opcode opcode, + Stream sourceStream, + Action completed + ) { - var cache = new Dictionary (); + var cache = new Dictionary (); try { foreach (var session in Sessions) { if (_state != ServerState.Start) { - _log.Error ("The service is shutting down."); + _log.Error ("The send is cancelled."); break; } - session.Context.WebSocket.Send (opcode, stream, cache); + session.WebSocket.Send (opcode, sourceStream, cache); } if (completed != null) @@ -377,25 +382,29 @@ private void broadcastAsync (Opcode opcode, byte[] data, Action completed) ); } - private void broadcastAsync (Opcode opcode, Stream stream, Action completed) + private void broadcastAsync ( + Opcode opcode, + Stream sourceStream, + Action completed + ) { ThreadPool.QueueUserWorkItem ( - state => broadcast (opcode, stream, completed) + state => broadcast (opcode, sourceStream, completed) ); } - private Dictionary broadping (byte[] frameAsBytes) + private Dictionary broadping (byte[] rawFrame) { var ret = new Dictionary (); foreach (var session in Sessions) { if (_state != ServerState.Start) { - _log.Error ("The service is shutting down."); + ret.Clear (); break; } - var res = session.Context.WebSocket.Ping (frameAsBytes, _waitTime); + var res = session.WebSocket.Ping (rawFrame); ret.Add (session.ID, res); } @@ -408,11 +417,6 @@ private bool canSet () return _state == ServerState.Ready || _state == ServerState.Stop; } - private static string createID () - { - return Guid.NewGuid ().ToString ("N"); - } - private void setSweepTimer (double interval) { _sweepTimer = new System.Timers.Timer (interval); @@ -421,18 +425,18 @@ private void setSweepTimer (double interval) private void stop (PayloadData payloadData, bool send) { - var bytes = send - ? WebSocketFrame - .CreateCloseFrame (payloadData, false) - .ToArray () - : null; + var rawFrame = send + ? WebSocketFrame + .CreateCloseFrame (payloadData, false) + .ToArray () + : null; lock (_sync) { _state = ServerState.ShuttingDown; _sweepTimer.Enabled = false; foreach (var session in _sessions.Values.ToList ()) - session.Context.WebSocket.Close (payloadData, bytes); + session.WebSocket.Close (payloadData, rawFrame); _state = ServerState.Stop; } @@ -457,20 +461,23 @@ private bool tryGetSession (string id, out IWebSocketSession session) #region Internal Methods - internal string Add (IWebSocketSession session) + internal bool Add (IWebSocketSession session) { lock (_sync) { if (_state != ServerState.Start) - return null; - - var id = createID (); + return false; - _sessions.Add (id, session); + _sessions.Add (session.ID, session); - return id; + return true; } } + internal static string CreateID () + { + return Guid.NewGuid ().ToString ("N"); + } + internal bool Remove (string id) { lock (_sync) @@ -494,7 +501,7 @@ internal void Stop (ushort code, string reason) } var payloadData = new PayloadData (code, reason); - var send = !code.IsReserved (); + var send = !code.IsReservedStatusCode (); stop (payloadData, send); } @@ -509,12 +516,12 @@ internal void Stop (ushort code, string reason) /// /// An array of that specifies the binary data to send. /// - /// - /// The current state of the service is not Start. - /// /// /// is . /// + /// + /// The current state of the service is not Start. + /// public void Broadcast (byte[] data) { if (_state != ServerState.Start) { @@ -538,14 +545,14 @@ public void Broadcast (byte[] data) /// /// A that specifies the text data to send. /// - /// - /// The current state of the service is not Start. + /// + /// could not be UTF-8-encoded. /// /// /// is . /// - /// - /// could not be UTF-8-encoded. + /// + /// The current state of the service is not Start. /// public void Broadcast (string data) { @@ -587,12 +594,6 @@ public void Broadcast (string data) /// /// An that specifies the number of bytes to send. /// - /// - /// The current state of the service is not Start. - /// - /// - /// is . - /// /// /// /// cannot be read. @@ -610,6 +611,12 @@ public void Broadcast (string data) /// No data could be read from . /// /// + /// + /// is . + /// + /// + /// The current state of the service is not Start. + /// public void Broadcast (Stream stream, int length) { if (_state != ServerState.Start) { @@ -628,7 +635,7 @@ public void Broadcast (Stream stream, int length) } if (length < 1) { - var msg = "It is less than 1."; + var msg = "Less than 1."; throw new ArgumentException (msg, "length"); } @@ -656,8 +663,8 @@ public void Broadcast (Stream stream, int length) } /// - /// Sends the specified data asynchronously to every client in - /// the WebSocket service. + /// Sends the specified data to every client in the WebSocket service + /// asynchronously. /// /// /// This method does not wait for the send to be complete. @@ -667,19 +674,21 @@ public void Broadcast (Stream stream, int length) /// /// /// - /// An delegate or - /// if not needed. + /// An delegate. /// /// - /// The delegate invokes the method called when the send is complete. + /// It specifies the delegate called when the send is complete. + /// + /// + /// if not necessary. /// /// - /// - /// The current state of the service is not Start. - /// /// /// is . /// + /// + /// The current state of the service is not Start. + /// public void BroadcastAsync (byte[] data, Action completed) { if (_state != ServerState.Start) { @@ -698,8 +707,8 @@ public void BroadcastAsync (byte[] data, Action completed) } /// - /// Sends the specified data asynchronously to every client in - /// the WebSocket service. + /// Sends the specified data to every client in the WebSocket service + /// asynchronously. /// /// /// This method does not wait for the send to be complete. @@ -709,21 +718,23 @@ public void BroadcastAsync (byte[] data, Action completed) /// /// /// - /// An delegate or - /// if not needed. + /// An delegate. + /// + /// + /// It specifies the delegate called when the send is complete. /// /// - /// The delegate invokes the method called when the send is complete. + /// if not necessary. /// /// - /// - /// The current state of the service is not Start. + /// + /// could not be UTF-8-encoded. /// /// /// is . /// - /// - /// could not be UTF-8-encoded. + /// + /// The current state of the service is not Start. /// public void BroadcastAsync (string data, Action completed) { @@ -751,8 +762,8 @@ public void BroadcastAsync (string data, Action completed) } /// - /// Sends the data from the specified stream instance asynchronously to - /// every client in the WebSocket service. + /// Sends the data from the specified stream instance to every client in + /// the WebSocket service asynchronously. /// /// /// This method does not wait for the send to be complete. @@ -770,19 +781,15 @@ public void BroadcastAsync (string data, Action completed) /// /// /// - /// An delegate or - /// if not needed. + /// An delegate. + /// + /// + /// It specifies the delegate called when the send is complete. /// /// - /// The delegate invokes the method called when the send is complete. + /// if not necessary. /// /// - /// - /// The current state of the service is not Start. - /// - /// - /// is . - /// /// /// /// cannot be read. @@ -800,6 +807,12 @@ public void BroadcastAsync (string data, Action completed) /// No data could be read from . /// /// + /// + /// is . + /// + /// + /// The current state of the service is not Start. + /// public void BroadcastAsync (Stream stream, int length, Action completed) { if (_state != ServerState.Start) { @@ -818,7 +831,7 @@ public void BroadcastAsync (Stream stream, int length, Action completed) } if (length < 1) { - var msg = "It is less than 1."; + var msg = "Less than 1."; throw new ArgumentException (msg, "length"); } @@ -851,12 +864,12 @@ public void BroadcastAsync (Stream stream, int length, Action completed) /// /// A that specifies the ID of the session to close. /// - /// - /// is . - /// /// /// is an empty string. /// + /// + /// is . + /// /// /// The session could not be found. /// @@ -870,11 +883,11 @@ public void CloseSession (string id) throw new InvalidOperationException (msg); } - session.Context.WebSocket.Close (); + session.WebSocket.Close (); } /// - /// Closes the session with the specified ID, code, and reason. + /// Closes the session with the specified ID, status code, and reason. /// /// /// A that specifies the ID of the session to close. @@ -895,12 +908,9 @@ public void CloseSession (string id) /// A that specifies the reason for the close. /// /// - /// The size must be 123 bytes or less in UTF-8. + /// Its size must be 123 bytes or less in UTF-8. /// /// - /// - /// is . - /// /// /// /// is an empty string. @@ -915,7 +925,8 @@ public void CloseSession (string id) /// -or- /// /// - /// is 1005 (no status) and there is reason. + /// is 1005 (no status) and + /// is specified. /// /// /// -or- @@ -924,8 +935,8 @@ public void CloseSession (string id) /// could not be UTF-8-encoded. /// /// - /// - /// The session could not be found. + /// + /// is . /// /// /// @@ -938,6 +949,9 @@ public void CloseSession (string id) /// The size of is greater than 123 bytes. /// /// + /// + /// The session could not be found. + /// public void CloseSession (string id, ushort code, string reason) { IWebSocketSession session; @@ -948,11 +962,11 @@ public void CloseSession (string id, ushort code, string reason) throw new InvalidOperationException (msg); } - session.Context.WebSocket.Close (code, reason); + session.WebSocket.Close (code, reason); } /// - /// Closes the session with the specified ID, code, and reason. + /// Closes the session with the specified ID, status code, and reason. /// /// /// A that specifies the ID of the session to close. @@ -970,12 +984,9 @@ public void CloseSession (string id, ushort code, string reason) /// A that specifies the reason for the close. /// /// - /// The size must be 123 bytes or less in UTF-8. + /// Its size must be 123 bytes or less in UTF-8. /// /// - /// - /// is . - /// /// /// /// is an empty string. @@ -984,15 +995,20 @@ public void CloseSession (string id, ushort code, string reason) /// -or- /// /// - /// is - /// . + /// is an undefined enum value. /// /// /// -or- /// /// - /// is - /// and there is reason. + /// is . + /// + /// + /// -or- + /// + /// + /// is and + /// is specified. /// /// /// -or- @@ -1001,12 +1017,15 @@ public void CloseSession (string id, ushort code, string reason) /// could not be UTF-8-encoded. /// /// - /// - /// The session could not be found. + /// + /// is . /// /// /// The size of is greater than 123 bytes. /// + /// + /// The session could not be found. + /// public void CloseSession (string id, CloseStatusCode code, string reason) { IWebSocketSession session; @@ -1017,25 +1036,25 @@ public void CloseSession (string id, CloseStatusCode code, string reason) throw new InvalidOperationException (msg); } - session.Context.WebSocket.Close (code, reason); + session.WebSocket.Close (code, reason); } /// /// Sends a ping to the client using the specified session. /// /// - /// true if the send has done with no error and a pong has been - /// received from the client within a time; otherwise, false. + /// true if the send has successfully done and a pong has been + /// received within a time; otherwise, false. /// /// /// A that specifies the ID of the session. /// - /// - /// is . - /// /// /// is an empty string. /// + /// + /// is . + /// /// /// The session could not be found. /// @@ -1049,7 +1068,7 @@ public bool PingTo (string id) throw new InvalidOperationException (msg); } - return session.Context.WebSocket.Ping (); + return session.WebSocket.Ping (); } /// @@ -1057,23 +1076,20 @@ public bool PingTo (string id) /// the specified session. /// /// - /// true if the send has done with no error and a pong has been - /// received from the client within a time; otherwise, false. + /// true if the send has successfully done and a pong has been + /// received within a time; otherwise, false. /// /// /// /// A that specifies the message to send. /// /// - /// The size must be 125 bytes or less in UTF-8. + /// Its size must be 125 bytes or less in UTF-8. /// /// /// /// A that specifies the ID of the session. /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1085,12 +1101,15 @@ public bool PingTo (string id) /// could not be UTF-8-encoded. /// /// - /// - /// The session could not be found. + /// + /// is . /// /// /// The size of is greater than 125 bytes. /// + /// + /// The session could not be found. + /// public bool PingTo (string message, string id) { IWebSocketSession session; @@ -1101,7 +1120,7 @@ public bool PingTo (string message, string id) throw new InvalidOperationException (msg); } - return session.Context.WebSocket.Ping (message); + return session.WebSocket.Ping (message); } /// @@ -1113,6 +1132,9 @@ public bool PingTo (string message, string id) /// /// A that specifies the ID of the session. /// + /// + /// is an empty string. + /// /// /// /// is . @@ -1124,9 +1146,6 @@ public bool PingTo (string message, string id) /// is . /// /// - /// - /// is an empty string. - /// /// /// /// The session could not be found. @@ -1135,7 +1154,7 @@ public bool PingTo (string message, string id) /// -or- /// /// - /// The current state of the WebSocket connection is not Open. + /// The current state of the WebSocket interface is not Open. /// /// public void SendTo (byte[] data, string id) @@ -1148,7 +1167,7 @@ public void SendTo (byte[] data, string id) throw new InvalidOperationException (msg); } - session.Context.WebSocket.Send (data); + session.WebSocket.Send (data); } /// @@ -1160,26 +1179,26 @@ public void SendTo (byte[] data, string id) /// /// A that specifies the ID of the session. /// - /// + /// /// - /// is . + /// is an empty string. /// /// /// -or- /// /// - /// is . + /// could not be UTF-8-encoded. /// /// - /// + /// /// - /// is an empty string. + /// is . /// /// /// -or- /// /// - /// could not be UTF-8-encoded. + /// is . /// /// /// @@ -1190,7 +1209,7 @@ public void SendTo (byte[] data, string id) /// -or- /// /// - /// The current state of the WebSocket connection is not Open. + /// The current state of the WebSocket interface is not Open. /// /// public void SendTo (string data, string id) @@ -1203,7 +1222,7 @@ public void SendTo (string data, string id) throw new InvalidOperationException (msg); } - session.Context.WebSocket.Send (data); + session.WebSocket.Send (data); } /// @@ -1224,17 +1243,6 @@ public void SendTo (string data, string id) /// /// A that specifies the ID of the session. /// - /// - /// - /// is . - /// - /// - /// -or- - /// - /// - /// is . - /// - /// /// /// /// is an empty string. @@ -1258,6 +1266,17 @@ public void SendTo (string data, string id) /// No data could be read from . /// /// + /// + /// + /// is . + /// + /// + /// -or- + /// + /// + /// is . + /// + /// /// /// /// The session could not be found. @@ -1266,7 +1285,7 @@ public void SendTo (string data, string id) /// -or- /// /// - /// The current state of the WebSocket connection is not Open. + /// The current state of the WebSocket interface is not Open. /// /// public void SendTo (Stream stream, int length, string id) @@ -1279,12 +1298,12 @@ public void SendTo (Stream stream, int length, string id) throw new InvalidOperationException (msg); } - session.Context.WebSocket.Send (stream, length); + session.WebSocket.Send (stream, length); } /// - /// Sends the specified data asynchronously to the client using - /// the specified session. + /// Sends the specified data to the client using the specified session + /// asynchronously. /// /// /// This method does not wait for the send to be complete. @@ -1297,17 +1316,23 @@ public void SendTo (Stream stream, int length, string id) /// /// /// - /// An Action<bool> delegate or - /// if not needed. + /// An delegate. + /// + /// + /// It specifies the delegate called when the send is complete. /// /// - /// The delegate invokes the method called when the send is complete. + /// The parameter passed to the delegate is + /// true if the send has successfully done; otherwise, + /// false. /// /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. + /// if not necessary. /// /// + /// + /// is an empty string. + /// /// /// /// is . @@ -1319,9 +1344,6 @@ public void SendTo (Stream stream, int length, string id) /// is . /// /// - /// - /// is an empty string. - /// /// /// /// The session could not be found. @@ -1330,7 +1352,7 @@ public void SendTo (Stream stream, int length, string id) /// -or- /// /// - /// The current state of the WebSocket connection is not Open. + /// The current state of the WebSocket interface is not Open. /// /// public void SendToAsync (byte[] data, string id, Action completed) @@ -1343,12 +1365,12 @@ public void SendToAsync (byte[] data, string id, Action completed) throw new InvalidOperationException (msg); } - session.Context.WebSocket.SendAsync (data, completed); + session.WebSocket.SendAsync (data, completed); } /// - /// Sends the specified data asynchronously to the client using - /// the specified session. + /// Sends the specified data to the client using the specified session + /// asynchronously. /// /// /// This method does not wait for the send to be complete. @@ -1361,37 +1383,40 @@ public void SendToAsync (byte[] data, string id, Action completed) /// /// /// - /// An Action<bool> delegate or - /// if not needed. + /// An delegate. /// /// - /// The delegate invokes the method called when the send is complete. + /// It specifies the delegate called when the send is complete. /// /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. + /// The parameter passed to the delegate is + /// true if the send has successfully done; otherwise, + /// false. + /// + /// + /// if not necessary. /// /// - /// + /// /// - /// is . + /// is an empty string. /// /// /// -or- /// /// - /// is . + /// could not be UTF-8-encoded. /// /// - /// + /// /// - /// is an empty string. + /// is . /// /// /// -or- /// /// - /// could not be UTF-8-encoded. + /// is . /// /// /// @@ -1402,7 +1427,7 @@ public void SendToAsync (byte[] data, string id, Action completed) /// -or- /// /// - /// The current state of the WebSocket connection is not Open. + /// The current state of the WebSocket interface is not Open. /// /// public void SendToAsync (string data, string id, Action completed) @@ -1415,12 +1440,12 @@ public void SendToAsync (string data, string id, Action completed) throw new InvalidOperationException (msg); } - session.Context.WebSocket.SendAsync (data, completed); + session.WebSocket.SendAsync (data, completed); } /// - /// Sends the data from the specified stream instance asynchronously to - /// the client using the specified session. + /// Sends the data from the specified stream instance to the client using + /// the specified session asynchronously. /// /// /// This method does not wait for the send to be complete. @@ -1441,28 +1466,20 @@ public void SendToAsync (string data, string id, Action completed) /// /// /// - /// An Action<bool> delegate or - /// if not needed. + /// An delegate. /// /// - /// The delegate invokes the method called when the send is complete. + /// It specifies the delegate called when the send is complete. /// /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. + /// The parameter passed to the delegate is + /// true if the send has successfully done; otherwise, + /// false. /// - /// - /// /// - /// is . + /// if not necessary. /// - /// - /// -or- - /// - /// - /// is . - /// - /// + /// /// /// /// is an empty string. @@ -1486,6 +1503,17 @@ public void SendToAsync (string data, string id, Action completed) /// No data could be read from . /// /// + /// + /// + /// is . + /// + /// + /// -or- + /// + /// + /// is . + /// + /// /// /// /// The session could not be found. @@ -1494,11 +1522,14 @@ public void SendToAsync (string data, string id, Action completed) /// -or- /// /// - /// The current state of the WebSocket connection is not Open. + /// The current state of the WebSocket interface is not Open. /// /// public void SendToAsync ( - Stream stream, int length, string id, Action completed + Stream stream, + int length, + string id, + Action completed ) { IWebSocketSession session; @@ -1509,7 +1540,7 @@ public void SendToAsync ( throw new InvalidOperationException (msg); } - session.Context.WebSocket.SendAsync (stream, length, completed); + session.WebSocket.SendAsync (stream, length, completed); } /// @@ -1518,14 +1549,14 @@ public void SendToAsync ( public void Sweep () { if (_sweeping) { - _log.Info ("The sweeping is already in progress."); + _log.Trace ("The sweep process is already in progress."); return; } lock (_forSweep) { if (_sweeping) { - _log.Info ("The sweeping is already in progress."); + _log.Trace ("The sweep process is already in progress."); return; } @@ -1546,10 +1577,10 @@ public void Sweep () if (!_sessions.TryGetValue (id, out session)) continue; - var state = session.ConnectionState; + var state = session.WebSocket.ReadyState; if (state == WebSocketState.Open) { - session.Context.WebSocket.Close (CloseStatusCode.Abnormal); + session.WebSocket.Close (CloseStatusCode.Abnormal); continue; } @@ -1561,35 +1592,37 @@ public void Sweep () } } - _sweeping = false; + lock (_forSweep) + _sweeping = false; } /// /// Tries to get the session instance with the specified ID. /// /// - /// true if the session is successfully found; otherwise, - /// false. + /// true if the try has succeeded; otherwise, false. /// /// - /// A that specifies the ID of the session to find. + /// A that specifies the ID of the session to get. /// /// /// - /// When this method returns, a - /// instance or if not found. + /// When this method returns, a instance + /// that receives the session instance. + /// + /// + /// It provides the function to access the information in the session. /// /// - /// The session instance provides the function to access - /// the information in the session. + /// if not found. /// /// - /// - /// is . - /// /// /// is an empty string. /// + /// + /// is . + /// public bool TryGetSession (string id, out IWebSocketSession session) { if (id == null) diff --git a/websocket-sharp/WebSocket.cs b/websocket-sharp/WebSocket.cs index 3b6619da8..5d4727f42 100644 --- a/websocket-sharp/WebSocket.cs +++ b/websocket-sharp/WebSocket.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2009 Adam MacBeth - * Copyright (c) 2010-2016 sta.blockhead + * Copyright (c) 2010-2025 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 @@ -74,16 +74,15 @@ public class WebSocket : IDisposable 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 static readonly byte[] _emptyBytes; private bool _enableRedirection; private string _extensions; - private bool _extensionsRequested; private object _forMessageEventQueue; private object _forPing; private object _forSend; @@ -93,30 +92,38 @@ public class WebSocket : IDisposable private Opcode _fragmentsOpcode; private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; private Func _handshakeRequestChecker; + private Action _handshakeRequestResponder; + private CookieCollection _handshakeResponseCookies; + private NameValueCollection _handshakeResponseHeaders; + private bool _hasExtension; + private bool _hasProtocol; private bool _ignoreExtensions; private bool _inContinuation; private volatile bool _inMessage; - private volatile Logger _logger; + private bool _isClient; + private bool _isSecure; + private volatile Logger _log; private static readonly int _maxRetryCountForConnect; private Action _message; private Queue _messageEventQueue; + private bool _noDelay; private uint _nonceCount; private string _origin; private ManualResetEvent _pongReceived; private bool _preAuth; private string _protocol; private string[] _protocols; - private bool _protocolsRequested; private NetworkCredential _proxyCredentials; private Uri _proxyUri; private volatile WebSocketState _readyState; private ManualResetEvent _receivingExited; private int _retryCountForConnect; - private bool _secure; + private Socket _socket; private ClientSslConfiguration _sslConfig; private Stream _stream; private TcpClient _tcpClient; private Uri _uri; + private WebHeaderCollection _userHeaders; private const string _version = "13"; private TimeSpan _waitTime; @@ -125,20 +132,17 @@ public class WebSocket : IDisposable #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. + /// 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. + /// The data will be fragmented if its 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. + /// 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; @@ -154,8 +158,9 @@ public class WebSocket : IDisposable static WebSocket () { + _emptyBytes = new byte[0]; _maxRetryCountForConnect = 10; - EmptyBytes = new byte[0]; + FragmentLength = 1016; RandomNumber = new RNGCryptoServiceProvider (); } @@ -171,9 +176,10 @@ internal WebSocket (HttpListenerWebSocketContext context, string protocol) _protocol = protocol; _closeContext = context.Close; - _logger = context.Log; + _isSecure = context.IsSecureConnection; + _log = context.Log; _message = messages; - _secure = context.IsSecureConnection; + _socket = context.Socket; _stream = context.Stream; _waitTime = TimeSpan.FromSeconds (1); @@ -187,9 +193,10 @@ internal WebSocket (TcpListenerWebSocketContext context, string protocol) _protocol = protocol; _closeContext = context.Close; - _logger = context.Log; + _isSecure = context.IsSecureConnection; + _log = context.Log; _message = messages; - _secure = context.IsSecureConnection; + _socket = context.Socket; _stream = context.Stream; _waitTime = TimeSpan.FromSeconds (1); @@ -202,7 +209,7 @@ internal WebSocket (TcpListenerWebSocketContext context, string protocol) /// /// Initializes a new instance of the class with - /// and optionally . + /// the specified URL and optionally subprotocols. /// /// /// @@ -226,9 +233,6 @@ internal WebSocket (TcpListenerWebSocketContext context, string protocol) /// RFC 2616. /// /// - /// - /// is . - /// /// /// /// is an empty string. @@ -252,6 +256,9 @@ internal WebSocket (TcpListenerWebSocketContext context, string protocol) /// contains a value twice. /// /// + /// + /// is . + /// public WebSocket (string url, params string[] protocols) { if (url == null) @@ -261,6 +268,7 @@ public WebSocket (string url, params string[] protocols) throw new ArgumentException ("An empty string.", "url"); string msg; + if (!url.TryCreateWebSocketUri (out _uri, out msg)) throw new ArgumentException (msg, "url"); @@ -269,13 +277,15 @@ public WebSocket (string url, params string[] protocols) throw new ArgumentException (msg, "protocols"); _protocols = protocols; + _hasProtocol = true; } _base64Key = CreateBase64Key (); - _client = true; - _logger = new Logger (); + _isClient = true; + _isSecure = _uri.Scheme == "wss"; + _log = new Logger (); _message = messagec; - _secure = _uri.Scheme == "wss"; + _retryCountForConnect = -1; _waitTime = TimeSpan.FromSeconds (5); init (); @@ -285,8 +295,11 @@ public WebSocket (string url, params string[] protocols) #region Internal Properties - internal CookieCollection CookieCollection { + internal CookieCollection Cookies { get { + if (_cookies == null) + _cookies = new CookieCollection (); + return _cookies; } } @@ -302,10 +315,14 @@ internal Func CustomHandshakeRequestChecker { } } - internal bool HasMessage { + // As server + internal Action CustomHandshakeRequestResponder { get { - lock (_forMessageEventQueue) - return _messageEventQueue.Count > 0; + return _handshakeRequestResponder; + } + + set { + _handshakeRequestResponder = value; } } @@ -320,9 +337,17 @@ internal bool IgnoreExtensions { } } - internal bool IsConnected { + internal WebHeaderCollection UserHeaders { get { - return _readyState == WebSocketState.Open || _readyState == WebSocketState.Closing; + if (_userHeaders == null) { + var state = _isClient + ? HttpHeaderType.Request + : HttpHeaderType.Response; + + _userHeaders = new WebHeaderCollection (state, false); + } + + return _userHeaders; } } @@ -333,23 +358,29 @@ internal bool IsConnected { /// /// Gets or sets the compression method used to compress a message. /// - /// - /// The set operation does nothing if the connection has already been - /// established or it is closing. - /// /// /// /// One of the enum values. /// /// - /// It specifies the compression method used to compress a message. + /// It indicates the compression method used to compress a message. /// /// /// The default value is . /// /// /// - /// The set operation is not available if this instance is not a client. + /// + /// The set operation is not available if the interface is not for + /// the client. + /// + /// + /// -or- + /// + /// + /// The set operation is not available when the current state of + /// the interface is neither New nor Closed. + /// /// public CompressionMethod Compression { get { @@ -357,22 +388,17 @@ public CompressionMethod Compression { } set { - string msg = null; + if (!_isClient) { + var msg = "The set operation is not available."; - if (!_client) { - msg = "This instance is not a client."; throw new InvalidOperationException (msg); } - if (!canSet (out msg)) { - _logger.Warn (msg); - return; - } - lock (_forState) { - if (!canSet (out msg)) { - _logger.Warn (msg); - return; + if (!canSet ()) { + var msg = "The set operation is not available."; + + throw new InvalidOperationException (msg); } _compression = value; @@ -380,28 +406,6 @@ public CompressionMethod Compression { } } - /// - /// Gets the HTTP cookies included in the handshake request/response. - /// - /// - /// - /// An - /// instance. - /// - /// - /// It 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; - } - } - } - /// /// Gets the credentials for the HTTP authentication (Basic/Digest). /// @@ -421,25 +425,37 @@ public NetworkCredential Credentials { } /// - /// Gets or sets a value indicating whether a event - /// is emitted when a ping is received. + /// Gets or sets a value indicating whether the interface emits + /// the message event when it receives a ping. /// /// /// - /// true if this instance emits a event - /// when receives a ping; otherwise, false. + /// true if the interface emits the message event when + /// it receives a ping; otherwise, false. /// /// /// The default value is false. /// /// + /// + /// The set operation is not available when the current state of + /// the interface is neither New nor Closed. + /// public bool EmitOnPing { get { return _emitOnPing; } set { - _emitOnPing = value; + lock (_forState) { + if (!canSet ()) { + var msg = "The set operation is not available."; + + throw new InvalidOperationException (msg); + } + + _emitOnPing = value; + } } } @@ -447,13 +463,9 @@ public bool EmitOnPing { /// Gets or sets a value indicating whether the URL redirection for /// the handshake request is allowed. /// - /// - /// The set operation does nothing if the connection has already been - /// established or it is closing. - /// /// /// - /// true if this instance allows the URL redirection for + /// true if the interface allows the URL redirection for /// the handshake request; otherwise, false. /// /// @@ -461,7 +473,17 @@ public bool EmitOnPing { /// /// /// - /// The set operation is not available if this instance is not a client. + /// + /// The set operation is not available if the interface is not for + /// the client. + /// + /// + /// -or- + /// + /// + /// The set operation is not available when the current state of + /// the interface is neither New nor Closed. + /// /// public bool EnableRedirection { get { @@ -469,22 +491,17 @@ public bool EnableRedirection { } set { - string msg = null; + if (!_isClient) { + var msg = "The set operation is not available."; - if (!_client) { - msg = "This instance is not a client."; throw new InvalidOperationException (msg); } - if (!canSet (out msg)) { - _logger.Warn (msg); - return; - } - lock (_forState) { - if (!canSet (out msg)) { - _logger.Warn (msg); - return; + if (!canSet ()) { + var msg = "The set operation is not available."; + + throw new InvalidOperationException (msg); } _enableRedirection = value; @@ -493,12 +510,16 @@ public bool EnableRedirection { } /// - /// Gets the extensions selected by server. + /// Gets the extensions selected by the server. /// /// - /// A that will be a list of the extensions - /// negotiated between client and server, or an empty string if - /// not specified or selected. + /// + /// A that represents a list of the extensions + /// negotiated between the client and server. + /// + /// + /// An empty string if not specified or selected. + /// /// public string Extensions { get { @@ -507,31 +528,122 @@ public string Extensions { } /// - /// Gets a value indicating whether the connection is alive. + /// Gets the HTTP cookies included in the handshake response. + /// + /// + /// + /// A that contains the cookies + /// included in the handshake response if any. + /// + /// + /// if the interface could not receive + /// the handshake response. + /// + /// + /// + /// + /// The get operation is not available if the interface is not for + /// the client. + /// + /// + /// -or- + /// + /// + /// The get operation is not available when the current state of + /// the interface is New or Connecting. + /// + /// + public CookieCollection HandshakeResponseCookies { + get { + if (!_isClient) { + var msg = "The get operation is not available."; + + throw new InvalidOperationException (msg); + } + + lock (_forState) { + var canGet = _readyState > WebSocketState.Connecting; + + if (!canGet) { + var msg = "The get operation is not available."; + + throw new InvalidOperationException (msg); + } + + return _handshakeResponseCookies; + } + } + } + + /// + /// Gets the HTTP headers included in the handshake response. + /// + /// + /// + /// A that contains the headers + /// included in the handshake response. + /// + /// + /// if the interface could not receive + /// the handshake response. + /// + /// + /// + /// + /// The get operation is not available if the interface is not for + /// the client. + /// + /// + /// -or- + /// + /// + /// The get operation is not available when the current state of + /// the interface is New or Connecting. + /// + /// + public NameValueCollection HandshakeResponseHeaders { + get { + if (!_isClient) { + var msg = "The get operation is not available."; + + throw new InvalidOperationException (msg); + } + + lock (_forState) { + var canGet = _readyState > WebSocketState.Connecting; + + if (!canGet) { + var msg = "The get operation is not available."; + + throw new InvalidOperationException (msg); + } + + return _handshakeResponseHeaders; + } + } + } + + /// + /// Gets a value indicating whether the communication is possible. /// - /// - /// The get operation returns the value by using a ping/pong - /// if the current state of the connection is Open. - /// /// - /// true if the connection is alive; otherwise, false. + /// true if the communication is possible; otherwise, false. /// public bool IsAlive { get { - return ping (EmptyBytes); + return ping (_emptyBytes); } } /// - /// Gets a value indicating whether a secure connection is used. + /// Gets a value indicating whether the connection is secure. /// /// - /// true if this instance uses a secure connection; otherwise, - /// false. + /// true if the connection is secure; otherwise, false. /// public bool IsSecure { get { - return _secure; + return _isSecure; } } @@ -544,13 +656,58 @@ public bool IsSecure { /// /// A that provides the logging function. /// + /// + /// The get operation is not available if the interface is not for + /// the client. + /// public Logger Log { get { - return _logger; + if (!_isClient) { + var msg = "The get operation is not available."; + + throw new InvalidOperationException (msg); + } + + return _log; } internal set { - _logger = value; + _log = value; + } + } + + /// + /// Gets or sets a value indicating whether the underlying TCP socket + /// disables a delay when send or receive buffer is not full. + /// + /// + /// + /// true if the delay is disabled; otherwise, false. + /// + /// + /// The default value is false. + /// + /// + /// + /// + /// The set operation is not available when the current state of + /// the interface is neither New nor Closed. + /// + public bool NoDelay { + get { + return _noDelay; + } + + set { + lock (_forState) { + if (!canSet ()) { + var msg = "The set operation is not available."; + + throw new InvalidOperationException (msg); + } + + _noDelay = value; + } } } @@ -565,11 +722,7 @@ internal set { /// Section 7 of RFC 6454. /// /// - /// This instance sends the Origin header if this property has any. - /// - /// - /// The set operation does nothing if the connection has already been - /// established or it is closing. + /// The interface sends the Origin header if this property has any. /// /// /// @@ -584,9 +737,6 @@ internal set { /// The default value is . /// /// - /// - /// The set operation is not available if this instance is not a client. - /// /// /// /// The value specified for a set operation is not an absolute URI string. @@ -598,41 +748,52 @@ internal set { /// The value specified for a set operation includes the path segments. /// /// + /// + /// + /// The set operation is not available if the interface is not for + /// the client. + /// + /// + /// -or- + /// + /// + /// The set operation is not available when the current state of + /// the interface is neither New nor Closed. + /// + /// public string Origin { get { return _origin; } set { - string msg = null; + if (!_isClient) { + var msg = "The set operation is not available."; - if (!_client) { - msg = "This instance is not a client."; throw new InvalidOperationException (msg); } if (!value.IsNullOrEmpty ()) { Uri uri; + if (!Uri.TryCreate (value, UriKind.Absolute, out uri)) { - msg = "Not an absolute URI string."; + var msg = "Not an absolute URI string."; + throw new ArgumentException (msg, "value"); } if (uri.Segments.Length > 1) { - msg = "It includes the path segments."; + var msg = "It includes the path segments."; + throw new ArgumentException (msg, "value"); } } - if (!canSet (out msg)) { - _logger.Warn (msg); - return; - } - lock (_forState) { - if (!canSet (out msg)) { - _logger.Warn (msg); - return; + if (!canSet ()) { + var msg = "The set operation is not available."; + + throw new InvalidOperationException (msg); } _origin = !value.IsNullOrEmpty () ? value.TrimEnd ('/') : value; @@ -663,17 +824,17 @@ internal set { } /// - /// Gets the current state of the connection. + /// Gets the current state of the interface. /// /// /// /// One of the enum values. /// /// - /// It indicates the current state of the connection. + /// It indicates the current state of the interface. /// /// - /// The default value is . + /// The default value is . /// /// public WebSocketState ReadyState { @@ -686,30 +847,37 @@ public WebSocketState ReadyState { /// Gets the configuration for secure connection. /// /// - /// This configuration will be referenced when attempts to connect, + /// The configuration is used when the interface attempts to connect, /// so it must be configured before any connect method is called. /// /// - /// A that represents - /// the configuration used to establish a secure connection. + /// A that represents the + /// configuration used to establish a secure connection. /// /// /// - /// This instance is not a client. + /// The get operation is not available if the interface is not for + /// the client. + /// + /// + /// -or- /// /// - /// This instance does not use a secure connection. + /// The get operation is not available if the interface does not use + /// a secure connection. /// /// public ClientSslConfiguration SslConfiguration { get { - if (!_client) { - var msg = "This instance is not a client."; + if (!_isClient) { + var msg = "The get operation is not available."; + throw new InvalidOperationException (msg); } - if (!_secure) { - var msg = "This instance does not use a secure connection."; + if (!_isSecure) { + var msg = "The get operation is not available."; + throw new InvalidOperationException (msg); } @@ -721,52 +889,57 @@ public ClientSslConfiguration SslConfiguration { /// Gets the URL to which to connect. /// /// - /// A that represents the URL to which to connect. + /// + /// A that represents the URL to which to connect. + /// + /// + /// Also it represents the URL requested by the client if the interface + /// is for the server. + /// /// public Uri Url { get { - return _client ? _uri : _context.RequestUri; + return _isClient ? _uri : _context.RequestUri; } } /// /// Gets or sets the time to wait for the response to the ping or close. /// - /// - /// The set operation does nothing if the connection has already been - /// established or it is closing. - /// /// /// - /// A to wait for the response. + /// A that represents the time to wait for + /// the response. /// /// - /// The default value is the same as 5 seconds if this instance is - /// a client. + /// The default value is the same as 5 seconds if the interface is + /// for the client. /// /// /// /// The value specified for a set operation is zero or less. /// + /// + /// The set operation is not available when the current state of + /// the interface is neither New nor Closed. + /// public TimeSpan WaitTime { get { return _waitTime; } set { - if (value <= TimeSpan.Zero) - throw new ArgumentOutOfRangeException ("value", "Zero or less."); + if (value <= TimeSpan.Zero) { + var msg = "Zero or less."; - string msg; - if (!canSet (out msg)) { - _logger.Warn (msg); - return; + throw new ArgumentOutOfRangeException ("value", msg); } lock (_forState) { - if (!canSet (out msg)) { - _logger.Warn (msg); - return; + if (!canSet ()) { + var msg = "The set operation is not available."; + + throw new InvalidOperationException (msg); } _waitTime = value; @@ -779,22 +952,22 @@ public TimeSpan WaitTime { #region Public Events /// - /// Occurs when the WebSocket connection has been closed. + /// Occurs when the connection has been closed. /// public event EventHandler OnClose; /// - /// Occurs when the gets an error. + /// Occurs when the interface gets an error. /// public event EventHandler OnError; /// - /// Occurs when the receives a message. + /// Occurs when the interface receives a message. /// public event EventHandler OnMessage; /// - /// Occurs when the WebSocket connection has been established. + /// Occurs when the connection has been established. /// public event EventHandler OnOpen; @@ -802,59 +975,67 @@ public TimeSpan WaitTime { #region Private Methods - // As server - private bool accept () + private void abort (string reason, Exception exception) { - if (_readyState == WebSocketState.Open) { - var msg = "The handshake request has already been accepted."; - _logger.Warn (msg); + var code = exception is WebSocketException + ? ((WebSocketException) exception).Code + : (ushort) 1006; - return false; - } + abort (code, reason); + } + + private void abort (ushort code, string reason) + { + var data = new PayloadData (code, reason); + close (data, false, false); + } + + // As server + private bool accept () + { lock (_forState) { if (_readyState == WebSocketState.Open) { - var msg = "The handshake request has already been accepted."; - _logger.Warn (msg); + _log.Trace ("The connection has already been established."); return false; } if (_readyState == WebSocketState.Closing) { - var msg = "The close process has set in."; - _logger.Error (msg); + _log.Error ("The close process is in progress."); - msg = "An interruption has occurred while attempting to accept."; - error (msg, null); + error ("An error has occurred before accepting.", null); return false; } if (_readyState == WebSocketState.Closed) { - var msg = "The connection has been closed."; - _logger.Error (msg); + _log.Error ("The connection has been closed."); - msg = "An interruption has occurred while attempting to accept."; - error (msg, null); + error ("An error has occurred before accepting.", null); return false; } + _readyState = WebSocketState.Connecting; + + var accepted = false; + try { - if (!acceptHandshake ()) - return false; + accepted = acceptHandshake (); } catch (Exception ex) { - _logger.Fatal (ex.Message); - _logger.Debug (ex.ToString ()); + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); - var msg = "An exception has occurred while attempting to accept."; - fatal (msg, ex); + abort (1011, "An exception has occurred while accepting."); + } + if (!accepted) return false; - } _readyState = WebSocketState.Open; + return true; } } @@ -862,25 +1043,22 @@ private bool accept () // As server private bool acceptHandshake () { - var fmt = "A handshake request from {0}:\n{1}"; - var msg = String.Format (fmt, _context.UserEndPoint, _context); - - _logger.Debug (msg); + string msg; if (!checkHandshakeRequest (_context, out msg)) { - _logger.Error (msg); + _log.Error (msg); + _log.Debug (_context.ToString ()); - var reason = "A handshake error has occurred while attempting to accept."; - refuseHandshake (CloseStatusCode.ProtocolError, reason); + refuseHandshake (1002, "A handshake error has occurred."); return false; } if (!customCheckHandshakeRequest (_context, out msg)) { - _logger.Error (msg); + _log.Error (msg); + _log.Debug (_context.ToString ()); - var reason = "A handshake error has occurred while attempting to accept."; - refuseHandshake (CloseStatusCode.PolicyViolation, reason); + refuseHandshake (1002, "A handshake error has occurred."); return false; } @@ -888,9 +1066,12 @@ private bool acceptHandshake () _base64Key = _context.Headers["Sec-WebSocket-Key"]; if (_protocol != null) { - var vals = _context.SecWebSocketProtocols; + var matched = _context + .SecWebSocketProtocols + .Contains (p => p == _protocol); - processSecWebSocketProtocolClientHeader (vals); + if (!matched) + _protocol = null; } if (!_ignoreExtensions) { @@ -899,31 +1080,26 @@ private bool acceptHandshake () processSecWebSocketExtensionsClientHeader (val); } - var res = createHandshakeResponse (); - - return sendHttpResponse (res); - } - - private bool canSet (out string message) - { - message = null; + customRespondToHandshakeRequest (_context); - if (_readyState == WebSocketState.Open) { - message = "The connection has already been established."; - return false; - } + if (_noDelay) + _socket.NoDelay = true; - if (_readyState == WebSocketState.Closing) { - message = "The connection is closing."; - return false; - } + createHandshakeResponse ().WriteTo (_stream); return true; } + private bool canSet () + { + return _readyState == WebSocketState.New + || _readyState == WebSocketState.Closed; + } + // As server private bool checkHandshakeRequest ( - WebSocketContext context, out string message + WebSocketContext context, + out string message ) { message = null; @@ -934,12 +1110,6 @@ private bool checkHandshakeRequest ( return false; } - if (context.RequestUri == null) { - message = "The Request-URI is invalid."; - - return false; - } - var headers = context.Headers; var key = headers["Sec-WebSocket-Key"]; @@ -972,19 +1142,23 @@ private bool checkHandshakeRequest ( var subps = headers["Sec-WebSocket-Protocol"]; - if (subps != null && subps.Length == 0) { - message = "The Sec-WebSocket-Protocol header is invalid."; + if (subps != null) { + if (subps.Length == 0) { + message = "The Sec-WebSocket-Protocol header is invalid."; - return false; + return false; + } } if (!_ignoreExtensions) { var exts = headers["Sec-WebSocket-Extensions"]; - if (exts != null && exts.Length == 0) { - message = "The Sec-WebSocket-Extensions header is invalid."; + if (exts != null) { + if (exts.Length == 0) { + message = "The Sec-WebSocket-Extensions header is invalid."; - return false; + return false; + } } } @@ -993,7 +1167,8 @@ private bool checkHandshakeRequest ( // As client private bool checkHandshakeResponse ( - HttpResponse response, out string message + HttpResponse response, + out string message ) { message = null; @@ -1034,27 +1209,29 @@ private bool checkHandshakeResponse ( var ver = headers["Sec-WebSocket-Version"]; - if (ver != null && ver != _version) { - message = "The Sec-WebSocket-Version header is invalid."; + if (ver != null) { + if (ver != _version) { + message = "The Sec-WebSocket-Version header is invalid."; - return false; + return false; + } } var subp = headers["Sec-WebSocket-Protocol"]; if (subp == null) { - if (_protocolsRequested) { + if (_hasProtocol) { message = "The Sec-WebSocket-Protocol header is non-existent."; return false; } } else { - var valid = subp.Length > 0 - && _protocolsRequested - && _protocols.Contains (p => p == subp); + var isValid = _hasProtocol + && subp.Length > 0 + && _protocols.Contains (p => p == subp); - if (!valid) { + if (!isValid) { message = "The Sec-WebSocket-Protocol header is invalid."; return false; @@ -1063,10 +1240,12 @@ private bool checkHandshakeResponse ( var exts = headers["Sec-WebSocket-Extensions"]; - if (!validateSecWebSocketExtensionsServerHeader (exts)) { - message = "The Sec-WebSocket-Extensions header is invalid."; - - return false; + if (exts != null) { + if (!validateSecWebSocketExtensionsServerHeader (exts)) { + message = "The Sec-WebSocket-Extensions header is invalid."; + + return false; + } } return true; @@ -1076,54 +1255,110 @@ private static bool checkProtocols (string[] protocols, out string message) { message = null; - Func cond = protocol => protocol.IsNullOrEmpty () - || !protocol.IsToken (); + Func cond = p => p.IsNullOrEmpty () || !p.IsToken (); if (protocols.Contains (cond)) { message = "It contains a value that is not a token."; + return false; } if (protocols.ContainsTwice ()) { message = "It contains a value twice."; + return false; } return true; } - private bool checkReceivedFrame (WebSocketFrame frame, out string message) + // As client + private bool checkProxyConnectResponse ( + HttpResponse response, + out string message + ) { message = null; - var masked = frame.IsMasked; - if (_client && masked) { - message = "A frame from the server is masked."; + if (response.IsProxyAuthenticationRequired) { + message = "The proxy authentication is required."; + return false; } - if (!_client && !masked) { - message = "A frame from a client is not masked."; + if (!response.IsSuccess) { + message = "The proxy has failed a connection to the requested URL."; + return false; } - if (_inContinuation && frame.IsData) { - message = "A data frame has been received while receiving continuation frames."; - return false; + return true; + } + + private bool checkReceivedFrame (WebSocketFrame frame, out string message) + { + message = null; + + if (frame.IsMasked) { + if (_isClient) { + message = "A frame from the server is masked."; + + return false; + } } + else { + if (!_isClient) { + message = "A frame from a client is not masked."; - if (frame.IsCompressed && _compression == CompressionMethod.None) { - message = "A compressed frame has been received without any agreement for it."; - return false; + return false; + } + } + + if (frame.IsCompressed) { + if (_compression == CompressionMethod.None) { + message = "A frame is compressed without any agreement for it."; + + return false; + } + + if (!frame.IsData) { + message = "A non data frame is compressed."; + + return false; + } + } + + if (frame.IsData) { + if (_inContinuation) { + message = "A data frame was received while receiving continuation frames."; + + return false; + } + } + + if (frame.IsControl) { + if (frame.Fin == Fin.More) { + message = "A control frame is fragmented."; + + return false; + } + + if (frame.PayloadLength > 125) { + message = "The payload length of a control frame is greater than 125."; + + 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; } @@ -1133,51 +1368,56 @@ private bool checkReceivedFrame (WebSocketFrame frame, out string message) private void close (ushort code, string reason) { if (_readyState == WebSocketState.Closing) { - _logger.Info ("The closing is already in progress."); + _log.Trace ("The close process is already in progress."); + return; } if (_readyState == WebSocketState.Closed) { - _logger.Info ("The connection has already been closed."); + _log.Trace ("The connection has already been closed."); + return; } - if (code == 1005) { // == no status - close (PayloadData.Empty, true, true, false); + if (code == 1005) { + close (PayloadData.Empty, true, false); + return; } - var send = !code.IsReserved (); - close (new PayloadData (code, reason), send, send, false); + var data = new PayloadData (code, reason); + var send = !code.IsReservedStatusCode (); + + close (data, send, false); } - private void close ( - PayloadData payloadData, bool send, bool receive, bool received - ) + private void close (PayloadData payloadData, bool send, bool received) { lock (_forState) { if (_readyState == WebSocketState.Closing) { - _logger.Info ("The closing is already in progress."); + _log.Trace ("The close process is already in progress."); + return; } if (_readyState == WebSocketState.Closed) { - _logger.Info ("The connection has already been closed."); + _log.Trace ("The connection has already been closed."); + return; } send = send && _readyState == WebSocketState.Open; - receive = send && receive; _readyState = WebSocketState.Closing; } - _logger.Trace ("Begin closing the connection."); + _log.Trace ("Begin closing the connection."); + + var res = closeHandshake (payloadData, send, received); - var res = closeHandshake (payloadData, send, receive, received); releaseResources (); - _logger.Trace ("End closing the connection."); + _log.Trace ("End closing the connection."); _readyState = WebSocketState.Closed; @@ -1187,85 +1427,83 @@ private void close ( OnClose.Emit (this, e); } catch (Exception ex) { - _logger.Error (ex.Message); - _logger.Debug (ex.ToString ()); + _log.Error (ex.Message); + _log.Debug (ex.ToString ()); } } private void closeAsync (ushort code, string reason) { if (_readyState == WebSocketState.Closing) { - _logger.Info ("The closing is already in progress."); + _log.Trace ("The close process is already in progress."); + return; } if (_readyState == WebSocketState.Closed) { - _logger.Info ("The connection has already been closed."); + _log.Trace ("The connection has already been closed."); + return; } - if (code == 1005) { // == no status - closeAsync (PayloadData.Empty, true, true, false); + if (code == 1005) { + closeAsync (PayloadData.Empty, true, false); + return; } - var send = !code.IsReserved (); - closeAsync (new PayloadData (code, reason), send, send, false); - } + var data = new PayloadData (code, reason); + var send = !code.IsReservedStatusCode (); - private void closeAsync ( - PayloadData payloadData, bool send, bool receive, bool received - ) - { - Action closer = close; - closer.BeginInvoke ( - payloadData, send, receive, received, ar => closer.EndInvoke (ar), null - ); + closeAsync (data, send, false); } - private bool closeHandshake (byte[] frameAsBytes, bool receive, bool received) + private void closeAsync (PayloadData payloadData, bool send, bool received) { - var sent = frameAsBytes != null && sendBytes (frameAsBytes); - - var wait = !received && sent && receive && _receivingExited != null; - if (wait) - received = _receivingExited.WaitOne (_waitTime); - - var ret = sent && received; + Action closer = close; - _logger.Debug ( - String.Format ( - "Was clean?: {0}\n sent: {1}\n received: {2}", ret, sent, received - ) + closer.BeginInvoke ( + payloadData, + send, + received, + ar => closer.EndInvoke (ar), + null ); - - return ret; } private bool closeHandshake ( - PayloadData payloadData, bool send, bool receive, bool received + PayloadData payloadData, + bool send, + bool received ) { var sent = false; + if (send) { - var frame = WebSocketFrame.CreateCloseFrame (payloadData, _client); - sent = sendBytes (frame.ToArray ()); + var frame = WebSocketFrame.CreateCloseFrame (payloadData, _isClient); + var bytes = frame.ToArray (); + + sent = sendBytes (bytes); - if (_client) + if (_isClient) frame.Unmask (); } - var wait = !received && sent && receive && _receivingExited != null; + var wait = !received && sent && _receivingExited != null; + if (wait) received = _receivingExited.WaitOne (_waitTime); var ret = sent && received; - _logger.Debug ( - String.Format ( - "Was clean?: {0}\n sent: {1}\n received: {2}", ret, sent, received - ) - ); + var msg = String.Format ( + "The closing was clean? {0} (sent: {1} received: {2})", + ret, + sent, + received + ); + + _log.Debug (msg); return ret; } @@ -1273,59 +1511,59 @@ private bool closeHandshake ( // As client private bool connect () { - if (_readyState == WebSocketState.Open) { - var msg = "The connection has already been established."; - _logger.Warn (msg); + if (_readyState == WebSocketState.Connecting) { + _log.Trace ("The connect process is in progress."); return false; } lock (_forState) { if (_readyState == WebSocketState.Open) { - var msg = "The connection has already been established."; - _logger.Warn (msg); + _log.Trace ("The connection has already been established."); return false; } if (_readyState == WebSocketState.Closing) { - var msg = "The close process has set in."; - _logger.Error (msg); + _log.Error ("The close process is in progress."); - msg = "An interruption has occurred while attempting to connect."; - error (msg, null); + error ("An error has occurred before connecting.", null); return false; } - if (_retryCountForConnect > _maxRetryCountForConnect) { - var msg = "An opportunity for reconnecting has been lost."; - _logger.Error (msg); + if (_retryCountForConnect >= _maxRetryCountForConnect) { + _log.Error ("An opportunity for reconnecting has been lost."); - msg = "An interruption has occurred while attempting to connect."; - error (msg, null); + error ("An error has occurred before connecting.", null); return false; } + if (_readyState == WebSocketState.Closed) + initr (); + + _retryCountForConnect++; + _readyState = WebSocketState.Connecting; + var done = false; + try { - doHandshake (); + done = doHandshake (); } catch (Exception ex) { - _retryCountForConnect++; - - _logger.Fatal (ex.Message); - _logger.Debug (ex.ToString ()); + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); - var msg = "An exception has occurred while attempting to connect."; - fatal (msg, ex); + abort ("An exception has occurred while connecting.", ex); + } + if (!done) return false; - } - _retryCountForConnect = 1; + _retryCountForConnect = -1; + _readyState = WebSocketState.Open; return true; @@ -1338,17 +1576,18 @@ private AuthenticationResponse createAuthenticationResponse () if (_credentials == null) return null; - if (_authChallenge != null) { - var ret = new AuthenticationResponse ( - _authChallenge, _credentials, _nonceCount - ); + if (_authChallenge == null) + return _preAuth ? new AuthenticationResponse (_credentials) : null; - _nonceCount = ret.NonceCount; + var ret = new AuthenticationResponse ( + _authChallenge, + _credentials, + _nonceCount + ); - return ret; - } + _nonceCount = ret.NonceCount; - return _preAuth ? new AuthenticationResponse (_credentials) : null; + return ret; } // As client @@ -1358,7 +1597,8 @@ private string createExtensions () if (_compression != CompressionMethod.None) { var str = _compression.ToExtensionString ( - "server_no_context_takeover", "client_no_context_takeover" + "server_no_context_takeover", + "client_no_context_takeover" ); buff.AppendFormat ("{0}, ", str); @@ -1375,9 +1615,9 @@ private string createExtensions () } // As server - private HttpResponse createHandshakeFailureResponse (HttpStatusCode code) + private HttpResponse createHandshakeFailureResponse () { - var ret = HttpResponse.CreateCloseResponse (code); + var ret = HttpResponse.CreateCloseResponse (HttpStatusCode.BadRequest); ret.Headers["Sec-WebSocket-Version"] = _version; @@ -1397,26 +1637,29 @@ private HttpRequest createHandshakeRequest () if (!_origin.IsNullOrEmpty ()) headers["Origin"] = _origin; - if (_protocols != null) { + if (_hasProtocol) headers["Sec-WebSocket-Protocol"] = _protocols.ToString (", "); - _protocolsRequested = true; - } - var exts = createExtensions (); - if (exts != null) { - headers["Sec-WebSocket-Extensions"] = exts; + _hasExtension = exts != null; - _extensionsRequested = true; - } + if (_hasExtension) + headers["Sec-WebSocket-Extensions"] = exts; var ares = createAuthenticationResponse (); if (ares != null) headers["Authorization"] = ares.ToString (); - if (_cookies.Count > 0) + var hasUserHeader = _userHeaders != null && _userHeaders.Count > 0; + + if (hasUserHeader) + headers.Add (_userHeaders); + + var hasCookie = _cookies != null && _cookies.Count > 0; + + if (hasCookie) ret.SetCookies (_cookies); return ret; @@ -1437,15 +1680,34 @@ private HttpResponse createHandshakeResponse () if (_extensions != null) headers["Sec-WebSocket-Extensions"] = _extensions; - if (_cookies.Count > 0) + var hasUserHeader = _userHeaders != null && _userHeaders.Count > 0; + + if (hasUserHeader) + headers.Add (_userHeaders); + + var hasCookie = _cookies != null && _cookies.Count > 0; + + if (hasCookie) ret.SetCookies (_cookies); return ret; } + // As client + private TcpClient createTcpClient (string hostname, int port) + { + var ret = new TcpClient (hostname, port); + + if (_noDelay) + ret.NoDelay = true; + + return ret; + } + // As server private bool customCheckHandshakeRequest ( - WebSocketContext context, out string message + WebSocketContext context, + out string message ) { message = null; @@ -1458,6 +1720,15 @@ private bool customCheckHandshakeRequest ( return message == null; } + // As server + private void customRespondToHandshakeRequest (WebSocketContext context) + { + if (_handshakeRequestResponder == null) + return; + + _handshakeRequestResponder (context); + } + private MessageEventArgs dequeueFromMessageEventQueue () { lock (_forMessageEventQueue) { @@ -1468,27 +1739,43 @@ private MessageEventArgs dequeueFromMessageEventQueue () } // As client - private void doHandshake () + private bool doHandshake () { setClientStream (); var res = sendHandshakeRequest (); + _log.Debug (res.ToString ()); + + _handshakeResponseHeaders = res.Headers; + _handshakeResponseCookies = res.Cookies; + string msg; - if (!checkHandshakeResponse (res, out msg)) - throw new WebSocketException (CloseStatusCode.ProtocolError, msg); + if (!checkHandshakeResponse (res, out msg)) { + _log.Error (msg); + + abort (1002, "A handshake error has occurred."); + + return false; + } - if (_protocolsRequested) - _protocol = res.Headers["Sec-WebSocket-Protocol"]; + if (_hasProtocol) + _protocol = _handshakeResponseHeaders["Sec-WebSocket-Protocol"]; - if (_extensionsRequested) { - var val = res.Headers["Sec-WebSocket-Extensions"]; + if (_hasExtension) { + var exts = _handshakeResponseHeaders["Sec-WebSocket-Extensions"]; - processSecWebSocketExtensionsServerHeader (val); + if (exts != null) + _extensions = exts; + else + _compression = CompressionMethod.None; } - processCookies (res.Cookies); + if (_handshakeResponseCookies.Count > 0) + Cookies.SetOrRemove (_handshakeResponseCookies); + + return true; } private void enqueueToMessageEventQueue (MessageEventArgs e) @@ -1499,35 +1786,17 @@ private void enqueueToMessageEventQueue (MessageEventArgs e) private void error (string message, Exception exception) { + var e = new ErrorEventArgs (message, exception); + try { - OnError.Emit (this, new ErrorEventArgs (message, exception)); + OnError.Emit (this, e); } catch (Exception ex) { - _logger.Error (ex.Message); - _logger.Debug (ex.ToString ()); + _log.Error (ex.Message); + _log.Debug (ex.ToString ()); } } - private void fatal (string message, Exception exception) - { - var code = exception is WebSocketException - ? ((WebSocketException) exception).Code - : CloseStatusCode.Abnormal; - - fatal (message, (ushort) code); - } - - private void fatal (string message, ushort code) - { - var payload = new PayloadData (code, message); - close (payload, !code.IsReserved (), false, false); - } - - private void fatal (string message, CloseStatusCode code) - { - fatal (message, (ushort) code); - } - private ClientSslConfiguration getSslConfiguration () { if (_sslConfig == null) @@ -1539,24 +1808,38 @@ private ClientSslConfiguration getSslConfiguration () private void init () { _compression = CompressionMethod.None; - _cookies = new CookieCollection (); _forPing = new object (); _forSend = new object (); _forState = new object (); _messageEventQueue = new Queue (); _forMessageEventQueue = ((ICollection) _messageEventQueue).SyncRoot; - _readyState = WebSocketState.Connecting; + _readyState = WebSocketState.New; + } + + // As client + private void initr () + { + _handshakeResponseCookies = null; + _handshakeResponseHeaders = null; } private void message () { MessageEventArgs e = null; + lock (_forMessageEventQueue) { - if (_inMessage || _messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) + if (_inMessage) + return; + + if (_messageEventQueue.Count == 0) + return; + + if (_readyState != WebSocketState.Open) return; - _inMessage = true; e = _messageEventQueue.Dequeue (); + + _inMessage = true; } _message (e); @@ -1569,13 +1852,22 @@ private void messagec (MessageEventArgs e) OnMessage.Emit (this, e); } catch (Exception ex) { - _logger.Error (ex.ToString ()); - error ("An error has occurred during an OnMessage event.", ex); + _log.Error (ex.Message); + _log.Debug (ex.ToString ()); + + error ("An exception has occurred during an OnMessage event.", ex); } lock (_forMessageEventQueue) { - if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) { + if (_messageEventQueue.Count == 0) { + _inMessage = false; + + break; + } + + if (_readyState != WebSocketState.Open) { _inMessage = false; + break; } @@ -1591,13 +1883,22 @@ private void messages (MessageEventArgs e) OnMessage.Emit (this, e); } catch (Exception ex) { - _logger.Error (ex.ToString ()); - error ("An error has occurred during an OnMessage event.", ex); + _log.Error (ex.Message); + _log.Debug (ex.ToString ()); + + error ("An exception has occurred during an OnMessage event.", ex); } lock (_forMessageEventQueue) { - if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) { + if (_messageEventQueue.Count == 0) { + _inMessage = false; + + return; + } + + if (_readyState != WebSocketState.Open) { _inMessage = false; + return; } @@ -1610,19 +1911,31 @@ private void messages (MessageEventArgs e) private void open () { _inMessage = true; + startReceiving (); + try { OnOpen.Emit (this, EventArgs.Empty); } catch (Exception ex) { - _logger.Error (ex.ToString ()); - error ("An error has occurred during the OnOpen event.", ex); + _log.Error (ex.Message); + _log.Debug (ex.ToString ()); + + error ("An exception has occurred during the OnOpen event.", ex); } MessageEventArgs e = null; + lock (_forMessageEventQueue) { - if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) { + if (_messageEventQueue.Count == 0) { + _inMessage = false; + + return; + } + + if (_readyState != WebSocketState.Open) { _inMessage = false; + return; } @@ -1637,17 +1950,21 @@ private bool ping (byte[] data) if (_readyState != WebSocketState.Open) return false; - var pongReceived = _pongReceived; - if (pongReceived == null) + var received = _pongReceived; + + if (received == null) return false; lock (_forPing) { try { - pongReceived.Reset (); - if (!send (Fin.Final, Opcode.Ping, data, false)) + received.Reset (); + + var sent = send (Fin.Final, Opcode.Ping, data, false); + + if (!sent) return false; - return pongReceived.WaitOne (_waitTime); + return received.WaitOne (_waitTime); } catch (ObjectDisposedException) { return false; @@ -1657,28 +1974,24 @@ private bool ping (byte[] data) private bool processCloseFrame (WebSocketFrame frame) { - var payload = frame.PayloadData; - close (payload, !payload.HasReservedCode, false, true); - - return false; - } + var data = frame.PayloadData; + var send = !data.HasReservedCode; - // As client - private void processCookies (CookieCollection cookies) - { - if (cookies.Count == 0) - return; + close (data, send, true); - _cookies.SetOrRemove (cookies); + return false; } private bool processDataFrame (WebSocketFrame frame) { - enqueueToMessageEventQueue ( - frame.IsCompressed - ? new MessageEventArgs ( - frame.Opcode, frame.PayloadData.ApplicationData.Decompress (_compression)) - : new MessageEventArgs (frame)); + var e = frame.IsCompressed + ? new MessageEventArgs ( + frame.Opcode, + frame.PayloadData.ApplicationData.Decompress (_compression) + ) + : new MessageEventArgs (frame); + + enqueueToMessageEventQueue (e); return true; } @@ -1686,7 +1999,6 @@ private bool processDataFrame (WebSocketFrame frame) private bool processFragmentFrame (WebSocketFrame frame) { if (!_inContinuation) { - // Must process first fragment. if (frame.IsContinuation) return true; @@ -1697,13 +2009,16 @@ private bool processFragmentFrame (WebSocketFrame frame) } _fragmentsBuffer.WriteBytes (frame.PayloadData.ApplicationData, 1024); + if (frame.IsFinal) { using (_fragmentsBuffer) { var data = _fragmentsCompressed ? _fragmentsBuffer.DecompressToArray (_compression) : _fragmentsBuffer.ToArray (); - enqueueToMessageEventQueue (new MessageEventArgs (_fragmentsOpcode, data)); + var e = new MessageEventArgs (_fragmentsOpcode, data); + + enqueueToMessageEventQueue (e); } _fragmentsBuffer = null; @@ -1715,27 +2030,33 @@ private bool processFragmentFrame (WebSocketFrame frame) private bool processPingFrame (WebSocketFrame frame) { - _logger.Trace ("A ping was received."); + _log.Trace ("A ping was received."); - var pong = WebSocketFrame.CreatePongFrame (frame.PayloadData, _client); + var pong = WebSocketFrame.CreatePongFrame (frame.PayloadData, _isClient); lock (_forState) { if (_readyState != WebSocketState.Open) { - _logger.Error ("The connection is closing."); + _log.Trace ("A pong to this ping cannot be sent."); + return true; } - if (!sendBytes (pong.ToArray ())) + var bytes = pong.ToArray (); + var sent = sendBytes (bytes); + + if (!sent) return false; } - _logger.Trace ("A pong to this ping has been sent."); + _log.Trace ("A pong to this ping has been sent."); if (_emitOnPing) { - if (_client) + if (_isClient) pong.Unmask (); - enqueueToMessageEventQueue (new MessageEventArgs (frame)); + var e = new MessageEventArgs (frame); + + enqueueToMessageEventQueue (e); } return true; @@ -1743,25 +2064,19 @@ private bool processPingFrame (WebSocketFrame frame) private bool processPongFrame (WebSocketFrame frame) { - _logger.Trace ("A pong was received."); + _log.Trace ("A pong was received."); try { _pongReceived.Set (); } - catch (NullReferenceException ex) { - _logger.Error (ex.Message); - _logger.Debug (ex.ToString ()); - + catch (NullReferenceException) { return false; } - catch (ObjectDisposedException ex) { - _logger.Error (ex.Message); - _logger.Debug (ex.ToString ()); - + catch (ObjectDisposedException) { return false; } - _logger.Trace ("It has been signaled."); + _log.Trace ("It has been signaled."); return true; } @@ -1769,10 +2084,18 @@ private bool processPongFrame (WebSocketFrame frame) private bool processReceivedFrame (WebSocketFrame frame) { string msg; - if (!checkReceivedFrame (frame, out msg)) - throw new WebSocketException (CloseStatusCode.ProtocolError, msg); + + if (!checkReceivedFrame (frame, out msg)) { + _log.Error (msg); + _log.Debug (frame.ToString (false)); + + abort (1002, "An error has occurred while receiving."); + + return false; + } frame.Unmask (); + return frame.IsFragment ? processFragmentFrame (frame) : frame.IsData @@ -1794,7 +2117,7 @@ private void processSecWebSocketExtensionsClientHeader (string value) var buff = new StringBuilder (80); - var comp = false; + var compRequested = false; foreach (var elm in value.SplitHeaderValue (',')) { var ext = elm.Trim (); @@ -1802,7 +2125,7 @@ private void processSecWebSocketExtensionsClientHeader (string value) if (ext.Length == 0) continue; - if (!comp) { + if (!compRequested) { if (ext.IsCompressionExtension (CompressionMethod.Deflate)) { _compression = CompressionMethod.Deflate; @@ -1813,7 +2136,7 @@ private void processSecWebSocketExtensionsClientHeader (string value) buff.AppendFormat ("{0}, ", str); - comp = true; + compRequested = true; } } } @@ -1828,58 +2151,22 @@ private void processSecWebSocketExtensionsClientHeader (string value) _extensions = buff.ToString (); } - // As client - private void processSecWebSocketExtensionsServerHeader (string value) - { - if (value == null) { - _compression = CompressionMethod.None; - - return; - } - - _extensions = value; - } - - // As server - private void processSecWebSocketProtocolClientHeader ( - IEnumerable values - ) - { - if (values.Contains (val => val == _protocol)) - return; - - _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); + _log.Fatal ("An unsupported frame was received."); + _log.Debug (frame.ToString (false)); + + abort (1003, "There is no way to handle it."); return false; } // As server - private void refuseHandshake (CloseStatusCode code, string reason) + private void refuseHandshake (ushort code, string reason) { - _readyState = WebSocketState.Closing; + createHandshakeFailureResponse ().WriteTo (_stream); - var res = createHandshakeFailureResponse (HttpStatusCode.BadRequest); - sendHttpResponse (res); - - releaseServerResources (); - - _readyState = WebSocketState.Closed; - - var e = new CloseEventArgs ((ushort) code, reason, false); - - try { - OnClose.Emit (this, e); - } - catch (Exception ex) { - _logger.Error (ex.Message); - _logger.Debug (ex.ToString ()); - } + abort (code, reason); } // As client @@ -1887,11 +2174,13 @@ private void releaseClientResources () { if (_stream != null) { _stream.Dispose (); + _stream = null; } if (_tcpClient != null) { _tcpClient.Close (); + _tcpClient = null; } } @@ -1900,24 +2189,27 @@ private void releaseCommonResources () { if (_fragmentsBuffer != null) { _fragmentsBuffer.Dispose (); + _fragmentsBuffer = null; _inContinuation = false; } if (_pongReceived != null) { _pongReceived.Close (); + _pongReceived = null; } if (_receivingExited != null) { _receivingExited.Close (); + _receivingExited = null; } } private void releaseResources () { - if (_client) + if (_isClient) releaseClientResources (); else releaseServerResources (); @@ -1928,81 +2220,108 @@ private void releaseResources () // As server private void releaseServerResources () { - if (_closeContext == null) - return; + if (_closeContext != null) { + _closeContext (); + + _closeContext = null; + } - _closeContext (); - _closeContext = null; _stream = null; _context = null; } - private bool send (Opcode opcode, Stream stream) + private bool send (byte[] rawFrame) + { + lock (_forState) { + if (_readyState != WebSocketState.Open) { + _log.Error ("The current state of the interface is not Open."); + + return false; + } + + return sendBytes (rawFrame); + } + } + + private bool send (Opcode opcode, Stream sourceStream) { lock (_forSend) { - var src = stream; + var dataStream = sourceStream; var compressed = false; var sent = false; + try { if (_compression != CompressionMethod.None) { - stream = stream.Compress (_compression); + dataStream = sourceStream.Compress (_compression); compressed = true; } - sent = send (opcode, stream, compressed); + sent = send (opcode, dataStream, compressed); + if (!sent) - error ("A send has been interrupted.", null); + error ("A send has failed.", null); } catch (Exception ex) { - _logger.Error (ex.ToString ()); - error ("An error has occurred during a send.", ex); + _log.Error (ex.Message); + _log.Debug (ex.ToString ()); + + error ("An exception has occurred during a send.", ex); } finally { if (compressed) - stream.Dispose (); + dataStream.Dispose (); - src.Dispose (); + sourceStream.Dispose (); } return sent; } } - private bool send (Opcode opcode, Stream stream, bool compressed) + private bool send (Opcode opcode, Stream dataStream, bool compressed) { - var len = stream.Length; + var len = dataStream.Length; + if (len == 0) - return send (Fin.Final, opcode, EmptyBytes, false); + return send (Fin.Final, opcode, _emptyBytes, false); 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 + + return dataStream.Read (buff, 0, rem) == rem && send (Fin.Final, opcode, buff, compressed); } if (quo == 1 && rem == 0) { buff = new byte[FragmentLength]; - return stream.Read (buff, 0, FragmentLength) == FragmentLength + + return dataStream.Read (buff, 0, FragmentLength) == FragmentLength && send (Fin.Final, opcode, buff, compressed); } /* Send fragments */ // Begin + buff = new byte[FragmentLength]; - var sent = stream.Read (buff, 0, FragmentLength) == FragmentLength + + var sent = dataStream.Read (buff, 0, FragmentLength) == FragmentLength && send (Fin.More, opcode, buff, compressed); if (!sent) return false; + // Continue + var n = rem == 0 ? quo - 2 : quo - 1; + for (long i = 0; i < n; i++) { - sent = stream.Read (buff, 0, FragmentLength) == FragmentLength + sent = dataStream.Read (buff, 0, FragmentLength) == FragmentLength && send (Fin.More, Opcode.Cont, buff, false); if (!sent) @@ -2010,44 +2329,48 @@ private bool send (Opcode opcode, Stream stream, bool compressed) } // End + if (rem == 0) rem = FragmentLength; else buff = new byte[rem]; - return stream.Read (buff, 0, rem) == rem + return dataStream.Read (buff, 0, rem) == rem && send (Fin.Final, Opcode.Cont, buff, false); } private bool send (Fin fin, Opcode opcode, byte[] data, bool compressed) { - lock (_forState) { - if (_readyState != WebSocketState.Open) { - _logger.Error ("The connection is closing."); - return false; - } + var frame = new WebSocketFrame (fin, opcode, data, compressed, _isClient); + var rawFrame = frame.ToArray (); - var frame = new WebSocketFrame (fin, opcode, data, compressed, _client); - return sendBytes (frame.ToArray ()); - } + return send (rawFrame); } - private void sendAsync (Opcode opcode, Stream stream, Action completed) + private void sendAsync ( + Opcode opcode, + Stream sourceStream, + Action completed + ) { Func sender = send; + sender.BeginInvoke ( opcode, - stream, + sourceStream, ar => { try { var sent = sender.EndInvoke (ar); + if (completed != null) completed (sent); } catch (Exception ex) { - _logger.Error (ex.ToString ()); + _log.Error (ex.Message); + _log.Debug (ex.ToString ()); + error ( - "An error has occurred during the callback for an async send.", + "An exception has occurred during the callback for an async send.", ex ); } @@ -2062,8 +2385,8 @@ private bool sendBytes (byte[] bytes) _stream.Write (bytes, 0, bytes.Length); } catch (Exception ex) { - _logger.Error (ex.Message); - _logger.Debug (ex.ToString ()); + _log.Error (ex.Message); + _log.Debug (ex.ToString ()); return false; } @@ -2075,19 +2398,17 @@ private bool sendBytes (byte[] bytes) private HttpResponse sendHandshakeRequest () { var req = createHandshakeRequest (); - var res = sendHttpRequest (req, 90000); - if (res.IsUnauthorized) { - if (_credentials == null) { - _logger.Error ("No credential is specified."); + _log.Debug (req.ToString ()); - return res; - } + var timeout = 90000; + var res = req.GetResponse (_stream, timeout); + if (res.IsUnauthorized) { var val = res.Headers["WWW-Authenticate"]; if (val.IsNullOrEmpty ()) { - _logger.Error ("No authentication challenge is specified."); + _log.Debug ("No authentication challenge is specified."); return res; } @@ -2095,24 +2416,20 @@ private HttpResponse sendHandshakeRequest () var achal = AuthenticationChallenge.Parse (val); if (achal == null) { - _logger.Error ("An invalid authentication challenge is specified."); + _log.Debug ("An invalid authentication challenge is specified."); return res; } _authChallenge = achal; - var failed = _preAuth - && _authChallenge.Scheme == AuthenticationSchemes.Basic; - - if (failed) { - _logger.Error ("The authentication has failed."); - + if (_credentials == null) return res; - } var ares = new AuthenticationResponse ( - _authChallenge, _credentials, _nonceCount + _authChallenge, + _credentials, + _nonceCount ); _nonceCount = ares.NonceCount; @@ -2124,7 +2441,10 @@ private HttpResponse sendHandshakeRequest () setClientStream (); } - res = sendHttpRequest (req, 15000); + _log.Debug (req.ToString ()); + + timeout = 15000; + res = req.GetResponse (_stream, timeout); } if (res.IsRedirect) { @@ -2134,7 +2454,7 @@ private HttpResponse sendHandshakeRequest () var val = res.Headers["Location"]; if (val.IsNullOrEmpty ()) { - _logger.Error ("No url to redirect is located."); + _log.Debug ("No URL to redirect is located."); return res; } @@ -2143,7 +2463,7 @@ private HttpResponse sendHandshakeRequest () string msg; if (!val.TryCreateWebSocketUri (out uri, out msg)) { - _logger.Error ("An invalid url to redirect is located."); + _log.Debug ("An invalid URL to redirect is located."); return res; } @@ -2151,7 +2471,7 @@ private HttpResponse sendHandshakeRequest () releaseClientResources (); _uri = uri; - _secure = uri.Scheme == "wss"; + _isSecure = uri.Scheme == "wss"; setClientStream (); @@ -2162,63 +2482,31 @@ private HttpResponse sendHandshakeRequest () } // As client - private HttpResponse sendHttpRequest ( - HttpRequest request, int millisecondsTimeout - ) - { - var msg = "An HTTP request to the server:\n" + request.ToString (); - - _logger.Debug (msg); - - var res = request.GetResponse (_stream, millisecondsTimeout); - - msg = "An HTTP response from the server:\n" + res.ToString (); - - _logger.Debug (msg); - - return res; - } - - // As server - private bool sendHttpResponse (HttpResponse response) - { - var fmt = "An HTTP response to {0}:\n{1}"; - var msg = String.Format (fmt, _context.UserEndPoint, response); - - _logger.Debug (msg); - - var bytes = response.ToByteArray (); - - return sendBytes (bytes); - } - - // As client - private void sendProxyConnectRequest () + private HttpResponse sendProxyConnectRequest () { var req = HttpRequest.CreateConnectRequest (_uri); - var res = sendHttpRequest (req, 90000); - if (res.IsProxyAuthenticationRequired) { - if (_proxyCredentials == null) { - var msg = "No credential for the proxy is specified."; + var timeout = 90000; + var res = req.GetResponse (_stream, timeout); - throw new WebSocketException (msg); - } + if (res.IsProxyAuthenticationRequired) { + if (_proxyCredentials == null) + return res; var val = res.Headers["Proxy-Authenticate"]; if (val.IsNullOrEmpty ()) { - var msg = "No proxy authentication challenge is specified."; + _log.Debug ("No proxy authentication challenge is specified."); - throw new WebSocketException (msg); + return res; } var achal = AuthenticationChallenge.Parse (val); if (achal == null) { - var msg = "An invalid proxy authentication challenge is specified."; + _log.Debug ("An invalid proxy authentication challenge is specified."); - throw new WebSocketException (msg); + return res; } var ares = new AuthenticationResponse (achal, _proxyCredentials, 0); @@ -2228,41 +2516,37 @@ private void sendProxyConnectRequest () if (res.CloseConnection) { releaseClientResources (); - _tcpClient = new TcpClient (_proxyUri.DnsSafeHost, _proxyUri.Port); + _tcpClient = createTcpClient (_proxyUri.DnsSafeHost, _proxyUri.Port); _stream = _tcpClient.GetStream (); } - res = sendHttpRequest (req, 15000); - - if (res.IsProxyAuthenticationRequired) { - var msg = "The proxy authentication has failed."; - - throw new WebSocketException (msg); - } + timeout = 15000; + res = req.GetResponse (_stream, timeout); } - if (!res.IsSuccess) { - var msg = "The proxy has failed a connection to the requested URL."; - - throw new WebSocketException (msg); - } + return res; } // As client private void setClientStream () { if (_proxyUri != null) { - _tcpClient = new TcpClient (_proxyUri.DnsSafeHost, _proxyUri.Port); + _tcpClient = createTcpClient (_proxyUri.DnsSafeHost, _proxyUri.Port); _stream = _tcpClient.GetStream (); - sendProxyConnectRequest (); + var res = sendProxyConnectRequest (); + + string msg; + + if (!checkProxyConnectResponse (res, out msg)) + throw new WebSocketException (msg); } else { - _tcpClient = new TcpClient (_uri.DnsSafeHost, _uri.Port); + _tcpClient = createTcpClient (_uri.DnsSafeHost, _uri.Port); _stream = _tcpClient.GetStream (); } - if (_secure) { + if (_isSecure) { var conf = getSslConfiguration (); var host = conf.TargetHost; @@ -2270,7 +2554,8 @@ private void setClientStream () var msg = "An invalid host name is specified."; throw new WebSocketException ( - CloseStatusCode.TlsHandshakeFailure, msg + CloseStatusCode.TlsHandshakeFailure, + msg ); } @@ -2293,7 +2578,8 @@ private void setClientStream () } catch (Exception ex) { throw new WebSocketException ( - CloseStatusCode.TlsHandshakeFailure, ex + CloseStatusCode.TlsHandshakeFailure, + ex ); } } @@ -2309,32 +2595,36 @@ private void startReceiving () Action receive = null; receive = - () => - WebSocketFrame.ReadFrameAsync ( - _stream, - false, - frame => { - if (!processReceivedFrame (frame) || _readyState == WebSocketState.Closed) { - var exited = _receivingExited; - if (exited != null) - exited.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); - } - ); + () => WebSocketFrame.ReadFrameAsync ( + _stream, + false, + frame => { + var doNext = processReceivedFrame (frame) + && _readyState != WebSocketState.Closed; + + if (!doNext) { + var exited = _receivingExited; + + if (exited != null) + exited.Set (); + + return; + } + + receive (); + + if (_inMessage) + return; + + message (); + }, + ex => { + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); + + abort ("An exception has occurred while receiving.", ex); + } + ); receive (); } @@ -2342,50 +2632,45 @@ private void startReceiving () // As client private bool validateSecWebSocketExtensionsServerHeader (string value) { - if (value == null) - return true; - - if (value.Length == 0) + if (!_hasExtension) return false; - if (!_extensionsRequested) + if (value.Length == 0) return false; - var comp = _compression != CompressionMethod.None; + var compRequested = _compression != CompressionMethod.None; foreach (var elm in value.SplitHeaderValue (',')) { var ext = elm.Trim (); - if (comp && ext.IsCompressionExtension (_compression)) { + if (compRequested && ext.IsCompressionExtension (_compression)) { var param1 = "server_no_context_takeover"; var param2 = "client_no_context_takeover"; if (!ext.Contains (param1)) { - var fmt = "The server did not send back '{0}'."; - var msg = String.Format (fmt, param1); - - _logger.Error (msg); + // The server did not send back "server_no_context_takeover". return false; } var name = _compression.ToExtensionString (); - var invalid = ext.SplitHeaderValue (';').Contains ( - t => { - t = t.Trim (); - var valid = t == name - || t == param1 - || t == param2; + var isInvalid = ext.SplitHeaderValue (';').Contains ( + t => { + t = t.Trim (); + + var isValid = t == name + || t == param1 + || t == param2; - return !valid; - } - ); + return !isValid; + } + ); - if (invalid) + if (isInvalid) return false; - comp = false; + compRequested = false; } else { return false; @@ -2400,58 +2685,75 @@ private bool validateSecWebSocketExtensionsServerHeader (string value) #region Internal Methods // As server - internal void Close (HttpResponse response) + internal void Accept () { - _readyState = WebSocketState.Closing; + var accepted = accept (); - sendHttpResponse (response); - releaseServerResources (); + if (!accepted) + return; - _readyState = WebSocketState.Closed; + open (); } // As server - internal void Close (HttpStatusCode code) + internal void AcceptAsync () { - Close (createHandshakeFailureResponse (code)); + Func acceptor = accept; + + acceptor.BeginInvoke ( + ar => { + var accepted = acceptor.EndInvoke (ar); + + if (!accepted) + return; + + open (); + }, + null + ); } // As server - internal void Close (PayloadData payloadData, byte[] frameAsBytes) + internal void Close (PayloadData payloadData, byte[] rawFrame) { lock (_forState) { if (_readyState == WebSocketState.Closing) { - _logger.Info ("The closing is already in progress."); + _log.Trace ("The close process is already in progress."); + return; } if (_readyState == WebSocketState.Closed) { - _logger.Info ("The connection has already been closed."); + _log.Trace ("The connection has already been closed."); + return; } _readyState = WebSocketState.Closing; } - _logger.Trace ("Begin closing the connection."); + _log.Trace ("Begin closing the connection."); - var sent = frameAsBytes != null && sendBytes (frameAsBytes); + var sent = rawFrame != null && sendBytes (rawFrame); var received = sent && _receivingExited != null ? _receivingExited.WaitOne (_waitTime) : false; var res = sent && received; - _logger.Debug ( - String.Format ( - "Was clean?: {0}\n sent: {1}\n received: {2}", res, sent, received - ) - ); + var msg = String.Format ( + "The closing was clean? {0} (sent: {1} received: {2})", + res, + sent, + received + ); + + _log.Debug (msg); releaseServerResources (); releaseCommonResources (); - _logger.Trace ("End closing the connection."); + _log.Trace ("End closing the connection."); _readyState = WebSocketState.Closed; @@ -2461,75 +2763,53 @@ internal void Close (PayloadData payloadData, byte[] frameAsBytes) OnClose.Emit (this, e); } catch (Exception ex) { - _logger.Error (ex.Message); - _logger.Debug (ex.ToString ()); + _log.Error (ex.Message); + _log.Debug (ex.ToString ()); } } // As client internal static string CreateBase64Key () { - var src = new byte[16]; - RandomNumber.GetBytes (src); + var key = new byte[16]; - return Convert.ToBase64String (src); + RandomNumber.GetBytes (key); + + return Convert.ToBase64String (key); } 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 ().GetUTF8EncodedBytes ()); - - return Convert.ToBase64String (src); - } - - // As server - internal void InternalAccept () - { - try { - if (!acceptHandshake ()) - return; - } - catch (Exception ex) { - _logger.Fatal (ex.Message); - _logger.Debug (ex.ToString ()); - - var msg = "An exception has occurred while attempting to accept."; - fatal (msg, ex); - return; - } - - _readyState = WebSocketState.Open; + var src = base64Key + _guid; + var bytes = src.GetUTF8EncodedBytes (); + var key = sha1.ComputeHash (bytes); - open (); + return Convert.ToBase64String (key); } // As server - internal bool Ping (byte[] frameAsBytes, TimeSpan timeout) + internal bool Ping (byte[] rawFrame) { if (_readyState != WebSocketState.Open) return false; - var pongReceived = _pongReceived; - if (pongReceived == null) + var received = _pongReceived; + + if (received == null) return false; lock (_forPing) { try { - pongReceived.Reset (); + received.Reset (); - lock (_forState) { - if (_readyState != WebSocketState.Open) - return false; + var sent = send (rawFrame); - if (!sendBytes (frameAsBytes)) - return false; - } + if (!sent) + return false; - return pongReceived.WaitOne (timeout); + return received.WaitOne (_waitTime); } catch (ObjectDisposedException) { return false; @@ -2539,44 +2819,44 @@ internal bool Ping (byte[] frameAsBytes, TimeSpan timeout) // As server internal void Send ( - Opcode opcode, byte[] data, Dictionary cache + Opcode opcode, + byte[] data, + Dictionary cache ) { lock (_forSend) { - lock (_forState) { - if (_readyState != WebSocketState.Open) { - _logger.Error ("The connection is closing."); - return; - } + byte[] found; - 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); - } + if (!cache.TryGetValue (_compression, out found)) { + found = new WebSocketFrame ( + Fin.Final, + opcode, + data.Compress (_compression), + _compression != CompressionMethod.None, + false + ) + .ToArray (); - sendBytes (found); + cache.Add (_compression, found); } + + send (found); } } // As server internal void Send ( - Opcode opcode, Stream stream, Dictionary cache + Opcode opcode, + Stream sourceStream, + Dictionary cache ) { lock (_forSend) { Stream found; + if (!cache.TryGetValue (_compression, out found)) { - found = stream.Compress (_compression); + found = sourceStream.Compress (_compression); + cache.Add (_compression, found); } else { @@ -2591,112 +2871,11 @@ internal void Send ( #region Public Methods - /// - /// Accepts the handshake request. - /// - /// - /// This method does nothing if the handshake request has already been - /// accepted. - /// - /// - /// - /// This instance is a client. - /// - /// - /// -or- - /// - /// - /// The close process is in progress. - /// - /// - /// -or- - /// - /// - /// The connection has already been closed. - /// - /// - public void Accept () - { - if (_client) { - var msg = "This instance is a client."; - throw new InvalidOperationException (msg); - } - - if (_readyState == WebSocketState.Closing) { - var msg = "The close process is in progress."; - throw new InvalidOperationException (msg); - } - - if (_readyState == WebSocketState.Closed) { - var msg = "The connection has already been closed."; - throw new InvalidOperationException (msg); - } - - if (accept ()) - open (); - } - - /// - /// Accepts the handshake request asynchronously. - /// - /// - /// - /// This method does not wait for the accept process to be complete. - /// - /// - /// This method does nothing if the handshake request has already been - /// accepted. - /// - /// - /// - /// - /// This instance is a client. - /// - /// - /// -or- - /// - /// - /// The close process is in progress. - /// - /// - /// -or- - /// - /// - /// The connection has already been closed. - /// - /// - public void AcceptAsync () - { - if (_client) { - var msg = "This instance is a client."; - throw new InvalidOperationException (msg); - } - - if (_readyState == WebSocketState.Closing) { - var msg = "The close process is in progress."; - throw new InvalidOperationException (msg); - } - - if (_readyState == WebSocketState.Closed) { - var msg = "The connection has already been closed."; - throw new InvalidOperationException (msg); - } - - Func acceptor = accept; - acceptor.BeginInvoke ( - ar => { - if (acceptor.EndInvoke (ar)) - open (); - }, - null - ); - } - /// /// Closes the connection. /// /// - /// This method does nothing if the current state of the connection is + /// This method does nothing if the current state of the interface is /// Closing or Closed. /// public void Close () @@ -2705,15 +2884,15 @@ public void Close () } /// - /// Closes the connection with the specified code. + /// Closes the connection with the specified status code. /// /// - /// This method does nothing if the current state of the connection is + /// This method does nothing if the current state of the interface is /// Closing or Closed. /// /// /// - /// A that represents the status code indicating + /// A that specifies the status code indicating /// the reason for the close. /// /// @@ -2722,47 +2901,32 @@ public void Close () /// Section 7.4 of RFC 6455. /// /// - /// - /// is less than 1000 or greater than 4999. - /// /// /// /// is 1011 (server error). - /// It cannot be used by clients. + /// It cannot be used by a client. /// /// /// -or- /// /// /// is 1010 (mandatory extension). - /// It cannot be used by servers. + /// It cannot be used by a server. /// /// + /// + /// is less than 1000 or greater than 4999. + /// public void Close (ushort code) { - if (!code.IsCloseStatusCode ()) { - var msg = "Less than 1000 or greater than 4999."; - throw new ArgumentOutOfRangeException ("code", msg); - } - - if (_client && code == 1011) { - var msg = "1011 cannot be used."; - throw new ArgumentException (msg, "code"); - } - - if (!_client && code == 1010) { - var msg = "1010 cannot be used."; - throw new ArgumentException (msg, "code"); - } - - close (code, String.Empty); + Close (code, String.Empty); } /// - /// Closes the connection with the specified code. + /// Closes the connection with the specified status code. /// /// - /// This method does nothing if the current state of the connection is + /// This method does nothing if the current state of the interface is /// Closing or Closed. /// /// @@ -2770,49 +2934,43 @@ public void Close (ushort code) /// One of the enum values. /// /// - /// It represents the status code indicating the reason for the close. + /// It specifies the status code indicating the reason for the close. /// /// /// /// - /// is - /// . - /// It cannot be used by clients. + /// is an undefined enum value. + /// + /// + /// -or- + /// + /// + /// is . + /// It cannot be used by a client. /// /// /// -or- /// /// - /// is - /// . - /// It cannot be used by servers. + /// is . + /// It cannot be used by a server. /// /// public void Close (CloseStatusCode code) { - if (_client && code == CloseStatusCode.ServerError) { - var msg = "ServerError cannot be used."; - throw new ArgumentException (msg, "code"); - } - - if (!_client && code == CloseStatusCode.MandatoryExtension) { - var msg = "MandatoryExtension cannot be used."; - throw new ArgumentException (msg, "code"); - } - - close ((ushort) code, String.Empty); + Close (code, String.Empty); } /// - /// Closes the connection with the specified code and reason. + /// Closes the connection with the specified status code and reason. /// /// - /// This method does nothing if the current state of the connection is + /// This method does nothing if the current state of the interface is /// Closing or Closed. /// /// /// - /// A that represents the status code indicating + /// A that specifies the status code indicating /// the reason for the close. /// /// @@ -2823,83 +2981,95 @@ public void Close (CloseStatusCode code) /// /// /// - /// A that represents the reason for the close. + /// A that specifies the reason for the close. /// /// - /// The size must be 123 bytes or less in UTF-8. + /// Its size must be 123 bytes or less in UTF-8. /// /// - /// + /// /// - /// is less than 1000 or greater than 4999. + /// is 1011 (server error). + /// It cannot be used by a client. /// /// /// -or- /// /// - /// The size of is greater than 123 bytes. - /// - /// - /// - /// - /// is 1011 (server error). - /// It cannot be used by clients. + /// is 1010 (mandatory extension). + /// It cannot be used by a server. /// /// /// -or- /// /// - /// is 1010 (mandatory extension). - /// It cannot be used by servers. + /// is 1005 (no status) and + /// is specified. /// /// /// -or- /// /// - /// is 1005 (no status) and there is reason. + /// could not be UTF-8-encoded. + /// + /// + /// + /// + /// is less than 1000 or greater than 4999. /// /// /// -or- /// /// - /// could not be UTF-8-encoded. + /// The size of is greater than 123 bytes. /// /// public void Close (ushort code, string reason) { if (!code.IsCloseStatusCode ()) { var msg = "Less than 1000 or greater than 4999."; + throw new ArgumentOutOfRangeException ("code", msg); } - if (_client && code == 1011) { - var msg = "1011 cannot be used."; - throw new ArgumentException (msg, "code"); + if (_isClient) { + if (code == 1011) { + var msg = "1011 cannot be used."; + + throw new ArgumentException (msg, "code"); + } } + else { + if (code == 1010) { + var msg = "1010 cannot be used."; - if (!_client && code == 1010) { - var msg = "1010 cannot be used."; - throw new ArgumentException (msg, "code"); + throw new ArgumentException (msg, "code"); + } } if (reason.IsNullOrEmpty ()) { close (code, String.Empty); + return; } if (code == 1005) { var msg = "1005 cannot be used."; + throw new ArgumentException (msg, "code"); } byte[] bytes; + if (!reason.TryGetUTF8EncodedBytes (out bytes)) { var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "reason"); } if (bytes.Length > 123) { var msg = "Its size is greater than 123 bytes."; + throw new ArgumentOutOfRangeException ("reason", msg); } @@ -2907,10 +3077,10 @@ public void Close (ushort code, string reason) } /// - /// Closes the connection with the specified code and reason. + /// Closes the connection with the specified status code and reason. /// /// - /// This method does nothing if the current state of the connection is + /// This method does nothing if the current state of the interface is /// Closing or Closed. /// /// @@ -2918,37 +3088,41 @@ public void Close (ushort code, string reason) /// One of the enum values. /// /// - /// It represents the status code indicating the reason for the close. + /// It specifies the status code indicating the reason for the close. /// /// /// /// - /// A that represents the reason for the close. + /// A that specifies the reason for the close. /// /// - /// The size must be 123 bytes or less in UTF-8. + /// Its size must be 123 bytes or less in UTF-8. /// /// /// /// - /// is - /// . - /// It cannot be used by clients. + /// is an undefined enum value. + /// + /// + /// -or- + /// + /// + /// is . + /// It cannot be used by a client. /// /// /// -or- /// /// - /// is - /// . - /// It cannot be used by servers. + /// is . + /// It cannot be used by a server. /// /// /// -or- /// /// - /// is - /// and there is reason. + /// is and + /// is specified. /// /// /// -or- @@ -2962,34 +3136,50 @@ public void Close (ushort code, string reason) /// public void Close (CloseStatusCode code, string reason) { - if (_client && code == CloseStatusCode.ServerError) { - var msg = "ServerError cannot be used."; + if (!code.IsDefined ()) { + var msg = "An undefined enum value."; + throw new ArgumentException (msg, "code"); } - if (!_client && code == CloseStatusCode.MandatoryExtension) { - var msg = "MandatoryExtension cannot be used."; - throw new ArgumentException (msg, "code"); + if (_isClient) { + if (code == CloseStatusCode.ServerError) { + var msg = "ServerError cannot be used."; + + throw new ArgumentException (msg, "code"); + } + } + else { + if (code == CloseStatusCode.MandatoryExtension) { + var msg = "MandatoryExtension cannot be used."; + + throw new ArgumentException (msg, "code"); + } } if (reason.IsNullOrEmpty ()) { close ((ushort) code, String.Empty); + return; } if (code == CloseStatusCode.NoStatus) { var msg = "NoStatus cannot be used."; + throw new ArgumentException (msg, "code"); } byte[] bytes; + if (!reason.TryGetUTF8EncodedBytes (out bytes)) { var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "reason"); } if (bytes.Length > 123) { var msg = "Its size is greater than 123 bytes."; + throw new ArgumentOutOfRangeException ("reason", msg); } @@ -3004,7 +3194,7 @@ public void Close (CloseStatusCode code, string reason) /// This method does not wait for the close to be complete. /// /// - /// This method does nothing if the current state of the connection is + /// This method does nothing if the current state of the interface is /// Closing or Closed. /// /// @@ -3014,20 +3204,20 @@ public void CloseAsync () } /// - /// Closes the connection asynchronously with the specified code. + /// Closes the connection asynchronously with the specified status code. /// /// /// /// This method does not wait for the close to be complete. /// /// - /// This method does nothing if the current state of the connection is + /// This method does nothing if the current state of the interface is /// Closing or Closed. /// /// /// /// - /// A that represents the status code indicating + /// A that specifies the status code indicating /// the reason for the close. /// /// @@ -3036,51 +3226,36 @@ public void CloseAsync () /// Section 7.4 of RFC 6455. /// /// - /// - /// is less than 1000 or greater than 4999. - /// /// /// /// is 1011 (server error). - /// It cannot be used by clients. + /// It cannot be used by a client. /// /// /// -or- /// /// /// is 1010 (mandatory extension). - /// It cannot be used by servers. + /// It cannot be used by a server. /// /// + /// + /// is less than 1000 or greater than 4999. + /// public void CloseAsync (ushort code) { - if (!code.IsCloseStatusCode ()) { - var msg = "Less than 1000 or greater than 4999."; - throw new ArgumentOutOfRangeException ("code", msg); - } - - if (_client && code == 1011) { - var msg = "1011 cannot be used."; - throw new ArgumentException (msg, "code"); - } - - if (!_client && code == 1010) { - var msg = "1010 cannot be used."; - throw new ArgumentException (msg, "code"); - } - - closeAsync (code, String.Empty); + CloseAsync (code, String.Empty); } /// - /// Closes the connection asynchronously with the specified code. + /// Closes the connection asynchronously with the specified status code. /// /// /// /// This method does not wait for the close to be complete. /// /// - /// This method does nothing if the current state of the connection is + /// This method does nothing if the current state of the interface is /// Closing or Closed. /// /// @@ -3089,54 +3264,49 @@ public void CloseAsync (ushort code) /// One of the enum values. /// /// - /// It represents the status code indicating the reason for the close. + /// It specifies the status code indicating the reason for the close. /// /// /// /// - /// is - /// . - /// It cannot be used by clients. + /// is an undefined enum value. /// /// /// -or- /// /// - /// is - /// . - /// It cannot be used by servers. + /// is . + /// It cannot be used by a client. + /// + /// + /// -or- + /// + /// + /// is . + /// It cannot be used by a server. /// /// public void CloseAsync (CloseStatusCode code) { - if (_client && code == CloseStatusCode.ServerError) { - var msg = "ServerError cannot be used."; - throw new ArgumentException (msg, "code"); - } - - if (!_client && code == CloseStatusCode.MandatoryExtension) { - var msg = "MandatoryExtension cannot be used."; - throw new ArgumentException (msg, "code"); - } - - closeAsync ((ushort) code, String.Empty); + CloseAsync (code, String.Empty); } /// - /// Closes the connection asynchronously with the specified code and reason. + /// Closes the connection asynchronously with the specified status code and + /// reason. /// /// /// /// This method does not wait for the close to be complete. /// /// - /// This method does nothing if the current state of the connection is + /// This method does nothing if the current state of the interface is /// Closing or Closed. /// /// /// /// - /// A that represents the status code indicating + /// A that specifies the status code indicating /// the reason for the close. /// /// @@ -3147,83 +3317,95 @@ public void CloseAsync (CloseStatusCode code) /// /// /// - /// A that represents the reason for the close. + /// A that specifies the reason for the close. /// /// - /// The size must be 123 bytes or less in UTF-8. + /// Its size must be 123 bytes or less in UTF-8. /// /// - /// + /// /// - /// is less than 1000 or greater than 4999. + /// is 1011 (server error). + /// It cannot be used by a client. /// /// /// -or- /// /// - /// The size of is greater than 123 bytes. - /// - /// - /// - /// - /// is 1011 (server error). - /// It cannot be used by clients. + /// is 1010 (mandatory extension). + /// It cannot be used by a server. /// /// /// -or- /// /// - /// is 1010 (mandatory extension). - /// It cannot be used by servers. + /// is 1005 (no status) and + /// is specified. /// /// /// -or- /// /// - /// is 1005 (no status) and there is reason. + /// could not be UTF-8-encoded. + /// + /// + /// + /// + /// is less than 1000 or greater than 4999. /// /// /// -or- /// /// - /// could not be UTF-8-encoded. + /// The size of is greater than 123 bytes. /// /// public void CloseAsync (ushort code, string reason) { if (!code.IsCloseStatusCode ()) { var msg = "Less than 1000 or greater than 4999."; + throw new ArgumentOutOfRangeException ("code", msg); } - if (_client && code == 1011) { - var msg = "1011 cannot be used."; - throw new ArgumentException (msg, "code"); + if (_isClient) { + if (code == 1011) { + var msg = "1011 cannot be used."; + + throw new ArgumentException (msg, "code"); + } } + else { + if (code == 1010) { + var msg = "1010 cannot be used."; - if (!_client && code == 1010) { - var msg = "1010 cannot be used."; - throw new ArgumentException (msg, "code"); + throw new ArgumentException (msg, "code"); + } } if (reason.IsNullOrEmpty ()) { closeAsync (code, String.Empty); + return; } if (code == 1005) { var msg = "1005 cannot be used."; + throw new ArgumentException (msg, "code"); } byte[] bytes; + if (!reason.TryGetUTF8EncodedBytes (out bytes)) { var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "reason"); } if (bytes.Length > 123) { var msg = "Its size is greater than 123 bytes."; + throw new ArgumentOutOfRangeException ("reason", msg); } @@ -3231,14 +3413,15 @@ public void CloseAsync (ushort code, string reason) } /// - /// Closes the connection asynchronously with the specified code and reason. + /// Closes the connection asynchronously with the specified status code and + /// reason. /// /// /// /// This method does not wait for the close to be complete. /// /// - /// This method does nothing if the current state of the connection is + /// This method does nothing if the current state of the interface is /// Closing or Closed. /// /// @@ -3247,37 +3430,41 @@ public void CloseAsync (ushort code, string reason) /// One of the enum values. /// /// - /// It represents the status code indicating the reason for the close. + /// It specifies the status code indicating the reason for the close. /// /// /// /// - /// A that represents the reason for the close. + /// A that specifies the reason for the close. /// /// - /// The size must be 123 bytes or less in UTF-8. + /// Its size must be 123 bytes or less in UTF-8. /// /// /// /// - /// is - /// . - /// It cannot be used by clients. + /// is an undefined enum value. + /// + /// + /// -or- + /// + /// + /// is . + /// It cannot be used by a client. /// /// /// -or- /// /// - /// is - /// . - /// It cannot be used by servers. + /// is . + /// It cannot be used by a server. /// /// /// -or- /// /// - /// is - /// and there is reason. + /// is and + /// is specified. /// /// /// -or- @@ -3291,34 +3478,50 @@ public void CloseAsync (ushort code, string reason) /// public void CloseAsync (CloseStatusCode code, string reason) { - if (_client && code == CloseStatusCode.ServerError) { - var msg = "ServerError cannot be used."; + if (!code.IsDefined ()) { + var msg = "An undefined enum value."; + throw new ArgumentException (msg, "code"); } - if (!_client && code == CloseStatusCode.MandatoryExtension) { - var msg = "MandatoryExtension cannot be used."; - throw new ArgumentException (msg, "code"); + if (_isClient) { + if (code == CloseStatusCode.ServerError) { + var msg = "ServerError cannot be used."; + + throw new ArgumentException (msg, "code"); + } + } + else { + if (code == CloseStatusCode.MandatoryExtension) { + var msg = "MandatoryExtension cannot be used."; + + throw new ArgumentException (msg, "code"); + } } if (reason.IsNullOrEmpty ()) { closeAsync ((ushort) code, String.Empty); + return; } if (code == CloseStatusCode.NoStatus) { var msg = "NoStatus cannot be used."; + throw new ArgumentException (msg, "code"); } byte[] bytes; + if (!reason.TryGetUTF8EncodedBytes (out bytes)) { var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "reason"); } if (bytes.Length > 123) { var msg = "Its size is greater than 123 bytes."; + throw new ArgumentOutOfRangeException ("reason", msg); } @@ -3329,44 +3532,42 @@ public void CloseAsync (CloseStatusCode code, string reason) /// Establishes a connection. /// /// - /// This method does nothing if the connection has already been established. + /// This method does nothing when the current state of the interface is + /// Connecting or Open. /// /// /// - /// This instance is not a client. - /// - /// - /// -or- - /// - /// - /// The close process is in progress. + /// The Connect method is not available if the interface is not for + /// the client. /// /// /// -or- /// /// - /// A series of reconnecting has failed. + /// The Connect method is not available if a series of reconnecting + /// has failed. /// /// public void Connect () { - if (!_client) { - var msg = "This instance is not a client."; - throw new InvalidOperationException (msg); - } + if (!_isClient) { + var msg = "The Connect method is not available."; - if (_readyState == WebSocketState.Closing) { - var msg = "The close process is in progress."; throw new InvalidOperationException (msg); } - if (_retryCountForConnect > _maxRetryCountForConnect) { - var msg = "A series of reconnecting has failed."; + if (_retryCountForConnect >= _maxRetryCountForConnect) { + var msg = "The Connect method is not available."; + throw new InvalidOperationException (msg); } - if (connect ()) - open (); + var connected = connect (); + + if (!connected) + return; + + open (); } /// @@ -3377,80 +3578,77 @@ public void Connect () /// This method does not wait for the connect process to be complete. /// /// - /// This method does nothing if the connection has already been - /// established. + /// This method does nothing when the current state of the interface is + /// Connecting or Open. /// /// /// /// - /// This instance is not a client. - /// - /// - /// -or- - /// - /// - /// The close process is in progress. + /// The ConnectAsync method is not available if the interface is not + /// for the client. /// /// /// -or- /// /// - /// A series of reconnecting has failed. + /// The ConnectAsync method is not available if a series of reconnecting + /// has failed. /// /// public void ConnectAsync () { - if (!_client) { - var msg = "This instance is not a client."; - throw new InvalidOperationException (msg); - } + if (!_isClient) { + var msg = "The ConnectAsync method is not available."; - if (_readyState == WebSocketState.Closing) { - var msg = "The close process is in progress."; throw new InvalidOperationException (msg); } - if (_retryCountForConnect > _maxRetryCountForConnect) { - var msg = "A series of reconnecting has failed."; + if (_retryCountForConnect >= _maxRetryCountForConnect) { + var msg = "The ConnectAsync method is not available."; + throw new InvalidOperationException (msg); } Func connector = connect; + connector.BeginInvoke ( ar => { - if (connector.EndInvoke (ar)) - open (); + var connected = connector.EndInvoke (ar); + + if (!connected) + return; + + open (); }, null ); } /// - /// Sends a ping using the WebSocket connection. + /// Sends a ping to the remote endpoint. /// /// - /// true if the send has done with no error and a pong has been + /// true if the send has successfully done and a pong has been /// received within a time; otherwise, false. /// public bool Ping () { - return ping (EmptyBytes); + return ping (_emptyBytes); } /// - /// Sends a ping with using the WebSocket - /// connection. + /// Sends a ping with the specified message to the remote endpoint. /// /// - /// true if the send has done with no error and a pong has been + /// true if the send has successfully done and a pong has been /// received within a time; otherwise, false. /// /// /// - /// A that represents the message to send. + /// A that specifies the message to send. /// /// - /// The size must be 125 bytes or less in UTF-8. + /// Its size must be 125 bytes or less in UTF-8. /// /// /// @@ -3462,16 +3660,19 @@ public bool Ping () public bool Ping (string message) { if (message.IsNullOrEmpty ()) - return ping (EmptyBytes); + return ping (_emptyBytes); byte[] bytes; + if (!message.TryGetUTF8EncodedBytes (out bytes)) { var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "message"); } if (bytes.Length > 125) { var msg = "Its size is greater than 125 bytes."; + throw new ArgumentOutOfRangeException ("message", msg); } @@ -3479,21 +3680,23 @@ public bool Ping (string message) } /// - /// Sends the specified data using the WebSocket connection. + /// Sends the specified data to the remote endpoint. /// /// - /// An array of that represents the binary data to send. + /// An array of that specifies the binary data to send. /// - /// - /// The current state of the connection is not Open. - /// /// /// is . /// + /// + /// The Send method is not available when the current state of + /// the interface is not Open. + /// public void Send (byte[] data) { if (_readyState != WebSocketState.Open) { - var msg = "The current state of the connection is not Open."; + var msg = "The Send method is not available."; + throw new InvalidOperationException (msg); } @@ -3504,7 +3707,7 @@ public void Send (byte[] data) } /// - /// Sends the specified file using the WebSocket connection. + /// Sends the specified file to the remote endpoint. /// /// /// @@ -3514,12 +3717,6 @@ public void Send (byte[] data) /// The file is sent as the binary data. /// /// - /// - /// The current state of the connection is not Open. - /// - /// - /// is . - /// /// /// /// The file does not exist. @@ -3531,10 +3728,18 @@ public void Send (byte[] data) /// The file could not be opened. /// /// + /// + /// is . + /// + /// + /// The Send method is not available when the current state of + /// the interface is not Open. + /// public void Send (FileInfo fileInfo) { if (_readyState != WebSocketState.Open) { - var msg = "The current state of the connection is not Open."; + var msg = "The Send method is not available."; + throw new InvalidOperationException (msg); } @@ -3543,12 +3748,15 @@ public void Send (FileInfo fileInfo) if (!fileInfo.Exists) { var msg = "The file does not exist."; + throw new ArgumentException (msg, "fileInfo"); } FileStream stream; + if (!fileInfo.TryOpenRead (out stream)) { var msg = "The file could not be opened."; + throw new ArgumentException (msg, "fileInfo"); } @@ -3556,24 +3764,26 @@ public void Send (FileInfo fileInfo) } /// - /// Sends the specified data using the WebSocket connection. + /// Sends the specified data to the remote endpoint. /// /// - /// A that represents the text data to send. + /// A that specifies the text data to send. /// - /// - /// The current state of the connection is not Open. + /// + /// could not be UTF-8-encoded. /// /// /// is . /// - /// - /// could not be UTF-8-encoded. + /// + /// The Send method is not available when the current state of + /// the interface is not Open. /// public void Send (string data) { if (_readyState != WebSocketState.Open) { - var msg = "The current state of the connection is not Open."; + var msg = "The Send method is not available."; + throw new InvalidOperationException (msg); } @@ -3581,8 +3791,10 @@ public void Send (string data) throw new ArgumentNullException ("data"); byte[] bytes; + if (!data.TryGetUTF8EncodedBytes (out bytes)) { var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "data"); } @@ -3590,7 +3802,7 @@ public void Send (string data) } /// - /// Sends the data from the specified stream using the WebSocket connection. + /// Sends the data from the specified stream instance to the remote endpoint. /// /// /// @@ -3603,12 +3815,6 @@ public void Send (string data) /// /// An that specifies the number of bytes to send. /// - /// - /// The current state of the connection is not Open. - /// - /// - /// is . - /// /// /// /// cannot be read. @@ -3626,10 +3832,18 @@ public void Send (string data) /// No data could be read from . /// /// + /// + /// is . + /// + /// + /// The Send method is not available when the current state of + /// the interface is not Open. + /// public void Send (Stream stream, int length) { if (_readyState != WebSocketState.Open) { - var msg = "The current state of the connection is not Open."; + var msg = "The Send method is not available."; + throw new InvalidOperationException (msg); } @@ -3638,66 +3852,71 @@ public void Send (Stream stream, int length) if (!stream.CanRead) { var msg = "It cannot be read."; + throw new ArgumentException (msg, "stream"); } if (length < 1) { var msg = "Less than 1."; + throw new ArgumentException (msg, "length"); } var bytes = stream.ReadBytes (length); - var len = bytes.Length; + if (len == 0) { var msg = "No data could be read from it."; + throw new ArgumentException (msg, "stream"); } if (len < length) { - _logger.Warn ( - String.Format ( - "Only {0} byte(s) of data could be read from the stream.", - len - ) - ); + var fmt = "Only {0} byte(s) of data could be read from the stream."; + var msg = String.Format (fmt, len); + + _log.Warn (msg); } send (Opcode.Binary, new MemoryStream (bytes)); } /// - /// Sends the specified data asynchronously using the WebSocket connection. + /// Sends the specified data to the remote endpoint asynchronously. /// /// /// This method does not wait for the send to be complete. /// /// - /// An array of that represents the binary data to send. + /// An array of that specifies the binary data to send. /// /// /// - /// An Action<bool> delegate or - /// if not needed. + /// An delegate. /// /// - /// The delegate invokes the method called when the send is complete. + /// It specifies the delegate called when the send is complete. /// /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. + /// The parameter passed to the delegate is true + /// if the send has successfully done; otherwise, false. + /// + /// + /// if not necessary. /// /// - /// - /// The current state of the connection is not Open. - /// /// /// is . /// + /// + /// The SendAsync method is not available when the current state of + /// the interface is not Open. + /// public void SendAsync (byte[] data, Action completed) { if (_readyState != WebSocketState.Open) { - var msg = "The current state of the connection is not Open."; + var msg = "The SendAsync method is not available."; + throw new InvalidOperationException (msg); } @@ -3708,7 +3927,7 @@ public void SendAsync (byte[] data, Action completed) } /// - /// Sends the specified file asynchronously using the WebSocket connection. + /// Sends the specified file to the remote endpoint asynchronously. /// /// /// This method does not wait for the send to be complete. @@ -3723,23 +3942,19 @@ public void SendAsync (byte[] data, Action completed) /// /// /// - /// An Action<bool> delegate or - /// if not needed. + /// An delegate. + /// + /// + /// It specifies the delegate called when the send is complete. /// /// - /// The delegate invokes the method called when the send is complete. + /// The parameter passed to the delegate is true + /// if the send has successfully done; otherwise, false. /// /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. + /// if not necessary. /// /// - /// - /// The current state of the connection is not Open. - /// - /// - /// is . - /// /// /// /// The file does not exist. @@ -3751,10 +3966,18 @@ public void SendAsync (byte[] data, Action completed) /// The file could not be opened. /// /// + /// + /// is . + /// + /// + /// The SendAsync method is not available when the current state of + /// the interface is not Open. + /// public void SendAsync (FileInfo fileInfo, Action completed) { if (_readyState != WebSocketState.Open) { - var msg = "The current state of the connection is not Open."; + var msg = "The SendAsync method is not available."; + throw new InvalidOperationException (msg); } @@ -3763,12 +3986,15 @@ public void SendAsync (FileInfo fileInfo, Action completed) if (!fileInfo.Exists) { var msg = "The file does not exist."; + throw new ArgumentException (msg, "fileInfo"); } FileStream stream; + if (!fileInfo.TryOpenRead (out stream)) { var msg = "The file could not be opened."; + throw new ArgumentException (msg, "fileInfo"); } @@ -3776,40 +4002,44 @@ public void SendAsync (FileInfo fileInfo, Action completed) } /// - /// Sends the specified data asynchronously using the WebSocket connection. + /// Sends the specified data to the remote endpoint asynchronously. /// /// /// This method does not wait for the send to be complete. /// /// - /// A that represents the text data to send. + /// A that specifies the text data to send. /// /// /// - /// An Action<bool> delegate or - /// if not needed. + /// An delegate. + /// + /// + /// It specifies the delegate called when the send is complete. /// /// - /// The delegate invokes the method called when the send is complete. + /// The parameter passed to the delegate is true + /// if the send has successfully done; otherwise, false. /// /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. + /// if not necessary. /// /// - /// - /// The current state of the connection is not Open. + /// + /// could not be UTF-8-encoded. /// /// /// is . /// - /// - /// could not be UTF-8-encoded. + /// + /// The SendAsync method is not available when the current state of + /// the interface is not Open. /// public void SendAsync (string data, Action completed) { if (_readyState != WebSocketState.Open) { - var msg = "The current state of the connection is not Open."; + var msg = "The SendAsync method is not available."; + throw new InvalidOperationException (msg); } @@ -3817,8 +4047,10 @@ public void SendAsync (string data, Action completed) throw new ArgumentNullException ("data"); byte[] bytes; + if (!data.TryGetUTF8EncodedBytes (out bytes)) { var msg = "It could not be UTF-8-encoded."; + throw new ArgumentException (msg, "data"); } @@ -3826,8 +4058,8 @@ public void SendAsync (string data, Action completed) } /// - /// Sends the data from the specified stream asynchronously using - /// the WebSocket connection. + /// Sends the data from the specified stream instance to the remote + /// endpoint asynchronously. /// /// /// This method does not wait for the send to be complete. @@ -3845,23 +4077,19 @@ public void SendAsync (string data, Action completed) /// /// /// - /// An Action<bool> delegate or - /// if not needed. + /// An delegate. + /// + /// + /// It specifies the delegate called when the send is complete. /// /// - /// The delegate invokes the method called when the send is complete. + /// The parameter passed to the delegate is true + /// if the send has successfully done; otherwise, false. /// /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. + /// if not necessary. /// /// - /// - /// The current state of the connection is not Open. - /// - /// - /// is . - /// /// /// /// cannot be read. @@ -3879,10 +4107,18 @@ public void SendAsync (string data, Action completed) /// No data could be read from . /// /// + /// + /// is . + /// + /// + /// The SendAsync method is not available when the current state of + /// the interface is not Open. + /// public void SendAsync (Stream stream, int length, Action completed) { if (_readyState != WebSocketState.Open) { - var msg = "The current state of the connection is not Open."; + var msg = "The SendAsync method is not available."; + throw new InvalidOperationException (msg); } @@ -3891,89 +4127,71 @@ public void SendAsync (Stream stream, int length, Action completed) if (!stream.CanRead) { var msg = "It cannot be read."; + throw new ArgumentException (msg, "stream"); } if (length < 1) { var msg = "Less than 1."; + throw new ArgumentException (msg, "length"); } var bytes = stream.ReadBytes (length); - var len = bytes.Length; + if (len == 0) { var msg = "No data could be read from it."; + throw new ArgumentException (msg, "stream"); } if (len < length) { - _logger.Warn ( - String.Format ( - "Only {0} byte(s) of data could be read from the stream.", - len - ) - ); + var fmt = "Only {0} byte(s) of data could be read from the stream."; + var msg = String.Format (fmt, len); + + _log.Warn (msg); } sendAsync (Opcode.Binary, new MemoryStream (bytes), completed); } /// - /// Sets an HTTP cookie to send with the handshake request. + /// Sets an HTTP cookie to send with the handshake request or response. /// - /// - /// This method does nothing if the connection has already been - /// established or it is closing. - /// /// - /// A that represents the cookie to send. + /// A that specifies the cookie to send. /// - /// - /// This instance is not a client. - /// /// /// is . /// + /// + /// The SetCookie method is not available when the current state of + /// the interface is neither New nor Closed. + /// public void SetCookie (Cookie cookie) { - string msg = null; - - if (!_client) { - msg = "This instance is not a client."; - throw new InvalidOperationException (msg); - } - if (cookie == null) throw new ArgumentNullException ("cookie"); - if (!canSet (out msg)) { - _logger.Warn (msg); - return; - } - lock (_forState) { - if (!canSet (out msg)) { - _logger.Warn (msg); - return; + if (!canSet ()) { + var msg = "The SetCookie method is not available."; + + throw new InvalidOperationException (msg); } - lock (_cookies.SyncRoot) - _cookies.SetOrRemove (cookie); + Cookies.SetOrRemove (cookie); } } /// /// Sets the credentials for the HTTP authentication (Basic/Digest). /// - /// - /// This method does nothing if the connection has already been - /// established or it is closing. - /// /// /// - /// A that represents the username associated with - /// the credentials. + /// A that specifies the username associated + /// with the credentials. /// /// /// or an empty string if initializes @@ -3982,20 +4200,18 @@ public void SetCookie (Cookie cookie) /// /// /// - /// A that represents the password for the username - /// associated with the credentials. + /// A that specifies the password for the + /// username associated with the credentials. /// /// /// or an empty string if not necessary. /// /// /// - /// true if sends the credentials for the Basic authentication in - /// advance with the first handshake request; otherwise, false. + /// A : true if sends the credentials for + /// the Basic authentication in advance with the first handshake + /// request; otherwise, false. /// - /// - /// This instance is not a client. - /// /// /// /// contains an invalid character. @@ -4007,38 +4223,48 @@ public void SetCookie (Cookie cookie) /// contains an invalid character. /// /// + /// + /// + /// The SetCredentials method is not available if the interface is not for + /// the client. + /// + /// + /// -or- + /// + /// + /// The SetCredentials method is not available when the current state of + /// the interface is neither New nor Closed. + /// + /// public void SetCredentials (string username, string password, bool preAuth) { - string msg = null; + if (!_isClient) { + var msg = "The SetCredentials method is not available."; - if (!_client) { - msg = "This instance is not a client."; throw new InvalidOperationException (msg); } if (!username.IsNullOrEmpty ()) { if (username.Contains (':') || !username.IsText ()) { - msg = "It contains an invalid character."; + var msg = "It contains an invalid character."; + throw new ArgumentException (msg, "username"); } } if (!password.IsNullOrEmpty ()) { if (!password.IsText ()) { - msg = "It contains an invalid character."; + var msg = "It contains an invalid character."; + throw new ArgumentException (msg, "password"); } } - if (!canSet (out msg)) { - _logger.Warn (msg); - return; - } - lock (_forState) { - if (!canSet (out msg)) { - _logger.Warn (msg); - return; + if (!canSet ()) { + var msg = "The SetCredentials method is not available."; + + throw new InvalidOperationException (msg); } if (username.IsNullOrEmpty ()) { @@ -4049,7 +4275,9 @@ public void SetCredentials (string username, string password, bool preAuth) } _credentials = new NetworkCredential ( - username, password, _uri.PathAndQuery + username, + password, + _uri.PathAndQuery ); _preAuth = preAuth; @@ -4060,45 +4288,38 @@ public void SetCredentials (string username, string password, bool preAuth) /// Sets the URL of the HTTP proxy server through which to connect and /// the credentials for the HTTP proxy authentication (Basic/Digest). /// - /// - /// This method does nothing if the connection has already been - /// established or it is closing. - /// /// /// - /// A that represents the URL of the proxy server - /// through which to connect. + /// A that specifies the URL of the proxy + /// server through which to connect. /// /// /// The syntax is http://<host>[:<port>]. /// /// - /// or an empty string if initializes the URL and - /// the credentials. + /// or an empty string if initializes + /// the URL and the credentials. /// /// /// /// - /// A that represents the username associated with - /// the credentials. + /// A that specifies the username associated + /// with the credentials. /// /// - /// or an empty string if the credentials are not - /// necessary. + /// or an empty string if the credentials + /// are not necessary. /// /// /// /// - /// A that represents the password for the username - /// associated with the credentials. + /// A that specifies the password for the + /// username associated with the credentials. /// /// /// or an empty string if not necessary. /// /// - /// - /// This instance is not a client. - /// /// /// /// is not an absolute URI string. @@ -4128,12 +4349,24 @@ public void SetCredentials (string username, string password, bool preAuth) /// contains an invalid character. /// /// + /// + /// + /// The SetProxy method is not available if the interface is not for + /// the client. + /// + /// + /// -or- + /// + /// + /// The SetProxy method is not available when the current state of + /// the interface is neither New nor Closed. + /// + /// public void SetProxy (string url, string username, string password) { - string msg = null; + if (!_isClient) { + var msg = "The SetProxy method is not available."; - if (!_client) { - msg = "This instance is not a client."; throw new InvalidOperationException (msg); } @@ -4141,44 +4374,45 @@ public void SetProxy (string url, string username, string password) if (!url.IsNullOrEmpty ()) { if (!Uri.TryCreate (url, UriKind.Absolute, out uri)) { - msg = "Not an absolute URI string."; + var msg = "Not an absolute URI string."; + throw new ArgumentException (msg, "url"); } if (uri.Scheme != "http") { - msg = "The scheme part is not http."; + var msg = "The scheme part is not http."; + throw new ArgumentException (msg, "url"); } if (uri.Segments.Length > 1) { - msg = "It includes the path segments."; + var msg = "It includes the path segments."; + throw new ArgumentException (msg, "url"); } } if (!username.IsNullOrEmpty ()) { if (username.Contains (':') || !username.IsText ()) { - msg = "It contains an invalid character."; + var msg = "It contains an invalid character."; + throw new ArgumentException (msg, "username"); } } if (!password.IsNullOrEmpty ()) { if (!password.IsText ()) { - msg = "It contains an invalid character."; + var msg = "It contains an invalid character."; + throw new ArgumentException (msg, "password"); } } - if (!canSet (out msg)) { - _logger.Warn (msg); - return; - } - lock (_forState) { - if (!canSet (out msg)) { - _logger.Warn (msg); - return; + if (!canSet ()) { + var msg = "The SetProxy method is not available."; + + throw new InvalidOperationException (msg); } if (url.IsNullOrEmpty ()) { @@ -4189,15 +4423,87 @@ public void SetProxy (string url, string username, string password) } _proxyUri = uri; - _proxyCredentials = !username.IsNullOrEmpty () - ? new NetworkCredential ( - username, - password, - String.Format ( - "{0}:{1}", _uri.DnsSafeHost, _uri.Port - ) - ) - : null; + + if (username.IsNullOrEmpty ()) { + _proxyCredentials = null; + + return; + } + + var domain = String.Format ("{0}:{1}", _uri.DnsSafeHost, _uri.Port); + + _proxyCredentials = new NetworkCredential (username, password, domain); + } + } + + /// + /// Sets a user header to send with the handshake request or response. + /// + /// + /// A that specifies the name of the header to set. + /// + /// + /// A that specifies the value of the header to set. + /// + /// + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is a string of spaces. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. + /// + /// + /// -or- + /// + /// + /// is a restricted header name. + /// + /// + /// + /// is . + /// + /// + /// The length of is greater than 65,535 + /// characters. + /// + /// + /// + /// The SetUserHeader method is not available when the current state of + /// the interface is neither New nor Closed. + /// + /// + /// -or- + /// + /// + /// The SetUserHeader method is not available if the interface does not + /// allow the header type. + /// + /// + public void SetUserHeader (string name, string value) + { + lock (_forState) { + if (!canSet ()) { + var msg = "The SetUserHeader method is not available."; + + throw new InvalidOperationException (msg); + } + + UserHeaders.Set (name, value); } } @@ -4213,7 +4519,7 @@ public void SetProxy (string url, string username, string password) /// This method closes the connection with close status 1001 (going away). /// /// - /// And this method does nothing if the current state of the connection is + /// This method does nothing if the current state of the interface is /// Closing or Closed. /// /// diff --git a/websocket-sharp/WebSocketException.cs b/websocket-sharp/WebSocketException.cs index 81d7c8081..12bfc48f8 100644 --- a/websocket-sharp/WebSocketException.cs +++ b/websocket-sharp/WebSocketException.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2016 sta.blockhead + * Copyright (c) 2012-2024 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 @@ -38,7 +38,21 @@ public class WebSocketException : Exception { #region Private Fields - private CloseStatusCode _code; + private ushort _code; + + #endregion + + #region Private Constructors + + private WebSocketException ( + ushort code, + string message, + Exception innerException + ) + : base (message ?? code.GetErrorMessage (), innerException) + { + _code = code; + } #endregion @@ -80,11 +94,12 @@ internal WebSocketException (CloseStatusCode code, string message) } internal WebSocketException ( - CloseStatusCode code, string message, Exception innerException + CloseStatusCode code, + string message, + Exception innerException ) - : base (message ?? code.GetMessage (), innerException) + : this ((ushort) code, message, innerException) { - _code = code; } #endregion @@ -95,10 +110,15 @@ internal WebSocketException ( /// Gets the status code indicating the cause of the exception. /// /// - /// One of the enum values that represents - /// the status code indicating the cause of the exception. + /// + /// A that represents the status code indicating + /// the cause of the exception. + /// + /// + /// It is one of the status codes for the WebSocket connection close. + /// /// - public CloseStatusCode Code { + public ushort Code { get { return _code; } diff --git a/websocket-sharp/WebSocketFrame.cs b/websocket-sharp/WebSocketFrame.cs index cbc53f32c..9ce51b945 100644 --- a/websocket-sharp/WebSocketFrame.cs +++ b/websocket-sharp/WebSocketFrame.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2021 sta.blockhead + * Copyright (c) 2012-2025 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 @@ -45,36 +45,49 @@ internal class WebSocketFrame : IEnumerable { #region Private Fields - private byte[] _extPayloadLength; - private Fin _fin; - private Mask _mask; - private byte[] _maskingKey; - private Opcode _opcode; - private PayloadData _payloadData; - private byte _payloadLength; - private Rsv _rsv1; - private Rsv _rsv2; - private Rsv _rsv3; + private static readonly int _defaultHeaderLength; + private static readonly int _defaultMaskingKeyLength; + private static readonly byte[] _emptyBytes; + private byte[] _extPayloadLength; + private Fin _fin; + private Mask _mask; + private byte[] _maskingKey; + private Opcode _opcode; + private PayloadData _payloadData; + private int _payloadLength; + private Rsv _rsv1; + private Rsv _rsv2; + private Rsv _rsv3; #endregion - #region Private Constructors + #region Static Constructor - private WebSocketFrame () + static WebSocketFrame () { + _defaultHeaderLength = 2; + _defaultMaskingKeyLength = 4; + _emptyBytes = new byte[0]; } #endregion - #region Internal Constructors + #region Private Constructors - internal WebSocketFrame (Opcode opcode, PayloadData payloadData, bool mask) - : this (Fin.Final, opcode, payloadData, false, mask) + private WebSocketFrame () { } + #endregion + + #region Internal Constructors + internal WebSocketFrame ( - Fin fin, Opcode opcode, byte[] data, bool compressed, bool mask + Fin fin, + Opcode opcode, + byte[] data, + bool compressed, + bool mask ) : this (fin, opcode, new PayloadData (data), compressed, mask) { @@ -91,22 +104,22 @@ bool mask _fin = fin; _opcode = opcode; - _rsv1 = opcode.IsData () && compressed ? Rsv.On : Rsv.Off; + _rsv1 = compressed ? Rsv.On : Rsv.Off; _rsv2 = Rsv.Off; _rsv3 = Rsv.Off; var len = payloadData.Length; if (len < 126) { - _payloadLength = (byte) len; - _extPayloadLength = WebSocket.EmptyBytes; + _payloadLength = (int) len; + _extPayloadLength = _emptyBytes; } else if (len < 0x010000) { - _payloadLength = (byte) 126; + _payloadLength = 126; _extPayloadLength = ((ushort) len).ToByteArray (ByteOrder.Big); } else { - _payloadLength = (byte) 127; + _payloadLength = 127; _extPayloadLength = len.ToByteArray (ByteOrder.Big); } @@ -118,7 +131,7 @@ bool mask } else { _mask = Mask.Off; - _maskingKey = WebSocket.EmptyBytes; + _maskingKey = _emptyBytes; } _payloadData = payloadData; @@ -131,7 +144,7 @@ bool mask internal ulong ExactPayloadLength { get { return _payloadLength < 126 - ? _payloadLength + ? (ulong) _payloadLength : _payloadLength == 126 ? _extPayloadLength.ToUInt16 (ByteOrder.Big) : _extPayloadLength.ToUInt64 (ByteOrder.Big); @@ -238,8 +251,11 @@ public bool IsText { public ulong Length { get { - return 2 - + (ulong) (_extPayloadLength.Length + _maskingKey.Length) + return (ulong) ( + _defaultHeaderLength + + _extPayloadLength.Length + + _maskingKey.Length + ) + _payloadData.Length; } } @@ -268,7 +284,7 @@ public PayloadData PayloadData { } } - public byte PayloadLength { + public int PayloadLength { get { return _payloadLength; } @@ -298,162 +314,16 @@ public Rsv Rsv3 { private static byte[] createMaskingKey () { - var key = new byte[4]; + var key = new byte[_defaultMaskingKeyLength]; WebSocket.RandomNumber.GetBytes (key); return key; } - private static string dump (WebSocketFrame frame) - { - var len = frame.Length; - var cnt = (long) (len / 4); - var rem = (int) (len % 4); - - int cntDigit; - string cntFmt; - - if (cnt < 10000) { - cntDigit = 4; - cntFmt = "{0,4}"; - } - else if (cnt < 0x010000) { - cntDigit = 4; - cntFmt = "{0,4:X}"; - } - else if (cnt < 0x0100000000) { - cntDigit = 8; - cntFmt = "{0,8:X}"; - } - else { - cntDigit = 16; - cntFmt = "{0,16:X}"; - } - - var spFmt = String.Format ("{{0,{0}}}", cntDigit); - - var headerFmt = String.Format ( - @" -{0} 01234567 89ABCDEF 01234567 89ABCDEF -{0}+--------+--------+--------+--------+\n", - spFmt - ); - - var lineFmt = String.Format ( - "{0}|{{1,8}} {{2,8}} {{3,8}} {{4,8}}|\n", cntFmt - ); - - var footerFmt = String.Format ( - "{0}+--------+--------+--------+--------+", spFmt - ); - - var buff = new StringBuilder (64); - - Func> linePrinter = - () => { - long lineCnt = 0; - - return (arg1, arg2, arg3, arg4) => { - buff.AppendFormat ( - lineFmt, ++lineCnt, arg1, arg2, arg3, arg4 - ); - }; - }; - - var printLine = linePrinter (); - var bytes = frame.ToArray (); - - buff.AppendFormat (headerFmt, String.Empty); - - for (long i = 0; i <= cnt; i++) { - var j = i * 4; - - if (i < cnt) { - printLine ( - Convert.ToString (bytes[j], 2).PadLeft (8, '0'), - Convert.ToString (bytes[j + 1], 2).PadLeft (8, '0'), - Convert.ToString (bytes[j + 2], 2).PadLeft (8, '0'), - Convert.ToString (bytes[j + 3], 2).PadLeft (8, '0') - ); - - continue; - } - - if (rem > 0) { - printLine ( - Convert.ToString (bytes[j], 2).PadLeft (8, '0'), - rem >= 2 - ? Convert.ToString (bytes[j + 1], 2).PadLeft (8, '0') - : String.Empty, - rem == 3 - ? Convert.ToString (bytes[j + 2], 2).PadLeft (8, '0') - : String.Empty, - String.Empty - ); - } - } - - buff.AppendFormat (footerFmt, String.Empty); - - return buff.ToString (); - } - - private static string print (WebSocketFrame frame) - { - // Payload Length - var payloadLen = frame._payloadLength; - - // Extended Payload Length - var extPayloadLen = payloadLen > 125 - ? frame.ExactPayloadLength.ToString () - : String.Empty; - - // Masking Key - var maskingKey = BitConverter.ToString (frame._maskingKey); - - // Payload Data - var payload = payloadLen == 0 - ? String.Empty - : payloadLen > 125 - ? "---" - : !frame.IsText - || frame.IsFragment - || frame.IsMasked - || frame.IsCompressed - ? frame._payloadData.ToString () - : utf8Decode (frame._payloadData.ApplicationData); - - var fmt = @" - FIN: {0} - RSV1: {1} - RSV2: {2} - RSV3: {3} - Opcode: {4} - MASK: {5} - Payload Length: {6} -Extended Payload Length: {7} - Masking Key: {8} - Payload Data: {9}"; - - return String.Format ( - fmt, - frame._fin, - frame._rsv1, - frame._rsv2, - frame._rsv3, - frame._opcode, - frame._mask, - payloadLen, - extPayloadLen, - maskingKey, - payload - ); - } - private static WebSocketFrame processHeader (byte[] header) { - if (header.Length != 2) { + if (header.Length != _defaultHeaderLength) { var msg = "The header part of a frame could not be read."; throw new WebSocketException (msg); @@ -472,41 +342,22 @@ private static WebSocketFrame processHeader (byte[] header) var rsv3 = (header[0] & 0x10) == 0x10 ? Rsv.On : Rsv.Off; // Opcode - var opcode = (byte) (header[0] & 0x0f); + var opcode = header[0] & 0x0f; // MASK var mask = (header[1] & 0x80) == 0x80 ? Mask.On : Mask.Off; // Payload Length - var payloadLen = (byte) (header[1] & 0x7f); + var payloadLen = header[1] & 0x7f; - if (!opcode.IsSupported ()) { - var msg = "A frame has an unsupported opcode."; + if (!opcode.IsSupportedOpcode ()) { + var msg = "The opcode of a frame is not supported."; - throw new WebSocketException (CloseStatusCode.ProtocolError, msg); - } - - if (!opcode.IsData () && rsv1 == Rsv.On) { - var msg = "A non data frame is compressed."; - - throw new WebSocketException (CloseStatusCode.ProtocolError, msg); - } - - if (opcode.IsControl ()) { - if (fin == Fin.More) { - var msg = "A control frame is fragmented."; - - throw new WebSocketException (CloseStatusCode.ProtocolError, msg); - } - - if (payloadLen > 125) { - var msg = "A control frame has too long payload length."; - - throw new WebSocketException (CloseStatusCode.ProtocolError, msg); - } + throw new WebSocketException (CloseStatusCode.UnsupportedData, msg); } var frame = new WebSocketFrame (); + frame._fin = fin; frame._rsv1 = rsv1; frame._rsv2 = rsv2; @@ -519,13 +370,14 @@ private static WebSocketFrame processHeader (byte[] header) } private static WebSocketFrame readExtendedPayloadLength ( - Stream stream, WebSocketFrame frame + Stream stream, + WebSocketFrame frame ) { var len = frame.ExtendedPayloadLengthWidth; if (len == 0) { - frame._extPayloadLength = WebSocket.EmptyBytes; + frame._extPayloadLength = _emptyBytes; return frame; } @@ -553,7 +405,7 @@ Action error var len = frame.ExtendedPayloadLengthWidth; if (len == 0) { - frame._extPayloadLength = WebSocket.EmptyBytes; + frame._extPayloadLength = _emptyBytes; completed (frame); @@ -579,17 +431,19 @@ Action error private static WebSocketFrame readHeader (Stream stream) { - var bytes = stream.ReadBytes (2); + var bytes = stream.ReadBytes (_defaultHeaderLength); return processHeader (bytes); } private static void readHeaderAsync ( - Stream stream, Action completed, Action error + Stream stream, + Action completed, + Action error ) { stream.ReadBytesAsync ( - 2, + _defaultHeaderLength, bytes => { var frame = processHeader (bytes); @@ -600,19 +454,19 @@ private static void readHeaderAsync ( } private static WebSocketFrame readMaskingKey ( - Stream stream, WebSocketFrame frame + Stream stream, + WebSocketFrame frame ) { if (!frame.IsMasked) { - frame._maskingKey = WebSocket.EmptyBytes; + frame._maskingKey = _emptyBytes; return frame; } - var len = 4; - var bytes = stream.ReadBytes (len); + var bytes = stream.ReadBytes (_defaultMaskingKeyLength); - if (bytes.Length != len) { + if (bytes.Length != _defaultMaskingKeyLength) { var msg = "The masking key of a frame could not be read."; throw new WebSocketException (msg); @@ -631,19 +485,17 @@ Action error ) { if (!frame.IsMasked) { - frame._maskingKey = WebSocket.EmptyBytes; + frame._maskingKey = _emptyBytes; completed (frame); return; } - var len = 4; - stream.ReadBytesAsync ( - len, + _defaultMaskingKeyLength, bytes => { - if (bytes.Length != len) { + if (bytes.Length != _defaultMaskingKeyLength) { var msg = "The masking key of a frame could not be read."; throw new WebSocketException (msg); @@ -658,27 +510,28 @@ Action error } private static WebSocketFrame readPayloadData ( - Stream stream, WebSocketFrame frame + Stream stream, + WebSocketFrame frame ) { - var exactLen = frame.ExactPayloadLength; + var exactPayloadLen = frame.ExactPayloadLength; - if (exactLen > PayloadData.MaxLength) { - var msg = "A frame has too long payload length."; + if (exactPayloadLen > PayloadData.MaxLength) { + var msg = "The payload data of a frame is too big."; throw new WebSocketException (CloseStatusCode.TooBig, msg); } - if (exactLen == 0) { + if (exactPayloadLen == 0) { frame._payloadData = PayloadData.Empty; return frame; } - var len = (long) exactLen; - var bytes = frame._payloadLength < 127 - ? stream.ReadBytes ((int) exactLen) - : stream.ReadBytes (len, 1024); + var len = (long) exactPayloadLen; + var bytes = frame._payloadLength > 126 + ? stream.ReadBytes (len, 1024) + : stream.ReadBytes ((int) len); if (bytes.LongLength != len) { var msg = "The payload data of a frame could not be read."; @@ -698,15 +551,15 @@ private static void readPayloadDataAsync ( Action error ) { - var exactLen = frame.ExactPayloadLength; + var exactPayloadLen = frame.ExactPayloadLength; - if (exactLen > PayloadData.MaxLength) { - var msg = "A frame has too long payload length."; + if (exactPayloadLen > PayloadData.MaxLength) { + var msg = "The payload data of a frame is too big."; throw new WebSocketException (CloseStatusCode.TooBig, msg); } - if (exactLen == 0) { + if (exactPayloadLen == 0) { frame._payloadData = PayloadData.Empty; completed (frame); @@ -714,7 +567,7 @@ Action error return; } - var len = (long) exactLen; + var len = (long) exactPayloadLen; Action comp = bytes => { @@ -729,23 +582,148 @@ Action error completed (frame); }; - if (frame._payloadLength < 127) { - stream.ReadBytesAsync ((int) exactLen, comp, error); + if (frame._payloadLength > 126) { + stream.ReadBytesAsync (len, 1024, comp, error); return; } - stream.ReadBytesAsync (len, 1024, comp, error); + stream.ReadBytesAsync ((int) len, comp, error); } - private static string utf8Decode (byte[] bytes) + private string toDumpString () { - try { - return Encoding.UTF8.GetString (bytes); + var len = Length; + var cnt = (long) (len / 4); + var rem = (int) (len % 4); + + string spFmt; + string cntFmt; + + if (cnt < 10000) { + spFmt = "{0,4}"; + cntFmt = "{0,4}"; } - catch { - return null; + else if (cnt < 0x010000) { + spFmt = "{0,4}"; + cntFmt = "{0,4:X}"; + } + else if (cnt < 0x0100000000) { + spFmt = "{0,8}"; + cntFmt = "{0,8:X}"; } + else { + spFmt = "{0,16}"; + cntFmt = "{0,16:X}"; + } + + var baseFmt = @"{0} 01234567 89ABCDEF 01234567 89ABCDEF +{0}+--------+--------+--------+--------+ +"; + var headerFmt = String.Format (baseFmt, spFmt); + + baseFmt = "{0}|{{1,8}} {{2,8}} {{3,8}} {{4,8}}|\n"; + var lineFmt = String.Format (baseFmt, cntFmt); + + baseFmt = "{0}+--------+--------+--------+--------+"; + var footerFmt = String.Format (baseFmt, spFmt); + + var buff = new StringBuilder (64); + + Func> lineWriter = + () => { + long lineCnt = 0; + + return (arg1, arg2, arg3, arg4) => { + buff.AppendFormat ( + lineFmt, + ++lineCnt, + arg1, + arg2, + arg3, + arg4 + ); + }; + }; + + var writeLine = lineWriter (); + var bytes = ToArray (); + + buff.AppendFormat (headerFmt, String.Empty); + + for (long i = 0; i <= cnt; i++) { + var j = i * 4; + + if (i < cnt) { + var arg1 = Convert.ToString (bytes[j], 2).PadLeft (8, '0'); + var arg2 = Convert.ToString (bytes[j + 1], 2).PadLeft (8, '0'); + var arg3 = Convert.ToString (bytes[j + 2], 2).PadLeft (8, '0'); + var arg4 = Convert.ToString (bytes[j + 3], 2).PadLeft (8, '0'); + + writeLine (arg1, arg2, arg3, arg4); + + continue; + } + + if (rem > 0) { + var arg1 = Convert.ToString (bytes[j], 2).PadLeft (8, '0'); + var arg2 = rem >= 2 + ? Convert.ToString (bytes[j + 1], 2).PadLeft (8, '0') + : String.Empty; + + var arg3 = rem == 3 + ? Convert.ToString (bytes[j + 2], 2).PadLeft (8, '0') + : String.Empty; + + writeLine (arg1, arg2, arg3, String.Empty); + } + } + + buff.AppendFormat (footerFmt, String.Empty); + + return buff.ToString (); + } + + private string toString () + { + var extPayloadLen = _payloadLength >= 126 + ? ExactPayloadLength.ToString () + : String.Empty; + + var maskingKey = _mask == Mask.On + ? BitConverter.ToString (_maskingKey) + : String.Empty; + + var payloadData = _payloadLength >= 126 + ? "***" + : _payloadLength > 0 + ? _payloadData.ToString () + : String.Empty; + + var fmt = @" FIN: {0} + RSV1: {1} + RSV2: {2} + RSV3: {3} + Opcode: {4} + MASK: {5} + Payload Length: {6} +Extended Payload Length: {7} + Masking Key: {8} + Payload Data: {9}"; + + return String.Format ( + fmt, + _fin, + _rsv1, + _rsv2, + _rsv3, + _opcode, + _mask, + _payloadLength, + extPayloadLen, + maskingKey, + payloadData + ); } #endregion @@ -753,34 +731,52 @@ private static string utf8Decode (byte[] bytes) #region Internal Methods internal static WebSocketFrame CreateCloseFrame ( - PayloadData payloadData, bool mask + PayloadData payloadData, + bool mask ) { return new WebSocketFrame ( - Fin.Final, Opcode.Close, payloadData, false, mask + Fin.Final, + Opcode.Close, + payloadData, + false, + mask ); } internal static WebSocketFrame CreatePingFrame (bool mask) { return new WebSocketFrame ( - Fin.Final, Opcode.Ping, PayloadData.Empty, false, mask + Fin.Final, + Opcode.Ping, + PayloadData.Empty, + false, + mask ); } internal static WebSocketFrame CreatePingFrame (byte[] data, bool mask) { return new WebSocketFrame ( - Fin.Final, Opcode.Ping, new PayloadData (data), false, mask + Fin.Final, + Opcode.Ping, + new PayloadData (data), + false, + mask ); } internal static WebSocketFrame CreatePongFrame ( - PayloadData payloadData, bool mask + PayloadData payloadData, + bool mask ) { return new WebSocketFrame ( - Fin.Final, Opcode.Pong, payloadData, false, mask + Fin.Final, + Opcode.Pong, + payloadData, + false, + mask ); } @@ -835,6 +831,11 @@ Action error ); } + internal string ToString (bool dump) + { + return dump ? toDumpString () : toString (); + } + internal void Unmask () { if (_mask == Mask.Off) @@ -842,7 +843,7 @@ internal void Unmask () _payloadData.Mask (_maskingKey); - _maskingKey = WebSocket.EmptyBytes; + _maskingKey = _emptyBytes; _mask = Mask.Off; } @@ -856,50 +857,36 @@ public IEnumerator GetEnumerator () yield return b; } - public void Print (bool dumped) - { - var val = dumped ? dump (this) : print (this); - - Console.WriteLine (val); - } - - public string PrintToString (bool dumped) - { - return dumped ? dump (this) : print (this); - } - public byte[] ToArray () { using (var buff = new MemoryStream ()) { var header = (int) _fin; + header = (header << 1) + (int) _rsv1; header = (header << 1) + (int) _rsv2; header = (header << 1) + (int) _rsv3; header = (header << 4) + (int) _opcode; header = (header << 1) + (int) _mask; - header = (header << 7) + (int) _payloadLength; + header = (header << 7) + _payloadLength; - var headerAsUshort = (ushort) header; - var headerAsBytes = headerAsUshort.ToByteArray (ByteOrder.Big); + var headerAsUInt16 = (ushort) header; + var headerAsBytes = headerAsUInt16.ToByteArray (ByteOrder.Big); - buff.Write (headerAsBytes, 0, 2); + buff.Write (headerAsBytes, 0, _defaultHeaderLength); - if (_payloadLength > 125) { - var cnt = _payloadLength == 126 ? 2 : 8; - - buff.Write (_extPayloadLength, 0, cnt); - } + if (_payloadLength >= 126) + buff.Write (_extPayloadLength, 0, _extPayloadLength.Length); if (_mask == Mask.On) - buff.Write (_maskingKey, 0, 4); + buff.Write (_maskingKey, 0, _defaultMaskingKeyLength); if (_payloadLength > 0) { var bytes = _payloadData.ToArray (); - if (_payloadLength < 127) - buff.Write (bytes, 0, bytes.Length); - else + if (_payloadLength > 126) buff.WriteBytes (bytes, 1024); + else + buff.Write (bytes, 0, bytes.Length); } buff.Close (); diff --git a/websocket-sharp/WebSocketState.cs b/websocket-sharp/WebSocketState.cs index 2cbcd688d..fa1704970 100644 --- a/websocket-sharp/WebSocketState.cs +++ b/websocket-sharp/WebSocketState.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2010-2016 sta.blockhead + * Copyright (c) 2010-2022 sta.blockhead * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -31,35 +31,34 @@ namespace WebSocketSharp { /// - /// Indicates the state of a WebSocket connection. + /// Indicates the state of the WebSocket interface. /// - /// - /// The values of this enumeration are defined in - /// - /// The WebSocket API. - /// public enum WebSocketState : ushort { /// - /// Equivalent to numeric value 0. Indicates that the connection has not - /// yet been established. + /// Equivalent to numeric value 0. Indicates that a new interface has + /// been created. /// - Connecting = 0, + New = 0, /// - /// Equivalent to numeric value 1. Indicates that the connection has - /// been established, and the communication is possible. + /// Equivalent to numeric value 1. Indicates that the connect process is + /// in progress. /// - Open = 1, + Connecting = 1, /// - /// Equivalent to numeric value 2. Indicates that the connection is - /// going through the closing handshake, or the close method has - /// been invoked. + /// Equivalent to numeric value 2. Indicates that the connection has + /// been established and the communication is possible. /// - Closing = 2, + Open = 2, /// - /// Equivalent to numeric value 3. Indicates that the connection has + /// Equivalent to numeric value 3. Indicates that the close process is + /// in progress. + /// + Closing = 3, + /// + /// Equivalent to numeric value 4. Indicates that the connection has /// been closed or could not be established. /// - Closed = 3 + Closed = 4 } } diff --git a/websocket-sharp/websocket-sharp.csproj b/websocket-sharp/websocket-sharp.csproj index 0860c0313..9dac290aa 100644 --- a/websocket-sharp/websocket-sharp.csproj +++ b/websocket-sharp/websocket-sharp.csproj @@ -51,7 +51,7 @@ true - + @@ -127,7 +127,6 @@ -