diff --git a/Example/Example.csproj b/Example/Example.csproj index 38c5b4200..ee89d9f6e 100644 --- a/Example/Example.csproj +++ b/Example/Example.csproj @@ -34,7 +34,7 @@ full false bin\Debug_Ubuntu - DEBUG,UBUNTU + DEBUG prompt 4 true @@ -43,16 +43,12 @@ none false bin\Release_Ubuntu - UBUNTU prompt 4 true - - notify-sharp - @@ -65,7 +61,5 @@ - - \ No newline at end of file diff --git a/Example/NotificationMessage.cs b/Example/NotificationMessage.cs deleted file mode 100644 index fd1bd3071..000000000 --- a/Example/NotificationMessage.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Example -{ - internal class NotificationMessage - { - public string Body { - get; set; - } - - public string Icon { - get; set; - } - - public string Summary { - get; set; - } - - public override string ToString () - { - return String.Format ("{0}: {1}", Summary, Body); - } - } -} diff --git a/Example/Notifier.cs b/Example/Notifier.cs deleted file mode 100644 index 5371c37a4..000000000 --- a/Example/Notifier.cs +++ /dev/null @@ -1,81 +0,0 @@ -#if UBUNTU -using Notifications; -#endif -using System; -using System.Collections; -using System.Collections.Generic; -using System.Threading; - -namespace Example -{ - internal class Notifier : IDisposable - { - private volatile bool _enabled; - private ManualResetEvent _exited; - private Queue _queue; - private object _sync; - - public Notifier () - { - _enabled = true; - _exited = new ManualResetEvent (false); - _queue = new Queue (); - _sync = ((ICollection) _queue).SyncRoot; - - ThreadPool.QueueUserWorkItem ( - state => { - while (_enabled || Count > 0) { - var msg = dequeue (); - if (msg != null) { -#if UBUNTU - var nf = new Notification (msg.Summary, msg.Body, msg.Icon); - nf.AddHint ("append", "allowed"); - nf.Show (); -#else - Console.WriteLine (msg); -#endif - } - else { - Thread.Sleep (500); - } - } - - _exited.Set (); - } - ); - } - - public int Count { - get { - lock (_sync) - return _queue.Count; - } - } - - private NotificationMessage dequeue () - { - lock (_sync) - return _queue.Count > 0 ? _queue.Dequeue () : null; - } - - public void Close () - { - _enabled = false; - _exited.WaitOne (); - _exited.Close (); - } - - public void Notify (NotificationMessage message) - { - lock (_sync) { - if (_enabled) - _queue.Enqueue (message); - } - } - - void IDisposable.Dispose () - { - Close (); - } - } -} diff --git a/Example/Program.cs b/Example/Program.cs index d414bb1e5..b7313a9d4 100644 --- a/Example/Program.cs +++ b/Example/Program.cs @@ -18,13 +18,8 @@ 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 nf = new Notifier ()) - using (var ws = new WebSocket ("ws://echo.websocket.org")) - //using (var ws = new WebSocket ("wss://echo.websocket.org")) - //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/Echo?name=nobita")) - //using (var ws = new WebSocket ("wss://localhost:5963/Echo?name=nobita")) //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")) @@ -34,32 +29,24 @@ public static void Main (string[] args) ws.OnOpen += (sender, e) => ws.Send ("Hi, there!"); - ws.OnMessage += (sender, e) => - nf.Notify ( - new NotificationMessage { - Summary = "WebSocket Message", - Body = !e.IsPing ? e.Data : "Received a ping.", - Icon = "notification-message-im" - } - ); - - ws.OnError += (sender, e) => - nf.Notify ( - new NotificationMessage { - Summary = "WebSocket Error", - Body = e.Message, - Icon = "notification-message-im" - } - ); - - ws.OnClose += (sender, e) => - nf.Notify ( - new NotificationMessage { - Summary = String.Format ("WebSocket Close ({0})", e.Code), - Body = e.Reason, - Icon = "notification-message-im" - } - ); + ws.OnMessage += (sender, e) => { + var fmt = "[WebSocket Message] {0}"; + var body = !e.IsPing ? e.Data : "A ping was received."; + + Console.WriteLine (fmt, body); + }; + + ws.OnError += (sender, e) => { + var fmt = "[WebSocket Error] {0}"; + + Console.WriteLine (fmt, e.Message); + }; + + ws.OnClose += (sender, e) => { + var fmt = "[WebSocket Close ({0})] {1}"; + + Console.WriteLine (fmt, e.Code, e.Reason); + }; #if DEBUG // To change the logging level. ws.Log.Level = LogLevel.Trace; @@ -77,13 +64,12 @@ public static void Main (string[] args) /* ws.SslConfiguration.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { - ws.Log.Debug ( - String.Format ( - "Certificate:\n- Issuer: {0}\n- Subject: {1}", - certificate.Issuer, - certificate.Subject - ) - ); + var fmt = "Certificate:\n- Issuer: {0}\n- Subject: {1}"; + var msg = String.Format ( + fmt, certificate.Issuer, certificate.Subject + ); + + ws.Log.Debug (msg); return true; // If the server certificate is valid. }; @@ -112,10 +98,13 @@ public static void Main (string[] args) //ws.ConnectAsync (); Console.WriteLine ("\nType 'exit' to exit.\n"); + while (true) { Thread.Sleep (1000); Console.Write ("> "); + var msg = Console.ReadLine (); + if (msg == "exit") break; diff --git a/Example1/AssemblyInfo.cs b/Example1/AssemblyInfo.cs deleted file mode 100644 index a78e6c6de..000000000 --- a/Example1/AssemblyInfo.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -// Information about this assembly is defined by the following attributes. -// Change them to the values specific to your project. - -[assembly: AssemblyTitle("Example1")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("sta.blockhead")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". -// The form "{Major}.{Minor}.*" will automatically update the build and revision, -// and "{Major}.{Minor}.{Build}.*" will update just the revision. - -[assembly: AssemblyVersion("1.0.*")] - -// The following attributes are used to specify the signing key for the assembly, -// if desired. See the Mono documentation for more information about signing. - -//[assembly: AssemblyDelaySign(false)] -//[assembly: AssemblyKeyFile("")] diff --git a/Example1/AudioStreamer.cs b/Example1/AudioStreamer.cs deleted file mode 100644 index 711694e9a..000000000 --- a/Example1/AudioStreamer.cs +++ /dev/null @@ -1,192 +0,0 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; -using System.Threading; -using WebSocketSharp; - -namespace Example1 -{ - internal class AudioStreamer : IDisposable - { - private Dictionary _audioBox; - private uint? _id; - private string _name; - private Notifier _notifier; - private Timer _timer; - private WebSocket _websocket; - - public AudioStreamer (string url) - { - _websocket = new WebSocket (url); - - _audioBox = new Dictionary (); - _id = null; - _notifier = new Notifier (); - _timer = new Timer (sendHeartbeat, null, -1, -1); - - configure (); - } - - private void configure () - { -#if DEBUG - _websocket.Log.Level = LogLevel.Trace; -#endif - _websocket.OnOpen += (sender, e) => - _websocket.Send (createTextMessage ("connection", String.Empty)); - - _websocket.OnMessage += (sender, e) => { - if (e.IsText) { - _notifier.Notify (processTextMessage (e.Data)); - return; - } - - if (e.IsBinary) { - processBinaryMessage (e.RawData); - return; - } - }; - - _websocket.OnError += (sender, e) => - _notifier.Notify ( - new NotificationMessage { - Summary = "AudioStreamer (error)", - Body = e.Message, - Icon = "notification-message-im" - } - ); - - _websocket.OnClose += (sender, e) => - _notifier.Notify ( - new NotificationMessage { - Summary = "AudioStreamer (disconnect)", - Body = String.Format ("code: {0} reason: {1}", e.Code, e.Reason), - Icon = "notification-message-im" - } - ); - } - - private byte[] createBinaryMessage (float[,] bufferArray) - { - return new BinaryMessage { - UserID = (uint) _id, - ChannelNumber = (byte) bufferArray.GetLength (0), - BufferLength = (uint) bufferArray.GetLength (1), - BufferArray = bufferArray - } - .ToArray (); - } - - private string createTextMessage (string type, string message) - { - return new TextMessage { - UserID = _id, - Name = _name, - Type = type, - Message = message - } - .ToString (); - } - - private void processBinaryMessage (byte[] data) - { - var msg = BinaryMessage.Parse (data); - - var id = msg.UserID; - if (id == _id) - return; - - Queue queue; - if (_audioBox.TryGetValue (id, out queue)) { - queue.Enqueue (msg.BufferArray); - return; - } - - queue = Queue.Synchronized (new Queue ()); - queue.Enqueue (msg.BufferArray); - _audioBox.Add (id, queue); - } - - private NotificationMessage processTextMessage (string data) - { - var json = JObject.Parse (data); - var id = (uint) json["user_id"]; - var name = (string) json["name"]; - var type = (string) json["type"]; - - string body; - if (type == "message") { - body = String.Format ("{0}: {1}", name, (string) json["message"]); - } - else if (type == "start_music") { - body = String.Format ("{0}: Started playing music!", name); - } - else if (type == "connection") { - var users = (JArray) json["message"]; - var buff = new StringBuilder ("Now keeping connections:"); - foreach (JToken user in users) { - buff.AppendFormat ( - "\n- user_id: {0} name: {1}", (uint) user["user_id"], (string) user["name"] - ); - } - - body = buff.ToString (); - } - else if (type == "connected") { - _id = id; - _timer.Change (30000, 30000); - - body = String.Format ("user_id: {0} name: {1}", id, name); - } - else { - body = "Received unknown type message."; - } - - return new NotificationMessage { - Summary = String.Format ("AudioStreamer ({0})", type), - Body = body, - Icon = "notification-message-im" - }; - } - - private void sendHeartbeat (object state) - { - _websocket.Send (createTextMessage ("heartbeat", String.Empty)); - } - - public void Close () - { - Disconnect (); - _timer.Dispose (); - _notifier.Close (); - } - - public void Connect (string username) - { - _name = username; - _websocket.Connect (); - } - - public void Disconnect () - { - _timer.Change (-1, -1); - _websocket.Close (CloseStatusCode.Away); - _audioBox.Clear (); - _id = null; - _name = null; - } - - public void Write (string message) - { - _websocket.Send (createTextMessage ("message", message)); - } - - void IDisposable.Dispose () - { - Close (); - } - } -} diff --git a/Example1/BinaryMessage.cs b/Example1/BinaryMessage.cs deleted file mode 100644 index eafa7aa98..000000000 --- a/Example1/BinaryMessage.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System; -using System.Collections.Generic; -using WebSocketSharp; - -namespace Example1 -{ - internal class BinaryMessage - { - public uint UserID { - get; set; - } - - public byte ChannelNumber { - get; set; - } - - public uint BufferLength { - get; set; - } - - public float[,] BufferArray { - get; set; - } - - public static BinaryMessage Parse (byte[] data) - { - var id = data.SubArray (0, 4).To (ByteOrder.Big); - var num = data.SubArray (4, 1)[0]; - var len = data.SubArray (5, 4).To (ByteOrder.Big); - var arr = new float[num, len]; - - var offset = 9; - ((uint) num).Times ( - i => - len.Times ( - j => { - arr[i, j] = data.SubArray (offset, 4).To (ByteOrder.Big); - offset += 4; - } - ) - ); - - return new BinaryMessage { - UserID = id, - ChannelNumber = num, - BufferLength = len, - BufferArray = arr - }; - } - - public byte[] ToArray () - { - var buff = new List (); - - var id = UserID; - var num = ChannelNumber; - var len = BufferLength; - var arr = BufferArray; - - buff.AddRange (id.ToByteArray (ByteOrder.Big)); - buff.Add (num); - buff.AddRange (len.ToByteArray (ByteOrder.Big)); - - ((uint) num).Times ( - i => - len.Times ( - j => buff.AddRange (arr[i, j].ToByteArray (ByteOrder.Big)) - ) - ); - - return buff.ToArray (); - } - } -} diff --git a/Example1/Example1.csproj b/Example1/Example1.csproj deleted file mode 100644 index 81c52eff2..000000000 --- a/Example1/Example1.csproj +++ /dev/null @@ -1,77 +0,0 @@ - - - - Debug - AnyCPU - 9.0.21022 - 2.0 - {390E2568-57B7-4D17-91E5-C29336368CCF} - Exe - Example - example1 - v3.5 - - - true - full - false - bin\Debug - DEBUG; - prompt - 4 - true - - - none - false - bin\Release - prompt - 4 - true - - - true - full - false - bin\Debug_Ubuntu - DEBUG;UBUNTU - prompt - 4 - true - - - none - false - bin\Release_Ubuntu - prompt - 4 - true - UBUNTU - - - - - False - notify-sharp - - - False - - - - - - - - - - - - - - - {B357BAC7-529E-4D81-A0D2-71041B19C8DE} - websocket-sharp - - - \ No newline at end of file diff --git a/Example1/NotificationMessage.cs b/Example1/NotificationMessage.cs deleted file mode 100644 index 01f1692a8..000000000 --- a/Example1/NotificationMessage.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; - -namespace Example1 -{ - internal class NotificationMessage - { - public string Body { - get; set; - } - - public string Icon { - get; set; - } - - public string Summary { - get; set; - } - - public override string ToString () - { - return String.Format ("{0}: {1}", Summary, Body); - } - } -} diff --git a/Example1/Notifier.cs b/Example1/Notifier.cs deleted file mode 100644 index adf53ec9a..000000000 --- a/Example1/Notifier.cs +++ /dev/null @@ -1,81 +0,0 @@ -#if UBUNTU -using Notifications; -#endif -using System; -using System.Collections; -using System.Collections.Generic; -using System.Threading; - -namespace Example1 -{ - internal class Notifier : IDisposable - { - private volatile bool _enabled; - private ManualResetEvent _exited; - private Queue _queue; - private object _sync; - - public Notifier () - { - _enabled = true; - _exited = new ManualResetEvent (false); - _queue = new Queue (); - _sync = ((ICollection) _queue).SyncRoot; - - ThreadPool.QueueUserWorkItem ( - state => { - while (_enabled || Count > 0) { - var msg = dequeue (); - if (msg != null) { -#if UBUNTU - var nf = new Notification (msg.Summary, msg.Body, msg.Icon); - nf.AddHint ("append", "allowed"); - nf.Show (); -#else - Console.WriteLine (msg); -#endif - } - else { - Thread.Sleep (500); - } - } - - _exited.Set (); - } - ); - } - - public int Count { - get { - lock (_sync) - return _queue.Count; - } - } - - private NotificationMessage dequeue () - { - lock (_sync) - return _queue.Count > 0 ? _queue.Dequeue () : null; - } - - public void Close () - { - _enabled = false; - _exited.WaitOne (); - _exited.Close (); - } - - public void Notify (NotificationMessage message) - { - lock (_sync) { - if (_enabled) - _queue.Enqueue (message); - } - } - - void IDisposable.Dispose () - { - Close (); - } - } -} diff --git a/Example1/Program.cs b/Example1/Program.cs deleted file mode 100644 index 88c0bedfe..000000000 --- a/Example1/Program.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Threading; - -namespace Example1 -{ - public class Program - { - public static void Main (string[] args) - { - // The AudioStreamer class provides a client (chat) for AudioStreamer - // (https://github.com/agektmr/AudioStreamer). - - using (var streamer = new AudioStreamer ("ws://localhost:3000/socket")) - { - string name; - do { - Console.Write ("Input your name> "); - name = Console.ReadLine (); - } - while (name.Length == 0); - - streamer.Connect (name); - - Console.WriteLine ("\nType 'exit' to exit.\n"); - while (true) { - Thread.Sleep (1000); - Console.Write ("> "); - var msg = Console.ReadLine (); - if (msg == "exit") - break; - - streamer.Write (msg); - } - } - } - } -} diff --git a/Example1/TextMessage.cs b/Example1/TextMessage.cs deleted file mode 100644 index 2b177d845..000000000 --- a/Example1/TextMessage.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Newtonsoft.Json; -using System; - -namespace Example1 -{ - internal class TextMessage - { - [JsonProperty ("user_id")] - public uint? UserID { - get; set; - } - - [JsonProperty ("name")] - public string Name { - get; set; - } - - [JsonProperty ("type")] - public string Type { - get; set; - } - - [JsonProperty ("message")] - public string Message { - get; set; - } - - public override string ToString () - { - return JsonConvert.SerializeObject (this); - } - } -} diff --git a/Example2/Chat.cs b/Example2/Chat.cs index a6b367d96..2391bf313 100644 --- a/Example2/Chat.cs +++ b/Example2/Chat.cs @@ -12,18 +12,24 @@ public class Chat : WebSocketBehavior private string _prefix; public Chat () - : this (null) { + _prefix = "anon#"; } - public Chat (string prefix) - { - _prefix = !prefix.IsNullOrEmpty () ? prefix : "anon#"; + public string Prefix { + get { + return _prefix; + } + + set { + _prefix = !value.IsNullOrEmpty () ? value : "anon#"; + } } private string getName () { - var name = Context.QueryString["name"]; + var name = QueryString["name"]; + return !name.IsNullOrEmpty () ? name : _prefix + getNumber (); } @@ -34,17 +40,31 @@ private static int getNumber () protected override void OnClose (CloseEventArgs e) { - Sessions.Broadcast (String.Format ("{0} got logged off...", _name)); + if (_name == null) + return; + + var fmt = "{0} got logged off..."; + var msg = String.Format (fmt, _name); + + Sessions.Broadcast (msg); } protected override void OnMessage (MessageEventArgs e) { - Sessions.Broadcast (String.Format ("{0}: {1}", _name, e.Data)); + var fmt = "{0}: {1}"; + var msg = String.Format (fmt, _name, e.Data); + + Sessions.Broadcast (msg); } protected override void OnOpen () { _name = getName (); + + var fmt = "{0} has logged in!"; + var msg = String.Format (fmt, _name); + + Sessions.Broadcast (msg); } } } diff --git a/Example2/Echo.cs b/Example2/Echo.cs index dd780c8d1..edc8872f9 100644 --- a/Example2/Echo.cs +++ b/Example2/Echo.cs @@ -8,8 +8,7 @@ public class Echo : WebSocketBehavior { protected override void OnMessage (MessageEventArgs e) { - var name = Context.QueryString["name"]; - Send (!name.IsNullOrEmpty () ? String.Format ("\"{0}\" to {1}", e.Data, name) : e.Data); + Send (e.Data); } } } diff --git a/Example2/Program.cs b/Example2/Program.cs index c9bd7ef3d..e83c762a4 100644 --- a/Example2/Program.cs +++ b/Example2/Program.cs @@ -14,8 +14,8 @@ 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 a wss scheme WebSocket URL. + // create a new instance with the 'secure' parameter set to true or + // with a wss scheme WebSocket URL. var wssv = new WebSocketServer (4649); //var wssv = new WebSocketServer (5963, true); @@ -53,8 +53,8 @@ public static void Main (string[] args) // 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; + // To remove the inactive sessions periodically. + //wssv.KeepClean = true; #endif // To provide the secure connection. /* @@ -73,7 +73,7 @@ public static void Main (string[] args) // Return user name, password, and roles. return name == "nobita" ? new NetworkCredential (name, "password", "gunfighter") - : null; // If the user credentials aren't found. + : null; // If the user credentials are not found. }; */ @@ -88,40 +88,48 @@ public static void Main (string[] args) /* wssv.AddWebSocketService ( "/Chat", - () => - new Chat ("Anon#") { - // To send the Sec-WebSocket-Protocol header that has a subprotocol name. - Protocol = "chat", - // To ignore the Sec-WebSocket-Extensions header. - IgnoreExtensions = true, - // To emit a WebSocket.OnMessage event when receives a ping. - EmitOnPing = true, - // To validate the Origin header. - 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. - CookiesValidator = (req, res) => { - // Check the cookies in 'req', and set the cookies to send to - // the client with 'res' if necessary. - foreach (Cookie cookie in req) { - cookie.Expired = true; - res.Add (cookie); - } - - return true; // If valid. + s => { + s.Prefix = "Anon#"; + + // To send the Sec-WebSocket-Protocol header that has a subprotocol name. + s.Protocol = "chat"; + + // To ignore the Sec-WebSocket-Extensions header. + s.IgnoreExtensions = true; + + // To emit a WebSocket.OnMessage event when receives a ping. + s.EmitOnPing = true; + + // To validate the Origin header. + 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); } - } + + return true; // If valid. + }; + } ); */ wssv.Start (); + if (wssv.IsListening) { Console.WriteLine ("Listening on port {0}, and providing WebSocket services:", wssv.Port); + foreach (var path in wssv.WebSocketServices.Paths) Console.WriteLine ("- {0}", path); } diff --git a/Example3/Chat.cs b/Example3/Chat.cs index b1a3f4d7d..0e3f38214 100644 --- a/Example3/Chat.cs +++ b/Example3/Chat.cs @@ -12,18 +12,24 @@ public class Chat : WebSocketBehavior private string _prefix; public Chat () - : this (null) { + _prefix = "anon#"; } - public Chat (string prefix) - { - _prefix = !prefix.IsNullOrEmpty () ? prefix : "anon#"; + public string Prefix { + get { + return _prefix; + } + + set { + _prefix = !value.IsNullOrEmpty () ? value : "anon#"; + } } private string getName () { - var name = Context.QueryString["name"]; + var name = QueryString["name"]; + return !name.IsNullOrEmpty () ? name : _prefix + getNumber (); } @@ -34,17 +40,31 @@ private static int getNumber () protected override void OnClose (CloseEventArgs e) { - Sessions.Broadcast (String.Format ("{0} got logged off...", _name)); + if (_name == null) + return; + + var fmt = "{0} got logged off..."; + var msg = String.Format (fmt, _name); + + Sessions.Broadcast (msg); } protected override void OnMessage (MessageEventArgs e) { - Sessions.Broadcast (String.Format ("{0}: {1}", _name, e.Data)); + var fmt = "{0}: {1}"; + var msg = String.Format (fmt, _name, e.Data); + + Sessions.Broadcast (msg); } protected override void OnOpen () { _name = getName (); + + var fmt = "{0} has logged in!"; + var msg = String.Format (fmt, _name); + + Sessions.Broadcast (msg); } } } diff --git a/Example3/Echo.cs b/Example3/Echo.cs index eb7c33410..01e2f16af 100644 --- a/Example3/Echo.cs +++ b/Example3/Echo.cs @@ -8,8 +8,7 @@ public class Echo : WebSocketBehavior { protected override void OnMessage (MessageEventArgs e) { - var name = Context.QueryString["name"]; - Send (!name.IsNullOrEmpty () ? String.Format ("\"{0}\" to {1}", e.Data, name) : e.Data); + Send (e.Data); } } } diff --git a/Example3/Program.cs b/Example3/Program.cs index b9699531f..33744993b 100644 --- a/Example3/Program.cs +++ b/Example3/Program.cs @@ -15,8 +15,8 @@ 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 an https scheme HTTP URL. + // create a new instance with the 'secure' parameter set to true or + // with an https scheme HTTP URL. var httpsv = new HttpServer (4649); //var httpsv = new HttpServer (5963, true); @@ -54,8 +54,8 @@ public static void Main (string[] args) // 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; + // To remove the inactive WebSocket sessions periodically. + //httpsv.KeepClean = true; #endif // To provide the secure connection. /* @@ -74,7 +74,7 @@ public static void Main (string[] args) // Return user name, password, and roles. return name == "nobita" ? new NetworkCredential (name, "password", "gunfighter") - : null; // If the user credentials aren't found. + : null; // If the user credentials are not found. }; */ @@ -90,12 +90,15 @@ public static void Main (string[] args) var res = e.Response; var path = req.RawUrl; + if (path == "/") path += "index.html"; byte[] contents; + if (!e.TryReadFile (path, out contents)) { res.StatusCode = (int) HttpStatusCode.NotFound; + return; } @@ -109,6 +112,7 @@ public static void Main (string[] args) } res.ContentLength64 = contents.LongLength; + res.Close (contents, true); }; @@ -120,40 +124,48 @@ public static void Main (string[] args) /* httpsv.AddWebSocketService ( "/Chat", - () => - new Chat ("Anon#") { - // To send the Sec-WebSocket-Protocol header that has a subprotocol name. - Protocol = "chat", - // To ignore the Sec-WebSocket-Extensions header. - IgnoreExtensions = true, - // To emit a WebSocket.OnMessage event when receives a ping. - EmitOnPing = true, - // To validate the Origin header. - 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. - CookiesValidator = (req, res) => { - // Check the cookies in 'req', and set the cookies to send to - // the client with 'res' if necessary. - foreach (Cookie cookie in req) { - cookie.Expired = true; - res.Add (cookie); - } - - return true; // If valid. + s => { + s.Prefix = "Anon#"; + + // To send the Sec-WebSocket-Protocol header that has a subprotocol name. + s.Protocol = "chat"; + + // To ignore the Sec-WebSocket-Extensions header. + s.IgnoreExtensions = true; + + // To emit a WebSocket.OnMessage event when receives a ping. + s.EmitOnPing = true; + + // To validate the Origin header. + 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); } - } + + return true; // If valid. + }; + } ); */ httpsv.Start (); + if (httpsv.IsListening) { Console.WriteLine ("Listening on port {0}, and providing WebSocket services:", httpsv.Port); + foreach (var path in httpsv.WebSocketServices.Paths) Console.WriteLine ("- {0}", path); } diff --git a/LICENSE.txt b/LICENSE.txt index 71d3e3128..6903008b3 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2010-2020 sta.blockhead +Copyright (c) 2010-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/README.md b/README.md index e7049b224..268712354 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ websocket-sharp supports: - [HTTP Authentication](#http-authentication) - [Query string, Origin header, and Cookies](#query-string-origin-header-and-cookies) - [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. @@ -277,7 +258,7 @@ namespace Example protected override void OnMessage (MessageEventArgs e) { var msg = e.Data == "BALUS" - ? "I've been balused already..." + ? "Are you kidding?" : "I'm not available now."; Send (msg); @@ -289,6 +270,7 @@ namespace Example public static void Main (string[] args) { var wssv = new WebSocketServer ("ws://dragonsnest.far"); + wssv.AddWebSocketService ("/Laputa"); wssv.Start (); Console.ReadKey (true); @@ -340,13 +322,18 @@ public class Chat : WebSocketBehavior private string _suffix; public Chat () - : this (null) { + _suffix = String.Empty; } - public Chat (string suffix) - { - _suffix = suffix ?? String.Empty; + public string Suffix { + get { + return _suffix; + } + + set { + _suffix = value ?? String.Empty; + } } protected override void OnMessage (MessageEventArgs e) @@ -374,16 +361,15 @@ Creating a new instance of the `WebSocketServer` class. ```csharp var wssv = new WebSocketServer (4649); + wssv.AddWebSocketService ("/Echo"); wssv.AddWebSocketService ("/Chat"); -wssv.AddWebSocketService ("/ChatWithNyan", () => new Chat (" Nyan!")); +wssv.AddWebSocketService ("/ChatWithNyan", s => s.Suffix = " Nyan!"); ``` -You can add any WebSocket service to your `WebSocketServer` with the specified behavior and absolute path to the service, by using the `WebSocketServer.AddWebSocketService (string)` or `WebSocketServer.AddWebSocketService (string, Func)` method. - -The type of `TBehaviorWithNew` must inherit the `WebSocketBehavior` class, and must have a public parameterless constructor. +You can add any WebSocket service to your `WebSocketServer` with the specified behavior and absolute path to the service, by using the `WebSocketServer.AddWebSocketService (string)` or `WebSocketServer.AddWebSocketService (string, Action)` method. -The type of `TBehavior` must inherit the `WebSocketBehavior` class. +The type of `TBehavior` must inherit the `WebSocketBehavior` class, and must have a public parameterless constructor. So you can use a class in the above Step 2 to add the service. @@ -404,26 +390,23 @@ wssv.Start (); Stopping the WebSocket server. ```csharp -wssv.Stop (code, reason); +wssv.Stop (); ``` -The `WebSocketServer.Stop` method is overloaded. - -You can use the `WebSocketServer.Stop ()`, `WebSocketServer.Stop (ushort, string)`, or `WebSocketServer.Stop (WebSocketSharp.CloseStatusCode, string)` method to stop the server. - ### HTTP Server with the WebSocket ### I have modified the `System.Net.HttpListener`, `System.Net.HttpListenerContext`, and some other classes from **[Mono]** to create an HTTP server that allows to accept the WebSocket handshake requests. So websocket-sharp provides the `WebSocketSharp.Server.HttpServer` class. -You can add any WebSocket service to your `HttpServer` with the specified behavior and path to the service, by using the `HttpServer.AddWebSocketService (string)` or `HttpServer.AddWebSocketService (string, Func)` method. +You can add any WebSocket service to your `HttpServer` with the specified behavior and path to the service, by using the `HttpServer.AddWebSocketService (string)` or `HttpServer.AddWebSocketService (string, Action)` method. ```csharp var httpsv = new HttpServer (4649); + httpsv.AddWebSocketService ("/Echo"); httpsv.AddWebSocketService ("/Chat"); -httpsv.AddWebSocketService ("/ChatWithNyan", () => new Chat (" Nyan!")); +httpsv.AddWebSocketService ("/ChatWithNyan", s => s.Suffix = " Nyan!"); ``` For more information, would you see **[Example3]**? @@ -432,7 +415,7 @@ For more information, would you see **[Example3]**? #### Per-message Compression #### -websocket-sharp supports the [Per-message Compression][compression] extension (but does not support it with the [context take over]). +websocket-sharp supports the [Per-message Compression][rfc7692] extension (but does not support it with the [context take over]). As a WebSocket client, if you would like to enable this extension, you should set the `WebSocket.Compression` property to a compression method before calling the connect method. @@ -455,11 +438,7 @@ As a WebSocket server, if you would like to ignore the extensions requested from ```csharp wssv.AddWebSocketService ( "/Chat", - () => - new Chat () { - // To ignore the extensions requested from a client. - IgnoreExtensions = true - } + s => s.IgnoreExtensions = true // To ignore the extensions requested from a client. ); ``` @@ -495,8 +474,9 @@ As a WebSocket server, you should create a new instance of the `WebSocketServer` ```csharp var wssv = new WebSocketServer (5963, true); -wssv.SslConfiguration.ServerCertificate = - new X509Certificate2 ("/path/to/cert.pfx", "password for cert.pfx"); +wssv.SslConfiguration.ServerCertificate = new X509Certificate2 ( + "/path/to/cert.pfx", "password for cert.pfx" + ); ``` ### HTTP Authentication ### @@ -542,19 +522,19 @@ 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. +And 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. +And also 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.Context.QueryString` property, such as the following. +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 public class Chat : WebSocketBehavior @@ -564,42 +544,44 @@ public class Chat : WebSocketBehavior protected override void OnOpen () { - _name = Context.QueryString["name"]; + _name = QueryString["name"]; } ... } ``` -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. - -If you would like to get the cookies included in a handshake request, you should access the `WebSocketBehavior.Context.CookieCollection` property. - -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, Func)` method with initializing, such as the following. +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. ```csharp wssv.AddWebSocketService ( "/Chat", - () => - new Chat () { - 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"; - }, - CookiesValidator = (req, res) => { - // Check the cookies in 'req', and set the cookies to send to - // the client with 'res' if necessary. - foreach (Cookie cookie in req) { - cookie.Expired = true; - res.Add (cookie); - } - - return true; // If valid. + s => { + 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"; + }; + + 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); } - } + + return true; // If valid. + }; + } ); ``` @@ -649,7 +631,7 @@ Examples using websocket-sharp. ### Example ### -[Example] connects to the [Echo server]. +[Example] connects to the server executed by [Example2] or [Example3]. ### Example2 ### @@ -667,7 +649,7 @@ websocket-sharp supports **RFC 6455**, and it is based on the following referenc - [The WebSocket Protocol][rfc6455] - [The WebSocket API][api] -- [Compression Extensions for WebSocket][compression] +- [Compression Extensions for WebSocket][rfc7692] Thanks for translating to japanese. @@ -679,7 +661,6 @@ Thanks for translating to japanese. websocket-sharp is provided under [The MIT License]. -[Echo server]: http://www.websocket.org/echo.html [Example]: https://github.com/sta/websocket-sharp/tree/master/Example [Example2]: https://github.com/sta/websocket-sharp/tree/master/Example2 [Example3]: https://github.com/sta/websocket-sharp/tree/master/Example3 @@ -689,16 +670,12 @@ 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 -[compression]: http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19 -[context take over]: http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-19#section-8.1.1 +[context take over]: https://datatracker.ietf.org/doc/html/rfc7692#section-7.1.1 [draft-hixie-thewebsocketprotocol-75]: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 [draft-ietf-hybi-thewebsocketprotocol-00]: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00 [draft75]: https://github.com/sta/websocket-sharp/tree/draft75 @@ -707,3 +684,4 @@ websocket-sharp is provided under [The MIT License]. [rfc2617]: http://tools.ietf.org/html/rfc2617 [rfc6455]: http://tools.ietf.org/html/rfc6455 [rfc6455_ja]: http://www.hcn.zaq.ne.jp/___/WEB/RFC6455-ja.html +[rfc7692]: https://datatracker.ietf.org/doc/html/rfc7692 diff --git a/websocket-sharp.sln b/websocket-sharp.sln index 3c20e06a0..6ff1c0362 100644 --- a/websocket-sharp.sln +++ b/websocket-sharp.sln @@ -5,8 +5,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "websocket-sharp", "websocke EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Example\Example.csproj", "{52805AEC-EFB1-4F42-BB8E-3ED4E692C568}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example1", "Example1\Example1.csproj", "{390E2568-57B7-4D17-91E5-C29336368CCF}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example2", "Example2\Example2.csproj", "{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example3", "Example3\Example3.csproj", "{C648BA25-77E5-4A40-A97F-D0AA37B9FB26}" @@ -19,14 +17,6 @@ Global Release_Ubuntu|Any CPU = Release_Ubuntu|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {390E2568-57B7-4D17-91E5-C29336368CCF}.Debug_Ubuntu|Any CPU.ActiveCfg = Debug_Ubuntu|Any CPU - {390E2568-57B7-4D17-91E5-C29336368CCF}.Debug_Ubuntu|Any CPU.Build.0 = Debug_Ubuntu|Any CPU - {390E2568-57B7-4D17-91E5-C29336368CCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {390E2568-57B7-4D17-91E5-C29336368CCF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {390E2568-57B7-4D17-91E5-C29336368CCF}.Release_Ubuntu|Any CPU.ActiveCfg = Release_Ubuntu|Any CPU - {390E2568-57B7-4D17-91E5-C29336368CCF}.Release_Ubuntu|Any CPU.Build.0 = Release_Ubuntu|Any CPU - {390E2568-57B7-4D17-91E5-C29336368CCF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {390E2568-57B7-4D17-91E5-C29336368CCF}.Release|Any CPU.Build.0 = Release|Any CPU {52805AEC-EFB1-4F42-BB8E-3ED4E692C568}.Debug_Ubuntu|Any CPU.ActiveCfg = Debug_Ubuntu|Any CPU {52805AEC-EFB1-4F42-BB8E-3ED4E692C568}.Debug_Ubuntu|Any CPU.Build.0 = Debug_Ubuntu|Any CPU {52805AEC-EFB1-4F42-BB8E-3ED4E692C568}.Debug|Any CPU.ActiveCfg = Debug|Any CPU diff --git a/websocket-sharp/CloseEventArgs.cs b/websocket-sharp/CloseEventArgs.cs index 8127ce418..50b01ce32 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-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,11 +35,12 @@ 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 @@ -59,12 +60,6 @@ internal CloseEventArgs (PayloadData payloadData, bool clean) _clean = clean; } - internal CloseEventArgs (ushort code, string reason, bool clean) - { - _payloadData = new PayloadData (code, reason); - _clean = clean; - } - #endregion #region Public Properties @@ -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 { 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 027a9b437..0df7f3c25 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) - * - IsPredefinedScheme is derived from Uri.cs (System) - * - MaybeUri 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-2016 sta.blockhead + * Copyright (c) 2010-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 @@ -67,7 +67,7 @@ public static class Ext #region Private Fields private static readonly byte[] _last = new byte[] { 0x00 }; - private static readonly int _retry = 5; + private static readonly int _maxRetry = 5; private const string _tspecials = "()<>@,;:\\\"/[]?={} \t"; #endregion @@ -77,7 +77,6 @@ public static class Ext private static byte[] compress (this byte[] data) { if (data.LongLength == 0) - //return new byte[] { 0x00, 0x00, 0x00, 0xff, 0xff }; return data; using (var input = new MemoryStream (data)) @@ -86,18 +85,23 @@ private static byte[] compress (this byte[] data) private static MemoryStream compress (this Stream stream) { - var output = new MemoryStream (); + var ret = new MemoryStream (); + if (stream.Length == 0) - return output; + return ret; stream.Position = 0; - using (var ds = new DeflateStream (output, CompressionMode.Compress, true)) { + + var mode = CompressionMode.Compress; + + using (var ds = new DeflateStream (ret, mode, true)) { stream.CopyTo (ds, 1024); ds.Close (); // BFINAL set to 1. - output.Write (_last, 0, 1); - output.Position = 0; + ret.Write (_last, 0, 1); + + ret.Position = 0; - return output; + return ret; } } @@ -105,6 +109,7 @@ private static byte[] compressToArray (this Stream stream) { using (var output = stream.compress ()) { output.Close (); + return output.ToArray (); } } @@ -120,16 +125,21 @@ private static byte[] decompress (this byte[] data) private static MemoryStream decompress (this Stream stream) { - var output = new MemoryStream (); + var ret = new MemoryStream (); + if (stream.Length == 0) - return output; + return ret; stream.Position = 0; - using (var ds = new DeflateStream (stream, CompressionMode.Decompress, true)) { - ds.CopyTo (output, 1024); - output.Position = 0; - return output; + var mode = CompressionMode.Decompress; + + using (var ds = new DeflateStream (stream, mode, true)) { + ds.CopyTo (ret, 1024); + + ret.Position = 0; + + return ret; } } @@ -137,27 +147,39 @@ private static byte[] decompressToArray (this Stream stream) { using (var output = stream.decompress ()) { output.Close (); + return output.ToArray (); } } - private static bool isHttpMethod (this string value) + private static bool isPredefinedScheme (this string value) { - return value == "GET" - || value == "HEAD" - || value == "POST" - || value == "PUT" - || value == "DELETE" - || value == "CONNECT" - || value == "OPTIONS" - || value == "TRACE"; - } + var c = value[0]; - private static bool isHttpMethod10 (this string value) - { - return value == "GET" - || value == "HEAD" - || value == "POST"; + if (c == 'h') + return value == "http" || value == "https"; + + if (c == 'w') + return value == "ws" || value == "wss"; + + if (c == 'f') + return value == "file" || value == "ftp"; + + if (c == 'g') + return value == "gopher"; + + if (c == 'm') + return value == "mailto"; + + if (c == 'n') { + c = value[1]; + + return c == 'e' + ? value == "news" || value == "net.pipe" || value == "net.tcp" + : value == "nntp"; + } + + return false; } #endregion @@ -166,69 +188,35 @@ private static bool isHttpMethod10 (this string value) internal static byte[] Append (this ushort code, string reason) { - var bytes = code.InternalToByteArray (ByteOrder.Big); + var codeAsBytes = code.ToByteArray (ByteOrder.Big); if (reason == null || reason.Length == 0) - return bytes; + return codeAsBytes; + + var buff = new List (codeAsBytes); + var reasonAsBytes = Encoding.UTF8.GetBytes (reason); - var buff = new List (bytes); - buff.AddRange (Encoding.UTF8.GetBytes (reason)); + buff.AddRange (reasonAsBytes); return buff.ToArray (); } - internal static void Close ( - this HttpListenerResponse response, HttpStatusCode code + internal static byte[] Compress ( + this byte[] data, + CompressionMethod method ) { - response.StatusCode = (int) code; - response.OutputStream.Close (); + return method == CompressionMethod.Deflate ? data.compress () : data; } - internal static void CloseWithAuthChallenge ( - this HttpListenerResponse response, string challenge + internal static Stream Compress ( + this Stream stream, + CompressionMethod method ) { - response.Headers.InternalSet ("WWW-Authenticate", challenge, true); - response.Close (HttpStatusCode.Unauthorized); - } - - internal static byte[] Compress (this byte[] data, CompressionMethod method) - { - return method == CompressionMethod.Deflate - ? data.compress () - : data; - } - - internal static Stream Compress (this Stream stream, CompressionMethod method) - { - return method == CompressionMethod.Deflate - ? stream.compress () - : stream; - } - - internal static byte[] CompressToArray (this Stream stream, CompressionMethod method) - { - return method == CompressionMethod.Deflate - ? stream.compressToArray () - : stream.ToByteArray (); + return method == CompressionMethod.Deflate ? stream.compress () : stream; } - /// - /// Determines whether the specified string contains any of characters in - /// the specified array of . - /// - /// - /// true if contains any of characters in - /// ; otherwise, false. - /// - /// - /// A to test. - /// - /// - /// An array of that contains one or more characters to - /// seek. - /// internal static bool Contains (this string value, params char[] anyOf) { return anyOf != null && anyOf.Length > 0 @@ -237,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; @@ -251,6 +240,7 @@ StringComparison comparisonTypeForValue ) { var val = collection[name]; + if (val == null) return false; @@ -263,7 +253,8 @@ StringComparison comparisonTypeForValue } internal static bool Contains ( - this IEnumerable source, Func condition + this IEnumerable source, + Func condition ) { foreach (T elm in source) { @@ -285,6 +276,7 @@ internal static bool ContainsTwice (this string[] values) return false; var val = values[idx]; + for (var i = idx + 1; i < len; i++) { if (values[i] == val) return true; @@ -296,41 +288,45 @@ internal static bool ContainsTwice (this string[] values) return seek (0); } - internal static T[] Copy (this T[] source, int length) + internal static T[] Copy (this T[] sourceArray, int length) { var dest = new T[length]; - Array.Copy (source, 0, dest, 0, length); + + Array.Copy (sourceArray, 0, dest, 0, length); return dest; } - internal static T[] Copy (this T[] source, long length) + internal static T[] Copy (this T[] sourceArray, long length) { var dest = new T[length]; - Array.Copy (source, 0, dest, 0, length); + + Array.Copy (sourceArray, 0, dest, 0, length); return dest; } internal static void CopyTo ( - this Stream source, Stream destination, int bufferLength + this Stream sourceStream, + Stream destinationStream, + int bufferLength ) { var buff = new byte[bufferLength]; - var nread = 0; while (true) { - nread = source.Read (buff, 0, bufferLength); + var nread = sourceStream.Read (buff, 0, bufferLength); + if (nread <= 0) break; - destination.Write (buff, 0, nread); + destinationStream.Write (buff, 0, nread); } } internal static void CopyToAsync ( - this Stream source, - Stream destination, + this Stream sourceStream, + Stream destinationStream, int bufferLength, Action completed, Action error @@ -342,7 +338,8 @@ Action error callback = ar => { try { - var nread = source.EndRead (ar); + var nread = sourceStream.EndRead (ar); + if (nread <= 0) { if (completed != null) completed (); @@ -350,8 +347,9 @@ Action error return; } - destination.Write (buff, 0, nread); - source.BeginRead (buff, 0, bufferLength, callback, null); + destinationStream.Write (buff, 0, nread); + + sourceStream.BeginRead (buff, 0, bufferLength, callback, null); } catch (Exception ex) { if (error != null) @@ -360,7 +358,7 @@ Action error }; try { - source.BeginRead (buff, 0, bufferLength, callback, null); + sourceStream.BeginRead (buff, 0, bufferLength, callback, null); } catch (Exception ex) { if (error != null) @@ -368,21 +366,28 @@ Action error } } - internal static byte[] Decompress (this byte[] data, CompressionMethod method) + internal static byte[] Decompress ( + 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) + internal static Stream Decompress ( + this Stream stream, + CompressionMethod method + ) { return method == CompressionMethod.Deflate ? stream.decompress () : stream; } - internal static byte[] DecompressToArray (this Stream stream, CompressionMethod method) + internal static byte[] DecompressToArray ( + this Stream stream, + CompressionMethod method + ) { return method == CompressionMethod.Deflate ? stream.decompressToArray () @@ -390,7 +395,9 @@ internal static byte[] DecompressToArray (this Stream stream, CompressionMethod } internal static void Emit ( - this EventHandler eventHandler, object sender, EventArgs e + this EventHandler eventHandler, + object sender, + EventArgs e ) { if (eventHandler == null) @@ -400,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 { @@ -410,59 +419,29 @@ internal static void Emit ( eventHandler (sender, e); } - /// - /// Determines whether the specified equals the specified , - /// and invokes the specified Action<int> delegate at the same time. - /// - /// - /// true if equals ; - /// otherwise, false. - /// - /// - /// An to compare. - /// - /// - /// A to compare. - /// - /// - /// An Action<int> delegate that references the method(s) called - /// at the same time as comparing. An parameter to pass to - /// the method(s) is . - /// - internal static bool EqualsWith (this int value, char c, Action action) - { - action (value); - return value == c - 0; - } - - /// - /// Gets the absolute path from the specified . - /// - /// - /// A that represents the absolute path if it's successfully found; - /// otherwise, . - /// - /// - /// A that represents the URI to get the absolute path from. - /// internal static string GetAbsolutePath (this Uri uri) { if (uri.IsAbsoluteUri) return uri.AbsolutePath; var original = uri.OriginalString; + if (original[0] != '/') return null; var idx = original.IndexOfAny (new[] { '?', '#' }); + return idx > 0 ? original.Substring (0, idx) : original; } 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) : new CookieCollection (); @@ -475,184 +454,130 @@ internal static string GetDnsSafeHost (this Uri uri, bool bracketIPv6) : uri.DnsSafeHost; } - internal static string GetMessage (this CloseStatusCode code) - { - return code == CloseStatusCode.ProtocolError - ? "A WebSocket protocol error has occurred." - : code == CloseStatusCode.UnsupportedData - ? "Unsupported data has been received." - : code == CloseStatusCode.Abnormal - ? "An exception has occurred." - : code == CloseStatusCode.InvalidData - ? "Invalid data has been received." - : code == CloseStatusCode.PolicyViolation - ? "A policy violation has occurred." - : code == CloseStatusCode.TooBig - ? "A too big message has been received." - : code == CloseStatusCode.MandatoryExtension - ? "WebSocket client didn't receive expected extension(s)." - : code == CloseStatusCode.ServerError - ? "WebSocket server got an internal error." - : code == CloseStatusCode.TlsHandshakeFailure - ? "An error has occurred during a TLS handshake." - : String.Empty; + internal static string GetErrorMessage (this ushort code) + { + switch (code) { + case 1002: + return "A protocol error has occurred."; + case 1003: + return "Unsupported data has been received."; + case 1006: + return "An abnormal error has occurred."; + case 1007: + return "Invalid data has been received."; + case 1008: + return "A policy violation has occurred."; + case 1009: + return "A too big message has been received."; + case 1010: + return "The client did not receive expected extension(s)."; + case 1011: + return "The server got an internal error."; + 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 (); } - /// - /// Gets the name from the specified string that contains a pair of - /// name and value separated by a character. - /// - /// - /// - /// A that represents the name. - /// - /// - /// if the name is not present. - /// - /// - /// - /// A that contains a pair of name and value. - /// - /// - /// A used to separate name and value. - /// internal static string GetName (this string nameAndValue, char separator) { var idx = nameAndValue.IndexOf (separator); + return idx > 0 ? nameAndValue.Substring (0, idx).Trim () : null; } 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; + } } - /// - /// Gets the value from the specified string that contains a pair of - /// name and value separated by a character. - /// - /// - /// - /// A that represents the value. - /// - /// - /// if the value is not present. - /// - /// - /// - /// A that contains a pair of name and value. - /// - /// - /// A used to separate name and value. - /// internal static string GetValue (this string nameAndValue, char separator) { return nameAndValue.GetValue (separator, false); } - /// - /// Gets the value from the specified string that contains a pair of - /// name and value separated by a character. - /// - /// - /// - /// A that represents the value. - /// - /// - /// if the value is not present. - /// - /// - /// - /// A that contains a pair of name and value. - /// - /// - /// A used to separate name and value. - /// - /// - /// A : true if unquotes the value; otherwise, - /// false. - /// internal static string GetValue ( - this string nameAndValue, char separator, bool unquote + this string nameAndValue, + char separator, + bool unquote ) { var idx = nameAndValue.IndexOf (separator); + if (idx < 0 || idx == nameAndValue.Length - 1) return null; var val = nameAndValue.Substring (idx + 1).Trim (); - return unquote ? val.Unquote () : val; - } - - internal static byte[] InternalToByteArray ( - this ushort value, ByteOrder order - ) - { - var ret = BitConverter.GetBytes (value); - - if (!order.IsHostOrder ()) - Array.Reverse (ret); - - return ret; - } - - internal static byte[] InternalToByteArray ( - this ulong value, ByteOrder order - ) - { - var ret = BitConverter.GetBytes (value); - - if (!order.IsHostOrder ()) - Array.Reverse (ret); - return ret; + return unquote ? val.Unquote () : val; } internal static bool IsCompressionExtension ( - this string value, CompressionMethod method + this string value, + CompressionMethod method ) { - return value.StartsWith (method.ToExtensionString ()); - } + var extStr = method.ToExtensionString (); + var compType = StringComparison.Ordinal; - internal static bool IsControl (this byte opcode) - { - return opcode > 0x7 && opcode < 0x10; + return value.StartsWith (extStr, compType); } - internal static bool IsControl (this Opcode opcode) + internal static bool IsEqualTo ( + this int value, + char c, + Action beforeComparing + ) { - return opcode >= Opcode.Close; - } + beforeComparing (value); - internal static bool IsData (this byte opcode) - { - return opcode == 0x1 || opcode == 0x2; + return value == c - 0; } - internal static bool IsData (this Opcode opcode) + internal static bool IsHttpMethod (this string value) { - return opcode == Opcode.Text || opcode == Opcode.Binary; + return value == "GET" + || value == "HEAD" + || value == "POST" + || value == "PUT" + || value == "DELETE" + || value == "CONNECT" + || value == "OPTIONS" + || value == "TRACE"; } - internal static bool IsHttpMethod (this string value, Version version) + internal static bool IsPortNumber (this int value) { - return version == HttpVersion.Version10 - ? value.isHttpMethod10 () - : value.isHttpMethod (); + return value > 0 && value < 65536; } - internal static bool IsPortNumber (this int value) + internal static bool IsReserved (this CloseStatusCode code) { - return value > 0 && value < 65536; + return ((ushort) code).IsReservedStatusCode (); } - internal static bool IsReserved (this ushort code) + internal static bool IsReservedStatusCode (this ushort code) { return code == 1004 || code == 1005 @@ -660,15 +585,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 byte opcode) { return Enum.IsDefined (typeof (Opcode), opcode); } @@ -679,16 +596,19 @@ internal static bool IsText (this string value) for (var i = 0; i < len; i++) { var c = value[i]; + if (c < 0x20) { if ("\r\n\t".IndexOf (c) == -1) return false; if (c == '\n') { i++; + if (i == len) break; c = value[i]; + if (" \t".IndexOf (c) == -1) return false; } @@ -720,36 +640,55 @@ internal static bool IsToken (this string value) } internal static bool KeepsAlive ( - this NameValueCollection headers, Version version + this NameValueCollection headers, + Version version ) { - var comparison = StringComparison.OrdinalIgnoreCase; - return version < HttpVersion.Version11 - ? headers.Contains ("Connection", "keep-alive", comparison) - : !headers.Contains ("Connection", "close", comparison); + var compType = StringComparison.OrdinalIgnoreCase; + + return version > HttpVersion.Version10 + ? !headers.Contains ("Connection", "close", compType) + : headers.Contains ("Connection", "keep-alive", compType); + } + + internal static bool MaybeUri (this string value) + { + var idx = value.IndexOf (':'); + + if (idx < 2 || idx > 9) + return false; + + var schm = value.Substring (0, idx); + + return schm.isPredefinedScheme (); } internal static string Quote (this string value) { - return String.Format ("\"{0}\"", value.Replace ("\"", "\\\"")); + var fmt = "\"{0}\""; + var val = value.Replace ("\"", "\\\""); + + return String.Format (fmt, val); } internal static byte[] ReadBytes (this Stream stream, int length) { - var buff = new byte[length]; + var ret = new byte[length]; + var offset = 0; var retry = 0; - var nread = 0; while (length > 0) { - nread = stream.Read (buff, offset, length); + var nread = stream.Read (ret, offset, length); + if (nread <= 0) { - if (retry < _retry) { + if (retry < _maxRetry) { retry++; + continue; } - return buff.SubArray (0, offset); + return ret.SubArray (0, offset); } retry = 0; @@ -758,26 +697,29 @@ internal static byte[] ReadBytes (this Stream stream, int length) length -= nread; } - return buff; + return ret; } internal static byte[] ReadBytes ( - this Stream stream, long length, int bufferLength + this Stream stream, + long length, + int bufferLength ) { using (var dest = new MemoryStream ()) { var buff = new byte[bufferLength]; var retry = 0; - var nread = 0; while (length > 0) { if (length < bufferLength) bufferLength = (int) length; - nread = stream.Read (buff, 0, bufferLength); + var nread = stream.Read (buff, 0, bufferLength); + if (nread <= 0) { - if (retry < _retry) { + if (retry < _maxRetry) { retry++; + continue; } @@ -787,10 +729,12 @@ internal static byte[] ReadBytes ( retry = 0; dest.Write (buff, 0, nread); + length -= nread; } dest.Close (); + return dest.ToArray (); } } @@ -802,7 +746,8 @@ internal static void ReadBytesAsync ( Action error ) { - var buff = new byte[length]; + var ret = new byte[length]; + var offset = 0; var retry = 0; @@ -811,23 +756,25 @@ Action error ar => { try { var nread = stream.EndRead (ar); + if (nread <= 0) { - if (retry < _retry) { + if (retry < _maxRetry) { retry++; - stream.BeginRead (buff, offset, length, callback, null); + + stream.BeginRead (ret, offset, length, callback, null); return; } if (completed != null) - completed (buff.SubArray (0, offset)); + completed (ret.SubArray (0, offset)); return; } if (nread == length) { if (completed != null) - completed (buff); + completed (ret); return; } @@ -837,7 +784,7 @@ Action error offset += nread; length -= nread; - stream.BeginRead (buff, offset, length, callback, null); + stream.BeginRead (ret, offset, length, callback, null); } catch (Exception ex) { if (error != null) @@ -846,7 +793,7 @@ Action error }; try { - stream.BeginRead (buff, offset, length, callback, null); + stream.BeginRead (ret, offset, length, callback, null); } catch (Exception ex) { if (error != null) @@ -863,6 +810,7 @@ Action error ) { var dest = new MemoryStream (); + var buff = new byte[bufferLength]; var retry = 0; @@ -879,9 +827,11 @@ Action error ar => { try { var nread = stream.EndRead (ar); + if (nread <= 0) { - if (retry < _retry) { + if (retry < _maxRetry) { retry++; + read (len); return; @@ -889,10 +839,14 @@ Action error if (completed != null) { dest.Close (); - completed (dest.ToArray ()); + + var ret = dest.ToArray (); + + completed (ret); } dest.Dispose (); + return; } @@ -901,10 +855,14 @@ Action error if (nread == len) { if (completed != null) { dest.Close (); - completed (dest.ToArray ()); + + var ret = dest.ToArray (); + + completed (ret); } dest.Dispose (); + return; } @@ -914,6 +872,7 @@ Action error } catch (Exception ex) { dest.Dispose (); + if (error != null) error (ex); } @@ -927,6 +886,7 @@ Action error } catch (Exception ex) { dest.Dispose (); + if (error != null) error (ex); } @@ -934,38 +894,43 @@ Action error internal static T[] Reverse (this T[] array) { - var len = array.Length; + var len = array.LongLength; var ret = new T[len]; var end = len - 1; - for (var i = 0; i <= end; i++) + + for (long i = 0; i <= end; i++) ret[i] = array[end - i]; return ret; } internal static IEnumerable SplitHeaderValue ( - this string value, params char[] separators + this string value, + params char[] separators ) { var len = value.Length; + var end = len - 1; var buff = new StringBuilder (32); - var end = len - 1; var escaped = false; var quoted = false; for (var i = 0; i <= end; i++) { var c = value[i]; + buff.Append (c); if (c == '"') { if (escaped) { escaped = false; + continue; } quoted = !quoted; + continue; } @@ -984,9 +949,11 @@ internal static IEnumerable SplitHeaderValue ( continue; buff.Length -= 1; + yield return buff.ToString (); buff.Length = 0; + continue; } } @@ -996,18 +963,40 @@ internal static IEnumerable SplitHeaderValue ( internal static byte[] ToByteArray (this Stream stream) { - using (var output = new MemoryStream ()) { - stream.Position = 0; - stream.CopyTo (output, 1024); - output.Close (); + stream.Position = 0; - return output.ToArray (); + using (var buff = new MemoryStream ()) { + stream.CopyTo (buff, 1024); + buff.Close (); + + return buff.ToArray (); } } + internal static byte[] ToByteArray (this ushort value, ByteOrder order) + { + var ret = BitConverter.GetBytes (value); + + if (!order.IsHostOrder ()) + Array.Reverse (ret); + + return ret; + } + + internal static byte[] ToByteArray (this ulong value, ByteOrder order) + { + var ret = BitConverter.GetBytes (value); + + if (!order.IsHostOrder ()) + Array.Reverse (ret); + + return ret; + } + internal static CompressionMethod ToCompressionMethod (this string value) { var methods = Enum.GetValues (typeof (CompressionMethod)); + foreach (CompressionMethod method in methods) { if (method.ToExtensionString () == value) return method; @@ -1017,19 +1006,27 @@ 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) return String.Empty; - var name = String.Format ( - "permessage-{0}", method.ToString ().ToLower () - ); + var name = method.ToString ().ToLower (); + var ename = String.Format ("permessage-{0}", name); + + if (parameters == null || parameters.Length == 0) + return ename; - return parameters != null && parameters.Length > 0 - ? String.Format ("{0}; {1}", name, parameters.ToString ("; ")) - : name; + var eparams = parameters.ToString ("; "); + + return String.Format ("{0}; {1}", ename, eparams); + } + + internal static int ToInt32 (this string numericString) + { + return Int32.Parse (numericString); } internal static System.Net.IPAddress ToIPAddress (this string value) @@ -1038,11 +1035,13 @@ internal static System.Net.IPAddress ToIPAddress (this string value) return null; System.Net.IPAddress addr; + if (System.Net.IPAddress.TryParse (value, out addr)) return addr; try { var addrs = System.Net.Dns.GetHostAddresses (value); + return addrs[0]; } catch { @@ -1058,26 +1057,38 @@ this IEnumerable source } internal static string ToString ( - this System.Net.IPAddress address, bool bracketIPv6 + this System.Net.IPAddress address, + bool bracketIPv6 ) { return bracketIPv6 && address.AddressFamily == AddressFamily.InterNetworkV6 - ? String.Format ("[{0}]", address.ToString ()) + ? String.Format ("[{0}]", address) : address.ToString (); } internal static ushort ToUInt16 (this byte[] source, ByteOrder sourceOrder) { - return BitConverter.ToUInt16 (source.ToHostOrder (sourceOrder), 0); + var val = source.ToHostOrder (sourceOrder); + + return BitConverter.ToUInt16 (val, 0); } internal static ulong ToUInt64 (this byte[] source, ByteOrder sourceOrder) { - return BitConverter.ToUInt64 (source.ToHostOrder (sourceOrder), 0); + var val = source.ToHostOrder (sourceOrder); + + return BitConverter.ToUInt64 (val, 0); } - internal static IEnumerable Trim (this IEnumerable source) + internal static Version ToVersion (this string versionString) + { + return new Version (versionString); + } + + internal static IEnumerable TrimEach ( + this IEnumerable source + ) { foreach (var elm in source) yield return elm.Trim (); @@ -1086,17 +1097,20 @@ internal static IEnumerable Trim (this IEnumerable source) internal static string TrimSlashFromEnd (this string value) { var ret = value.TrimEnd ('/'); + return ret.Length > 0 ? ret : "/"; } internal static string TrimSlashOrBackslashFromEnd (this string value) { var ret = value.TrimEnd ('/', '\\'); + return ret.Length > 0 ? ret : value[0].ToString (); } internal static bool TryCreateVersion ( - this string versionString, out Version result + this string versionString, + out Version result ) { result = null; @@ -1111,78 +1125,75 @@ internal static bool TryCreateVersion ( return true; } - /// - /// Tries to create a new for WebSocket with - /// the specified . - /// - /// - /// true if the was successfully created; - /// otherwise, false. - /// - /// - /// A that represents a WebSocket URL to try. - /// - /// - /// When this method returns, a that - /// represents the WebSocket URL or - /// if is invalid. - /// - /// - /// When this method returns, a that - /// represents an error message or - /// if is valid. - /// internal static bool TryCreateWebSocketUri ( - this string uriString, out Uri result, out string message + this string uriString, + out Uri result, + out string message ) { result = null; message = null; var uri = uriString.ToUri (); + if (uri == null) { message = "An invalid URI string."; + return false; } if (!uri.IsAbsoluteUri) { message = "A relative URI."; + return false; } var schm = uri.Scheme; - if (!(schm == "ws" || schm == "wss")) { - message = "The scheme part is not 'ws' or 'wss'."; + var valid = schm == "ws" || schm == "wss"; + + if (!valid) { + message = "The scheme part is not \"ws\" or \"wss\"."; + return false; } var port = uri.Port; + if (port == 0) { message = "The port part is zero."; + return false; } if (uri.Fragment.Length > 0) { message = "It includes the fragment component."; + return false; } - result = port != -1 - ? uri - : new Uri ( - String.Format ( - "{0}://{1}:{2}{3}", - schm, - uri.Host, - schm == "ws" ? 80 : 443, - uri.PathAndQuery - ) - ); + if (port == -1) { + port = schm == "ws" ? 80 : 443; + uriString = String.Format ( + "{0}://{1}:{2}{3}", + schm, + uri.Host, + port, + uri.PathAndQuery + ); + + result = new Uri (uriString); + } + else { + result = uri; + } return true; } - internal static bool TryGetUTF8DecodedString (this byte[] bytes, out string s) + internal static bool TryGetUTF8DecodedString ( + this byte[] bytes, + out string s + ) { s = null; @@ -1196,7 +1207,10 @@ internal static bool TryGetUTF8DecodedString (this byte[] bytes, out string s) return true; } - internal static bool TryGetUTF8EncodedBytes (this string s, out byte[] bytes) + internal static bool TryGetUTF8EncodedBytes ( + this string s, + out byte[] bytes + ) { bytes = null; @@ -1211,7 +1225,8 @@ internal static bool TryGetUTF8EncodedBytes (this string s, out byte[] bytes) } internal static bool TryOpenRead ( - this FileInfo fileInfo, out FileStream fileStream + this FileInfo fileInfo, + out FileStream fileStream ) { fileStream = null; @@ -1228,32 +1243,39 @@ internal static bool TryOpenRead ( internal static string Unquote (this string value) { - var start = value.IndexOf ('"'); - if (start == -1) + var first = value.IndexOf ('"'); + + if (first == -1) return value; - var end = value.LastIndexOf ('"'); - if (end == start) + var last = value.LastIndexOf ('"'); + + if (last == first) return value; - var len = end - start - 1; + var len = last - first - 1; + return len > 0 - ? value.Substring (start + 1, len).Replace ("\\\"", "\"") + ? value.Substring (first + 1, len).Replace ("\\\"", "\"") : String.Empty; } internal static bool Upgrades ( - this NameValueCollection headers, string protocol + this NameValueCollection headers, + string protocol ) { - var comparison = StringComparison.OrdinalIgnoreCase; - return headers.Contains ("Upgrade", protocol, comparison) - && headers.Contains ("Connection", "Upgrade", comparison); + var compType = StringComparison.OrdinalIgnoreCase; + + return headers.Contains ("Upgrade", protocol, compType) + && headers.Contains ("Connection", "Upgrade", compType); } internal static string UrlDecode (this string value, Encoding encoding) { - return HttpUtility.UrlDecode (value, encoding); + return value.IndexOfAny (new[] { '%', '+' }) > -1 + ? HttpUtility.UrlDecode (value, encoding) + : value; } internal static string UrlEncode (this string value, Encoding encoding) @@ -1262,7 +1284,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)) @@ -1278,6 +1302,7 @@ Action error ) { var src = new MemoryStream (bytes); + src.CopyToAsync ( stream, bufferLength, @@ -1289,6 +1314,7 @@ Action error }, ex => { src.Dispose (); + if (error != null) error (ex); } @@ -1448,10 +1474,8 @@ public static bool IsEnclosedIn (this string value, char c) return false; var len = value.Length; - if (len < 2) - return false; - return value[0] == c && value[len - 1] == c; + return len > 1 ? value[0] == c && value[len - 1] == c : false; } /// @@ -1507,8 +1531,9 @@ public static bool IsLocal (this System.Net.IPAddress address) return true; } - var host = System.Net.Dns.GetHostName (); - var addrs = System.Net.Dns.GetHostAddresses (host); + var name = System.Net.Dns.GetHostName (); + var addrs = System.Net.Dns.GetHostAddresses (name); + foreach (var addr in addrs) { if (address.Equals (addr)) return true; @@ -1533,76 +1558,6 @@ public static bool IsNullOrEmpty (this string value) return value == null || value.Length == 0; } - /// - /// Determines whether the specified string is a predefined scheme. - /// - /// - /// true if is a predefined scheme; - /// otherwise, false. - /// - /// - /// A to test. - /// - public static bool IsPredefinedScheme (this string value) - { - if (value == null || value.Length < 2) - return false; - - var c = value[0]; - if (c == 'h') - return value == "http" || value == "https"; - - if (c == 'w') - return value == "ws" || value == "wss"; - - if (c == 'f') - return value == "file" || value == "ftp"; - - if (c == 'g') - return value == "gopher"; - - if (c == 'm') - return value == "mailto"; - - if (c == 'n') { - c = value[1]; - return c == 'e' - ? value == "news" || value == "net.pipe" || value == "net.tcp" - : value == "nntp"; - } - - return false; - } - - /// - /// Determines whether the specified string is a URI string. - /// - /// - /// true if may be a URI string; - /// otherwise, false. - /// - /// - /// A to test. - /// - public static bool MaybeUri (this string value) - { - if (value == null) - return false; - - if (value.Length == 0) - return false; - - var idx = value.IndexOf (':'); - if (idx == -1) - return false; - - if (idx >= 10) - return false; - - var schm = value.Substring (0, idx); - return schm.IsPredefinedScheme (); - } - /// /// Retrieves a sub-array from the specified array. A sub-array starts at /// the specified index in the array. @@ -1614,11 +1569,11 @@ public static bool MaybeUri (this string value) /// An array of T from which to retrieve a sub-array. /// /// - /// An that represents the zero-based index in the array + /// An that specifies the zero-based index in the array /// at which retrieving starts. /// /// - /// An that represents the number of elements to retrieve. + /// An that specifies the number of elements to retrieve. /// /// /// The type of elements in the array. @@ -1656,6 +1611,7 @@ public static T[] SubArray (this T[] array, int startIndex, int length) throw new ArgumentNullException ("array"); var len = array.Length; + if (len == 0) { if (startIndex != 0) throw new ArgumentOutOfRangeException ("startIndex"); @@ -1678,10 +1634,11 @@ public static T[] SubArray (this T[] array, int startIndex, int length) if (length == len) return array; - var subArray = new T[length]; - Array.Copy (array, startIndex, subArray, 0, length); + var ret = new T[length]; - return subArray; + Array.Copy (array, startIndex, ret, 0, length); + + return ret; } /// @@ -1695,11 +1652,11 @@ public static T[] SubArray (this T[] array, int startIndex, int length) /// An array of T from which to retrieve a sub-array. /// /// - /// A that represents the zero-based index in the array + /// A that specifies the zero-based index in the array /// at which retrieving starts. /// /// - /// A that represents the number of elements to retrieve. + /// A that specifies the number of elements to retrieve. /// /// /// The type of elements in the array. @@ -1737,6 +1694,7 @@ public static T[] SubArray (this T[] array, long startIndex, long length) throw new ArgumentNullException ("array"); var len = array.LongLength; + if (len == 0) { if (startIndex != 0) throw new ArgumentOutOfRangeException ("startIndex"); @@ -1759,94 +1717,11 @@ public static T[] SubArray (this T[] array, long startIndex, long length) if (length == len) return array; - var subArray = new T[length]; - Array.Copy (array, startIndex, subArray, 0, length); + var ret = new T[length]; - return subArray; - } + Array.Copy (array, startIndex, ret, 0, length); - /// - /// Executes the specified delegate times. - /// - /// - /// An that specifies the number of times to execute. - /// - /// - /// An delegate to execute. - /// - public static void Times (this int n, Action action) - { - if (n <= 0) - return; - - if (action == null) - return; - - for (int i = 0; i < n; i++) - action (); - } - - /// - /// Executes the specified delegate times. - /// - /// - /// A that specifies the number of times to execute. - /// - /// - /// An delegate to execute. - /// - public static void Times (this long n, Action action) - { - if (n <= 0) - return; - - if (action == null) - return; - - for (long i = 0; i < n; i++) - action (); - } - - /// - /// Executes the specified delegate times. - /// - /// - /// A that specifies the number of times to execute. - /// - /// - /// An delegate to execute. - /// - public static void Times (this uint n, Action action) - { - if (n == 0) - return; - - if (action == null) - return; - - for (uint i = 0; i < n; i++) - action (); - } - - /// - /// Executes the specified delegate times. - /// - /// - /// A that specifies the number of times to execute. - /// - /// - /// An delegate to execute. - /// - public static void Times (this ulong n, Action action) - { - if (n == 0) - return; - - if (action == null) - return; - - for (ulong i = 0; i < n; i++) - action (); + return ret; } /// @@ -1901,195 +1776,6 @@ public static void Times (this long n, Action action) action (i); } - /// - /// Executes the specified delegate times. - /// - /// - /// A that specifies the number of times to execute. - /// - /// - /// - /// An Action<uint> delegate to execute. - /// - /// - /// The parameter is the zero-based count of iteration. - /// - /// - public static void Times (this uint n, Action action) - { - if (n == 0) - return; - - if (action == null) - return; - - for (uint i = 0; i < n; i++) - action (i); - } - - /// - /// Executes the specified delegate times. - /// - /// - /// A that specifies the number of times to execute. - /// - /// - /// - /// An Action<ulong> delegate to execute. - /// - /// - /// The parameter is the zero-based count of iteration. - /// - /// - public static void Times (this ulong n, Action action) - { - if (n == 0) - return; - - if (action == null) - return; - - for (ulong i = 0; i < n; i++) - action (i); - } - - /// - /// Converts the specified byte array to the specified type value. - /// - /// - /// - /// A T converted from . - /// - /// - /// The default value of T if not converted. - /// - /// - /// - /// An array of to convert. - /// - /// - /// - /// One of the enum values. - /// - /// - /// It specifies the byte order of . - /// - /// - /// - /// - /// The type of the return. - /// - /// - /// , , , - /// , , , - /// , , , - /// or . - /// - /// - /// - /// is . - /// - [Obsolete ("This method will be removed.")] - public static T To (this byte[] source, ByteOrder sourceOrder) - where T : struct - { - if (source == null) - throw new ArgumentNullException ("source"); - - if (source.Length == 0) - return default (T); - - var type = typeof (T); - var val = source.ToHostOrder (sourceOrder); - - return type == typeof (Boolean) - ? (T)(object) BitConverter.ToBoolean (val, 0) - : type == typeof (Char) - ? (T)(object) BitConverter.ToChar (val, 0) - : type == typeof (Double) - ? (T)(object) BitConverter.ToDouble (val, 0) - : type == typeof (Int16) - ? (T)(object) BitConverter.ToInt16 (val, 0) - : type == typeof (Int32) - ? (T)(object) BitConverter.ToInt32 (val, 0) - : type == typeof (Int64) - ? (T)(object) BitConverter.ToInt64 (val, 0) - : type == typeof (Single) - ? (T)(object) BitConverter.ToSingle (val, 0) - : type == typeof (UInt16) - ? (T)(object) BitConverter.ToUInt16 (val, 0) - : type == typeof (UInt32) - ? (T)(object) BitConverter.ToUInt32 (val, 0) - : type == typeof (UInt64) - ? (T)(object) BitConverter.ToUInt64 (val, 0) - : default (T); - } - - /// - /// Converts the specified value to a byte array. - /// - /// - /// An array of converted from . - /// - /// - /// A T to convert. - /// - /// - /// - /// One of the enum values. - /// - /// - /// It specifies the byte order of the return. - /// - /// - /// - /// - /// The type of . - /// - /// - /// , , , - /// , , , - /// , , , - /// , or . - /// - /// - [Obsolete ("This method will be removed.")] - public static byte[] ToByteArray (this T value, ByteOrder order) - where T : struct - { - var type = typeof (T); - var bytes = type == typeof (Boolean) - ? BitConverter.GetBytes ((Boolean)(object) value) - : type == typeof (Byte) - ? new byte[] { (Byte)(object) value } - : type == typeof (Char) - ? BitConverter.GetBytes ((Char)(object) value) - : type == typeof (Double) - ? BitConverter.GetBytes ((Double)(object) value) - : type == typeof (Int16) - ? BitConverter.GetBytes ((Int16)(object) value) - : type == typeof (Int32) - ? BitConverter.GetBytes ((Int32)(object) value) - : type == typeof (Int64) - ? BitConverter.GetBytes ((Int64)(object) value) - : type == typeof (Single) - ? BitConverter.GetBytes ((Single)(object) value) - : type == typeof (UInt16) - ? BitConverter.GetBytes ((UInt16)(object) value) - : type == typeof (UInt32) - ? BitConverter.GetBytes ((UInt32)(object) value) - : type == typeof (UInt64) - ? BitConverter.GetBytes ((UInt64)(object) value) - : WebSocket.EmptyBytes; - - if (bytes.Length > 1) { - if (!order.IsHostOrder ()) - Array.Reverse (bytes); - } - - return bytes; - } - /// /// Converts the order of elements in the specified byte array to /// host (this computer architecture) byte order. @@ -2164,19 +1850,18 @@ public static string ToString (this T[] array, string separator) throw new ArgumentNullException ("array"); var len = array.Length; + if (len == 0) return String.Empty; - if (separator == null) - separator = String.Empty; - var buff = new StringBuilder (64); var end = len - 1; for (var i = 0; i < end; i++) buff.AppendFormat ("{0}{1}", array[i], separator); - buff.Append (array[end].ToString ()); + buff.AppendFormat ("{0}", array[end]); + return buff.ToString (); } @@ -2196,62 +1881,15 @@ public static string ToString (this T[] array, string separator) /// public static Uri ToUri (this string value) { - Uri ret; - Uri.TryCreate ( - value, value.MaybeUri () ? UriKind.Absolute : UriKind.Relative, out ret - ); - - return ret; - } - - /// - /// Sends the specified content data with the HTTP response. - /// - /// - /// A that represents the HTTP response - /// used to send the content data. - /// - /// - /// An array of that specifies the content data to send. - /// - /// - /// - /// is . - /// - /// - /// -or- - /// - /// - /// is . - /// - /// - [Obsolete ("This method will be removed.")] - public static void WriteContent ( - this HttpListenerResponse response, byte[] content - ) - { - if (response == null) - throw new ArgumentNullException ("response"); - - if (content == null) - throw new ArgumentNullException ("content"); - - var len = content.LongLength; - if (len == 0) { - response.Close (); - return; - } - - response.ContentLength64 = len; + if (value == null || value.Length == 0) + return null; - var output = response.OutputStream; + var kind = value.MaybeUri () ? UriKind.Absolute : UriKind.Relative; + Uri ret; - if (len <= Int32.MaxValue) - output.Write (content, 0, (int) len); - else - output.WriteBytes (content, 1024); + Uri.TryCreate (value, kind, out ret); - output.Close (); + return ret; } #endregion diff --git a/websocket-sharp/HttpBase.cs b/websocket-sharp/HttpBase.cs index a7dbd4026..c4a244f45 100644 --- a/websocket-sharp/HttpBase.cs +++ b/websocket-sharp/HttpBase.cs @@ -4,7 +4,7 @@ * * 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 @@ -30,6 +30,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.IO; +using System.Linq; using System.Text; using System.Threading; using WebSocketSharp.Net; @@ -41,20 +42,31 @@ internal abstract class HttpBase #region Private Fields private NameValueCollection _headers; - private const int _headersMaxLength = 8192; + private static readonly int _maxMessageHeaderLength; + private string _messageBody; + private byte[] _messageBodyData; private Version _version; #endregion - #region Internal Fields + #region Protected Fields - internal byte[] EntityBodyData; + protected static readonly string CrLf; + protected static readonly string CrLfHt; + protected static readonly string CrLfSp; #endregion - #region Protected Fields + #region Static Constructor - protected const string CrLf = "\r\n"; + static HttpBase () + { + _maxMessageHeaderLength = 8192; + + CrLf = "\r\n"; + CrLfHt = "\r\n\t"; + CrLfSp = "\r\n "; + } #endregion @@ -68,20 +80,40 @@ protected HttpBase (Version version, NameValueCollection headers) #endregion - #region Public Properties + #region Internal Properties - public string EntityBody { + internal byte[] MessageBodyData { get { - if (EntityBodyData == null || EntityBodyData.LongLength == 0) - return String.Empty; + return _messageBodyData; + } + } + + #endregion + + #region Protected Properties + + protected string HeaderSection { + get { + var buff = new StringBuilder (64); + + var fmt = "{0}: {1}{2}"; + + foreach (var key in _headers.AllKeys) + buff.AppendFormat (fmt, key, _headers[key], CrLf); + + buff.Append (CrLf); + + return buff.ToString (); + } + } - Encoding enc = null; + #endregion - var contentType = _headers["Content-Type"]; - if (contentType != null && contentType.Length > 0) - enc = HttpUtility.GetEncoding (contentType); + #region Public Properties - return (enc ?? Encoding.UTF8).GetString (EntityBodyData); + public bool HasMessageBody { + get { + return _messageBodyData != null; } } @@ -91,6 +123,17 @@ public NameValueCollection Headers { } } + public string MessageBody { + get { + if (_messageBody == null) + _messageBody = getMessageBody (); + + return _messageBody; + } + } + + public abstract string MessageHeader { get; } + public Version ProtocolVersion { get { return _version; @@ -101,14 +144,35 @@ public Version ProtocolVersion { #region Private Methods - private static byte[] readEntityBody (Stream stream, string length) + private string getMessageBody () + { + if (_messageBodyData == null || _messageBodyData.LongLength == 0) + return String.Empty; + + var contentType = _headers["Content-Type"]; + + var enc = contentType != null && contentType.Length > 0 + ? HttpUtility.GetEncoding (contentType) + : Encoding.UTF8; + + return enc.GetString (_messageBodyData); + } + + private static byte[] readMessageBodyFrom (Stream stream, string length) { long len; - if (!Int64.TryParse (length, out len)) - throw new ArgumentException ("Cannot be parsed.", "length"); - if (len < 0) - throw new ArgumentOutOfRangeException ("length", "Less than zero."); + if (!Int64.TryParse (length, out len)) { + var msg = "It could not be parsed."; + + throw new ArgumentException (msg, "length"); + } + + if (len < 0) { + var msg = "Less than zero."; + + throw new ArgumentOutOfRangeException ("length", msg); + } return len > 1024 ? stream.ReadBytes (len, 1024) @@ -117,62 +181,93 @@ private static byte[] readEntityBody (Stream stream, string length) : null; } - private static string[] readHeaders (Stream stream, int maxLength) + private static string[] readMessageHeaderFrom (Stream stream) { var buff = new List (); var cnt = 0; - Action add = i => { - if (i == -1) - throw new EndOfStreamException ("The header cannot be read from the data source."); - - buff.Add ((byte) i); - cnt++; - }; - - var read = false; - while (cnt < maxLength) { - if (stream.ReadByte ().EqualsWith ('\r', add) && - stream.ReadByte ().EqualsWith ('\n', add) && - stream.ReadByte ().EqualsWith ('\r', add) && - stream.ReadByte ().EqualsWith ('\n', add)) { - read = true; - break; + Action add = + i => { + if (i == -1) { + var msg = "The header could not be read from the data stream."; + + throw new EndOfStreamException (msg); + } + + buff.Add ((byte) i); + + cnt++; + }; + + var end = false; + + do { + end = stream.ReadByte ().IsEqualTo ('\r', add) + && stream.ReadByte ().IsEqualTo ('\n', add) + && stream.ReadByte ().IsEqualTo ('\r', add) + && stream.ReadByte ().IsEqualTo ('\n', add); + + if (cnt > _maxMessageHeaderLength) { + var msg = "The length of the header is greater than the max length."; + + throw new InvalidOperationException (msg); } } + while (!end); - if (!read) - throw new WebSocketException ("The length of header part is greater than the max length."); + var bytes = buff.ToArray (); - return Encoding.UTF8.GetString (buff.ToArray ()) - .Replace (CrLf + " ", " ") - .Replace (CrLf + "\t", " ") + return Encoding.UTF8.GetString (bytes) + .Replace (CrLfSp, " ") + .Replace (CrLfHt, " ") .Split (new[] { CrLf }, StringSplitOptions.RemoveEmptyEntries); } #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) + protected static T Read ( + Stream stream, + Func parser, + int millisecondsTimeout + ) where T : HttpBase { + T ret = null; + var timeout = false; var timer = new Timer ( - state => { - timeout = true; - stream.Close (); - }, - null, - millisecondsTimeout, - -1); - - T http = null; + state => { + timeout = true; + + stream.Close (); + }, + null, + millisecondsTimeout, + -1 + ); + Exception exception = null; + try { - http = parser (readHeaders (stream, _headersMaxLength)); - var contentLen = http.Headers["Content-Length"]; + var header = readMessageHeaderFrom (stream); + ret = parser (header); + + var contentLen = ret.Headers["Content-Length"]; + if (contentLen != null && contentLen.Length > 0) - http.EntityBodyData = readEntityBody (stream, contentLen); + ret._messageBodyData = readMessageBodyFrom (stream, contentLen); } catch (Exception ex) { exception = ex; @@ -182,16 +277,19 @@ protected static T Read (Stream stream, Func parser, int millise timer.Dispose (); } - var msg = timeout - ? "A timeout has occurred while reading an HTTP request/response." - : exception != null - ? "An exception has occurred while reading an HTTP request/response." - : null; + if (timeout) { + var msg = "A timeout has occurred."; + + throw new WebSocketException (msg); + } + + if (exception != null) { + var msg = "An exception has occurred."; - if (msg != null) throw new WebSocketException (msg, exception); + } - return http; + return ret; } #endregion @@ -200,9 +298,20 @@ protected static T Read (Stream stream, Func parser, int millise public byte[] ToByteArray () { - return Encoding.UTF8.GetBytes (ToString ()); + var headerData = Encoding.UTF8.GetBytes (MessageHeader); + + return _messageBodyData != null + ? headerData.Concat (_messageBodyData).ToArray () + : headerData; + } + + public override string ToString () + { + return _messageBodyData != null + ? MessageHeader + MessageBody + : MessageHeader; } - + #endregion } } diff --git a/websocket-sharp/HttpRequest.cs b/websocket-sharp/HttpRequest.cs index fe74d5afb..dd51d0101 100644 --- a/websocket-sharp/HttpRequest.cs +++ b/websocket-sharp/HttpRequest.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2015 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,38 +47,56 @@ internal class HttpRequest : HttpBase private CookieCollection _cookies; private string _method; - private string _uri; + private string _target; #endregion #region Private Constructors - private HttpRequest (string method, string uri, Version version, NameValueCollection headers) + private HttpRequest ( + string method, + string target, + Version version, + NameValueCollection headers + ) : base (version, headers) { _method = method; - _uri = uri; + _target = target; } #endregion #region Internal Constructors - internal HttpRequest (string method, string uri) - : this (method, uri, HttpVersion.Version11, new NameValueCollection ()) + internal HttpRequest (string method, string target) + : this (method, target, HttpVersion.Version11, new NameValueCollection ()) { Headers["User-Agent"] = "websocket-sharp/1.0"; } #endregion + #region Internal Properties + + internal string RequestLine { + get { + var fmt = "{0} {1} HTTP/{2}{3}"; + + return String.Format (fmt, _method, _target, ProtocolVersion, CrLf); + } + } + + #endregion + #region Public Properties public AuthenticationResponse AuthenticationResponse { get { - var res = Headers["Authorization"]; - return res != null && res.Length > 0 - ? AuthenticationResponse.Parse (res) + var val = Headers["Authorization"]; + + return val != null && val.Length > 0 + ? AuthenticationResponse.Parse (val) : null; } } @@ -106,9 +124,15 @@ public bool IsWebSocketRequest { } } - public string RequestUri { + public override string MessageHeader { get { - return _uri; + return RequestLine + HeaderSection; + } + } + + public string RequestTarget { + get { + return _target; } } @@ -116,59 +140,82 @@ public string RequestUri { #region Internal Methods - internal static HttpRequest CreateConnectRequest (Uri uri) + internal static HttpRequest CreateConnectRequest (Uri targetUri) { - var host = uri.DnsSafeHost; - var port = uri.Port; - var authority = String.Format ("{0}:{1}", host, port); - var req = new HttpRequest ("CONNECT", authority); - req.Headers["Host"] = port == 80 ? host : authority; + var fmt = "{0}:{1}"; + var host = targetUri.DnsSafeHost; + var port = targetUri.Port; + var authority = String.Format (fmt, host, port); - return req; + var ret = new HttpRequest ("CONNECT", authority); + + ret.Headers["Host"] = port != 80 ? authority : host; + + return ret; } - internal static HttpRequest CreateWebSocketRequest (Uri uri) + internal static HttpRequest CreateWebSocketHandshakeRequest (Uri targetUri) { - var req = new HttpRequest ("GET", uri.PathAndQuery); - var headers = req.Headers; + var ret = new HttpRequest ("GET", targetUri.PathAndQuery); + + var headers = ret.Headers; + + var port = targetUri.Port; + var schm = targetUri.Scheme; + var isDefaultPort = (port == 80 && schm == "ws") + || (port == 443 && schm == "wss"); - // Only includes a port number in the Host header value if it's non-default. - // See: https://tools.ietf.org/html/rfc6455#page-17 - var port = uri.Port; - var schm = uri.Scheme; - headers["Host"] = (port == 80 && schm == "ws") || (port == 443 && schm == "wss") - ? uri.DnsSafeHost - : uri.Authority; + headers["Host"] = !isDefaultPort + ? targetUri.Authority + : targetUri.DnsSafeHost; headers["Upgrade"] = "websocket"; headers["Connection"] = "Upgrade"; - return req; + return ret; } internal HttpResponse GetResponse (Stream stream, int millisecondsTimeout) { - var buff = ToByteArray (); - stream.Write (buff, 0, buff.Length); + WriteTo (stream); - return Read (stream, HttpResponse.Parse, millisecondsTimeout); + return HttpResponse.ReadResponse (stream, millisecondsTimeout); } - internal static HttpRequest Parse (string[] headerParts) + internal static HttpRequest Parse (string[] messageHeader) { - var requestLine = headerParts[0].Split (new[] { ' ' }, 3); - if (requestLine.Length != 3) - throw new ArgumentException ("Invalid request line: " + headerParts[0]); + var len = messageHeader.Length; + + if (len == 0) { + var msg = "An empty request header."; + + throw new ArgumentException (msg); + } + + var rlParts = messageHeader[0].Split (new[] { ' ' }, 3); + + if (rlParts.Length != 3) { + var msg = "It includes an invalid request line."; + + throw new ArgumentException (msg); + } + + var method = rlParts[0]; + var target = rlParts[1]; + var ver = rlParts[2].Substring (5).ToVersion (); var headers = new WebHeaderCollection (); - for (int i = 1; i < headerParts.Length; i++) - headers.InternalSet (headerParts[i], false); - return new HttpRequest ( - requestLine[0], requestLine[1], new Version (requestLine[2].Substring (5)), headers); + for (var i = 1; i < len; i++) + headers.InternalSet (messageHeader[i], false); + + return new HttpRequest (method, target, ver, headers); } - internal static HttpRequest Read (Stream stream, int millisecondsTimeout) + internal static HttpRequest ReadRequest ( + Stream stream, + int millisecondsTimeout + ) { return Read (stream, Parse, millisecondsTimeout); } @@ -183,33 +230,22 @@ public void SetCookies (CookieCollection cookies) return; var buff = new StringBuilder (64); - foreach (var cookie in cookies.Sorted) - if (!cookie.Expired) - buff.AppendFormat ("{0}; ", cookie.ToString ()); - var len = buff.Length; - if (len > 2) { - buff.Length = len - 2; - Headers["Cookie"] = buff.ToString (); - } - } + foreach (var cookie in cookies.Sorted) { + if (cookie.Expired) + continue; - public override string ToString () - { - var output = new StringBuilder (64); - output.AppendFormat ("{0} {1} HTTP/{2}{3}", _method, _uri, ProtocolVersion, CrLf); + buff.AppendFormat ("{0}; ", cookie); + } - var headers = Headers; - foreach (var key in headers.AllKeys) - output.AppendFormat ("{0}: {1}{2}", key, headers[key], CrLf); + var len = buff.Length; - output.Append (CrLf); + if (len <= 2) + return; - var entity = EntityBody; - if (entity.Length > 0) - output.Append (entity); + buff.Length = len - 2; - return output.ToString (); + Headers["Cookie"] = buff.ToString (); } #endregion diff --git a/websocket-sharp/HttpResponse.cs b/websocket-sharp/HttpResponse.cs index 831b72783..fb2f9d312 100644 --- a/websocket-sharp/HttpResponse.cs +++ b/websocket-sharp/HttpResponse.cs @@ -4,7 +4,7 @@ * * 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 @@ -38,14 +38,19 @@ internal class HttpResponse : HttpBase { #region Private Fields - private string _code; + private int _code; private string _reason; #endregion #region Private Constructors - private HttpResponse (string code, string reason, Version version, NameValueCollection headers) + private HttpResponse ( + int code, + string reason, + Version version, + NameValueCollection headers + ) : base (version, headers) { _code = code; @@ -56,67 +61,118 @@ private HttpResponse (string code, string reason, Version version, NameValueColl #region Internal Constructors + internal HttpResponse (int code) + : this (code, code.GetStatusDescription ()) + { + } + internal HttpResponse (HttpStatusCode code) - : this (code, code.GetDescription ()) + : this ((int) code) { } - internal HttpResponse (HttpStatusCode code, string reason) - : this (((int) code).ToString (), reason, HttpVersion.Version11, new NameValueCollection ()) + internal HttpResponse (int code, string reason) + : this ( + code, + reason, + HttpVersion.Version11, + new NameValueCollection () + ) { Headers["Server"] = "websocket-sharp/1.0"; } + internal HttpResponse (HttpStatusCode code, string reason) + : this ((int) code, reason) + { + } + + #endregion + + #region Internal Properties + + internal string StatusLine { + get { + return _reason != null + ? String.Format ( + "HTTP/{0} {1} {2}{3}", + ProtocolVersion, + _code, + _reason, + CrLf + ) + : String.Format ( + "HTTP/{0} {1}{2}", + ProtocolVersion, + _code, + CrLf + ); + } + } + #endregion #region Public Properties - public CookieCollection Cookies { + public bool CloseConnection { get { - return Headers.GetCookies (true); + var compType = StringComparison.OrdinalIgnoreCase; + + return Headers.Contains ("Connection", "close", compType); } } - public bool HasConnectionClose { + public CookieCollection Cookies { get { - var comparison = StringComparison.OrdinalIgnoreCase; - return Headers.Contains ("Connection", "close", comparison); + return Headers.GetCookies (true); } } public bool IsProxyAuthenticationRequired { get { - return _code == "407"; + return _code == 407; } } public bool IsRedirect { get { - return _code == "301" || _code == "302"; + return _code == 301 || _code == 302; + } + } + + public bool IsSuccess { + get { + return _code >= 200 && _code <= 299; } } public bool IsUnauthorized { get { - return _code == "401"; + return _code == 401; } } public bool IsWebSocketResponse { get { return ProtocolVersion > HttpVersion.Version10 - && _code == "101" + && _code == 101 && Headers.Upgrades ("websocket"); } } + public override string MessageHeader { + get { + return StatusLine + HeaderSection; + } + } + public string Reason { get { return _reason; } } - public string StatusCode { + public int StatusCode { get { return _code; } @@ -128,46 +184,69 @@ public string StatusCode { internal static HttpResponse CreateCloseResponse (HttpStatusCode code) { - var res = new HttpResponse (code); - res.Headers["Connection"] = "close"; + var ret = new HttpResponse (code); + + ret.Headers["Connection"] = "close"; - return res; + return ret; } internal static HttpResponse CreateUnauthorizedResponse (string challenge) { - var res = new HttpResponse (HttpStatusCode.Unauthorized); - res.Headers["WWW-Authenticate"] = challenge; + var ret = new HttpResponse (HttpStatusCode.Unauthorized); + + ret.Headers["WWW-Authenticate"] = challenge; - return res; + return ret; } - internal static HttpResponse CreateWebSocketResponse () + internal static HttpResponse CreateWebSocketHandshakeResponse () { - var res = new HttpResponse (HttpStatusCode.SwitchingProtocols); + var ret = new HttpResponse (HttpStatusCode.SwitchingProtocols); + + var headers = ret.Headers; - var headers = res.Headers; headers["Upgrade"] = "websocket"; headers["Connection"] = "Upgrade"; - return res; + return ret; } - internal static HttpResponse Parse (string[] headerParts) + internal static HttpResponse Parse (string[] messageHeader) { - var statusLine = headerParts[0].Split (new[] { ' ' }, 3); - if (statusLine.Length != 3) - throw new ArgumentException ("Invalid status line: " + headerParts[0]); + var len = messageHeader.Length; + + if (len == 0) { + var msg = "An empty response header."; + + throw new ArgumentException (msg); + } + + var slParts = messageHeader[0].Split (new[] { ' ' }, 3); + var plen = slParts.Length; + + if (plen < 2) { + var msg = "It includes an invalid status line."; + + throw new ArgumentException (msg); + } + + var code = slParts[1].ToInt32 (); + var reason = plen == 3 ? slParts[2] : null; + var ver = slParts[0].Substring (5).ToVersion (); var headers = new WebHeaderCollection (); - for (int i = 1; i < headerParts.Length; i++) - headers.InternalSet (headerParts[i], true); - return new HttpResponse ( - statusLine[1], statusLine[2], new Version (statusLine[0].Substring (5)), headers); + for (var i = 1; i < len; i++) + headers.InternalSet (messageHeader[i], true); + + return new HttpResponse (code, reason, ver, headers); } - internal static HttpResponse Read (Stream stream, int millisecondsTimeout) + internal static HttpResponse ReadResponse ( + Stream stream, + int millisecondsTimeout + ) { return Read (stream, Parse, millisecondsTimeout); } @@ -182,26 +261,12 @@ public void SetCookies (CookieCollection cookies) return; var headers = Headers; - foreach (var cookie in cookies.Sorted) - headers.Add ("Set-Cookie", cookie.ToResponseString ()); - } - public override string ToString () - { - var output = new StringBuilder (64); - output.AppendFormat ("HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf); + foreach (var cookie in cookies.Sorted) { + var val = cookie.ToResponseString (); - var headers = Headers; - foreach (var key in headers.AllKeys) - output.AppendFormat ("{0}: {1}{2}", key, headers[key], CrLf); - - output.Append (CrLf); - - var entity = EntityBody; - if (entity.Length > 0) - output.Append (entity); - - return output.ToString (); + headers.Add ("Set-Cookie", val); + } } #endregion diff --git a/websocket-sharp/LogData.cs b/websocket-sharp/LogData.cs index 9c0843093..df8c96b89 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-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 @@ -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,8 @@ 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 that represents + /// the logging level of the log data. /// public LogLevel Level { get { @@ -113,34 +117,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]); + for (var i = 0; i < msgs.Length; i++) + buff.AppendFormat (" {0}\n", msgs[i]); - 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..ad6135803 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-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 @@ -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 references the method used to output 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,16 @@ 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 +235,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 +253,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 +271,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 +303,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 +321,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/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/Chunk.cs b/websocket-sharp/Net/Chunk.cs index 7b6268b7f..9ed28f864 100644 --- a/websocket-sharp/Net/Chunk.cs +++ b/websocket-sharp/Net/Chunk.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2003 Ximian, Inc (http://www.ximian.com) - * Copyright (c) 2014-2015 sta.blockhead + * Copyright (c) 2014-2021 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,13 +74,15 @@ public int ReadLeft { public int Read (byte[] buffer, int offset, int count) { var left = _data.Length - _offset; + if (left == 0) - return left; + return 0; if (count > left) count = left; Buffer.BlockCopy (_data, _offset, buffer, offset, count); + _offset += count; return count; diff --git a/websocket-sharp/Net/ChunkStream.cs b/websocket-sharp/Net/ChunkStream.cs index a5271b573..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-2015 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 @@ -53,8 +53,11 @@ internal class ChunkStream private int _chunkRead; private int _chunkSize; private List _chunks; + private int _count; + private byte[] _endBuffer; private bool _gotIt; private WebHeaderCollection _headers; + private int _offset; private StringBuilder _saved; private bool _sawCr; private InputChunkState _state; @@ -67,24 +70,31 @@ internal class ChunkStream public ChunkStream (WebHeaderCollection headers) { _headers = headers; + _chunkSize = -1; _chunks = new List (); _saved = new StringBuilder (); } - public ChunkStream (byte[] buffer, int offset, int count, WebHeaderCollection headers) - : this (headers) - { - Write (buffer, offset, count); - } - #endregion #region Internal Properties - internal WebHeaderCollection Headers { + internal int Count { get { - return _headers; + return _count; + } + } + + internal byte[] EndBuffer { + get { + return _endBuffer; + } + } + + internal int Offset { + get { + return _offset; } } @@ -92,15 +102,15 @@ internal WebHeaderCollection Headers { #region Public Properties - public int ChunkLeft { + public WebHeaderCollection Headers { get { - return _chunkSize - _chunkRead; + return _headers; } } - public bool WantMore { + public bool WantsMore { get { - return _state != InputChunkState.End; + return _state < InputChunkState.End; } } @@ -111,19 +121,22 @@ public bool WantMore { private int read (byte[] buffer, int offset, int count) { var nread = 0; - var cnt = _chunks.Count; + for (var i = 0; i < cnt; i++) { var chunk = _chunks[i]; + if (chunk == null) continue; if (chunk.ReadLeft == 0) { _chunks[i] = null; + continue; } nread += chunk.Read (buffer, offset + nread, count - nread); + if (nread == count) break; } @@ -131,12 +144,6 @@ private int read (byte[] buffer, int offset, int count) return nread; } - private static string removeChunkExtension (string value) - { - var idx = value.IndexOf (';'); - return idx > -1 ? value.Substring (0, idx) : value; - } - private InputChunkState seekCrLf (byte[] buffer, ref int offset, int length) { if (!_sawCr) { @@ -144,6 +151,7 @@ private InputChunkState seekCrLf (byte[] buffer, ref int offset, int length) throwProtocolViolation ("CR is expected."); _sawCr = true; + if (offset == length) return InputChunkState.DataEnded; } @@ -154,11 +162,17 @@ private InputChunkState seekCrLf (byte[] buffer, ref int offset, int length) return InputChunkState.None; } - private InputChunkState setChunkSize (byte[] buffer, ref int offset, int length) + private InputChunkState setChunkSize ( + byte[] buffer, + ref int offset, + int length + ) { byte b = 0; + while (offset < length) { b = buffer[offset++]; + if (_sawCr) { if (b != 10) throwProtocolViolation ("LF is expected."); @@ -168,71 +182,77 @@ private InputChunkState setChunkSize (byte[] buffer, ref int offset, int length) if (b == 13) { _sawCr = true; + continue; } if (b == 10) throwProtocolViolation ("LF is unexpected."); - if (b == 32) // SP + if (_gotIt) + continue; + + if (b == 32 || b == 59) { // SP or ';' _gotIt = true; - if (!_gotIt) - _saved.Append ((char) b); + continue; + } - if (_saved.Length > 20) - throwProtocolViolation ("The chunk size is too long."); + _saved.Append ((char) b); } - if (!_sawCr || b != 10) + if (_saved.Length > 20) + throwProtocolViolation ("The chunk size is too big."); + + if (b != 10) return InputChunkState.None; - _chunkRead = 0; + var s = _saved.ToString (); + try { - _chunkSize = Int32.Parse ( - removeChunkExtension (_saved.ToString ()), NumberStyles.HexNumber); + _chunkSize = Int32.Parse (s, NumberStyles.HexNumber); } catch { throwProtocolViolation ("The chunk size cannot be parsed."); } + _chunkRead = 0; + if (_chunkSize == 0) { _trailerState = 2; + return InputChunkState.Trailer; } return InputChunkState.Data; } - private InputChunkState setTrailer (byte[] buffer, ref int offset, int length) + private InputChunkState setTrailer ( + byte[] buffer, + ref int offset, + int length + ) { - // Check if no trailer. - if (_trailerState == 2 && buffer[offset] == 13 && _saved.Length == 0) { - offset++; - if (offset < length && buffer[offset] == 10) { - offset++; - return InputChunkState.End; - } - - offset--; - } + while (offset < length) { + if (_trailerState == 4) // CR LF CR LF + break; - while (offset < length && _trailerState < 4) { var b = buffer[offset++]; + _saved.Append ((char) b); - if (_saved.Length > 4196) - throwProtocolViolation ("The trailer is too long."); - if (_trailerState == 1 || _trailerState == 3) { + if (_trailerState == 1 || _trailerState == 3) { // CR or CR LF CR if (b != 10) throwProtocolViolation ("LF is expected."); _trailerState++; + continue; } if (b == 13) { _trailerState++; + continue; } @@ -242,31 +262,52 @@ private InputChunkState setTrailer (byte[] buffer, ref int offset, int length) _trailerState = 0; } + var len = _saved.Length; + + if (len > 4196) + throwProtocolViolation ("The trailer is too long."); + if (_trailerState < 4) return InputChunkState.Trailer; - _saved.Length -= 2; - var reader = new StringReader (_saved.ToString ()); + if (len == 2) + return InputChunkState.End; + + _saved.Length = len - 2; + + var val = _saved.ToString (); + var reader = new StringReader (val); + + while (true) { + var line = reader.ReadLine (); + + if (line == null || line.Length == 0) + break; - string line; - while ((line = reader.ReadLine ()) != null && line.Length > 0) _headers.Add (line); + } return InputChunkState.End; } private static void throwProtocolViolation (string message) { - throw new WebException (message, null, WebExceptionStatus.ServerProtocolViolation, null); + throw new WebException ( + message, + null, + WebExceptionStatus.ServerProtocolViolation, + null + ); } - private void write (byte[] buffer, ref int offset, int length) + private void write (byte[] buffer, int offset, int length) { if (_state == InputChunkState.End) throwProtocolViolation ("The chunks were ended."); if (_state == InputChunkState.None) { _state = setChunkSize (buffer, ref offset, length); + if (_state == InputChunkState.None) return; @@ -275,64 +316,92 @@ private void write (byte[] buffer, ref int offset, int length) _gotIt = false; } - if (_state == InputChunkState.Data && offset < length) { + if (_state == InputChunkState.Data) { + if (offset >= length) + return; + _state = writeData (buffer, ref offset, length); + if (_state == InputChunkState.Data) return; } - if (_state == InputChunkState.DataEnded && offset < length) { + if (_state == InputChunkState.DataEnded) { + if (offset >= length) + return; + _state = seekCrLf (buffer, ref offset, length); + if (_state == InputChunkState.DataEnded) return; _sawCr = false; } - if (_state == InputChunkState.Trailer && offset < length) { + if (_state == InputChunkState.Trailer) { + if (offset >= length) + return; + _state = setTrailer (buffer, ref offset, length); + if (_state == InputChunkState.Trailer) return; _saved.Length = 0; } - if (offset < length) - write (buffer, ref offset, length); + if (_state == InputChunkState.End) { + _endBuffer = buffer; + _offset = offset; + _count = length - offset; + + return; + } + + if (offset >= length) + return; + + write (buffer, offset, length); } - private InputChunkState writeData (byte[] buffer, ref int offset, int length) + private InputChunkState writeData ( + byte[] buffer, + ref int offset, + int length + ) { var cnt = length - offset; var left = _chunkSize - _chunkRead; + if (cnt > left) cnt = left; var data = new byte[cnt]; + Buffer.BlockCopy (buffer, offset, data, 0, cnt); - _chunks.Add (new Chunk (data)); + + var chunk = new Chunk (data); + + _chunks.Add (chunk); offset += cnt; _chunkRead += cnt; - return _chunkRead == _chunkSize ? InputChunkState.DataEnded : InputChunkState.Data; + return _chunkRead == _chunkSize + ? InputChunkState.DataEnded + : InputChunkState.Data; } #endregion #region Internal Methods - internal void ResetBuffer () + internal void ResetChunkStore () { _chunkRead = 0; _chunkSize = -1; - _chunks.Clear (); - } - internal int WriteAndReadBack (byte[] buffer, int offset, int writeCount, int readCount) - { - Write (buffer, offset, writeCount); - return Read (buffer, offset, readCount); + _chunks.Clear (); } #endregion @@ -352,7 +421,7 @@ public void Write (byte[] buffer, int offset, int count) if (count <= 0) return; - write (buffer, ref offset, offset + count); + write (buffer, offset, offset + count); } #endregion diff --git a/websocket-sharp/Net/ChunkedRequestStream.cs b/websocket-sharp/Net/ChunkedRequestStream.cs index 913b505c3..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-2015 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 @@ -46,7 +46,7 @@ internal class ChunkedRequestStream : RequestStream { #region Private Fields - private const int _bufferLength = 8192; + private static readonly int _bufferLength; private HttpListenerContext _context; private ChunkStream _decoder; private bool _disposed; @@ -54,27 +54,60 @@ internal class ChunkedRequestStream : RequestStream #endregion + #region Static Constructor + + static ChunkedRequestStream () + { + _bufferLength = 8192; + } + + #endregion + #region Internal Constructors internal ChunkedRequestStream ( - Stream stream, byte[] buffer, int offset, int count, HttpListenerContext context) - : base (stream, buffer, offset, count) + Stream innerStream, + byte[] initialBuffer, + int offset, + int count, + HttpListenerContext context + ) + : base (innerStream, initialBuffer, offset, count, -1) { _context = context; - _decoder = new ChunkStream ((WebHeaderCollection) context.Request.Headers); + + _decoder = new ChunkStream ( + (WebHeaderCollection) context.Request.Headers + ); } #endregion #region Internal Properties - internal ChunkStream Decoder { + internal bool HasRemainingBuffer { get { - return _decoder; + return _decoder.Count + Count > 0; } + } + + internal byte[] RemainingBuffer { + get { + using (var buff = new MemoryStream ()) { + var cnt = _decoder.Count; + + if (cnt > 0) + buff.Write (_decoder.EndBuffer, _decoder.Offset, cnt); + + cnt = Count; - set { - _decoder = value; + if (cnt > 0) + buff.Write (InitialBuffer, Offset, cnt); + + buff.Close (); + + return buff.ToArray (); + } } } @@ -86,26 +119,34 @@ private void onRead (IAsyncResult asyncResult) { var rstate = (ReadBufferState) asyncResult.AsyncState; var ares = rstate.AsyncResult; + try { var nread = base.EndRead (asyncResult); + _decoder.Write (ares.Buffer, ares.Offset, nread); + nread = _decoder.Read (rstate.Buffer, rstate.Offset, rstate.Count); + rstate.Offset += nread; rstate.Count -= nread; - if (rstate.Count == 0 || !_decoder.WantMore || nread == 0) { - _noMoreData = !_decoder.WantMore && nread == 0; + + if (rstate.Count == 0 || !_decoder.WantsMore || nread == 0) { + _noMoreData = !_decoder.WantsMore && nread == 0; + ares.Count = rstate.InitialCount - rstate.Count; + ares.Complete (); return; } - ares.Offset = 0; - ares.Count = Math.Min (_bufferLength, _decoder.ChunkLeft + 6); base.BeginRead (ares.Buffer, ares.Offset, ares.Count, onRead, rstate); } catch (Exception ex) { - _context.Connection.SendError (ex.Message, 400); + _context.ErrorMessage = "I/O operation aborted"; + + _context.SendError (); + ares.Complete (ex); } } @@ -115,45 +156,65 @@ 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) - throw new ObjectDisposedException (GetType ().ToString ()); + throw new ObjectDisposedException (ObjectName); if (buffer == null) throw new ArgumentNullException ("buffer"); - if (offset < 0) - throw new ArgumentOutOfRangeException ("offset", "A negative value."); + if (offset < 0) { + var msg = "A negative value."; - if (count < 0) - throw new ArgumentOutOfRangeException ("count", "A negative value."); + throw new ArgumentOutOfRangeException ("offset", msg); + } + + if (count < 0) { + var msg = "A negative value."; + + throw new ArgumentOutOfRangeException ("count", msg); + } var len = buffer.Length; - if (offset + count > len) - throw new ArgumentException ( - "The sum of 'offset' and 'count' is greater than 'buffer' length."); + + if (offset + count > len) { + var msg = "The sum of offset and count is greater than the length of buffer."; + + throw new ArgumentException (msg); + } var ares = new HttpStreamAsyncResult (callback, state); + if (_noMoreData) { ares.Complete (); + return ares; } var nread = _decoder.Read (buffer, offset, count); + offset += nread; count -= nread; + if (count == 0) { - // Got all we wanted, no need to bother the decoder yet. ares.Count = nread; + ares.Complete (); return ares; } - if (!_decoder.WantMore) { + if (!_decoder.WantsMore) { _noMoreData = nread == 0; + ares.Count = nread; + ares.Complete (); return ares; @@ -164,7 +225,9 @@ 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); return ares; @@ -175,27 +238,35 @@ public override void Close () if (_disposed) return; - _disposed = true; base.Close (); + + _disposed = true; } public override int EndRead (IAsyncResult asyncResult) { if (_disposed) - throw new ObjectDisposedException (GetType ().ToString ()); + throw new ObjectDisposedException (ObjectName); if (asyncResult == null) throw new ArgumentNullException ("asyncResult"); var ares = asyncResult as HttpStreamAsyncResult; - if (ares == null) - throw new ArgumentException ("A wrong IAsyncResult.", "asyncResult"); + + if (ares == null) { + var msg = "A wrong IAsyncResult instance."; + + throw new ArgumentException (msg, "asyncResult"); + } if (!ares.IsCompleted) ares.AsyncWaitHandle.WaitOne (); - if (ares.HasException) - throw new HttpListenerException (400, "I/O operation aborted."); + if (ares.HasException) { + var msg = "The I/O operation has been aborted."; + + throw new HttpListenerException (995, msg); + } return ares.Count; } @@ -203,6 +274,7 @@ public override int EndRead (IAsyncResult asyncResult) public override int Read (byte[] buffer, int offset, int count) { var ares = BeginRead (buffer, offset, count, null, null); + return EndRead (ares); } diff --git a/websocket-sharp/Net/ClientSslConfiguration.cs b/websocket-sharp/Net/ClientSslConfiguration.cs index 800bcb30d..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-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 @@ -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 { @@ -60,29 +61,35 @@ public class ClientSslConfiguration #region Public Constructors /// - /// Initializes a new instance of the class. - /// - public ClientSslConfiguration () - { - _enabledSslProtocols = SslProtocols.Default; - } - - /// - /// Initializes a new instance of the class - /// with the specified . + /// Initializes a new instance of the + /// class with the specified target host name. /// /// - /// A that represents the target host server name. + /// A that specifies the name of the server that + /// will share a secure connection with the client. /// + /// + /// is an empty string. + /// + /// + /// is . + /// public ClientSslConfiguration (string targetHost) { + if (targetHost == null) + throw new ArgumentNullException ("targetHost"); + + if (targetHost.Length == 0) + throw new ArgumentException ("An empty string.", "targetHost"); + _targetHost = targetHost; - _enabledSslProtocols = SslProtocols.Default; + + _enabledSslProtocols = SslProtocols.None; } /// - /// Copies the parameters from the specified to - /// a new instance of the class. + /// Initializes a new instance of the + /// class copying from the specified configuration. /// /// /// A from which to copy. @@ -131,15 +138,16 @@ public bool CheckCertificateRevocation { } /// - /// Gets or sets the certificates from which to select one to - /// supply to the server. + /// 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. /// /// - /// That collection contains client certificates from which to select. + /// if not present. /// /// /// The default value is . @@ -156,21 +164,23 @@ public X509CertificateCollection ClientCertificates { } /// - /// Gets or sets the callback used to select the certificate to - /// supply to the server. + /// Gets or sets the callback used to select the certificate to supply to + /// the server. /// /// - /// No certificate is supplied if the callback returns - /// . + /// No certificate is supplied if the callback returns . /// /// /// - /// 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 { @@ -187,15 +197,17 @@ public LocalCertificateSelectionCallback ClientCertificateSelectionCallback { } /// - /// Gets or sets the protocols used for authentication. + /// Gets or sets the enabled versions of the SSL/TLS protocols. /// /// /// - /// The enum values that represent - /// the protocols used for authentication. + /// Any of the enum values. /// /// - /// The default value is . + /// It represents the enabled versions of the SSL/TLS protocols. + /// + /// + /// The default value is . /// /// public SslProtocols EnabledSslProtocols { @@ -209,20 +221,22 @@ public SslProtocols EnabledSslProtocols { } /// - /// Gets or sets the callback used to validate the certificate - /// supplied by the server. + /// Gets or sets the callback used to validate the certificate supplied by + /// the server. /// /// /// The certificate is valid if the callback returns true. /// /// /// - /// A delegate that - /// invokes the method called for validating the certificate. + /// A delegate. + /// + /// + /// It represents the delegate called when the client validates + /// the certificate. /// /// - /// The default value is a delegate that invokes a method that - /// only returns true. + /// The default value invokes a method that only returns true. /// /// public RemoteCertificateValidationCallback ServerCertificateValidationCallback { @@ -239,24 +253,30 @@ public RemoteCertificateValidationCallback ServerCertificateValidationCallback { } /// - /// Gets or sets the target host server name. + /// Gets or sets the target host name. /// /// - /// - /// A or - /// if not specified. - /// - /// - /// That string represents the name of the server that - /// will share a secure connection with a client. - /// + /// A that represents the name of the server that + /// will share a secure connection with the client. /// + /// + /// 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; } set { + if (value == null) + throw new ArgumentNullException ("value"); + + if (value.Length == 0) + throw new ArgumentException ("An empty string.", "value"); + _targetHost = value; } } 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..9a8ce641b 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-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,6 +85,7 @@ internal IList List { internal IEnumerable Sorted { get { var list = new List (_list); + if (list.Count > 1) list.Sort (compareForSorted); @@ -223,8 +224,10 @@ public object SyncRoot { private void add (Cookie cookie) { var idx = search (cookie); + if (idx == -1) { _list.Add (cookie); + return; } @@ -240,11 +243,16 @@ private static int compareForSort (Cookie x, Cookie y) private static int compareForSorted (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 +261,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 +289,7 @@ private static CookieCollection parseRequest (string value) if (idx == 0) { if (cookie != null) { ret.add (cookie); + cookie = null; } @@ -293,11 +305,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 +325,7 @@ private static CookieCollection parseRequest (string value) continue; cookie.Path = val; + continue; } @@ -320,6 +337,7 @@ private static CookieCollection parseRequest (string value) continue; cookie.Domain = val; + continue; } @@ -331,6 +349,7 @@ private static CookieCollection parseRequest (string value) continue; cookie.Port = val; + continue; } @@ -355,37 +374,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 +420,7 @@ private static CookieCollection parseResponse (string value) if (idx == 0) { if (cookie != null) { ret.add (cookie); + cookie = null; } @@ -413,11 +439,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 +467,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 +501,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 +521,7 @@ out expires continue; cookie.Path = val; + continue; } @@ -490,6 +533,7 @@ out expires continue; cookie.Domain = val; + continue; } @@ -501,6 +545,7 @@ out expires continue; cookie.Port = val; + continue; } @@ -512,6 +557,7 @@ out expires continue; cookie.Comment = urlDecode (val, Encoding.UTF8); + continue; } @@ -523,6 +569,7 @@ out expires continue; cookie.CommentUri = val.Unquote ().ToUri (); + continue; } @@ -534,6 +581,7 @@ out expires continue; cookie.SameSite = val.Unquote (); + continue; } @@ -579,9 +627,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 +637,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 +664,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 +680,17 @@ internal void Sort () /// /// A to add. /// - /// - /// The collection is read-only. - /// /// /// is . /// + /// + /// 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 +706,17 @@ public void Add (Cookie cookie) /// /// A that contains the cookies to add. /// - /// - /// The collection is read-only. - /// /// /// is . /// + /// + /// The collection is read-only. + /// public void Add (CookieCollection cookies) { if (_readOnly) { var msg = "The collection is read-only."; + throw new InvalidOperationException (msg); } @@ -684,6 +737,7 @@ public void Clear () { if (_readOnly) { var msg = "The collection is read-only."; + throw new InvalidOperationException (msg); } @@ -723,26 +777,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); } @@ -776,16 +834,17 @@ public IEnumerator GetEnumerator () /// /// A to remove. /// - /// - /// The collection is read-only. - /// /// /// is . /// + /// + /// 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 +852,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 67fa26393..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-2016 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 @@ -61,16 +61,16 @@ internal sealed class EndPointListener { #region Private Fields - private List _all; // host == '+' - private static readonly string _defaultCertFolderPath; - private IPEndPoint _endpoint; - private Dictionary _prefixes; - private bool _secure; - private Socket _socket; - private ServerSslConfiguration _sslConfig; - private List _unhandled; // host == '*' - private Dictionary _unregistered; - private object _unregisteredSync; + private List _all; // host == '+' + private Dictionary _connections; + private object _connectionsSync; + private static readonly string _defaultCertFolderPath; + private IPEndPoint _endpoint; + private List _prefixes; + private bool _secure; + private Socket _socket; + private ServerSslConfiguration _sslConfig; + private List _unhandled; // host == '*' #endregion @@ -78,8 +78,9 @@ internal sealed class EndPointListener static EndPointListener () { - _defaultCertFolderPath = - Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData); + _defaultCertFolderPath = Environment.GetFolderPath ( + Environment.SpecialFolder.ApplicationData + ); } #endregion @@ -94,27 +95,43 @@ internal EndPointListener ( bool reuseAddress ) { + _endpoint = endpoint; + if (secure) { - var cert = - getCertificate (endpoint.Port, certificateFolderPath, sslConfig.ServerCertificate); + var cert = getCertificate ( + endpoint.Port, + certificateFolderPath, + sslConfig.ServerCertificate + ); + + if (cert == null) { + var msg = "No server certificate could be found."; - if (cert == null) - throw new ArgumentException ("No server certificate could be found."); + throw new ArgumentException (msg); + } _secure = true; _sslConfig = new ServerSslConfiguration (sslConfig); _sslConfig.ServerCertificate = cert; } - _endpoint = endpoint; - _prefixes = new Dictionary (); - _unregistered = new Dictionary (); - _unregisteredSync = ((ICollection) _unregistered).SyncRoot; - _socket = - new Socket (endpoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); - - if (reuseAddress) - _socket.SetSocketOption (SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); + _prefixes = new List (); + _connections = new Dictionary (); + _connectionsSync = ((ICollection) _connections).SyncRoot; + + _socket = new Socket ( + endpoint.Address.AddressFamily, + SocketType.Stream, + ProtocolType.Tcp + ); + + if (reuseAddress) { + _socket.SetSocketOption ( + SocketOptionLevel.Socket, + SocketOptionName.ReuseAddress, + true + ); + } _socket.Bind (endpoint); _socket.Listen (500); @@ -153,33 +170,59 @@ public ServerSslConfiguration SslConfiguration { #region Private Methods - private static void addSpecial (List prefixes, HttpListenerPrefix prefix) + private static void addSpecial ( + List prefixes, + HttpListenerPrefix prefix + ) { var path = prefix.Path; + foreach (var pref in prefixes) { - if (pref.Path == path) - throw new HttpListenerException (87, "The prefix is already in use."); + if (pref.Path == path) { + var msg = "The prefix is already in use."; + + throw new HttpListenerException (87, msg); + } } prefixes.Add (prefix); } - private static RSACryptoServiceProvider createRSAFromFile (string filename) + private void clearConnections () { - byte[] pvk = null; - using (var fs = File.Open (filename, FileMode.Open, FileAccess.Read, FileShare.Read)) { - pvk = new byte[fs.Length]; - fs.Read (pvk, 0, pvk.Length); + HttpConnection[] conns = null; + + lock (_connectionsSync) { + var cnt = _connections.Count; + + if (cnt == 0) + return; + + conns = new HttpConnection[cnt]; + + _connections.Values.CopyTo (conns, 0); + _connections.Clear (); } + foreach (var conn in conns) + conn.Close (true); + } + + private static RSACryptoServiceProvider createRSAFromFile (string path) + { var rsa = new RSACryptoServiceProvider (); - rsa.ImportCspBlob (pvk); + + 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) @@ -188,17 +231,21 @@ private static X509Certificate2 getCertificate ( try { var cer = Path.Combine (folderPath, String.Format ("{0}.cer", port)); var key = Path.Combine (folderPath, String.Format ("{0}.key", port)); - if (File.Exists (cer) && File.Exists (key)) { - var cert = new X509Certificate2 (cer); - cert.PrivateKey = createRSAFromFile (key); - return cert; - } + var exists = File.Exists (cer) && File.Exists (key); + + if (!exists) + return defaultCertificate; + + var cert = new X509Certificate2 (cer); + + cert.PrivateKey = createRSAFromFile (key); + + return cert; } catch { + return defaultCertificate; } - - return defaultCertificate; } private void leaveIfNoPrefix () @@ -207,14 +254,16 @@ private void leaveIfNoPrefix () return; var prefs = _unhandled; + if (prefs != null && prefs.Count > 0) return; prefs = _all; + if (prefs != null && prefs.Count > 0) return; - EndPointManager.RemoveEndPoint (_endpoint); + Close (); } private static void onAccept (IAsyncResult asyncResult) @@ -222,20 +271,23 @@ private static void onAccept (IAsyncResult asyncResult) var lsnr = (EndPointListener) asyncResult.AsyncState; Socket sock = null; + try { sock = lsnr._socket.EndAccept (asyncResult); } - catch (SocketException) { - // TODO: Should log the error code when this class has a logging. - } catch (ObjectDisposedException) { return; } + catch (Exception) { + // TODO: Logging. + } try { lsnr._socket.BeginAccept (onAccept, lsnr); } - catch { + catch (Exception) { + // TODO: Logging. + if (sock != null) sock.Close (); @@ -248,33 +300,42 @@ private static void onAccept (IAsyncResult asyncResult) processAccepted (sock, lsnr); } - private static void processAccepted (Socket socket, EndPointListener listener) + private static void processAccepted ( + Socket socket, + EndPointListener listener + ) { HttpConnection conn = null; + try { conn = new HttpConnection (socket, listener); - lock (listener._unregisteredSync) - listener._unregistered[conn] = conn; - - conn.BeginReadRequest (); } - catch { - if (conn != null) { - conn.Close (true); - return; - } + catch (Exception) { + // TODO: Logging. socket.Close (); + + return; } + + lock (listener._connectionsSync) + listener._connections.Add (conn, conn); + + conn.BeginReadRequest (); } - private static bool removeSpecial (List prefixes, HttpListenerPrefix prefix) + private static bool removeSpecial ( + List prefixes, + HttpListenerPrefix prefix + ) { var path = prefix.Path; var cnt = prefixes.Count; + for (var i = 0; i < cnt; i++) { if (prefixes[i].Path == path) { prefixes.RemoveAt (i); + return true; } } @@ -283,29 +344,34 @@ private static bool removeSpecial (List prefixes, HttpListen } private static HttpListener searchHttpListenerFromSpecial ( - string path, List prefixes + string path, + List prefixes ) { if (prefixes == null) return null; - HttpListener bestMatch = null; + HttpListener ret = null; var bestLen = -1; + foreach (var pref in prefixes) { var prefPath = pref.Path; - var len = prefPath.Length; + if (len < bestLen) continue; - if (path.StartsWith (prefPath)) { - bestLen = len; - bestMatch = pref.Listener; - } + var match = path.StartsWith (prefPath, StringComparison.Ordinal); + + if (!match) + continue; + + bestLen = len; + ret = pref.Listener; } - return bestMatch; + return ret; } #endregion @@ -325,8 +391,8 @@ internal static bool CertificateExists (int port, string folderPath) internal void RemoveConnection (HttpConnection connection) { - lock (_unregisteredSync) - _unregistered.Remove (connection); + lock (_connectionsSync) + _connections.Remove (connection); } internal bool TrySearchHttpListener (Uri uri, out HttpListener listener) @@ -340,48 +406,53 @@ internal bool TrySearchHttpListener (Uri uri, out HttpListener listener) var dns = Uri.CheckHostName (host) == UriHostNameType.Dns; var port = uri.Port.ToString (); var path = HttpUtility.UrlDecode (uri.AbsolutePath); - var pathSlash = path[path.Length - 1] != '/' ? path + "/" : path; + + if (path[path.Length - 1] != '/') + path += "/"; if (host != null && host.Length > 0) { + var prefs = _prefixes; var bestLen = -1; - foreach (var pref in _prefixes.Keys) { + + foreach (var pref in prefs) { if (dns) { var prefHost = pref.Host; - if (Uri.CheckHostName (prefHost) == UriHostNameType.Dns && prefHost != host) - continue; + var prefDns = Uri.CheckHostName (prefHost) == UriHostNameType.Dns; + + if (prefDns) { + if (prefHost != host) + continue; + } } if (pref.Port != port) continue; var prefPath = pref.Path; - var len = prefPath.Length; + if (len < bestLen) continue; - if (path.StartsWith (prefPath) || pathSlash.StartsWith (prefPath)) { - bestLen = len; - listener = _prefixes[pref]; - } + var match = path.StartsWith (prefPath, StringComparison.Ordinal); + + if (!match) + continue; + + bestLen = len; + listener = pref.Listener; } if (bestLen != -1) return true; } - var prefs = _unhandled; - listener = searchHttpListenerFromSpecial (path, prefs); - if (listener == null && pathSlash != path) - listener = searchHttpListenerFromSpecial (pathSlash, prefs); + listener = searchHttpListenerFromSpecial (path, _unhandled); if (listener != null) return true; - prefs = _all; - listener = searchHttpListenerFromSpecial (path, prefs); - if (listener == null && pathSlash != path) - listener = searchHttpListenerFromSpecial (pathSlash, prefs); + listener = searchHttpListenerFromSpecial (path, _all); return listener != null; } @@ -390,9 +461,10 @@ internal bool TrySearchHttpListener (Uri uri, out HttpListener listener) #region Public Methods - public void AddPrefix (HttpListenerPrefix prefix, HttpListener listener) + public void AddPrefix (HttpListenerPrefix prefix) { List current, future; + if (prefix.Host == "*") { do { current = _unhandled; @@ -400,10 +472,12 @@ public void AddPrefix (HttpListenerPrefix prefix, HttpListener listener) ? new List (current) : new List (); - prefix.Listener = listener; addSpecial (future, prefix); } - while (Interlocked.CompareExchange (ref _unhandled, future, current) != current); + while ( + Interlocked.CompareExchange (ref _unhandled, future, current) + != current + ); return; } @@ -415,97 +489,112 @@ public void AddPrefix (HttpListenerPrefix prefix, HttpListener listener) ? new List (current) : new List (); - prefix.Listener = listener; addSpecial (future, prefix); } - while (Interlocked.CompareExchange (ref _all, future, current) != current); + while ( + Interlocked.CompareExchange (ref _all, future, current) + != current + ); return; } - Dictionary prefs, prefs2; do { - prefs = _prefixes; - if (prefs.ContainsKey (prefix)) { - if (prefs[prefix] != listener) { - throw new HttpListenerException ( - 87, String.Format ("There's another listener for {0}.", prefix) - ); + current = _prefixes; + + var idx = current.IndexOf (prefix); + + if (idx > -1) { + if (current[idx].Listener != prefix.Listener) { + var fmt = "There is another listener for {0}."; + var msg = String.Format (fmt, prefix); + + throw new HttpListenerException (87, msg); } return; } - prefs2 = new Dictionary (prefs); - prefs2[prefix] = listener; + future = new List (current); + + future.Add (prefix); } - while (Interlocked.CompareExchange (ref _prefixes, prefs2, prefs) != prefs); + while ( + Interlocked.CompareExchange (ref _prefixes, future, current) + != current + ); } public void Close () { _socket.Close (); - HttpConnection[] conns = null; - lock (_unregisteredSync) { - if (_unregistered.Count == 0) - return; - - var keys = _unregistered.Keys; - conns = new HttpConnection[keys.Count]; - keys.CopyTo (conns, 0); - _unregistered.Clear (); - } - - for (var i = conns.Length - 1; i >= 0; i--) - conns[i].Close (true); + clearConnections (); + EndPointManager.RemoveEndPoint (_endpoint); } - public void RemovePrefix (HttpListenerPrefix prefix, HttpListener listener) + public void RemovePrefix (HttpListenerPrefix prefix) { List current, future; + if (prefix.Host == "*") { do { current = _unhandled; + if (current == null) break; future = new List (current); + if (!removeSpecial (future, prefix)) - break; // The prefix wasn't found. + break; } - while (Interlocked.CompareExchange (ref _unhandled, future, current) != current); + while ( + Interlocked.CompareExchange (ref _unhandled, future, current) + != current + ); leaveIfNoPrefix (); + return; } if (prefix.Host == "+") { do { current = _all; + if (current == null) break; future = new List (current); + if (!removeSpecial (future, prefix)) - break; // The prefix wasn't found. + break; } - while (Interlocked.CompareExchange (ref _all, future, current) != current); + while ( + Interlocked.CompareExchange (ref _all, future, current) + != current + ); leaveIfNoPrefix (); + return; } - Dictionary prefs, prefs2; do { - prefs = _prefixes; - if (!prefs.ContainsKey (prefix)) + current = _prefixes; + + if (!current.Contains (prefix)) break; - prefs2 = new Dictionary (prefs); - prefs2.Remove (prefix); + future = new List (current); + + future.Remove (prefix); } - while (Interlocked.CompareExchange (ref _prefixes, prefs2, prefs) != prefs); + while ( + Interlocked.CompareExchange (ref _prefixes, future, current) + != current + ); leaveIfNoPrefix (); } diff --git a/websocket-sharp/Net/EndPointManager.cs b/websocket-sharp/Net/EndPointManager.cs index c12349d56..ac4582b24 100644 --- a/websocket-sharp/Net/EndPointManager.cs +++ b/websocket-sharp/Net/EndPointManager.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2012-2016 sta.blockhead + * Copyright (c) 2012-2020 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 @@ -80,50 +80,74 @@ private EndPointManager () private static void addPrefix (string uriPrefix, HttpListener listener) { - var pref = new HttpListenerPrefix (uriPrefix); + var pref = new HttpListenerPrefix (uriPrefix, listener); var addr = convertToIPAddress (pref.Host); - if (addr == null) - throw new HttpListenerException (87, "Includes an invalid host."); - if (!addr.IsLocal ()) - throw new HttpListenerException (87, "Includes an invalid host."); + if (addr == null) { + var msg = "The URI prefix includes an invalid host."; + + throw new HttpListenerException (87, msg); + } + + if (!addr.IsLocal ()) { + var msg = "The URI prefix includes an invalid host."; + + throw new HttpListenerException (87, msg); + } int port; - if (!Int32.TryParse (pref.Port, out port)) - throw new HttpListenerException (87, "Includes an invalid port."); - if (!port.IsPortNumber ()) - throw new HttpListenerException (87, "Includes an invalid port."); + if (!Int32.TryParse (pref.Port, out port)) { + var msg = "The URI prefix includes an invalid port."; + + throw new HttpListenerException (87, msg); + } + + if (!port.IsPortNumber ()) { + var msg = "The URI prefix includes an invalid port."; + + throw new HttpListenerException (87, msg); + } var path = pref.Path; - if (path.IndexOf ('%') != -1) - throw new HttpListenerException (87, "Includes an invalid path."); - if (path.IndexOf ("//", StringComparison.Ordinal) != -1) - throw new HttpListenerException (87, "Includes an invalid path."); + if (path.IndexOf ('%') != -1) { + var msg = "The URI prefix includes an invalid path."; + + throw new HttpListenerException (87, msg); + } + + if (path.IndexOf ("//", StringComparison.Ordinal) != -1) { + var msg = "The URI prefix includes an invalid path."; + + throw new HttpListenerException (87, msg); + } var endpoint = new IPEndPoint (addr, port); EndPointListener lsnr; + if (_endpoints.TryGetValue (endpoint, out lsnr)) { - if (lsnr.IsSecure ^ pref.IsSecure) - throw new HttpListenerException (87, "Includes an invalid scheme."); + if (lsnr.IsSecure ^ pref.IsSecure) { + var msg = "The URI prefix includes an invalid scheme."; + + throw new HttpListenerException (87, msg); + } } else { - lsnr = - new EndPointListener ( - endpoint, - pref.IsSecure, - listener.CertificateFolderPath, - listener.SslConfiguration, - listener.ReuseAddress - ); + lsnr = new EndPointListener ( + endpoint, + pref.IsSecure, + listener.CertificateFolderPath, + listener.SslConfiguration, + listener.ReuseAddress + ); _endpoints.Add (endpoint, lsnr); } - lsnr.AddPrefix (pref, listener); + lsnr.AddPrefix (pref); } private static IPAddress convertToIPAddress (string hostname) @@ -139,9 +163,10 @@ private static IPAddress convertToIPAddress (string hostname) private static void removePrefix (string uriPrefix, HttpListener listener) { - var pref = new HttpListenerPrefix (uriPrefix); + var pref = new HttpListenerPrefix (uriPrefix, listener); var addr = convertToIPAddress (pref.Host); + if (addr == null) return; @@ -149,6 +174,7 @@ private static void removePrefix (string uriPrefix, HttpListener listener) return; int port; + if (!Int32.TryParse (pref.Port, out port)) return; @@ -156,6 +182,7 @@ private static void removePrefix (string uriPrefix, HttpListener listener) return; var path = pref.Path; + if (path.IndexOf ('%') != -1) return; @@ -165,13 +192,14 @@ private static void removePrefix (string uriPrefix, HttpListener listener) var endpoint = new IPEndPoint (addr, port); EndPointListener lsnr; + if (!_endpoints.TryGetValue (endpoint, out lsnr)) return; if (lsnr.IsSecure ^ pref.IsSecure) return; - lsnr.RemovePrefix (pref, listener); + lsnr.RemovePrefix (pref); } #endregion @@ -180,16 +208,8 @@ private static void removePrefix (string uriPrefix, HttpListener listener) internal static bool RemoveEndPoint (IPEndPoint endpoint) { - lock (((ICollection) _endpoints).SyncRoot) { - EndPointListener lsnr; - if (!_endpoints.TryGetValue (endpoint, out lsnr)) - return false; - - _endpoints.Remove (endpoint); - lsnr.Close (); - - return true; - } + lock (((ICollection) _endpoints).SyncRoot) + return _endpoints.Remove (endpoint); } #endregion @@ -199,6 +219,7 @@ internal static bool RemoveEndPoint (IPEndPoint endpoint) public static void AddListener (HttpListener listener) { var added = new List (); + lock (((ICollection) _endpoints).SyncRoot) { try { foreach (var pref in listener.Prefixes) { diff --git a/websocket-sharp/Net/HttpConnection.cs b/websocket-sharp/Net/HttpConnection.cs index 572d785c2..1f8a497ad 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-2016 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 @@ -60,17 +60,17 @@ internal sealed class HttpConnection { #region Private Fields + private int _attempts; private byte[] _buffer; - private const int _bufferLength = 8192; + private static readonly int _bufferLength; private HttpListenerContext _context; - private bool _contextRegistered; private StringBuilder _currentLine; + private EndPointListener _endPointListener; private InputState _inputState; private RequestStream _inputStream; - private HttpListener _lastListener; private LineState _lineState; - private EndPointListener _listener; private EndPoint _localEndPoint; + private static readonly int _maxInputLength; private ResponseStream _outputStream; private int _position; private EndPoint _remoteEndPoint; @@ -86,14 +86,25 @@ internal sealed class HttpConnection #endregion + #region Static Constructor + + static HttpConnection () + { + _bufferLength = 8192; + _maxInputLength = 32768; + } + + #endregion + #region Internal Constructors internal HttpConnection (Socket socket, EndPointListener listener) { _socket = socket; - _listener = listener; + _endPointListener = listener; var netStream = new NetworkStream (socket, false); + if (listener.IsSecure) { var sslConf = listener.SslConfiguration; var sslStream = new SslStream ( @@ -116,14 +127,15 @@ internal HttpConnection (Socket socket, EndPointListener listener) _stream = netStream; } + _buffer = new byte[_bufferLength]; _localEndPoint = socket.LocalEndPoint; _remoteEndPoint = socket.RemoteEndPoint; _sync = new object (); - _timeout = 90000; // 90k ms for first request, 15k ms from then on. _timeoutCanceled = new Dictionary (); _timer = new Timer (onTimeout, this, Timeout.Infinite, Timeout.Infinite); - init (); + // 90k ms for first request, 15k ms from then on. + init (new MemoryStream (), 90000); } #endregion @@ -188,8 +200,8 @@ private void close () closeSocket (); } - unregisterContext (); - removeConnection (); + _context.Unregister (); + _endPointListener.RemoveConnection (this); } private void closeSocket () @@ -201,15 +213,43 @@ private void closeSocket () } _socket.Close (); + _socket = null; } + private static MemoryStream createRequestBuffer ( + RequestStream inputStream + ) + { + var ret = new MemoryStream (); + + if (inputStream is ChunkedRequestStream) { + var crs = (ChunkedRequestStream) inputStream; + + if (crs.HasRemainingBuffer) { + var buff = crs.RemainingBuffer; + + ret.Write (buff, 0, buff.Length); + } + + return ret; + } + + var cnt = inputStream.Count; + + if (cnt > 0) + ret.Write (inputStream.InitialBuffer, inputStream.Offset, cnt); + + return ret; + } + private void disposeRequestBuffer () { if (_requestBuffer == null) return; _requestBuffer.Dispose (); + _requestBuffer = null; } @@ -218,10 +258,8 @@ private void disposeStream () if (_stream == null) return; - _inputStream = null; - _outputStream = null; - _stream.Dispose (); + _stream = null; } @@ -237,23 +275,29 @@ private void disposeTimer () } _timer.Dispose (); + _timer = null; } - private void init () + private void init (MemoryStream requestBuffer, int timeout) { + _requestBuffer = requestBuffer; + _timeout = timeout; + _context = new HttpListenerContext (this); + _currentLine = new StringBuilder (64); _inputState = InputState.RequestLine; _inputStream = null; _lineState = LineState.None; _outputStream = null; _position = 0; - _requestBuffer = new MemoryStream (); } private static void onRead (IAsyncResult asyncResult) { var conn = (HttpConnection) asyncResult.AsyncState; + var current = conn._attempts; + if (conn._socket == null) return; @@ -261,77 +305,42 @@ private static void onRead (IAsyncResult asyncResult) if (conn._socket == null) return; - var nread = -1; - var len = 0; - try { - var current = conn._reuses; - if (!conn._timeoutCanceled[current]) { - conn._timer.Change (Timeout.Infinite, Timeout.Infinite); - conn._timeoutCanceled[current] = true; - } + conn._timer.Change (Timeout.Infinite, Timeout.Infinite); + conn._timeoutCanceled[current] = true; + var nread = 0; + + try { nread = conn._stream.EndRead (asyncResult); - conn._requestBuffer.Write (conn._buffer, 0, nread); - len = (int) conn._requestBuffer.Length; } - catch (Exception ex) { - if (conn._requestBuffer != null && conn._requestBuffer.Length > 0) { - conn.SendError (ex.Message, 400); - return; - } + catch (Exception) { + // TODO: Logging. conn.close (); + return; } if (nread <= 0) { conn.close (); + return; } - if (conn.processInput (conn._requestBuffer.GetBuffer (), len)) { - if (!conn._context.HasError) - conn._context.Request.FinishInitialization (); - - if (conn._context.HasError) { - conn.SendError (); - return; - } - - HttpListener lsnr; - if (!conn._listener.TrySearchHttpListener (conn._context.Request.Url, out lsnr)) { - conn.SendError (null, 404); - return; - } - - if (conn._lastListener != lsnr) { - conn.removeConnection (); - if (!lsnr.AddConnection (conn)) { - conn.close (); - return; - } - - conn._lastListener = lsnr; - } - - conn._context.Listener = lsnr; - if (!conn._context.Authenticate ()) - return; - - if (conn._context.Register ()) - conn._contextRegistered = true; + conn._requestBuffer.Write (conn._buffer, 0, nread); + if (conn.processRequestBuffer ()) return; - } - conn._stream.BeginRead (conn._buffer, 0, _bufferLength, onRead, conn); + conn.BeginReadRequest (); } } private static void onTimeout (object state) { var conn = (HttpConnection) state; - var current = conn._reuses; + var current = conn._attempts; + if (conn._socket == null) return; @@ -342,107 +351,185 @@ private static void onTimeout (object state) if (conn._timeoutCanceled[current]) return; - conn.SendError (null, 408); + conn._context.SendError (408); } } - // true -> Done processing. - // false -> Need more input. private bool processInput (byte[] data, int length) { - if (_currentLine == null) - _currentLine = new StringBuilder (64); + // This method returns a bool: + // - true Done processing + // - false Need more input + + var req = _context.Request; - var nread = 0; try { - string line; - while ((line = readLineFrom (data, _position, length, out nread)) != null) { + while (true) { + int nread; + var line = readLineFrom (data, _position, length, out nread); + _position += nread; + + if (line == null) + break; + if (line.Length == 0) { if (_inputState == InputState.RequestLine) continue; - if (_position > 32768) + if (_position > _maxInputLength) _context.ErrorMessage = "Headers too long"; - _currentLine = null; return true; } if (_inputState == InputState.RequestLine) { - _context.Request.SetRequestLine (line); + req.SetRequestLine (line); + _inputState = InputState.Headers; } else { - _context.Request.AddHeader (line); + req.AddHeader (line); } - if (_context.HasError) + if (_context.HasErrorMessage) return true; } } - catch (Exception ex) { - _context.ErrorMessage = ex.Message; + catch (Exception) { + // TODO: Logging. + + _context.ErrorMessage = "Processing failure"; + return true; } - _position += nread; - if (_position >= 32768) { + if (_position >= _maxInputLength) { _context.ErrorMessage = "Headers too long"; + return true; } return false; } - private string readLineFrom (byte[] buffer, int offset, int length, out int read) + private bool processRequestBuffer () { - read = 0; + // This method returns a bool: + // - true Done processing + // - false Need more write + + var data = _requestBuffer.GetBuffer (); + var len = (int) _requestBuffer.Length; + + if (!processInput (data, len)) + return false; + + var req = _context.Request; + + if (!_context.HasErrorMessage) + req.FinishInitialization (); + + if (_context.HasErrorMessage) { + _context.SendError (); - for (var i = offset; i < length && _lineState != LineState.Lf; i++) { - read++; + return true; + } + + var uri = req.Url; + HttpListener httplsnr; + + if (!_endPointListener.TrySearchHttpListener (uri, out httplsnr)) { + _context.SendError (404); + + return true; + } + + httplsnr.RegisterContext (_context); + + return true; + } + + private string readLineFrom ( + byte[] buffer, + int offset, + int length, + out int nread + ) + { + nread = 0; + + for (var i = offset; i < length; i++) { + nread++; var b = buffer[i]; - if (b == 13) + + if (b == 13) { _lineState = LineState.Cr; - else if (b == 10) + + continue; + } + + if (b == 10) { _lineState = LineState.Lf; - else - _currentLine.Append ((char) b); + + break; + } + + _currentLine.Append ((char) b); } if (_lineState != LineState.Lf) return null; - var line = _currentLine.ToString (); + var ret = _currentLine.ToString (); _currentLine.Length = 0; _lineState = LineState.None; - return line; + return ret; } - private void removeConnection () + private MemoryStream takeOverRequestBuffer () { - if (_lastListener != null) - _lastListener.RemoveConnection (this); - else - _listener.RemoveConnection (this); - } + if (_inputStream != null) + return createRequestBuffer (_inputStream); - private void unregisterContext () - { - if (!_contextRegistered) - return; + var ret = new MemoryStream (); - _context.Unregister (); - _contextRegistered = false; + var buff = _requestBuffer.GetBuffer (); + var len = (int) _requestBuffer.Length; + var cnt = len - _position; + + if (cnt > 0) + ret.Write (buff, _position, cnt); + + disposeRequestBuffer (); + + return ret; } #endregion #region Internal Methods + internal void BeginReadRequest () + { + _attempts++; + + _timeoutCanceled.Add (_attempts, false); + _timer.Change (_timeout, Timeout.Infinite); + + try { + _stream.BeginRead (_buffer, 0, _bufferLength, onRead, this); + } + catch (Exception) { + // TODO: Logging. + + close (); + } + } + internal void Close (bool force) { if (_socket == null) @@ -457,6 +544,7 @@ internal void Close (bool force) _outputStream.Close (true); close (); + return; } @@ -464,19 +552,30 @@ internal void Close (bool force) if (_context.Response.CloseConnection) { close (); + return; } if (!_context.Request.FlushInput ()) { close (); + return; } - disposeRequestBuffer (); - unregisterContext (); - init (); + _context.Unregister (); _reuses++; + + var buff = takeOverRequestBuffer (); + var len = buff.Length; + + init (buff, 15000); + + if (len > 0) { + if (processRequestBuffer ()) + return; + } + BeginReadRequest (); } } @@ -485,24 +584,6 @@ internal void Close (bool force) #region Public Methods - public void BeginReadRequest () - { - if (_buffer == null) - _buffer = new byte[_bufferLength]; - - if (_reuses == 1) - _timeout = 15000; - - try { - _timeoutCanceled.Add (_reuses, false); - _timer.Change (_timeout, Timeout.Infinite); - _stream.BeginRead (_buffer, 0, _bufferLength, onRead, this); - } - catch { - close (); - } - } - public void Close () { Close (false); @@ -520,24 +601,31 @@ public RequestStream GetRequestStream (long contentLength, bool chunked) var buff = _requestBuffer.GetBuffer (); var len = (int) _requestBuffer.Length; var cnt = len - _position; - disposeRequestBuffer (); _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 (); + return _inputStream; } } public ResponseStream GetResponseStream () { - // TODO: Can we get this stream before reading the input? - lock (_sync) { if (_socket == null) return null; @@ -547,51 +635,13 @@ public ResponseStream GetResponseStream () var lsnr = _context.Listener; var ignore = lsnr != null ? lsnr.IgnoreWriteExceptions : true; + _outputStream = new ResponseStream (_stream, _context.Response, ignore); return _outputStream; } } - public void SendError () - { - SendError (_context.ErrorMessage, _context.ErrorStatus); - } - - public void SendError (string message, int status) - { - if (_socket == null) - return; - - lock (_sync) { - if (_socket == null) - return; - - try { - var res = _context.Response; - res.StatusCode = status; - res.ContentType = "text/html"; - - var content = new StringBuilder (64); - content.AppendFormat ("

{0} {1}", status, res.StatusDescription); - if (message != null && message.Length > 0) - content.AppendFormat (" ({0})

", message); - else - content.Append (""); - - var enc = Encoding.UTF8; - var entity = enc.GetBytes (content.ToString ()); - res.ContentEncoding = enc; - res.ContentLength64 = entity.LongLength; - - res.Close (entity, true); - } - catch { - Close (true); - } - } - } - #endregion } } 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/HttpHeaderInfo.cs b/websocket-sharp/Net/HttpHeaderInfo.cs index 717f8f46d..2b77e3d9d 100644 --- a/websocket-sharp/Net/HttpHeaderInfo.cs +++ b/websocket-sharp/Net/HttpHeaderInfo.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2013-2014 sta.blockhead + * Copyright (c) 2013-2020 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 @@ -34,17 +34,17 @@ internal class HttpHeaderInfo { #region Private Fields - private string _name; - private HttpHeaderType _type; + private string _headerName; + private HttpHeaderType _headerType; #endregion #region Internal Constructors - internal HttpHeaderInfo (string name, HttpHeaderType type) + internal HttpHeaderInfo (string headerName, HttpHeaderType headerType) { - _name = name; - _type = type; + _headerName = headerName; + _headerType = headerType; } #endregion @@ -53,13 +53,17 @@ internal HttpHeaderInfo (string name, HttpHeaderType type) internal bool IsMultiValueInRequest { get { - return (_type & HttpHeaderType.MultiValueInRequest) == HttpHeaderType.MultiValueInRequest; + var headerType = _headerType & HttpHeaderType.MultiValueInRequest; + + return headerType == HttpHeaderType.MultiValueInRequest; } } internal bool IsMultiValueInResponse { get { - return (_type & HttpHeaderType.MultiValueInResponse) == HttpHeaderType.MultiValueInResponse; + var headerType = _headerType & HttpHeaderType.MultiValueInResponse; + + return headerType == HttpHeaderType.MultiValueInResponse; } } @@ -67,27 +71,31 @@ internal bool IsMultiValueInResponse { #region Public Properties - public bool IsRequest { + public string HeaderName { get { - return (_type & HttpHeaderType.Request) == HttpHeaderType.Request; + return _headerName; } } - public bool IsResponse { + public HttpHeaderType HeaderType { get { - return (_type & HttpHeaderType.Response) == HttpHeaderType.Response; + return _headerType; } } - public string Name { + public bool IsRequest { get { - return _name; + var headerType = _headerType & HttpHeaderType.Request; + + return headerType == HttpHeaderType.Request; } } - public HttpHeaderType Type { + public bool IsResponse { get { - return _type; + var headerType = _headerType & HttpHeaderType.Response; + + return headerType == HttpHeaderType.Response; } } @@ -97,16 +105,22 @@ public HttpHeaderType Type { public bool IsMultiValue (bool response) { - return (_type & HttpHeaderType.MultiValue) == HttpHeaderType.MultiValue - ? (response ? IsResponse : IsRequest) - : (response ? IsMultiValueInResponse : IsMultiValueInRequest); + var headerType = _headerType & HttpHeaderType.MultiValue; + + if (headerType != HttpHeaderType.MultiValue) + return response ? IsMultiValueInResponse : IsMultiValueInRequest; + + return response ? IsResponse : IsRequest; } public bool IsRestricted (bool response) { - return (_type & HttpHeaderType.Restricted) == HttpHeaderType.Restricted - ? (response ? IsResponse : IsRequest) - : false; + var headerType = _headerType & HttpHeaderType.Restricted; + + if (headerType != HttpHeaderType.Restricted) + return false; + + return response ? IsResponse : IsRequest; } #endregion diff --git a/websocket-sharp/Net/HttpListener.cs b/websocket-sharp/Net/HttpListener.cs index 07970e14d..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-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 @@ -57,31 +57,39 @@ namespace WebSocketSharp.Net /// /// Provides a simple, programmatically controlled HTTP listener. /// + /// + /// + /// The listener supports HTTP/1.1 version request and response. + /// + /// + /// And the listener allows to accept WebSocket handshake requests. + /// + /// + /// This class cannot be inherited. + /// + /// public sealed class HttpListener : IDisposable { #region Private Fields - private AuthenticationSchemes _authSchemes; - private Func _authSchemeSelector; - private string _certFolderPath; - private Dictionary _connections; - private object _connectionsSync; - private List _ctxQueue; - private object _ctxQueueSync; - private Dictionary _ctxRegistry; - private object _ctxRegistrySync; - private static readonly string _defaultRealm; - private bool _disposed; - private bool _ignoreWriteExceptions; - private volatile bool _listening; - private Logger _logger; - private HttpListenerPrefixCollection _prefixes; - private string _realm; - private bool _reuseAddress; - private ServerSslConfiguration _sslConfig; - private Func _userCredFinder; - private List _waitQueue; - private object _waitQueueSync; + private AuthenticationSchemes _authSchemes; + private Func _authSchemeSelector; + private string _certFolderPath; + private Queue _contextQueue; + private LinkedList _contextRegistry; + private object _contextRegistrySync; + private static readonly string _defaultRealm; + private bool _disposed; + private bool _ignoreWriteExceptions; + private volatile bool _isListening; + private Logger _log; + private HttpListenerPrefixCollection _prefixes; + private string _realm; + private bool _reuseAddress; + private ServerSslConfiguration _sslConfig; + private object _sync; + private Func _userCredFinder; + private Queue _waitQueue; #endregion @@ -102,31 +110,22 @@ static HttpListener () public HttpListener () { _authSchemes = AuthenticationSchemes.Anonymous; - - _connections = new Dictionary (); - _connectionsSync = ((ICollection) _connections).SyncRoot; - - _ctxQueue = new List (); - _ctxQueueSync = ((ICollection) _ctxQueue).SyncRoot; - - _ctxRegistry = new Dictionary (); - _ctxRegistrySync = ((ICollection) _ctxRegistry).SyncRoot; - - _logger = new Logger (); - + _contextQueue = new Queue (); + _contextRegistry = new LinkedList (); + _contextRegistrySync = ((ICollection) _contextRegistry).SyncRoot; + _log = new Logger (); _prefixes = new HttpListenerPrefixCollection (this); - - _waitQueue = new List (); - _waitQueueSync = ((ICollection) _waitQueue).SyncRoot; + _sync = new object (); + _waitQueue = new Queue (); } #endregion #region Internal Properties - internal bool IsDisposed { + internal string ObjectName { get { - return _disposed; + return GetType ().ToString (); } } @@ -148,109 +147,164 @@ internal bool ReuseAddress { /// Gets or sets the scheme used to authenticate the clients. ///
/// - /// One of the enum values, - /// represents the scheme used to authenticate the clients. The default value is - /// . + /// + /// One of the + /// enum values. + /// + /// + /// It represents the scheme used to authenticate the clients. + /// + /// + /// The default value is + /// . + /// /// /// /// This listener has been closed. /// public AuthenticationSchemes AuthenticationSchemes { get { - CheckDisposed (); + if (_disposed) + throw new ObjectDisposedException (ObjectName); + return _authSchemes; } set { - CheckDisposed (); + if (_disposed) + throw new ObjectDisposedException (ObjectName); + _authSchemes = value; } } /// - /// Gets or sets the delegate called to select the scheme used to authenticate the clients. + /// Gets or sets the delegate called to determine the scheme used to + /// authenticate the clients. /// /// - /// If you set this property, the listener uses the authentication scheme selected by - /// the delegate for each request. Or if you don't set, the listener uses the value of - /// the property as the authentication - /// scheme for all requests. + /// + /// If this property is set, the listener uses the authentication + /// scheme selected by the delegate for each request. + /// + /// + /// Or if this property is not set, the listener uses the value of + /// the property + /// as the authentication scheme for all requests. + /// /// /// - /// A Func<, > - /// delegate that references the method used to select an authentication scheme. The default - /// value is . + /// + /// A + /// delegate. + /// + /// + /// It represents the delegate called when the listener selects + /// an authentication scheme. + /// + /// + /// if not necessary. + /// + /// + /// The default value is . + /// /// /// /// This listener has been closed. /// public Func AuthenticationSchemeSelector { get { - CheckDisposed (); + if (_disposed) + throw new ObjectDisposedException (ObjectName); + return _authSchemeSelector; } set { - CheckDisposed (); + if (_disposed) + throw new ObjectDisposedException (ObjectName); + _authSchemeSelector = value; } } /// - /// Gets or sets the path to the folder in which stores the certificate files used to - /// authenticate the server on the secure connection. + /// Gets or sets the path to the folder in which stores the certificate + /// files used to authenticate the server on the secure connection. /// /// /// - /// This property represents the path to the folder in which stores the certificate files - /// associated with each port number of added URI prefixes. A set of the certificate files - /// is a pair of the 'port number'.cer (DER) and 'port number'.key - /// (DER, RSA Private Key). + /// This property represents the path to the folder in which stores + /// the certificate files associated with each port number of added + /// URI prefixes. /// /// - /// If this property is or empty, the result of - /// System.Environment.GetFolderPath - /// () is used as the default path. + /// A set of the certificate files is a pair of <port number>.cer + /// (DER) and <port number>.key (DER, RSA Private Key). + /// + /// + /// If this property is or an empty string, + /// the result of the + /// with the method is used as + /// the default path. /// /// /// - /// A that represents the path to the folder in which stores - /// the certificate files. The default value is . + /// + /// A that represents the path to the folder + /// in which stores the certificate files. + /// + /// + /// The default value is . + /// /// /// /// This listener has been closed. /// public string CertificateFolderPath { get { - CheckDisposed (); + if (_disposed) + throw new ObjectDisposedException (ObjectName); + return _certFolderPath; } set { - CheckDisposed (); + if (_disposed) + throw new ObjectDisposedException (ObjectName); + _certFolderPath = value; } } /// - /// Gets or sets a value indicating whether the listener returns exceptions that occur when - /// sending the response to the client. + /// Gets or sets a value indicating whether the listener returns + /// exceptions that occur when sending the response to the client. /// /// - /// true if the listener shouldn't return those exceptions; otherwise, false. - /// The default value is false. + /// + /// true if the listener should not return those exceptions; + /// otherwise, false. + /// + /// + /// The default value is false. + /// /// /// /// This listener has been closed. /// public bool IgnoreWriteExceptions { get { - CheckDisposed (); + if (_disposed) + throw new ObjectDisposedException (ObjectName); + return _ignoreWriteExceptions; } set { - CheckDisposed (); + if (_disposed) + throw new ObjectDisposedException (ObjectName); + _ignoreWriteExceptions = value; } } @@ -263,12 +317,13 @@ public bool IgnoreWriteExceptions { /// public bool IsListening { get { - return _listening; + return _isListening; } } /// - /// Gets a value indicating whether the listener can be used with the current operating system. + /// Gets a value indicating whether the listener can be used with + /// the current operating system. /// /// /// true. @@ -283,16 +338,26 @@ public static bool IsSupported { /// Gets the logging functions. /// /// - /// The default logging level is . If you would like to change it, - /// you should set the Log.Level property to any of the enum - /// values. + /// + /// The default logging level is . + /// + /// + /// If you would like to change it, you should set the Log.Level + /// property to any of the enum values. + /// /// /// /// A that provides the logging functions. /// + /// + /// This listener has been closed. + /// public Logger Log { get { - return _logger; + if (_disposed) + throw new ObjectDisposedException (ObjectName); + + return _log; } } @@ -300,14 +365,17 @@ public Logger Log { /// Gets the URI prefixes handled by the listener. /// /// - /// A that contains the URI prefixes. + /// A that contains the URI + /// prefixes. /// /// /// This listener has been closed. /// public HttpListenerPrefixCollection Prefixes { get { - CheckDisposed (); + if (_disposed) + throw new ObjectDisposedException (ObjectName); + return _prefixes; } } @@ -316,48 +384,55 @@ public HttpListenerPrefixCollection Prefixes { /// Gets or sets the name of the realm associated with the listener. /// /// - /// If this property is or empty, "SECRET AREA" will be used as - /// the name of the realm. + /// If this property is or an empty string, + /// "SECRET AREA" is used as the name of the realm. /// /// - /// A that represents the name of the realm. The default value is - /// . + /// + /// A that represents the name of the realm. + /// + /// + /// The default value is . + /// /// /// /// This listener has been closed. /// public string Realm { get { - CheckDisposed (); + if (_disposed) + throw new ObjectDisposedException (ObjectName); + return _realm; } set { - CheckDisposed (); + if (_disposed) + throw new ObjectDisposedException (ObjectName); + _realm = value; } } /// - /// Gets or sets 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 configuration used to - /// authenticate the server and optionally the client for secure connection. + /// A that represents the + /// configuration used to provide secure connections. /// /// /// This listener has been closed. /// public ServerSslConfiguration SslConfiguration { get { - CheckDisposed (); - return _sslConfig ?? (_sslConfig = new ServerSslConfiguration ()); - } + if (_disposed) + throw new ObjectDisposedException (ObjectName); - set { - CheckDisposed (); - _sslConfig = value; + if (_sslConfig == null) + _sslConfig = new ServerSslConfiguration (); + + return _sslConfig; } } @@ -367,7 +442,7 @@ public ServerSslConfiguration SslConfiguration { /// additional requests on the same connection. /// /// - /// This property isn't currently supported and always throws + /// This property is not currently supported and always throws /// a . /// /// @@ -388,25 +463,44 @@ public bool UnsafeConnectionNtlmAuthentication { } /// - /// Gets or sets the delegate called to find the credentials for an identity used to - /// authenticate a client. + /// Gets or sets the delegate called to find the credentials for + /// an identity used to authenticate a client. /// /// - /// A Func<, > delegate - /// that references the method used to find the credentials. The default value is - /// . + /// + /// A + /// delegate. + /// + /// + /// It represents the delegate called when the listener finds + /// the credentials used to authenticate a client. + /// + /// + /// It must return if the credentials + /// are not found. + /// + /// + /// if not necessary. + /// + /// + /// The default value is . + /// /// /// /// This listener has been closed. /// public Func UserCredentialsFinder { get { - CheckDisposed (); + if (_disposed) + throw new ObjectDisposedException (ObjectName); + return _userCredFinder; } set { - CheckDisposed (); + if (_disposed) + throw new ObjectDisposedException (ObjectName); + _userCredFinder = value; } } @@ -415,198 +509,182 @@ public Func UserCredentialsFinder { #region Private Methods - private void cleanupConnections () + private bool authenticateClient (HttpListenerContext context) { - HttpConnection[] conns = null; - lock (_connectionsSync) { - if (_connections.Count == 0) - return; + var schm = selectAuthenticationScheme (context.Request); - // Need to copy this since closing will call the RemoveConnection method. - var keys = _connections.Keys; - conns = new HttpConnection[keys.Count]; - keys.CopyTo (conns, 0); - _connections.Clear (); - } + if (schm == AuthenticationSchemes.Anonymous) + return true; - for (var i = conns.Length - 1; i >= 0; i--) - conns[i].Close (true); - } + if (schm == AuthenticationSchemes.None) { + var msg = "Authentication not allowed"; - private void cleanupContextQueue (bool sendServiceUnavailable) - { - HttpListenerContext[] ctxs = null; - lock (_ctxQueueSync) { - if (_ctxQueue.Count == 0) - return; + context.SendError (403, msg); - ctxs = _ctxQueue.ToArray (); - _ctxQueue.Clear (); + return false; } - if (!sendServiceUnavailable) - return; + var realm = getRealm (); + + if (!context.SetUser (schm, realm, _userCredFinder)) { + context.SendAuthenticationChallenge (schm, realm); - foreach (var ctx in ctxs) { - var res = ctx.Response; - res.StatusCode = (int) HttpStatusCode.ServiceUnavailable; - res.Close (); + return false; } + + return true; } - private void cleanupContextRegistry () + private HttpListenerAsyncResult beginGetContext ( + AsyncCallback callback, + object state + ) { - HttpListenerContext[] ctxs = null; - lock (_ctxRegistrySync) { - if (_ctxRegistry.Count == 0) - return; + lock (_contextRegistrySync) { + if (!_isListening) { + var msg = "The method is canceled."; - // Need to copy this since closing will call the UnregisterContext method. - var keys = _ctxRegistry.Keys; - ctxs = new HttpListenerContext[keys.Count]; - keys.CopyTo (ctxs, 0); - _ctxRegistry.Clear (); - } + throw new HttpListenerException (995, msg); + } - for (var i = ctxs.Length - 1; i >= 0; i--) - ctxs[i].Connection.Close (true); - } + var ares = new HttpListenerAsyncResult (callback, state, _log); - private void cleanupWaitQueue (Exception exception) - { - HttpListenerAsyncResult[] aress = null; - lock (_waitQueueSync) { - if (_waitQueue.Count == 0) - return; + if (_contextQueue.Count == 0) { + _waitQueue.Enqueue (ares); - aress = _waitQueue.ToArray (); - _waitQueue.Clear (); - } + return ares; + } - foreach (var ares in aress) - ares.Complete (exception); + var ctx = _contextQueue.Dequeue (); + + ares.Complete (ctx, true); + + return ares; + } } - private void close (bool force) + private void cleanupContextQueue (bool force) { - if (_listening) { - _listening = false; - EndPointManager.RemoveListener (this); + if (_contextQueue.Count == 0) + return; + + if (force) { + _contextQueue.Clear (); + + return; } - lock (_ctxRegistrySync) - cleanupContextQueue (!force); + var ctxs = _contextQueue.ToArray (); - cleanupContextRegistry (); - cleanupConnections (); - cleanupWaitQueue (new ObjectDisposedException (GetType ().ToString ())); + _contextQueue.Clear (); - _disposed = true; + foreach (var ctx in ctxs) + ctx.SendError (503); } - private HttpListenerAsyncResult getAsyncResultFromQueue () + private void cleanupContextRegistry () { - if (_waitQueue.Count == 0) - return null; + var cnt = _contextRegistry.Count; - var ares = _waitQueue[0]; - _waitQueue.RemoveAt (0); + if (cnt == 0) + return; - return ares; - } + var ctxs = new HttpListenerContext[cnt]; - private HttpListenerContext getContextFromQueue () - { - if (_ctxQueue.Count == 0) - return null; - - var ctx = _ctxQueue[0]; - _ctxQueue.RemoveAt (0); + lock (_contextRegistrySync) { + _contextRegistry.CopyTo (ctxs, 0); + _contextRegistry.Clear (); + } - return ctx; + foreach (var ctx in ctxs) + ctx.Connection.Close (true); } - #endregion + private void cleanupWaitQueue (string message) + { + if (_waitQueue.Count == 0) + return; - #region Internal Methods + var aress = _waitQueue.ToArray (); - internal bool AddConnection (HttpConnection connection) - { - if (!_listening) - return false; + _waitQueue.Clear (); - lock (_connectionsSync) { - if (!_listening) - return false; + foreach (var ares in aress) { + var ex = new HttpListenerException (995, message); - _connections[connection] = connection; - return true; + ares.Complete (ex); } } - internal HttpListenerAsyncResult BeginGetContext (HttpListenerAsyncResult asyncResult) + private void close (bool force) { - lock (_ctxRegistrySync) { - if (!_listening) - throw new HttpListenerException (995); + lock (_sync) { + if (_disposed) + return; - var ctx = getContextFromQueue (); - if (ctx == null) - _waitQueue.Add (asyncResult); - else - asyncResult.Complete (ctx, true); + lock (_contextRegistrySync) { + if (!_isListening) { + _disposed = true; - return asyncResult; - } - } + return; + } - internal void CheckDisposed () - { - if (_disposed) - throw new ObjectDisposedException (GetType ().ToString ()); + _isListening = false; + } + + cleanupContextQueue (force); + cleanupContextRegistry (); + + var msg = "The listener is closed."; + + cleanupWaitQueue (msg); + + EndPointManager.RemoveListener (this); + + _disposed = true; + } } - internal string GetRealm () + private string getRealm () { var realm = _realm; + return realm != null && realm.Length > 0 ? realm : _defaultRealm; } - internal Func GetUserCredentialsFinder () + private bool registerContext (HttpListenerContext context) { - return _userCredFinder; - } - - internal bool RegisterContext (HttpListenerContext context) - { - if (!_listening) + if (!_isListening) return false; - lock (_ctxRegistrySync) { - if (!_listening) + lock (_contextRegistrySync) { + if (!_isListening) return false; - _ctxRegistry[context] = context; + context.Listener = this; + + _contextRegistry.AddLast (context); + + if (_waitQueue.Count == 0) { + _contextQueue.Enqueue (context); - var ares = getAsyncResultFromQueue (); - if (ares == null) - _ctxQueue.Add (context); - else - ares.Complete (context); + return true; + } + + var ares = _waitQueue.Dequeue (); + + ares.Complete (context, false); return true; } } - internal void RemoveConnection (HttpConnection connection) - { - lock (_connectionsSync) - _connections.Remove (connection); - } - - internal AuthenticationSchemes SelectAuthenticationScheme (HttpListenerRequest request) + private AuthenticationSchemes selectAuthenticationScheme ( + HttpListenerRequest request + ) { var selector = _authSchemeSelector; + if (selector == null) return _authSchemes; @@ -618,10 +696,34 @@ internal AuthenticationSchemes SelectAuthenticationScheme (HttpListenerRequest r } } + #endregion + + #region Internal Methods + + internal void CheckDisposed () + { + if (_disposed) + throw new ObjectDisposedException (ObjectName); + } + + internal bool RegisterContext (HttpListenerContext context) + { + if (!authenticateClient (context)) + return false; + + if (!registerContext (context)) { + context.SendError (503); + + return false; + } + + return true; + } + internal void UnregisterContext (HttpListenerContext context) { - lock (_ctxRegistrySync) - _ctxRegistry.Remove (context); + lock (_contextRegistrySync) + _contextRegistry.Remove (context); } #endregion @@ -643,44 +745,67 @@ public void Abort () /// Begins getting an incoming request asynchronously. /// /// - /// This asynchronous operation must be completed by calling the EndGetContext method. - /// Typically, the method is invoked by the delegate. + /// + /// This asynchronous operation must be ended by calling + /// the method. + /// + /// + /// Typically, the method is called by + /// . + /// /// /// - /// An that represents the status of the asynchronous operation. + /// 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 represents a user defined object to pass to - /// the delegate. + /// An that specifies a user defined object to pass to + /// . /// + /// + /// This method is canceled. + /// /// /// - /// This listener has no URI prefix on which listens. + /// This listener has not been started or is currently stopped. /// /// /// -or- /// /// - /// This listener hasn't been started, or is currently stopped. + /// This listener has no URI prefix on which listens. /// /// /// /// This listener has been closed. /// - public IAsyncResult BeginGetContext (AsyncCallback callback, Object state) + public IAsyncResult BeginGetContext (AsyncCallback callback, object state) { - CheckDisposed (); - if (_prefixes.Count == 0) - throw new InvalidOperationException ("The listener has no URI prefix on which listens."); + if (_disposed) + throw new ObjectDisposedException (ObjectName); + + if (!_isListening) { + var msg = "The listener has not been started."; + + throw new InvalidOperationException (msg); + } + + if (_prefixes.Count == 0) { + var msg = "The listener has no URI prefix on which listens."; - if (!_listening) - throw new InvalidOperationException ("The listener hasn't been started."); + throw new InvalidOperationException (msg); + } - return BeginGetContext (new HttpListenerAsyncResult (callback, state)); + return beginGetContext (callback, state); } /// @@ -698,65 +823,100 @@ public void Close () /// 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 obtained by calling the BeginGetContext method. + /// An instance obtained by calling + /// the method. /// + /// + /// was not obtained by calling + /// the method. + /// /// /// is . /// - /// - /// wasn't obtained by calling the BeginGetContext method. + /// + /// This method is canceled. /// /// - /// This method was already called for the specified . + /// + /// This listener has not been started or is currently stopped. + /// + /// + /// -or- + /// + /// + /// This method was already called for . + /// /// /// /// This listener has been closed. /// public HttpListenerContext EndGetContext (IAsyncResult asyncResult) { - CheckDisposed (); + if (_disposed) + throw new ObjectDisposedException (ObjectName); + + if (!_isListening) { + var msg = "The listener has not been started."; + + throw new InvalidOperationException (msg); + } + if (asyncResult == null) throw new ArgumentNullException ("asyncResult"); var ares = asyncResult as HttpListenerAsyncResult; - if (ares == null) - throw new ArgumentException ("A wrong IAsyncResult.", "asyncResult"); - if (ares.EndCalled) - throw new InvalidOperationException ("This IAsyncResult cannot be reused."); + if (ares == null) { + var msg = "A wrong IAsyncResult instance."; + + throw new ArgumentException (msg, "asyncResult"); + } + + lock (ares.SyncRoot) { + if (ares.EndCalled) { + var msg = "This IAsyncResult instance cannot be reused."; + + throw new InvalidOperationException (msg); + } + + ares.EndCalled = true; + } - ares.EndCalled = true; if (!ares.IsCompleted) ares.AsyncWaitHandle.WaitOne (); - return ares.GetContext (); // This may throw an exception. + return ares.Context; } /// /// Gets an incoming request. /// /// - /// This method waits for an incoming request, and returns when a request is received. + /// This method waits for an incoming request and returns when + /// a request is received. /// /// /// A that represents a request. /// + /// + /// This method is canceled. + /// /// /// - /// This listener has no URI prefix on which listens. + /// This listener has not been started or is currently stopped. /// /// /// -or- /// /// - /// This listener hasn't been started, or is currently stopped. + /// This listener has no URI prefix on which listens. /// /// /// @@ -764,17 +924,29 @@ public HttpListenerContext EndGetContext (IAsyncResult asyncResult) /// public HttpListenerContext GetContext () { - CheckDisposed (); - if (_prefixes.Count == 0) - throw new InvalidOperationException ("The listener has no URI prefix on which listens."); + if (_disposed) + throw new ObjectDisposedException (ObjectName); + + if (!_isListening) { + var msg = "The listener has not been started."; + + throw new InvalidOperationException (msg); + } - if (!_listening) - throw new InvalidOperationException ("The listener hasn't been started."); + if (_prefixes.Count == 0) { + var msg = "The listener has no URI prefix on which listens."; - var ares = BeginGetContext (new HttpListenerAsyncResult (null, null)); - ares.InGet = true; + throw new InvalidOperationException (msg); + } + + var ares = beginGetContext (null, null); + + ares.EndCalled = true; + + if (!ares.IsCompleted) + ares.AsyncWaitHandle.WaitOne (); - return EndGetContext (ares); + return ares.Context; } /// @@ -785,12 +957,22 @@ public HttpListenerContext GetContext () /// public void Start () { - CheckDisposed (); - if (_listening) - return; + if (_disposed) + throw new ObjectDisposedException (ObjectName); + + lock (_sync) { + if (_disposed) + throw new ObjectDisposedException (ObjectName); + + lock (_contextRegistrySync) { + if (_isListening) + return; - EndPointManager.AddListener (this); - _listening = true; + EndPointManager.AddListener (this); + + _isListening = true; + } + } } /// @@ -801,19 +983,29 @@ public void Start () /// public void Stop () { - CheckDisposed (); - if (!_listening) - return; + if (_disposed) + throw new ObjectDisposedException (ObjectName); + + lock (_sync) { + if (_disposed) + throw new ObjectDisposedException (ObjectName); + + lock (_contextRegistrySync) { + if (!_isListening) + return; + + _isListening = false; + } - _listening = false; - EndPointManager.RemoveListener (this); + cleanupContextQueue (false); + cleanupContextRegistry (); - lock (_ctxRegistrySync) - cleanupContextQueue (true); + var msg = "The listener is stopped."; - cleanupContextRegistry (); - cleanupConnections (); - cleanupWaitQueue (new HttpListenerException (995, "The listener is stopped.")); + cleanupWaitQueue (msg); + + EndPointManager.RemoveListener (this); + } } #endregion diff --git a/websocket-sharp/Net/HttpListenerAsyncResult.cs b/websocket-sharp/Net/HttpListenerAsyncResult.cs index a1c737421..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-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 @@ -55,23 +55,29 @@ internal class HttpListenerAsyncResult : IAsyncResult private AsyncCallback _callback; private bool _completed; + private bool _completedSynchronously; private HttpListenerContext _context; private bool _endCalled; private Exception _exception; - private bool _inGet; + private Logger _log; private object _state; private object _sync; - private bool _syncCompleted; private ManualResetEvent _waitHandle; #endregion #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 (); } @@ -79,6 +85,16 @@ internal HttpListenerAsyncResult (AsyncCallback callback, object state) #region Internal Properties + internal HttpListenerContext Context + { + get { + if (_exception != null) + throw _exception; + + return _context; + } + } + internal bool EndCalled { get { return _endCalled; @@ -89,13 +105,9 @@ internal bool EndCalled { } } - internal bool InGet { + internal object SyncRoot { get { - return _inGet; - } - - set { - _inGet = value; + return _sync; } } @@ -111,14 +123,18 @@ public object AsyncState { public WaitHandle AsyncWaitHandle { get { - lock (_sync) - return _waitHandle ?? (_waitHandle = new ManualResetEvent (_completed)); + lock (_sync) { + if (_waitHandle == null) + _waitHandle = new ManualResetEvent (_completed); + + return _waitHandle; + } } } public bool CompletedSynchronously { get { - return _syncCompleted; + return _completedSynchronously; } } @@ -133,26 +149,26 @@ public bool IsCompleted { #region Private Methods - private static void complete (HttpListenerAsyncResult asyncResult) + private void complete () { - lock (asyncResult._sync) { - asyncResult._completed = true; + lock (_sync) { + _completed = true; - var waitHandle = asyncResult._waitHandle; - if (waitHandle != null) - waitHandle.Set (); + if (_waitHandle != null) + _waitHandle.Set (); } - var callback = asyncResult._callback; - if (callback == null) + if (_callback == null) return; ThreadPool.QueueUserWorkItem ( state => { try { - callback (asyncResult); + _callback (this); } - catch { + catch (Exception ex) { + _log.Error (ex.Message); + _log.Debug (ex.ToString ()); } }, null @@ -165,32 +181,20 @@ private static void complete (HttpListenerAsyncResult asyncResult) internal void Complete (Exception exception) { - _exception = _inGet && (exception is ObjectDisposedException) - ? new HttpListenerException (995, "The listener is closed.") - : exception; + _exception = exception; - complete (this); - } - - internal void Complete (HttpListenerContext context) - { - Complete (context, false); + complete (); } - internal void Complete (HttpListenerContext context, bool syncCompleted) + internal void Complete ( + HttpListenerContext context, + bool completedSynchronously + ) { _context = context; - _syncCompleted = syncCompleted; - - complete (this); - } - - internal HttpListenerContext GetContext () - { - if (_exception != null) - throw _exception; + _completedSynchronously = completedSynchronously; - return _context; + complete (); } #endregion diff --git a/websocket-sharp/Net/HttpListenerContext.cs b/websocket-sharp/Net/HttpListenerContext.cs index 638078d4f..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-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 @@ -39,13 +39,14 @@ using System; using System.Security.Principal; +using System.Text; using WebSocketSharp.Net.WebSockets; namespace WebSocketSharp.Net { /// /// Provides the access to the HTTP request and response objects used by - /// the . + /// the class. /// /// /// This class cannot be inherited. @@ -55,8 +56,8 @@ public sealed class HttpListenerContext #region Private Fields private HttpConnection _connection; - private string _error; - private int _errorStatus; + private string _errorMessage; + private int _errorStatusCode; private HttpListener _listener; private HttpListenerRequest _request; private HttpListenerResponse _response; @@ -70,7 +71,8 @@ public sealed class HttpListenerContext internal HttpListenerContext (HttpConnection connection) { _connection = connection; - _errorStatus = 400; + + _errorStatusCode = 400; _request = new HttpListenerRequest (this); _response = new HttpListenerResponse (this); } @@ -87,27 +89,27 @@ internal HttpConnection Connection { internal string ErrorMessage { get { - return _error; + return _errorMessage; } set { - _error = value; + _errorMessage = value; } } - internal int ErrorStatus { + internal int ErrorStatusCode { get { - return _errorStatus; + return _errorStatusCode; } set { - _errorStatus = value; + _errorStatusCode = value; } } - internal bool HasError { + internal bool HasErrorMessage { get { - return _error != null; + return _errorMessage != null; } } @@ -141,7 +143,8 @@ public HttpListenerRequest Request { /// Gets the HTTP response object used to send a response to the client. /// /// - /// A that represents a response to the client request. + /// A that represents a response to + /// the client request. /// public HttpListenerResponse Response { get { @@ -150,10 +153,16 @@ public HttpListenerResponse Response { } /// - /// Gets the client information (identity, authentication, and security roles). + /// Gets the client information. /// /// - /// A instance that represents the client information. + /// + /// A instance that represents identity, + /// authentication, and security roles for the client. + /// + /// + /// if the client is not authenticated. + /// /// public IPrincipal User { get { @@ -163,45 +172,123 @@ public IPrincipal User { #endregion + #region Private Methods + + private static string createErrorContent ( + int statusCode, + string statusDescription, + string message + ) + { + return message != null && message.Length > 0 + ? String.Format ( + "

{0} {1} ({2})

", + statusCode, + statusDescription, + message + ) + : String.Format ( + "

{0} {1}

", + statusCode, + statusDescription + ); + } + + #endregion + #region Internal Methods - internal bool Authenticate () + internal HttpListenerWebSocketContext GetWebSocketContext (string protocol) { - var schm = _listener.SelectAuthenticationScheme (_request); - if (schm == AuthenticationSchemes.Anonymous) - return true; + _websocketContext = new HttpListenerWebSocketContext (this, protocol); - if (schm == AuthenticationSchemes.None) { - _response.Close (HttpStatusCode.Forbidden); - return false; - } + return _websocketContext; + } - var realm = _listener.GetRealm (); - var user = - HttpUtility.CreateUser ( - _request.Headers["Authorization"], - schm, - realm, - _request.HttpMethod, - _listener.GetUserCredentialsFinder () - ); - - if (user == null || !user.Identity.IsAuthenticated) { - _response.CloseWithAuthChallenge (new AuthenticationChallenge (schm, realm).ToString ()); - return false; + internal void SendAuthenticationChallenge ( + AuthenticationSchemes scheme, + string realm + ) + { + _response.StatusCode = 401; + + var val = new AuthenticationChallenge (scheme, realm).ToString (); + + _response.Headers.InternalSet ("WWW-Authenticate", val, true); + + _response.Close (); + } + + internal void SendError () + { + try { + _response.StatusCode = _errorStatusCode; + _response.ContentType = "text/html"; + + var content = createErrorContent ( + _errorStatusCode, + _response.StatusDescription, + _errorMessage + ); + + var enc = Encoding.UTF8; + var entity = enc.GetBytes (content); + + _response.ContentEncoding = enc; + _response.ContentLength64 = entity.LongLength; + + _response.Close (entity, true); + } + catch { + _connection.Close (true); } + } - _user = user; - return true; + internal void SendError (int statusCode) + { + _errorStatusCode = statusCode; + + SendError (); + } + + internal void SendError (int statusCode, string message) + { + _errorStatusCode = statusCode; + _errorMessage = message; + + SendError (); } - internal bool Register () + internal bool SetUser ( + AuthenticationSchemes scheme, + string realm, + Func credentialsFinder + ) { - return _listener.RegisterContext (this); + 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) + return; + _listener.UnregisterContext (this); } @@ -210,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 represents the subprotocol supported on - /// this 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- @@ -232,23 +324,127 @@ 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) { - if (_websocketContext != null) - throw new InvalidOperationException ("The accepting is already in progress."); + 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 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); + } if (protocol != null) { - if (protocol.Length == 0) - throw new ArgumentException ("An empty string.", "protocol"); + if (protocol.Length == 0) { + var msg = "An empty string."; + + throw new ArgumentException (msg, "protocol"); + } - if (!protocol.IsToken ()) - throw new ArgumentException ("Contains an invalid character.", "protocol"); + if (!protocol.IsToken ()) { + var msg = "It contains an invalid character."; + + throw new ArgumentException (msg, "protocol"); + } } - _websocketContext = new HttpListenerWebSocketContext (this, protocol); - return _websocketContext; + 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 a52eeec03..dec858d53 100644 --- a/websocket-sharp/Net/HttpListenerException.cs +++ b/websocket-sharp/Net/HttpListenerException.cs @@ -2,13 +2,13 @@ /* * HttpListenerException.cs * - * This code is derived from System.Net.HttpListenerException.cs of Mono + * This code is derived from HttpListenerException.cs (System.Net) of Mono * (http://www.mono-project.com). * * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * 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 @@ -44,8 +44,8 @@ namespace WebSocketSharp.Net { /// - /// The exception that is thrown when a gets an error - /// processing an HTTP request. + /// The exception that is thrown when an error occurs processing + /// an HTTP request. /// [Serializable] public class HttpListenerException : Win32Exception @@ -53,17 +53,24 @@ public class HttpListenerException : Win32Exception #region Protected Constructors /// - /// Initializes a new instance of the class from - /// the specified and . + /// Initializes a new instance of the + /// class with the specified serialized data. /// /// - /// A that contains the serialized object data. + /// A that contains the serialized + /// object data. /// /// - /// A that specifies the source for the deserialization. + /// A that specifies the source for + /// the deserialization. /// + /// + /// is . + /// protected HttpListenerException ( - SerializationInfo serializationInfo, StreamingContext streamingContext) + SerializationInfo serializationInfo, + StreamingContext streamingContext + ) : base (serializationInfo, streamingContext) { } @@ -73,18 +80,19 @@ protected HttpListenerException ( #region Public Constructors /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the + /// class. /// public HttpListenerException () { } /// - /// Initializes a new instance of the class - /// with the specified . + /// Initializes a new instance of the + /// class with the specified error code. /// /// - /// An that identifies the error. + /// An that specifies the error code. /// public HttpListenerException (int errorCode) : base (errorCode) @@ -92,14 +100,14 @@ public HttpListenerException (int errorCode) } /// - /// Initializes a new instance of the class - /// with the specified and . + /// Initializes a new instance of the + /// class with the specified error code and message. /// /// - /// An that identifies the error. + /// An that specifies the error code. /// /// - /// A that describes the error. + /// A that specifies the message. /// public HttpListenerException (int errorCode, string message) : base (errorCode, message) @@ -114,7 +122,12 @@ public HttpListenerException (int errorCode, string message) /// Gets the error code that identifies the error that occurred. ///
/// - /// An that identifies the error. + /// + /// An that represents the error code. + /// + /// + /// It is any of the Win32 error codes. + /// /// public override int ErrorCode { get { diff --git a/websocket-sharp/Net/HttpListenerPrefix.cs b/websocket-sharp/Net/HttpListenerPrefix.cs index 960d02edf..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-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 @@ -39,7 +39,6 @@ #endregion using System; -using System.Net; namespace WebSocketSharp.Net { @@ -48,30 +47,23 @@ 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 . - /// - /// - /// This constructor must be called after calling the CheckPrefix method. - /// - /// - /// A that represents the URI prefix. - /// - internal HttpListenerPrefix (string uriPrefix) + internal HttpListenerPrefix (string uriPrefix, HttpListener listener) { _original = uriPrefix; + _listener = listener; + parse (uriPrefix); } @@ -87,7 +79,7 @@ public string Host { public bool IsSecure { get { - return _secure; + return _isSecure; } } @@ -95,10 +87,6 @@ public HttpListener Listener { get { return _listener; } - - set { - _listener = value; - } } public string Original { @@ -119,33 +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 startHost = uriPrefix.IndexOf (':') + 3; - var root = uriPrefix.IndexOf ('/', startHost + 1, len - startHost - 1); + var rootIdx = uriPrefix + .IndexOf ('/', hostStartIdx + 1, len - hostStartIdx - 1); - var colon = uriPrefix.LastIndexOf (':', root - 1, root - startHost - 1); - if (uriPrefix[root - 1] != ']' && colon > startHost) { - _host = uriPrefix.Substring (startHost, colon - startHost); - _port = uriPrefix.Substring (colon + 1, root - colon - 1); + var colonIdx = uriPrefix + .LastIndexOf (':', rootIdx - 1, rootIdx - hostStartIdx - 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 (startHost, root - startHost); - _port = _secure ? "443" : "80"; + _host = uriPrefix.Substring (hostStartIdx, rootIdx - hostStartIdx); + _port = _isSecure ? "443" : "80"; } - _path = uriPrefix.Substring (root); + _path = uriPrefix.Substring (rootIdx); - _prefix = - String.Format ("http{0}://{1}:{2}{3}", _secure ? "s" : "", _host, _port, _path); + var fmt = "{0}://{1}:{2}{3}"; + + _prefix = String.Format (fmt, _scheme, _host, _port, _path); } #endregion @@ -158,61 +161,73 @@ public static void CheckPrefix (string uriPrefix) throw new ArgumentNullException ("uriPrefix"); var len = uriPrefix.Length; - if (len == 0) - throw new ArgumentException ("An empty string.", "uriPrefix"); - if (!(uriPrefix.StartsWith ("http://") || uriPrefix.StartsWith ("https://"))) - throw new ArgumentException ("The scheme isn't 'http' or 'https'.", "uriPrefix"); + if (len == 0) { + var msg = "An empty string."; + + throw new ArgumentException (msg, "uriPrefix"); + } + + var compType = StringComparison.Ordinal; + var isHttpSchm = uriPrefix.StartsWith ("http://", compType) + || uriPrefix.StartsWith ("https://", compType); + + if (!isHttpSchm) { + var msg = "The scheme is not http or https."; + + throw new ArgumentException (msg, "uriPrefix"); + } + + var endIdx = len - 1; + + if (uriPrefix[endIdx] != '/') { + var msg = "It ends without a forward slash."; + + throw new ArgumentException (msg, "uriPrefix"); + } - var startHost = uriPrefix.IndexOf (':') + 3; - if (startHost >= len) - throw new ArgumentException ("No host is specified.", "uriPrefix"); + var hostStartIdx = uriPrefix.IndexOf (':') + 3; - if (uriPrefix[startHost] == ':') - throw new ArgumentException ("No host is specified.", "uriPrefix"); + if (hostStartIdx >= endIdx) { + var msg = "No host is specified."; - var root = uriPrefix.IndexOf ('/', startHost, len - startHost); - if (root == startHost) - throw new ArgumentException ("No host is specified.", "uriPrefix"); + throw new ArgumentException (msg, "uriPrefix"); + } + + if (uriPrefix[hostStartIdx] == ':') { + var msg = "No host is specified."; + + throw new ArgumentException (msg, "uriPrefix"); + } - if (root == -1 || uriPrefix[len - 1] != '/') - throw new ArgumentException ("Ends without '/'.", "uriPrefix"); + var rootIdx = uriPrefix.IndexOf ('/', hostStartIdx, len - hostStartIdx); - if (uriPrefix[root - 1] == ':') - throw new ArgumentException ("No port is specified.", "uriPrefix"); + if (rootIdx == hostStartIdx) { + var msg = "No host is specified."; - if (root == len - 2) - throw new ArgumentException ("No path is specified.", "uriPrefix"); + throw new ArgumentException (msg, "uriPrefix"); + } + + if (uriPrefix[rootIdx - 1] == ':') { + var msg = "No port is specified."; + + throw new ArgumentException (msg, "uriPrefix"); + } + + if (rootIdx == endIdx - 1) { + var msg = "No path is specified."; + + throw new ArgumentException (msg, "uriPrefix"); + } } - /// - /// Determines whether this instance and the specified have the same value. - /// - /// - /// This method will be required to detect duplicates in any collection. - /// - /// - /// An to compare to this instance. - /// - /// - /// true if is a and - /// its value is the same as this instance; otherwise, false. - /// - public override bool Equals (Object obj) + public override bool Equals (object obj) { var pref = obj as HttpListenerPrefix; - return pref != null && pref._prefix == _prefix; + + return pref != null && _prefix.Equals (pref._prefix); } - /// - /// Gets the hash code for this 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 6373b8d65..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-2015 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 @@ -44,13 +44,14 @@ namespace WebSocketSharp.Net { /// - /// Provides the collection used to store the URI prefixes for the . + /// Provides a collection used to store the URI prefixes for a instance of + /// the class. /// /// - /// The 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, IEnumerable, IEnumerable + public class HttpListenerPrefixCollection : ICollection { #region Private Fields @@ -64,6 +65,7 @@ public class HttpListenerPrefixCollection : ICollection, IEnumerable (); } @@ -84,7 +86,8 @@ public int Count { } /// - /// Gets a value indicating whether the access to the collection is read-only. + /// Gets a value indicating whether the access to the collection is + /// read-only. /// /// /// Always returns false. @@ -96,7 +99,8 @@ public bool IsReadOnly { } /// - /// Gets a value indicating whether the access to the collection is synchronized. + /// Gets a value indicating whether the access to the collection is + /// synchronized. /// /// /// Always returns false. @@ -112,67 +116,81 @@ public bool IsSynchronized { #region Public Methods /// - /// Adds the specified to the collection. + /// Adds the specified URI prefix to the collection. /// /// - /// A that represents the URI prefix to add. The prefix must be - /// a well-formed URI prefix with http or https scheme, and must end with a '/'. + /// + /// A that specifies the URI prefix to add. + /// + /// + /// It must be a well-formed URI prefix with http or https scheme, + /// and must end with a forward slash (/). + /// /// - /// - /// is . - /// /// /// is invalid. /// + /// + /// is . + /// /// - /// The associated with this collection is closed. + /// The instance associated with this + /// collection is closed. /// public void Add (string uriPrefix) { _listener.CheckDisposed (); + HttpListenerPrefix.CheckPrefix (uriPrefix); + if (_prefixes.Contains (uriPrefix)) return; - _prefixes.Add (uriPrefix); if (_listener.IsListening) EndPointManager.AddPrefix (uriPrefix, _listener); + + _prefixes.Add (uriPrefix); } /// /// Removes all URI prefixes from the collection. /// /// - /// The associated with this collection is closed. + /// The instance associated with this + /// collection is closed. /// public void Clear () { _listener.CheckDisposed (); - _prefixes.Clear (); + if (_listener.IsListening) EndPointManager.RemoveListener (_listener); + + _prefixes.Clear (); } /// - /// Returns a value indicating whether the collection contains the specified - /// . + /// Returns a value indicating whether the collection contains the + /// specified URI prefix. /// /// - /// true if the collection contains ; - /// otherwise, false. + /// true if the collection contains the URI prefix; otherwise, + /// false. /// /// - /// A that represents the URI prefix to test. + /// A that specifies the URI prefix to test. /// /// /// is . /// /// - /// The associated with this collection is closed. + /// The instance associated with this + /// collection is closed. /// public bool Contains (string uriPrefix) { _listener.CheckDisposed (); + if (uriPrefix == null) throw new ArgumentNullException ("uriPrefix"); @@ -180,49 +198,43 @@ public bool Contains (string uriPrefix) } /// - /// Copies the contents of the collection to the specified . + /// Copies the contents of the collection to the specified array of string. /// /// - /// An that receives the URI prefix strings in the collection. + /// An array of that specifies the destination of + /// the URI prefix strings copied from the collection. /// /// - /// An that represents the zero-based index in - /// at which copying begins. + /// An that specifies the zero-based index in + /// the array at which copying begins. /// - /// - /// The associated with this collection is closed. + /// + /// The space from to the end of + /// is not enough to copy to. + /// + /// + /// is . + /// + /// + /// is less than zero. /// - public void CopyTo (Array array, int offset) - { - _listener.CheckDisposed (); - ((ICollection) _prefixes).CopyTo (array, offset); - } - - /// - /// Copies the contents of the collection to the specified array of . - /// - /// - /// An array of that receives the URI prefix strings in the collection. - /// - /// - /// An that represents the zero-based index in - /// at which copying begins. - /// /// - /// The associated with this collection is closed. + /// The instance associated with this + /// collection is closed. /// public void CopyTo (string[] array, int offset) { _listener.CheckDisposed (); + _prefixes.CopyTo (array, offset); } /// - /// Gets the enumerator used to iterate through the . + /// Gets the enumerator that iterates through the collection. /// /// - /// An instance used to iterate - /// through the collection. + /// An + /// instance that can be used to iterate through the collection. /// public IEnumerator GetEnumerator () { @@ -230,32 +242,36 @@ public IEnumerator GetEnumerator () } /// - /// Removes the specified from the collection. + /// Removes the specified URI prefix from the collection. /// /// - /// true if is successfully found and removed; - /// otherwise, false. + /// true if the URI prefix is successfully removed; otherwise, + /// false. /// /// - /// A that represents the URI prefix to remove. + /// A that specifies the URI prefix to remove. /// /// /// is . /// /// - /// The associated with this collection is closed. + /// The instance associated with this + /// collection is closed. /// public bool Remove (string uriPrefix) { _listener.CheckDisposed (); + if (uriPrefix == null) throw new ArgumentNullException ("uriPrefix"); - var ret = _prefixes.Remove (uriPrefix); - if (ret && _listener.IsListening) + if (!_prefixes.Contains (uriPrefix)) + return false; + + if (_listener.IsListening) EndPointManager.RemovePrefix (uriPrefix, _listener); - return ret; + return _prefixes.Remove (uriPrefix); } #endregion @@ -263,10 +279,11 @@ public bool Remove (string uriPrefix) #region Explicit Interface Implementations /// - /// Gets the enumerator used to iterate through the . + /// Gets the enumerator that iterates through the collection. /// /// - /// An instance used to iterate through the collection. + /// An instance that can be used to iterate + /// through the collection. /// IEnumerator IEnumerable.GetEnumerator () { diff --git a/websocket-sharp/Net/HttpListenerRequest.cs b/websocket-sharp/Net/HttpListenerRequest.cs index 375d5cd4f..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-2018 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 @@ -58,26 +58,27 @@ public sealed class HttpListenerRequest { #region Private Fields - private static readonly byte[] _100continue; - private string[] _acceptTypes; - private bool _chunked; - private HttpConnection _connection; - private Encoding _contentEncoding; - private long _contentLength; - private HttpListenerContext _context; - private CookieCollection _cookies; - private WebHeaderCollection _headers; - private string _httpMethod; - private Stream _inputStream; - private Version _protocolVersion; - private NameValueCollection _queryString; - private string _rawUrl; - private Guid _requestTraceIdentifier; - private Uri _url; - private Uri _urlReferrer; - private bool _urlSet; - private string _userHostName; - private string[] _userLanguages; + private static readonly byte[] _100continue; + private string[] _acceptTypes; + private bool _chunked; + private HttpConnection _connection; + private Encoding _contentEncoding; + private long _contentLength; + private HttpListenerContext _context; + private CookieCollection _cookies; + private static readonly Encoding _defaultEncoding; + private WebHeaderCollection _headers; + private string _httpMethod; + private Stream _inputStream; + private Version _protocolVersion; + private NameValueCollection _queryString; + private string _rawUrl; + private Guid _requestTraceIdentifier; + private Uri _url; + private Uri _urlReferrer; + private bool _urlSet; + private string _userHostName; + private string[] _userLanguages; #endregion @@ -86,6 +87,7 @@ public sealed class HttpListenerRequest static HttpListenerRequest () { _100continue = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n"); + _defaultEncoding = Encoding.UTF8; } #endregion @@ -111,8 +113,8 @@ internal HttpListenerRequest (HttpListenerContext context) /// /// /// - /// An array of that 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. @@ -121,13 +123,14 @@ internal HttpListenerRequest (HttpListenerContext context) public string[] AcceptTypes { get { var val = _headers["Accept"]; + if (val == null) return null; if (_acceptTypes == null) { _acceptTypes = val .SplitHeaderValue (',') - .Trim () + .TrimEach () .ToList () .ToArray (); } @@ -167,7 +170,7 @@ public int ClientCertificateError { public Encoding ContentEncoding { get { if (_contentEncoding == null) - _contentEncoding = getContentEncoding () ?? Encoding.UTF8; + _contentEncoding = getContentEncoding (); return _contentEncoding; } @@ -211,7 +214,7 @@ public string ContentType { } /// - /// Gets the cookies included in the request. + /// Gets the HTTP cookies included in the request. /// /// /// @@ -244,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. @@ -282,8 +285,12 @@ public string HttpMethod { /// public Stream InputStream { get { - if (_inputStream == null) - _inputStream = getInputStream () ?? Stream.Null; + if (_inputStream == null) { + _inputStream = _contentLength > 0 || _chunked + ? _connection + .GetRequestStream (_contentLength, _chunked) + : Stream.Null; + } return _inputStream; } @@ -302,12 +309,12 @@ public bool IsAuthenticated { } /// - /// Gets a value indicating whether the request is sent from the local - /// computer. + /// Gets a value indicating whether the request is sent from the + /// local computer. /// /// - /// true if the request is sent from the same computer as the server; - /// otherwise, false. + /// true if the request is sent from the same computer as + /// the server; otherwise, false. /// public bool IsLocal { get { @@ -338,9 +345,7 @@ public bool IsSecureConnection { /// public bool IsWebSocketRequest { get { - return _httpMethod == "GET" - && _protocolVersion > HttpVersion.Version10 - && _headers.Upgrades ("websocket"); + return _httpMethod == "GET" && _headers.Upgrades ("websocket"); } } @@ -361,8 +366,8 @@ public bool KeepAlive { /// Gets the endpoint to which the request is sent. /// /// - /// A that represents the server IP - /// address and port number. + /// A that represents the server + /// IP address and port number. /// public System.Net.IPEndPoint LocalEndPoint { get { @@ -392,6 +397,9 @@ public Version ProtocolVersion { /// parameters. /// /// + /// Each query parameter is decoded in UTF-8. + /// + /// /// An empty collection if not included. /// /// @@ -399,10 +407,9 @@ public NameValueCollection QueryString { get { if (_queryString == null) { var url = Url; - _queryString = QueryStringCollection.Parse ( - url != null ? url.Query : null, - Encoding.UTF8 - ); + var query = url != null ? url.Query : null; + + _queryString = QueryStringCollection.Parse (query, _defaultEncoding); } return _queryString; @@ -426,8 +433,8 @@ public string RawUrl { /// Gets the endpoint from which the request is sent. /// /// - /// A that represents the client IP - /// address and port number. + /// A that represents the client + /// IP address and port number. /// public System.Net.IPEndPoint RemoteEndPoint { get { @@ -461,9 +468,10 @@ public Guid RequestTraceIdentifier { public Uri Url { get { if (!_urlSet) { - _url = HttpUtility.CreateRequestUrl ( + _url = HttpUtility + .CreateRequestUrl ( _rawUrl, - _userHostName ?? UserHostAddress, + _userHostName, IsWebSocketRequest, IsSecureConnection ); @@ -480,7 +488,7 @@ public Uri Url { /// /// /// - /// A converted from the value of the Referer header. + /// A that represents the value of the Referer header. /// /// /// if the header value is not available. @@ -489,6 +497,7 @@ public Uri Url { public Uri UrlReferrer { get { var val = _headers["Referer"]; + if (val == null) return null; @@ -521,8 +530,8 @@ public string UserAgent { /// Gets the IP address and port number to which the request is sent. /// /// - /// A that represents the server IP address and port - /// number. + /// A that represents the server IP address and + /// port number. /// public string UserHostAddress { get { @@ -540,9 +549,6 @@ public string UserHostAddress { /// /// It includes the port number if provided. /// - /// - /// if the header is not present. - /// /// public string UserHostName { get { @@ -566,11 +572,12 @@ public string UserHostName { public string[] UserLanguages { get { var val = _headers["Accept-Language"]; + if (val == null) return null; if (_userLanguages == null) - _userLanguages = val.Split (',').Trim ().ToList ().ToArray (); + _userLanguages = val.Split (',').TrimEach ().ToList ().ToArray (); return _userLanguages; } @@ -580,44 +587,18 @@ public string[] UserLanguages { #region Private Methods - private void finishInitialization10 () - { - var transferEnc = _headers["Transfer-Encoding"]; - if (transferEnc != null) { - _context.ErrorMessage = "Invalid Transfer-Encoding header"; - return; - } - - if (_httpMethod == "POST") { - if (_contentLength == -1) { - _context.ErrorMessage = "Content-Length header required"; - return; - } - - if (_contentLength == 0) { - _context.ErrorMessage = "Invalid Content-Length header"; - return; - } - } - } - private Encoding getContentEncoding () { var val = _headers["Content-Type"]; + if (val == null) - return null; + return _defaultEncoding; Encoding ret; - HttpUtility.TryGetEncoding (val, out ret); - return ret; - } - - private RequestStream getInputStream () - { - return _contentLength > 0 || _chunked - ? _connection.GetRequestStream (_contentLength, _chunked) - : null; + return HttpUtility.TryGetEncoding (val, out ret) + ? ret + : _defaultEncoding; } #endregion @@ -627,20 +608,26 @@ private RequestStream getInputStream () internal void AddHeader (string headerField) { var start = headerField[0]; + if (start == ' ' || start == '\t') { _context.ErrorMessage = "Invalid header field"; + return; } var colon = headerField.IndexOf (':'); + if (colon < 1) { _context.ErrorMessage = "Invalid header field"; + return; } var name = headerField.Substring (0, colon).Trim (); + if (name.Length == 0 || !name.IsToken ()) { _context.ErrorMessage = "Invalid header name"; + return; } @@ -651,61 +638,68 @@ internal void AddHeader (string headerField) _headers.InternalSet (name, val, false); var lower = name.ToLower (CultureInfo.InvariantCulture); + if (lower == "host") { if (_userHostName != null) { _context.ErrorMessage = "Invalid Host header"; + return; } if (val.Length == 0) { _context.ErrorMessage = "Invalid Host header"; + return; } _userHostName = val; + return; } if (lower == "content-length") { if (_contentLength > -1) { _context.ErrorMessage = "Invalid Content-Length header"; + return; } long len; + if (!Int64.TryParse (val, out len)) { _context.ErrorMessage = "Invalid Content-Length header"; + return; } if (len < 0) { _context.ErrorMessage = "Invalid Content-Length header"; + return; } _contentLength = len; + return; } } internal void FinishInitialization () { - if (_protocolVersion == HttpVersion.Version10) { - finishInitialization10 (); - return; - } - if (_userHostName == null) { _context.ErrorMessage = "Host header required"; + return; } var transferEnc = _headers["Transfer-Encoding"]; + if (transferEnc != null) { - var comparison = StringComparison.OrdinalIgnoreCase; - if (!transferEnc.Equals ("chunked", comparison)) { - _context.ErrorMessage = String.Empty; - _context.ErrorStatus = 501; + var compType = StringComparison.OrdinalIgnoreCase; + + if (!transferEnc.Equals ("chunked", compType)) { + _context.ErrorStatusCode = 501; + _context.ErrorMessage = "Invalid Transfer-Encoding header"; return; } @@ -714,23 +708,35 @@ internal void FinishInitialization () } if (_httpMethod == "POST" || _httpMethod == "PUT") { - if (_contentLength <= 0 && !_chunked) { - _context.ErrorMessage = String.Empty; - _context.ErrorStatus = 411; + if (_contentLength == -1 && !_chunked) { + _context.ErrorStatusCode = 411; + _context.ErrorMessage = "Content-Length header required"; + + return; + } + + if (_contentLength == 0 && !_chunked) { + _context.ErrorStatusCode = 411; + _context.ErrorMessage = "Invalid Content-Length header"; return; } } var expect = _headers["Expect"]; + if (expect != null) { - var comparison = StringComparison.OrdinalIgnoreCase; - if (!expect.Equals ("100-continue", comparison)) { + var compType = StringComparison.OrdinalIgnoreCase; + + if (!expect.Equals ("100-continue", compType)) { + _context.ErrorStatusCode = 417; _context.ErrorMessage = "Invalid Expect header"; + return; } var output = _connection.GetResponseStream (); + output.InternalWrite (_100continue, 0, _100continue.Length); } } @@ -738,10 +744,12 @@ internal void FinishInitialization () internal bool FlushInput () { var input = InputStream; + if (input == Stream.Null) return true; var len = 2048; + if (_contentLength > 0 && _contentLength < len) len = (int) _contentLength; @@ -750,8 +758,10 @@ internal bool FlushInput () while (true) { try { var ares = input.BeginRead (buff, 0, len, null, null); + if (!ares.IsCompleted) { var timeout = 100; + if (!ares.AsyncWaitHandle.WaitOne (timeout)) return false; } @@ -773,47 +783,62 @@ internal bool IsUpgradeRequest (string protocol) internal void SetRequestLine (string requestLine) { var parts = requestLine.Split (new[] { ' ' }, 3); + if (parts.Length < 3) { _context.ErrorMessage = "Invalid request line (parts)"; + return; } var method = parts[0]; + if (method.Length == 0) { _context.ErrorMessage = "Invalid request line (method)"; + + return; + } + + if (!method.IsHttpMethod ()) { + _context.ErrorStatusCode = 501; + _context.ErrorMessage = "Invalid request line (method)"; + return; } var target = parts[1]; + if (target.Length == 0) { _context.ErrorMessage = "Invalid request line (target)"; + return; } var rawVer = parts[2]; + if (rawVer.Length != 8) { _context.ErrorMessage = "Invalid request line (version)"; + return; } - if (rawVer.IndexOf ("HTTP/") != 0) { + if (!rawVer.StartsWith ("HTTP/", StringComparison.Ordinal)) { _context.ErrorMessage = "Invalid request line (version)"; + return; } Version ver; + if (!rawVer.Substring (5).TryCreateVersion (out ver)) { _context.ErrorMessage = "Invalid request line (version)"; + return; } - if (ver.Major < 1) { + if (ver != HttpVersion.Version11) { + _context.ErrorStatusCode = 505; _context.ErrorMessage = "Invalid request line (version)"; - return; - } - if (!method.IsHttpMethod (ver)) { - _context.ErrorMessage = "Invalid request line (method)"; return; } @@ -830,38 +855,44 @@ 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 represents 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 (); } /// - /// Ends an asynchronous operation to get the certificate provided by the - /// client. + /// Ends an asynchronous operation to get the certificate provided by + /// the client. /// /// /// A that represents an X.509 certificate /// provided by the client. /// /// - /// An instance returned when the operation - /// started. + /// An instance obtained by calling + /// the method. /// /// /// This method is not supported. @@ -897,11 +928,12 @@ public override string ToString () { var buff = new StringBuilder (64); + var fmt = "{0} {1} HTTP/{2}\r\n"; + var headers = _headers.ToString (); + buff - .AppendFormat ( - "{0} {1} HTTP/{2}\r\n", _httpMethod, _rawUrl, _protocolVersion - ) - .Append (_headers.ToString ()); + .AppendFormat (fmt, _httpMethod, _rawUrl, _protocolVersion) + .Append (headers); return buff.ToString (); } diff --git a/websocket-sharp/Net/HttpListenerResponse.cs b/websocket-sharp/Net/HttpListenerResponse.cs index b088c94a4..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-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 @@ -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 (); @@ -503,65 +504,21 @@ public Stream OutputStream { } /// - /// Gets or sets the HTTP version used for the response. + /// Gets the HTTP version used for the response. /// /// - /// A that represents the HTTP version used for - /// the response. - /// - /// - /// The value specified for a set operation is . - /// - /// /// - /// The value specified for a set operation does not have its Major - /// property set to 1. - /// - /// - /// -or- + /// A that represents the HTTP version used for + /// the response. /// /// - /// The value specified for a set operation does not have its Minor - /// property set to either 0 or 1. + /// Always returns same as 1.1. /// - /// - /// - /// The response is already being sent. - /// - /// - /// This instance is closed. - /// + /// public Version ProtocolVersion { get { return _version; } - - set { - if (_disposed) { - var name = GetType ().ToString (); - throw new ObjectDisposedException (name); - } - - if (_headersSent) { - var msg = "The response is already being sent."; - throw new InvalidOperationException (msg); - } - - if (value == null) - throw new ArgumentNullException ("value"); - - if (value.Major != 1) { - var msg = "Its Major property is not 1."; - throw new ArgumentException (msg, "value"); - } - - if (value.Minor < 0 || value.Minor > 1) { - var msg = "Its Minor property is not 0 or 1."; - throw new ArgumentException (msg, "value"); - } - - _version = value; - } } /// @@ -608,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"); } @@ -663,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); } @@ -710,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); } @@ -749,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. /// @@ -767,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); } @@ -782,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"); } @@ -800,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; } @@ -818,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), @@ -841,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) @@ -909,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. @@ -927,20 +886,34 @@ public void AppendCookie (Cookie cookie) /// the headers for the response. /// /// - /// A that represents the name of the header to + /// A that specifies the name of the header to /// append. /// /// - /// A that represents the value of the header to + /// A that specifies the value of the header to /// append. /// - /// - /// is or empty. - /// /// /// - /// or contains - /// an invalid character. + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is a string of spaces. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. /// /// /// -or- @@ -949,12 +922,15 @@ public void AppendCookie (Cookie cookie) /// is a restricted header name. /// /// + /// + /// is . + /// /// /// The length of is greater than 65,535 /// characters. /// /// - /// The header cannot be allowed to append to the current headers. + /// The current headers do not allow the header. /// public void AppendHeader (string name, string value) { @@ -981,8 +957,8 @@ public void Close () /// An array of that contains the entity body data. /// /// - /// true if this method blocks execution while flushing the stream - /// to the client; otherwise, false. + /// A : true if this method blocks execution while + /// flushing the stream to the client; otherwise, false. /// /// /// is . @@ -992,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"); @@ -1004,6 +978,7 @@ public void Close (byte[] responseEntity, bool willBlock) if (len > Int32.MaxValue) { close (responseEntity, 1024, willBlock); + return; } @@ -1072,12 +1047,9 @@ public void CopyFrom (HttpListenerResponse templateResponse) /// 302, and the property to "Found". ///
/// - /// A that represents the absolute URL to which + /// A that specifies the absolute URL to which /// the client is redirected to locate a requested resource. /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1089,6 +1061,9 @@ public void CopyFrom (HttpListenerResponse templateResponse) /// is not an absolute URL. /// /// + /// + /// is . + /// /// /// The response is already being sent. /// @@ -1097,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"); } @@ -1127,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) @@ -1146,6 +1120,7 @@ public void SetCookie (Cookie cookie) if (!canSetCookie (cookie)) { var msg = "It cannot be updated."; + throw new ArgumentException (msg, "cookie"); } @@ -1157,18 +1132,32 @@ public void SetCookie (Cookie cookie) /// the headers for the response. /// /// - /// A that represents the name of the header to set. + /// A that specifies the name of the header to set. /// /// - /// A that represents the value of the header to set. + /// A that specifies the value of the header to set. /// - /// - /// is or empty. - /// /// /// - /// or contains - /// an invalid character. + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is a string of spaces. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. /// /// /// -or- @@ -1177,12 +1166,15 @@ public void SetCookie (Cookie cookie) /// is a restricted header name. /// /// + /// + /// is . + /// /// /// The length of is greater than 65,535 /// characters. /// /// - /// The header cannot be allowed to set in the current headers. + /// The current headers do not allow the header. /// public void SetHeader (string name, string value) { diff --git a/websocket-sharp/Net/HttpRequestHeader.cs b/websocket-sharp/Net/HttpRequestHeader.cs index 08785db34..aab3497ff 100644 --- a/websocket-sharp/Net/HttpRequestHeader.cs +++ b/websocket-sharp/Net/HttpRequestHeader.cs @@ -2,13 +2,13 @@ /* * HttpRequestHeader.cs * - * This code is derived from System.Net.HttpRequestHeader.cs of Mono + * This code is derived from HttpRequestHeader.cs (System.Net) of Mono * (http://www.mono-project.com). * * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2014 sta.blockhead + * Copyright (c) 2014-2020 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 @@ -40,12 +40,12 @@ namespace WebSocketSharp.Net { /// - /// Contains the HTTP headers that may be specified in a client request. + /// Indicates the HTTP header that may be specified in a client request. /// /// - /// The HttpRequestHeader enumeration contains the HTTP request headers defined in - /// RFC 2616 for the HTTP/1.1 and - /// RFC 6455 for the WebSocket. + /// The headers of this enumeration are defined in + /// RFC 2616 or + /// RFC 6455. /// public enum HttpRequestHeader { diff --git a/websocket-sharp/Net/HttpResponseHeader.cs b/websocket-sharp/Net/HttpResponseHeader.cs index d8f36ed84..d32afe6c9 100644 --- a/websocket-sharp/Net/HttpResponseHeader.cs +++ b/websocket-sharp/Net/HttpResponseHeader.cs @@ -2,13 +2,13 @@ /* * HttpResponseHeader.cs * - * This code is derived from System.Net.HttpResponseHeader.cs of Mono + * This code is derived from HttpResponseHeader.cs (System.Net) of Mono * (http://www.mono-project.com). * * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2014 sta.blockhead + * Copyright (c) 2014-2020 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 @@ -40,12 +40,12 @@ namespace WebSocketSharp.Net { /// - /// Contains the HTTP headers that can be specified in a server response. + /// Indicates the HTTP header that can be specified in a server response. /// /// - /// The HttpResponseHeader enumeration contains the HTTP response headers defined in - /// RFC 2616 for the HTTP/1.1 and - /// RFC 6455 for the WebSocket. + /// The headers of this enumeration are defined in + /// RFC 2616 or + /// RFC 6455. /// public enum HttpResponseHeader { diff --git a/websocket-sharp/Net/HttpStatusCode.cs b/websocket-sharp/Net/HttpStatusCode.cs index 123415f01..b773a4964 100644 --- a/websocket-sharp/Net/HttpStatusCode.cs +++ b/websocket-sharp/Net/HttpStatusCode.cs @@ -2,7 +2,7 @@ /* * HttpStatusCode.cs * - * This code is derived from System.Net.HttpStatusCode.cs of Mono + * This code is derived from HttpStatusCode.cs (System.Net) of Mono * (http://www.mono-project.com). * * It was automatically generated from ECMA CLI XML Library Specification. @@ -14,7 +14,7 @@ * The MIT License * * Copyright (c) 2001 Ximian, Inc. (http://www.ximian.com) - * Copyright (c) 2012-2014 sta.blockhead + * Copyright (c) 2012-2020 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 @@ -39,68 +39,64 @@ namespace WebSocketSharp.Net { /// - /// Contains the values of the HTTP status codes. + /// Indicates the HTTP status code that can be specified in a server response. /// /// - /// The HttpStatusCode enumeration contains the values of the HTTP status codes defined in - /// RFC 2616 for the HTTP/1.1. + /// The values of this enumeration are defined in + /// RFC 2616. /// public enum HttpStatusCode { /// - /// Equivalent to status code 100. - /// Indicates that the client should continue with its request. + /// Equivalent to status code 100. Indicates that the client should continue + /// with its request. /// Continue = 100, /// - /// Equivalent to status code 101. - /// Indicates that the server is switching the HTTP version or protocol on the connection. + /// Equivalent to status code 101. Indicates that the server is switching + /// the HTTP version or protocol on the connection. /// SwitchingProtocols = 101, /// - /// Equivalent to status code 200. - /// Indicates that the client's request has succeeded. + /// Equivalent to status code 200. Indicates that the client's request has + /// succeeded. /// OK = 200, /// - /// Equivalent to status code 201. - /// Indicates that the client's request has been fulfilled and resulted in a new resource being - /// created. + /// Equivalent to status code 201. Indicates that the client's request has + /// been fulfilled and resulted in a new resource being created. /// Created = 201, /// - /// Equivalent to status code 202. - /// Indicates that the client's request has been accepted for processing, but the processing - /// hasn't been completed. + /// Equivalent to status code 202. Indicates that the client's request has + /// been accepted for processing, but the processing has not been completed. /// Accepted = 202, /// - /// Equivalent to status code 203. - /// Indicates that the returned metainformation is from a local or a third-party copy instead of - /// the origin server. + /// Equivalent to status code 203. Indicates that the returned metainformation + /// is from a local or a third-party copy instead of the origin server. /// NonAuthoritativeInformation = 203, /// - /// Equivalent to status code 204. - /// Indicates that the server has fulfilled the client's request but doesn't need to return - /// an entity-body. + /// Equivalent to status code 204. Indicates that the server has fulfilled + /// the client's request but does not need to return an entity-body. /// NoContent = 204, /// - /// Equivalent to status code 205. - /// Indicates that the server has fulfilled the client's request, and the user agent should - /// reset the document view which caused the request to be sent. + /// Equivalent to status code 205. Indicates that the server has fulfilled + /// the client's request, and the user agent should reset the document view + /// which caused the request to be sent. /// ResetContent = 205, /// - /// Equivalent to status code 206. - /// Indicates that the server has fulfilled the partial GET request for the resource. + /// Equivalent to status code 206. Indicates that the server has fulfilled + /// the partial GET request for the resource. /// PartialContent = 206, /// /// - /// Equivalent to status code 300. - /// Indicates that the requested resource corresponds to any of multiple representations. + /// Equivalent to status code 300. Indicates that the requested resource + /// corresponds to any of multiple representations. /// /// /// MultipleChoices is a synonym for Ambiguous. @@ -109,8 +105,8 @@ public enum HttpStatusCode MultipleChoices = 300, /// /// - /// Equivalent to status code 300. - /// Indicates that the requested resource corresponds to any of multiple representations. + /// Equivalent to status code 300. Indicates that the requested resource + /// corresponds to any of multiple representations. /// /// /// Ambiguous is a synonym for MultipleChoices. @@ -119,9 +115,9 @@ public enum HttpStatusCode Ambiguous = 300, /// /// - /// Equivalent to status code 301. - /// Indicates that the requested resource has been assigned a new permanent URI and - /// any future references to this resource should use one of the returned URIs. + /// Equivalent to status code 301. Indicates that the requested resource + /// has been assigned a new permanent URI and any future references to + /// this resource should use one of the returned URIs. /// /// /// MovedPermanently is a synonym for Moved. @@ -130,9 +126,9 @@ public enum HttpStatusCode MovedPermanently = 301, /// /// - /// Equivalent to status code 301. - /// Indicates that the requested resource has been assigned a new permanent URI and - /// any future references to this resource should use one of the returned URIs. + /// Equivalent to status code 301. Indicates that the requested resource + /// has been assigned a new permanent URI and any future references to + /// this resource should use one of the returned URIs. /// /// /// Moved is a synonym for MovedPermanently. @@ -141,8 +137,8 @@ public enum HttpStatusCode Moved = 301, /// /// - /// Equivalent to status code 302. - /// Indicates that the requested resource is located temporarily under a different URI. + /// Equivalent to status code 302. Indicates that the requested resource + /// is located temporarily under a different URI. /// /// /// Found is a synonym for Redirect. @@ -151,8 +147,8 @@ public enum HttpStatusCode Found = 302, /// /// - /// Equivalent to status code 302. - /// Indicates that the requested resource is located temporarily under a different URI. + /// Equivalent to status code 302. Indicates that the requested resource + /// is located temporarily under a different URI. /// /// /// Redirect is a synonym for Found. @@ -161,9 +157,9 @@ public enum HttpStatusCode Redirect = 302, /// /// - /// Equivalent to status code 303. - /// Indicates that the response to the request can be found under a different URI and - /// should be retrieved using a GET method on that resource. + /// Equivalent to status code 303. Indicates that the response to + /// the request can be found under a different URI and should be + /// retrieved using a GET method on that resource. /// /// /// SeeOther is a synonym for RedirectMethod. @@ -172,9 +168,9 @@ public enum HttpStatusCode SeeOther = 303, /// /// - /// Equivalent to status code 303. - /// Indicates that the response to the request can be found under a different URI and - /// should be retrieved using a GET method on that resource. + /// Equivalent to status code 303. Indicates that the response to + /// the request can be found under a different URI and should be + /// retrieved using a GET method on that resource. /// /// /// RedirectMethod is a synonym for SeeOther. @@ -182,27 +178,26 @@ public enum HttpStatusCode /// RedirectMethod = 303, /// - /// Equivalent to status code 304. - /// Indicates that the client has performed a conditional GET request and access is allowed, - /// but the document hasn't been modified. + /// Equivalent to status code 304. Indicates that the client has performed + /// a conditional GET request and access is allowed, but the document has + /// not been modified. /// NotModified = 304, /// - /// Equivalent to status code 305. - /// Indicates that the requested resource must be accessed through the proxy given by - /// the Location field. + /// Equivalent to status code 305. Indicates that the requested resource + /// must be accessed through the proxy given by the Location field. /// UseProxy = 305, /// - /// Equivalent to status code 306. - /// This status code was used in a previous version of the specification, is no longer used, - /// and is reserved for future use. + /// Equivalent to status code 306. This status code was used in a previous + /// version of the specification, is no longer used, and is reserved for + /// future use. /// Unused = 306, /// /// - /// Equivalent to status code 307. - /// Indicates that the requested resource is located temporarily under a different URI. + /// Equivalent to status code 307. Indicates that the requested resource + /// is located temporarily under a different URI. /// /// /// TemporaryRedirect is a synonym for RedirectKeepVerb. @@ -211,8 +206,8 @@ public enum HttpStatusCode TemporaryRedirect = 307, /// /// - /// Equivalent to status code 307. - /// Indicates that the requested resource is located temporarily under a different URI. + /// Equivalent to status code 307. Indicates that the requested resource + /// is located temporarily under a different URI. /// /// /// RedirectKeepVerb is a synonym for TemporaryRedirect. @@ -220,139 +215,131 @@ public enum HttpStatusCode /// RedirectKeepVerb = 307, /// - /// Equivalent to status code 400. - /// Indicates that the client's request couldn't be understood by the server due to - /// malformed syntax. + /// Equivalent to status code 400. Indicates that the client's request could + /// not be understood by the server due to malformed syntax. /// BadRequest = 400, /// - /// Equivalent to status code 401. - /// Indicates that the client's request requires user authentication. + /// Equivalent to status code 401. Indicates that the client's request + /// requires user authentication. /// Unauthorized = 401, /// - /// Equivalent to status code 402. - /// This status code is reserved for future use. + /// Equivalent to status code 402. This status code is reserved for future + /// use. /// PaymentRequired = 402, /// - /// Equivalent to status code 403. - /// Indicates that the server understood the client's request but is refusing to fulfill it. + /// Equivalent to status code 403. Indicates that the server understood + /// the client's request but is refusing to fulfill it. /// Forbidden = 403, /// - /// Equivalent to status code 404. - /// Indicates that the server hasn't found anything matching the request URI. + /// Equivalent to status code 404. Indicates that the server has not found + /// anything matching the request URI. /// NotFound = 404, /// - /// Equivalent to status code 405. - /// Indicates that the method specified in the request line isn't allowed for the resource - /// identified by the request URI. + /// Equivalent to status code 405. Indicates that the method specified + /// in the request line is not allowed for the resource identified by + /// the request URI. /// MethodNotAllowed = 405, /// - /// Equivalent to status code 406. - /// Indicates that the server doesn't have the appropriate resource to respond to the Accept - /// headers in the client's request. + /// Equivalent to status code 406. Indicates that the server does not + /// have the appropriate resource to respond to the Accept headers in + /// the client's request. /// NotAcceptable = 406, /// - /// Equivalent to status code 407. - /// Indicates that the client must first authenticate itself with the proxy. + /// Equivalent to status code 407. Indicates that the client must first + /// authenticate itself with the proxy. /// ProxyAuthenticationRequired = 407, /// - /// Equivalent to status code 408. - /// Indicates that the client didn't produce a request within the time that the server was - /// prepared to wait. + /// Equivalent to status code 408. Indicates that the client did not produce + /// a request within the time that the server was prepared to wait. /// RequestTimeout = 408, /// - /// Equivalent to status code 409. - /// Indicates that the client's request couldn't be completed due to a conflict on the server. + /// Equivalent to status code 409. Indicates that the client's request could + /// not be completed due to a conflict on the server. /// Conflict = 409, /// - /// Equivalent to status code 410. - /// Indicates that the requested resource is no longer available at the server and - /// no forwarding address is known. + /// Equivalent to status code 410. Indicates that the requested resource is + /// no longer available at the server and no forwarding address is known. /// Gone = 410, /// - /// Equivalent to status code 411. - /// Indicates that the server refuses to accept the client's request without a defined - /// Content-Length. + /// Equivalent to status code 411. Indicates that the server refuses to + /// accept the client's request without a defined Content-Length. /// LengthRequired = 411, /// - /// Equivalent to status code 412. - /// Indicates that the precondition given in one or more of the request headers evaluated to - /// false when it was tested on the server. + /// Equivalent to status code 412. Indicates that the precondition given in + /// one or more of the request headers evaluated to false when it was tested + /// on the server. /// PreconditionFailed = 412, /// - /// Equivalent to status code 413. - /// Indicates that the entity of the client's request is larger than the server is willing or - /// able to process. + /// Equivalent to status code 413. Indicates that the entity of the client's + /// request is larger than the server is willing or able to process. /// RequestEntityTooLarge = 413, /// - /// Equivalent to status code 414. - /// Indicates that the request URI is longer than the server is willing to interpret. + /// Equivalent to status code 414. Indicates that the request URI is longer + /// than the server is willing to interpret. /// RequestUriTooLong = 414, /// - /// Equivalent to status code 415. - /// Indicates that the entity of the client's request is in a format not supported by - /// the requested resource for the requested method. + /// Equivalent to status code 415. Indicates that the entity of the client's + /// request is in a format not supported by the requested resource for the + /// requested method. /// UnsupportedMediaType = 415, /// - /// Equivalent to status code 416. - /// Indicates that none of the range specifier values in a Range request header overlap - /// the current extent of the selected resource. + /// Equivalent to status code 416. Indicates that none of the range + /// specifier values in a Range request header overlap the current + /// extent of the selected resource. /// RequestedRangeNotSatisfiable = 416, /// - /// Equivalent to status code 417. - /// Indicates that the expectation given in an Expect request header couldn't be met by - /// the server. + /// Equivalent to status code 417. Indicates that the expectation given in + /// an Expect request header could not be met by the server. /// ExpectationFailed = 417, /// - /// Equivalent to status code 500. - /// Indicates that the server encountered an unexpected condition which prevented it from - /// fulfilling the client's request. + /// Equivalent to status code 500. Indicates that the server encountered + /// an unexpected condition which prevented it from fulfilling the client's + /// request. /// InternalServerError = 500, /// - /// Equivalent to status code 501. - /// Indicates that the server doesn't support the functionality required to fulfill the client's - /// request. + /// Equivalent to status code 501. Indicates that the server does not + /// support the functionality required to fulfill the client's request. /// NotImplemented = 501, /// - /// Equivalent to status code 502. - /// Indicates that a gateway or proxy server received an invalid response from the upstream - /// server. + /// Equivalent to status code 502. Indicates that a gateway or proxy server + /// received an invalid response from the upstream server. /// BadGateway = 502, /// - /// Equivalent to status code 503. - /// Indicates that the server is currently unable to handle the client's request due to - /// a temporary overloading or maintenance of the server. + /// Equivalent to status code 503. Indicates that the server is currently + /// unable to handle the client's request due to a temporary overloading + /// or maintenance of the server. /// ServiceUnavailable = 503, /// - /// Equivalent to status code 504. - /// Indicates that a gateway or proxy server didn't receive a timely response from the upstream - /// server or some other auxiliary server. + /// Equivalent to status code 504. Indicates that a gateway or proxy server + /// did not receive a timely response from the upstream server or some other + /// auxiliary server. /// GatewayTimeout = 504, /// - /// Equivalent to status code 505. - /// Indicates that the server doesn't support the HTTP version used in the client's request. + /// Equivalent to status code 505. Indicates that the server does not + /// support the HTTP version used in the client's request. /// HttpVersionNotSupported = 505, } diff --git a/websocket-sharp/Net/HttpStreamAsyncResult.cs b/websocket-sharp/Net/HttpStreamAsyncResult.cs index 44189303c..09447ea21 100644 --- a/websocket-sharp/Net/HttpStreamAsyncResult.cs +++ b/websocket-sharp/Net/HttpStreamAsyncResult.cs @@ -8,7 +8,7 @@ * The MIT License * * Copyright (c) 2005 Novell, Inc. (http://www.novell.com) - * Copyright (c) 2012-2015 sta.blockhead + * Copyright (c) 2012-2021 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 @@ -65,6 +65,7 @@ internal HttpStreamAsyncResult (AsyncCallback callback, object state) { _callback = callback; _state = state; + _sync = new object (); } @@ -136,8 +137,12 @@ public object AsyncState { public WaitHandle AsyncWaitHandle { get { - lock (_sync) - return _waitHandle ?? (_waitHandle = new ManualResetEvent (_completed)); + lock (_sync) { + if (_waitHandle == null) + _waitHandle = new ManualResetEvent (_completed); + + return _waitHandle; + } } } @@ -165,6 +170,7 @@ internal void Complete () return; _completed = true; + if (_waitHandle != null) _waitHandle.Set (); @@ -175,8 +181,19 @@ internal void Complete () internal void Complete (Exception exception) { - _exception = exception; - Complete (); + lock (_sync) { + if (_completed) + return; + + _completed = true; + _exception = exception; + + if (_waitHandle != null) + _waitHandle.Set (); + + if (_callback != null) + _callback.BeginInvoke (this, ar => _callback.EndInvoke (ar), null); + } } #endregion diff --git a/websocket-sharp/Net/HttpUtility.cs b/websocket-sharp/Net/HttpUtility.cs index 47ea7ee3a..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) @@ -711,6 +767,7 @@ internal static Uri CreateRequestUrl ( } else if (requestUri.MaybeUri ()) { Uri uri; + if (!Uri.TryCreate (requestUri, UriKind.Absolute, out uri)) return null; @@ -729,6 +786,7 @@ internal static Uri CreateRequestUrl ( } else { // As the authority form. + host = requestUri; } @@ -742,8 +800,8 @@ internal static Uri CreateRequestUrl ( host = String.Format ("{0}:{1}", host, secure ? 443 : 80); var url = String.Format ("{0}://{1}{2}", schm, host, path); - Uri ret; + return Uri.TryCreate (url, UriKind.Absolute, out ret) ? ret : null; } @@ -774,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); } @@ -797,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; @@ -815,10 +879,12 @@ internal static Encoding GetEncoding (string contentType) foreach (var elm in contentType.SplitHeaderValue (';')) { var part = elm.Trim (); - if (part.IndexOf (name, compType) != 0) + + if (!part.StartsWith (name, compType)) continue; var val = part.GetValue ('=', true); + if (val == null || val.Length == 0) return null; @@ -829,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; @@ -867,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) @@ -889,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) @@ -911,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) @@ -925,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) @@ -941,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"); @@ -970,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) @@ -983,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) @@ -997,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); } @@ -1006,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"); @@ -1022,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) @@ -1033,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) @@ -1049,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) @@ -1067,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"); @@ -1083,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) @@ -1096,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; } @@ -1113,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); } @@ -1122,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 2e925e2d1..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 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 @@ -61,17 +61,6 @@ public QueryStringCollection (int capacity) #endregion - #region Private Methods - - private static string urlDecode (string s, Encoding encoding) - { - return s.IndexOfAny (new[] { '%', '+' }) > -1 - ? HttpUtility.UrlDecode (s, encoding) - : s; - } - - #endregion - #region Public Methods public static QueryStringCollection Parse (string query) @@ -84,8 +73,7 @@ public static QueryStringCollection Parse (string query, Encoding encoding) if (query == null) return new QueryStringCollection (1); - var len = query.Length; - if (len == 0) + if (query.Length == 0) return new QueryStringCollection (1); if (query == "?") @@ -99,32 +87,34 @@ public static QueryStringCollection Parse (string query, Encoding encoding) var ret = new QueryStringCollection (); - var components = query.Split ('&'); - foreach (var component in components) { - len = component.Length; + foreach (var component in query.Split ('&')) { + var len = component.Length; + if (len == 0) continue; if (component == "=") continue; - var i = component.IndexOf ('='); - if (i < 0) { - ret.Add (null, urlDecode (component, encoding)); - continue; - } + string name = null; + string val = null; - if (i == 0) { - ret.Add (null, urlDecode (component.Substring (1), encoding)); - continue; - } + var idx = component.IndexOf ('='); - var name = urlDecode (component.Substring (0, i), encoding); + if (idx < 0) { + val = component.UrlDecode (encoding); + } + else if (idx == 0) { + val = component.Substring (1).UrlDecode (encoding); + } + else { + name = component.Substring (0, idx).UrlDecode (encoding); - var start = i + 1; - var val = start < len - ? urlDecode (component.Substring (start), encoding) - : String.Empty; + var start = idx + 1; + val = start < len + ? component.Substring (start).UrlDecode (encoding) + : String.Empty; + } ret.Add (name, val); } @@ -134,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 780a69b5a..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-2015 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,13 +56,18 @@ 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; _offset = offset; _count = count; - _initialCount = count; _asyncResult = asyncResult; + + _initialCount = count; } #endregion diff --git a/websocket-sharp/Net/RequestStream.cs b/websocket-sharp/Net/RequestStream.cs index dd40b3784..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-2015 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 @@ -46,27 +46,27 @@ internal class RequestStream : Stream { #region Private Fields - private long _bodyLeft; - private byte[] _buffer; - private int _count; - private bool _disposed; - private int _offset; - private Stream _stream; + private long _bodyLeft; + private int _count; + private bool _disposed; + private byte[] _initialBuffer; + private Stream _innerStream; + private int _offset; #endregion #region Internal Constructors - internal RequestStream (Stream stream, byte[] buffer, int offset, int count) - : this (stream, buffer, offset, count, -1) - { - } - internal RequestStream ( - Stream stream, byte[] buffer, int offset, int count, long contentLength) + Stream innerStream, + byte[] initialBuffer, + int offset, + int count, + long contentLength + ) { - _stream = stream; - _buffer = buffer; + _innerStream = innerStream; + _initialBuffer = initialBuffer; _offset = offset; _count = count; _bodyLeft = contentLength; @@ -74,6 +74,34 @@ internal RequestStream ( #endregion + #region Internal Properties + + internal int Count { + get { + return _count; + } + } + + internal byte[] InitialBuffer { + get { + return _initialBuffer; + } + } + + internal string ObjectName { + get { + return GetType ().ToString (); + } + } + + internal int Offset { + get { + return _offset; + } + } + + #endregion + #region Public Properties public override bool CanRead { @@ -114,40 +142,30 @@ public override long Position { #region Private Methods - // Returns 0 if we can keep reading from the base stream, - // > 0 if we read something from the buffer, - // -1 if we had a content length set and we finished reading that many bytes. - private int fillFromBuffer (byte[] buffer, int offset, int count) + private int fillFromInitialBuffer (byte[] buffer, int offset, int count) { - if (buffer == null) - throw new ArgumentNullException ("buffer"); - - if (offset < 0) - throw new ArgumentOutOfRangeException ("offset", "A negative value."); - - if (count < 0) - throw new ArgumentOutOfRangeException ("count", "A negative value."); - - var len = buffer.Length; - if (offset + count > len) - throw new ArgumentException ( - "The sum of 'offset' and 'count' is greater than 'buffer' length."); + // This method returns a int: + // - > 0 The number of bytes read from the initial buffer + // - 0 No more bytes read from the initial buffer + // - -1 No more content data if (_bodyLeft == 0) return -1; - if (_count == 0 || count == 0) + if (_count == 0) return 0; if (count > _count) count = _count; - if (_bodyLeft > 0 && count > _bodyLeft) + if (_bodyLeft > 0 && _bodyLeft < count) count = (int) _bodyLeft; - Buffer.BlockCopy (_buffer, _offset, buffer, offset, count); + Buffer.BlockCopy (_initialBuffer, _offset, buffer, offset, count); + _offset += count; _count -= count; + if (_bodyLeft > 0) _bodyLeft -= count; @@ -159,32 +177,70 @@ private int fillFromBuffer (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) - throw new ObjectDisposedException (GetType ().ToString ()); + throw new ObjectDisposedException (ObjectName); + + if (buffer == null) + throw new ArgumentNullException ("buffer"); + + if (offset < 0) { + var msg = "A negative value."; + + throw new ArgumentOutOfRangeException ("offset", msg); + } + + if (count < 0) { + var msg = "A negative value."; + + throw new ArgumentOutOfRangeException ("count", msg); + } + + var len = buffer.Length; + + if (offset + count > len) { + var msg = "The sum of offset and count is greater than the length of buffer."; + + throw new ArgumentException (msg); + } - var nread = fillFromBuffer (buffer, offset, count); - if (nread > 0 || nread == -1) { + if (count == 0) + return _innerStream.BeginRead (buffer, offset, 0, callback, state); + + var nread = fillFromInitialBuffer (buffer, offset, count); + + if (nread != 0) { var ares = new HttpStreamAsyncResult (callback, state); + ares.Buffer = buffer; ares.Offset = offset; ares.Count = count; ares.SyncRead = nread > 0 ? nread : 0; + ares.Complete (); return ares; } - // Avoid reading past the end of the request to allow for HTTP pipelining. - if (_bodyLeft >= 0 && count > _bodyLeft) + if (_bodyLeft > 0 && _bodyLeft < count) count = (int) _bodyLeft; - return _stream.BeginRead (buffer, offset, count, callback, state); + return _innerStream.BeginRead (buffer, offset, count, callback, state); } 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 (); } @@ -197,21 +253,22 @@ public override void Close () public override int EndRead (IAsyncResult asyncResult) { if (_disposed) - throw new ObjectDisposedException (GetType ().ToString ()); + throw new ObjectDisposedException (ObjectName); if (asyncResult == null) throw new ArgumentNullException ("asyncResult"); if (asyncResult is HttpStreamAsyncResult) { var ares = (HttpStreamAsyncResult) asyncResult; + if (!ares.IsCompleted) ares.AsyncWaitHandle.WaitOne (); return ares.SyncRead; } - // Close on exception? - var nread = _stream.EndRead (asyncResult); + var nread = _innerStream.EndRead (asyncResult); + if (nread > 0 && _bodyLeft > 0) _bodyLeft -= nread; @@ -230,17 +287,47 @@ public override void Flush () public override int Read (byte[] buffer, int offset, int count) { if (_disposed) - throw new ObjectDisposedException (GetType ().ToString ()); + throw new ObjectDisposedException (ObjectName); + + if (buffer == null) + throw new ArgumentNullException ("buffer"); - // Call the fillFromBuffer method to check for buffer boundaries even when _bodyLeft is 0. - var nread = fillFromBuffer (buffer, offset, count); - if (nread == -1) // No more bytes available (Content-Length). + if (offset < 0) { + var msg = "A negative value."; + + throw new ArgumentOutOfRangeException ("offset", msg); + } + + if (count < 0) { + var msg = "A negative value."; + + throw new ArgumentOutOfRangeException ("count", msg); + } + + var len = buffer.Length; + + if (offset + count > len) { + var msg = "The sum of offset and count is greater than the length of buffer."; + + throw new ArgumentException (msg); + } + + if (count == 0) + return 0; + + var nread = fillFromInitialBuffer (buffer, offset, count); + + if (nread == -1) return 0; if (nread > 0) return nread; - nread = _stream.Read (buffer, offset, count); + if (_bodyLeft > 0 && _bodyLeft < count) + count = (int) _bodyLeft; + + nread = _innerStream.Read (buffer, offset, count); + if (nread > 0 && _bodyLeft > 0) _bodyLeft -= nread; 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 ad9b9e7c2..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-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 @@ -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 { @@ -59,30 +60,17 @@ public class ServerSslConfiguration #region Public Constructors /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the + /// class. /// public ServerSslConfiguration () { - _enabledSslProtocols = SslProtocols.Default; + _enabledSslProtocols = SslProtocols.None; } /// - /// Initializes a new instance of the class - /// with the specified . - /// - /// - /// A that represents the certificate used to - /// authenticate the server. - /// - public ServerSslConfiguration (X509Certificate2 serverCertificate) - { - _serverCert = serverCertificate; - _enabledSslProtocols = SslProtocols.Default; - } - - /// - /// Copies the parameters from the specified to - /// a new instance of the class. + /// Initializes a new instance of the + /// class copying from the specified configuration. /// /// /// A from which to copy. @@ -130,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. /// /// @@ -153,20 +141,22 @@ public bool ClientCertificateRequired { } /// - /// Gets or sets the callback used to validate the certificate - /// supplied by the client. + /// Gets or sets the callback used to validate the certificate supplied by + /// 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 { @@ -183,15 +173,17 @@ public RemoteCertificateValidationCallback ClientCertificateValidationCallback { } /// - /// Gets or sets the protocols used for authentication. + /// Gets or sets the enabled versions of the SSL/TLS protocols. /// /// /// - /// The enum values that represent - /// the protocols used for authentication. + /// Any of the enum values. + /// + /// + /// It represents the enabled versions of the SSL/TLS protocols. /// /// - /// The default value is . + /// The default value is . /// /// public SslProtocols EnabledSslProtocols { @@ -209,11 +201,13 @@ public SslProtocols EnabledSslProtocols { /// /// /// - /// A or - /// if not specified. + /// A that represents an X.509 certificate. + /// + /// + /// if not present. /// /// - /// That instance represents an X.509 certificate. + /// The default value is . /// /// public X509Certificate2 ServerCertificate { diff --git a/websocket-sharp/Net/WebHeaderCollection.cs b/websocket-sharp/Net/WebHeaderCollection.cs index 4463fdf5e..e41150811 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-2015 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 @@ -560,9 +560,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 +571,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) @@ -634,7 +634,8 @@ internal HttpHeaderType State { /// Gets all header names in the collection. /// /// - /// An array of that contains all header names in the collection. + /// An array of that contains all header names in + /// the collection. /// public override string[] AllKeys { get { @@ -646,7 +647,8 @@ public override string[] AllKeys { /// Gets the number of headers in the collection. /// /// - /// An that represents the number of headers in the collection. + /// An that represents the number of headers in + /// the collection. /// public override int Count { get { @@ -655,14 +657,18 @@ public override int Count { } /// - /// Gets or sets the specified request in the collection. + /// Gets or sets the specified request header. /// /// - /// A that represents the value of the request . + /// A that represents the value of the request header. /// /// - /// One of the enum values, represents - /// the request header to get or set. + /// + /// One of the enum values. + /// + /// + /// It specifies the request header to get or set. + /// /// /// /// @@ -672,19 +678,22 @@ public override int Count { /// -or- /// /// - /// contains invalid characters. + /// contains an invalid character. /// /// /// - /// The length of is greater than 65,535 characters. + /// The length of is greater than 65,535 + /// characters. /// /// - /// The current instance doesn't allow - /// the request . + /// This instance does not allow the request header. /// public string this[HttpRequestHeader header] { get { - return Get (Convert (header)); + var key = header.ToString (); + var name = getHeaderName (key); + + return Get (name); } set { @@ -693,14 +702,18 @@ public string this[HttpRequestHeader header] { } /// - /// Gets or sets the specified response in the collection. + /// Gets or sets the specified response header. /// /// - /// A that represents the value of the response . + /// A that represents the value of the response header. /// /// - /// One of the enum values, represents - /// the response header to get or set. + /// + /// One of the enum values. + /// + /// + /// It specifies the response header to get or set. + /// /// /// /// @@ -710,19 +723,22 @@ public string this[HttpRequestHeader header] { /// -or- /// /// - /// contains invalid characters. + /// contains an invalid character. /// /// /// - /// The length of is greater than 65,535 characters. + /// The length of is greater than 65,535 + /// characters. /// /// - /// The current instance doesn't allow - /// the response . + /// This instance does not allow the response header. /// public string this[HttpResponseHeader header] { get { - return Get (Convert (header)); + var key = header.ToString (); + var name = getHeaderName (key); + + return Get (name); } set { @@ -747,175 +763,175 @@ public override NameObjectCollectionBase.KeysCollection Keys { #region Private Methods - private void add (string name, string value, bool ignoreRestricted) + private void add (string name, string value, HttpHeaderType headerType) { - var act = ignoreRestricted - ? (Action ) addWithoutCheckingNameAndRestricted - : addWithoutCheckingName; + base.Add (name, value); - doWithCheckingState (act, checkName (name), value, true); - } + if (_state != HttpHeaderType.Unspecified) + return; - private void addWithoutCheckingName (string name, string value) - { - doWithoutCheckingName (base.Add, name, value); - } + if (headerType == HttpHeaderType.Unspecified) + return; - private void addWithoutCheckingNameAndRestricted (string name, string value) - { - base.Add (name, checkValue (value)); + _state = headerType; } - private static int checkColonSeparated (string header) + private void checkAllowed (HttpHeaderType headerType) { - var idx = header.IndexOf (':'); - if (idx == -1) - throw new ArgumentException ("No colon could be found.", "header"); + if (_state == HttpHeaderType.Unspecified) + return; - return idx; - } + if (headerType == HttpHeaderType.Unspecified) + return; - private static HttpHeaderType checkHeaderType (string name) - { - var info = getHeaderInfo (name); - return info == null - ? HttpHeaderType.Unspecified - : info.IsRequest && !info.IsResponse - ? HttpHeaderType.Request - : !info.IsRequest && info.IsResponse - ? HttpHeaderType.Response - : HttpHeaderType.Unspecified; + if (headerType != _state) { + var msg = "This instance does not allow the header."; + + throw new InvalidOperationException (msg); + } } - private static string checkName (string name) + private static string checkName (string name, string paramName) { - if (name == null) - throw new ArgumentNullException ("name"); + if (name == null) { + var msg = "The name is null."; - if (name.Length == 0) - throw new ArgumentException ("An empty string.", "name"); + throw new ArgumentNullException (paramName, msg); + } + + if (name.Length == 0) { + var msg = "The name is an empty string."; + + throw new ArgumentException (msg, paramName); + } name = name.Trim (); - if (name.Length == 0) - throw new ArgumentException ("A string of spaces.", "name"); + if (name.Length == 0) { + var msg = "The name is a string of spaces."; - if (!IsHeaderName (name)) { - var msg = "It contains an invalid character."; + throw new ArgumentException (msg, paramName); + } + + if (!name.IsToken ()) { + var msg = "The name contains an invalid character."; - throw new ArgumentException (msg, "name"); + throw new ArgumentException (msg, paramName); } return name; } - private void checkRestricted (string name) + private void checkRestricted (string name, HttpHeaderType headerType) { - if (!_internallyUsed && isRestricted (name, true)) - throw new ArgumentException ("This header must be modified with the appropiate property."); - } - - private void checkState (bool response) - { - if (_state == HttpHeaderType.Unspecified) + if (_internallyUsed) return; - if (response && _state == HttpHeaderType.Request) - throw new InvalidOperationException ( - "This collection has already been used to store the request headers."); + var res = headerType == HttpHeaderType.Response; - if (!response && _state == HttpHeaderType.Response) - throw new InvalidOperationException ( - "This collection has already been used to store the response headers."); + if (isRestricted (name, res)) { + var msg = "The header is a restricted header."; + + throw new ArgumentException (msg); + } } - private static string checkValue (string value) + private static string checkValue (string value, string paramName) { - if (value == null || value.Length == 0) + if (value == null) return String.Empty; value = value.Trim (); - if (value.Length > 65535) - throw new ArgumentOutOfRangeException ("value", "Greater than 65,535 characters."); - if (!IsHeaderValue (value)) - throw new ArgumentException ("Contains invalid characters.", "value"); + var len = value.Length; + + if (len == 0) + return value; + + if (len > 65535) { + var msg = "The length of the value is greater than 65,535 characters."; + + throw new ArgumentOutOfRangeException (paramName, msg); + } + + if (!value.IsText ()) { + var msg = "The value contains an invalid character."; + + throw new ArgumentException (msg, paramName); + } return value; } - private static string convert (string key) + private static HttpHeaderInfo getHeaderInfo (string name) { - HttpHeaderInfo info; - return _headers.TryGetValue (key, out info) ? info.Name : String.Empty; - } + var compType = StringComparison.InvariantCultureIgnoreCase; - private void doWithCheckingState ( - Action action, string name, string value, bool setState) - { - var type = checkHeaderType (name); - if (type == HttpHeaderType.Request) - doWithCheckingState (action, name, value, false, setState); - else if (type == HttpHeaderType.Response) - doWithCheckingState (action, name, value, true, setState); - else - action (name, value); + foreach (var headerInfo in _headers.Values) { + if (headerInfo.HeaderName.Equals (name, compType)) + return headerInfo; + } + + return null; } - private void doWithCheckingState ( - Action action, string name, string value, bool response, bool setState) + private static string getHeaderName (string key) { - checkState (response); - action (name, value); - if (setState && _state == HttpHeaderType.Unspecified) - _state = response ? HttpHeaderType.Response : HttpHeaderType.Request; + HttpHeaderInfo headerInfo; + + return _headers.TryGetValue (key, out headerInfo) + ? headerInfo.HeaderName + : null; } - private void doWithoutCheckingName (Action action, string name, string value) + private static HttpHeaderType getHeaderType (string name) { - checkRestricted (name); - action (name, checkValue (value)); + var headerInfo = getHeaderInfo (name); + + if (headerInfo == null) + return HttpHeaderType.Unspecified; + + if (headerInfo.IsRequest) { + return !headerInfo.IsResponse + ? HttpHeaderType.Request + : HttpHeaderType.Unspecified; + } + + return headerInfo.IsResponse + ? HttpHeaderType.Response + : HttpHeaderType.Unspecified; } - private static HttpHeaderInfo getHeaderInfo (string name) + private static bool isMultiValue (string name, bool response) { - foreach (var info in _headers.Values) - if (info.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase)) - return info; + var headerInfo = getHeaderInfo (name); - return null; + return headerInfo != null && headerInfo.IsMultiValue (response); } private static bool isRestricted (string name, bool response) { - var info = getHeaderInfo (name); - return info != null && info.IsRestricted (response); - } + var headerInfo = getHeaderInfo (name); - private void removeWithoutCheckingName (string name, string unuse) - { - checkRestricted (name); - base.Remove (name); + return headerInfo != null && headerInfo.IsRestricted (response); } - private void setWithoutCheckingName (string name, string value) + private void set (string name, string value, HttpHeaderType headerType) { - doWithoutCheckingName (base.Set, name, value); - } + base.Set (name, value); - #endregion + if (_state != HttpHeaderType.Unspecified) + return; - #region Internal Methods + if (headerType == HttpHeaderType.Unspecified) + return; - internal static string Convert (HttpRequestHeader header) - { - return convert (header.ToString ()); + _state = headerType; } - internal static string Convert (HttpResponseHeader header) - { - return convert (header.ToString ()); - } + #endregion + + #region Internal Methods internal void InternalRemove (string name) { @@ -924,52 +940,71 @@ internal void InternalRemove (string name) internal void InternalSet (string header, bool response) { - var pos = checkColonSeparated (header); - InternalSet (header.Substring (0, pos), header.Substring (pos + 1), response); + var idx = header.IndexOf (':'); + + if (idx == -1) { + var msg = "It does not contain a colon character."; + + throw new ArgumentException (msg, "header"); + } + + var name = header.Substring (0, idx); + var val = idx < header.Length - 1 + ? header.Substring (idx + 1) + : String.Empty; + + name = checkName (name, "header"); + val = checkValue (val, "header"); + + if (isMultiValue (name, response)) { + base.Add (name, val); + + return; + } + + base.Set (name, val); } internal void InternalSet (string name, string value, bool response) { - value = checkValue (value); - if (IsMultiValue (name, response)) + value = checkValue (value, "value"); + + if (isMultiValue (name, response)) { base.Add (name, value); - else - base.Set (name, value); - } - internal static bool IsHeaderName (string name) - { - return name.IsToken (); - } + return; + } - internal static bool IsHeaderValue (string value) - { - return value.IsText (); + base.Set (name, value); } - internal static bool IsMultiValue (string headerName, bool response) + internal string ToStringMultiValue (bool response) { - if (headerName == null || headerName.Length == 0) - return false; + var cnt = Count; - var info = getHeaderInfo (headerName); - return info != null && info.IsMultiValue (response); - } + if (cnt == 0) + return "\r\n"; - internal string ToStringMultiValue (bool response) - { var buff = new StringBuilder (); - Count.Times ( - i => { - var key = GetKey (i); - if (IsMultiValue (key, response)) - foreach (var val in GetValues (i)) - buff.AppendFormat ("{0}: {1}\r\n", key, val); - else - buff.AppendFormat ("{0}: {1}\r\n", key, Get (i)); - }); - - return buff.Append ("\r\n").ToString (); + + 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 (fmt, name, val); + + continue; + } + + buff.AppendFormat (fmt, name, Get (i)); + } + + buff.Append ("\r\n"); + + return buff.ToString (); } #endregion @@ -981,27 +1016,54 @@ internal string ToStringMultiValue (bool response) /// the restricted header list. /// /// - /// A that represents the name of the header to add. + /// A that specifies the name of the header to add. /// /// - /// A that represents the value of the header to add. + /// A that specifies the value of the header to add. /// - /// - /// is or empty. - /// /// - /// or contains invalid characters. + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is a string of spaces. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. + /// + /// + /// + /// is . /// /// - /// The length of is greater than 65,535 characters. + /// The length of is greater than 65,535 + /// characters. /// /// - /// The current instance doesn't allow - /// the . + /// This instance does not allow the header. /// protected void AddWithoutValidate (string headerName, string headerValue) { - add (headerName, headerValue, true); + headerName = checkName (headerName, "headerName"); + headerValue = checkValue (headerValue, "headerValue"); + + var headerType = getHeaderType (headerName); + + checkAllowed (headerType); + + add (headerName, headerValue, headerType); } #endregion @@ -1009,133 +1071,222 @@ protected void AddWithoutValidate (string headerName, string headerValue) #region Public Methods /// - /// Adds the specified to the collection. + /// Adds the specified header to the collection. /// /// - /// A that represents the header with the name and value separated by - /// a colon (':'). + /// A that specifies the header to add, + /// with the name and value separated by a colon character (':'). /// - /// - /// is , empty, or the name part of - /// is empty. - /// /// /// - /// doesn't contain a colon. + /// is an empty string. /// /// /// -or- /// /// - /// is a restricted header. + /// does not contain a colon character. + /// + /// + /// -or- + /// + /// + /// The name part of is an empty string. /// /// /// -or- /// /// - /// The name or value part of contains invalid characters. + /// The name part of is a string of spaces. + /// + /// + /// -or- + /// + /// + /// The name part of contains an invalid + /// character. + /// + /// + /// -or- + /// + /// + /// The value part of contains an invalid + /// character. + /// + /// + /// -or- + /// + /// + /// is a restricted header. /// /// + /// + /// is . + /// /// - /// The length of the value part of is greater than 65,535 characters. + /// The length of the value part of is greater + /// than 65,535 characters. /// /// - /// The current instance doesn't allow - /// the . + /// This instance does not allow the header. /// public void Add (string header) { - if (header == null || header.Length == 0) + if (header == null) throw new ArgumentNullException ("header"); - var pos = checkColonSeparated (header); - add (header.Substring (0, pos), header.Substring (pos + 1), false); + var len = header.Length; + + if (len == 0) { + var msg = "An empty string."; + + throw new ArgumentException (msg, "header"); + } + + var idx = header.IndexOf (':'); + + if (idx == -1) { + var msg = "It does not contain a colon character."; + + throw new ArgumentException (msg, "header"); + } + + var name = header.Substring (0, idx); + var val = idx < len - 1 ? header.Substring (idx + 1) : String.Empty; + + name = checkName (name, "header"); + val = checkValue (val, "header"); + + var headerType = getHeaderType (name); + + checkRestricted (name, headerType); + checkAllowed (headerType); + + add (name, val, headerType); } /// - /// Adds the specified request with - /// the specified to the collection. + /// Adds the specified request header with the specified value to + /// the collection. /// /// - /// One of the enum values, represents - /// the request header to add. + /// + /// One of the enum values. + /// + /// + /// It specifies the request header to add. + /// /// /// - /// A that represents the value of the header to add. + /// A that specifies the value of the header to add. /// /// /// - /// is a restricted header. + /// contains an invalid character. /// /// /// -or- /// /// - /// contains invalid characters. + /// is a restricted header. /// /// /// - /// The length of is greater than 65,535 characters. + /// The length of is greater than 65,535 + /// characters. /// /// - /// The current instance doesn't allow - /// the request . + /// This instance does not allow the request header. /// public void Add (HttpRequestHeader header, string value) { - doWithCheckingState (addWithoutCheckingName, Convert (header), value, false, true); + value = checkValue (value, "value"); + + var key = header.ToString (); + var name = getHeaderName (key); + + checkRestricted (name, HttpHeaderType.Request); + checkAllowed (HttpHeaderType.Request); + + add (name, value, HttpHeaderType.Request); } /// - /// Adds the specified response with - /// the specified to the collection. + /// Adds the specified response header with the specified value to + /// the collection. /// /// - /// One of the enum values, represents - /// the response header to add. + /// + /// One of the enum values. + /// + /// + /// It specifies the response header to add. + /// /// /// - /// A that represents the value of the header to add. + /// A that specifies the value of the header to add. /// /// /// - /// is a restricted header. + /// contains an invalid character. /// /// /// -or- /// /// - /// contains invalid characters. + /// is a restricted header. /// /// /// - /// The length of is greater than 65,535 characters. + /// The length of is greater than 65,535 + /// characters. /// /// - /// The current instance doesn't allow - /// the response . + /// This instance does not allow the response header. /// public void Add (HttpResponseHeader header, string value) { - doWithCheckingState (addWithoutCheckingName, Convert (header), value, true, true); + value = checkValue (value, "value"); + + var key = header.ToString (); + var name = getHeaderName (key); + + checkRestricted (name, HttpHeaderType.Response); + checkAllowed (HttpHeaderType.Response); + + add (name, value, HttpHeaderType.Response); } /// - /// Adds a header with the specified and - /// to the collection. + /// Adds a header with the specified name and value to the collection. /// /// - /// A that represents the name of the header to add. + /// A that specifies the name of the header to add. /// /// - /// A that represents the value of the header to add. + /// A that specifies the value of the header to add. /// - /// - /// is or empty. - /// /// /// - /// or contains invalid characters. + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is a string of spaces. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. /// /// /// -or- @@ -1144,16 +1295,27 @@ public void Add (HttpResponseHeader header, string value) /// is a restricted header name. /// /// + /// + /// is . + /// /// - /// The length of is greater than 65,535 characters. + /// The length of is greater than 65,535 + /// characters. /// /// - /// The current instance doesn't allow - /// the header . + /// This instance does not allow the header. /// public override void Add (string name, string value) { - add (name, value, false); + name = checkName (name, "name"); + value = checkValue (value, "value"); + + var headerType = getHeaderType (name); + + checkRestricted (name, headerType); + checkAllowed (headerType); + + add (name, value, headerType); } /// @@ -1162,20 +1324,23 @@ public override void Add (string name, string value) public override void Clear () { base.Clear (); + _state = HttpHeaderType.Unspecified; } /// - /// Get the value of the header at the specified in the collection. + /// Get the value of the header at the specified index in the collection. /// /// /// A that receives the value of the header. /// /// - /// An that represents the zero-based index of the header to find. + /// An that specifies the zero-based index of the header + /// to get. /// /// - /// is out of allowable range of indexes for the collection. + /// is out of allowable range of indexes for + /// the collection. /// public override string Get (int index) { @@ -1183,14 +1348,18 @@ public override string Get (int index) } /// - /// Get the value of the header with the specified in the collection. + /// Get the value of the header with the specified name in the collection. /// /// - /// A that receives the value of the header if found; - /// otherwise, . + /// + /// A that receives the value of the header. + /// + /// + /// if not found. + /// /// /// - /// A that represents the name of the header to find. + /// A that specifies the name of the header to get. /// public override string Get (string name) { @@ -1201,7 +1370,8 @@ public override string Get (string name) /// Gets the enumerator used to iterate through the collection. /// /// - /// An instance used to iterate through the collection. + /// An instance used to iterate through + /// the collection. /// public override IEnumerator GetEnumerator () { @@ -1209,16 +1379,18 @@ public override IEnumerator GetEnumerator () } /// - /// Get the name of the header at the specified in the collection. + /// Get the name of the header at the specified index in the collection. /// /// - /// A that receives the header name. + /// A that receives the name of the header. /// /// - /// An that represents the zero-based index of the header to find. + /// An that specifies the zero-based index of the header + /// to get. /// /// - /// is out of allowable range of indexes for the collection. + /// is out of allowable range of indexes for + /// the collection. /// public override string GetKey (int index) { @@ -1226,47 +1398,60 @@ public override string GetKey (int index) } /// - /// Gets an array of header values stored in the specified position of - /// the collection. + /// Get the values of the header at the specified index in the collection. /// /// - /// An array of that receives the header values if found; - /// otherwise, . + /// + /// An array of that receives the values of + /// the header. + /// + /// + /// if not present. + /// /// /// - /// An that represents the zero-based index of the header to find. + /// An that specifies the zero-based index of the header + /// to get. /// /// - /// is out of allowable range of indexes for the collection. + /// is out of allowable range of indexes for + /// the collection. /// public override string[] GetValues (int index) { var vals = base.GetValues (index); + return vals != null && vals.Length > 0 ? vals : null; } /// - /// Gets an array of header values stored in the specified . + /// Get the values of the header with the specified name in the collection. /// /// - /// An array of that receives the header values if found; - /// otherwise, . + /// + /// An array of that receives the values of + /// the header. + /// + /// + /// if not present. + /// /// - /// - /// A that represents the name of the header to find. + /// + /// A that specifies the name of the header to get. /// - public override string[] GetValues (string header) + public override string[] GetValues (string name) { - var vals = base.GetValues (header); + var vals = base.GetValues (name); + return vals != null && vals.Length > 0 ? vals : null; } /// - /// 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 @@ -1282,7 +1467,8 @@ public override string[] GetValues (string header) ) ] public override void GetObjectData ( - SerializationInfo serializationInfo, StreamingContext streamingContext + SerializationInfo serializationInfo, + StreamingContext streamingContext ) { if (serializationInfo == null) @@ -1305,106 +1491,166 @@ public override void GetObjectData ( /// Determines whether the specified header can be set for the request. /// /// - /// true if the header is restricted; otherwise, false. + /// true if the header cannot be set; otherwise, false. /// /// - /// A that represents the name of the header to test. + /// A that specifies the name of the header to test. /// - /// - /// is or empty. - /// /// - /// contains invalid characters. + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is a string of spaces. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. + /// + /// + /// + /// is . /// public static bool IsRestricted (string headerName) { - return isRestricted (checkName (headerName), false); + return IsRestricted (headerName, false); } /// - /// Determines whether the specified 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 is restricted; otherwise, false. + /// true if the header cannot be set; otherwise, false. /// /// - /// A that represents the name of the header to test. + /// A that specifies the name of the header to test. /// /// - /// true if does the test for the response; for the request, false. + /// A : true if the test is for the response; + /// otherwise, false. /// - /// - /// is or empty. - /// /// - /// contains invalid characters. + /// + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is a string of spaces. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. + /// + /// + /// + /// is . /// public static bool IsRestricted (string headerName, bool response) { - return isRestricted (checkName (headerName), response); + headerName = checkName (headerName, "headerName"); + + return isRestricted (headerName, response); } /// - /// Implements the interface and raises the deserialization event - /// when the deserialization is complete. + /// Implements the interface and raises + /// the deserialization event when the deserialization is complete. /// /// - /// An that represents the source of the deserialization event. + /// An instance that represents the source of + /// the deserialization event. /// public override void OnDeserialization (object sender) { } /// - /// Removes the specified request from the collection. + /// Removes the specified request header from the collection. /// /// - /// One of the enum values, represents - /// the request header to remove. + /// + /// One of the enum values. + /// + /// + /// It specifies the request header to remove. + /// /// /// /// is a restricted header. /// /// - /// The current instance doesn't allow - /// the request . + /// This instance does not allow the request header. /// public void Remove (HttpRequestHeader header) { - doWithCheckingState (removeWithoutCheckingName, Convert (header), null, false, false); + var key = header.ToString (); + var name = getHeaderName (key); + + checkRestricted (name, HttpHeaderType.Request); + checkAllowed (HttpHeaderType.Request); + + base.Remove (name); } /// - /// Removes the specified response from the collection. + /// Removes the specified response header from the collection. /// /// - /// One of the enum values, represents - /// the response header to remove. + /// + /// One of the enum values. + /// + /// + /// It specifies the response header to remove. + /// /// /// /// is a restricted header. /// /// - /// The current instance doesn't allow - /// the response . + /// This instance does not allow the response header. /// public void Remove (HttpResponseHeader header) { - doWithCheckingState (removeWithoutCheckingName, Convert (header), null, true, false); + var key = header.ToString (); + var name = getHeaderName (key); + + checkRestricted (name, HttpHeaderType.Response); + checkAllowed (HttpHeaderType.Response); + + base.Remove (name); } /// /// Removes the specified header from the collection. /// /// - /// A that represents the name of the header to remove. + /// A that specifies the name of the header to remove. /// - /// - /// is or empty. - /// /// /// - /// contains invalid characters. + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is a string of spaces. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. /// /// /// -or- @@ -1413,96 +1659,146 @@ public void Remove (HttpResponseHeader header) /// is a restricted header name. /// /// + /// + /// is . + /// /// - /// The current instance doesn't allow - /// the header . + /// This instance does not allow the header. /// public override void Remove (string name) { - doWithCheckingState (removeWithoutCheckingName, checkName (name), null, false); + name = checkName (name, "name"); + + var headerType = getHeaderType (name); + + checkRestricted (name, headerType); + checkAllowed (headerType); + + base.Remove (name); } /// - /// Sets the specified request to the specified value. + /// Sets the specified request header to the specified value. /// /// - /// One of the enum values, represents - /// the request header to set. + /// + /// One of the enum values. + /// + /// + /// It specifies the request header to set. + /// /// /// - /// A that represents the value of the request header to set. + /// A that specifies the value of the request header + /// to set. /// /// /// - /// is a restricted header. + /// contains an invalid character. /// /// /// -or- /// /// - /// contains invalid characters. + /// is a restricted header. /// /// /// - /// The length of is greater than 65,535 characters. + /// The length of is greater than 65,535 + /// characters. /// /// - /// The current instance doesn't allow - /// the request . + /// This instance does not allow the request header. /// public void Set (HttpRequestHeader header, string value) { - doWithCheckingState (setWithoutCheckingName, Convert (header), value, false, true); + value = checkValue (value, "value"); + + var key = header.ToString (); + var name = getHeaderName (key); + + checkRestricted (name, HttpHeaderType.Request); + checkAllowed (HttpHeaderType.Request); + + set (name, value, HttpHeaderType.Request); } /// - /// Sets the specified response to the specified value. + /// Sets the specified response header to the specified value. /// /// - /// One of the enum values, represents - /// the response header to set. + /// + /// One of the enum values. + /// + /// + /// It specifies the response header to set. + /// /// /// - /// A that represents the value of the response header to set. + /// A that specifies the value of the response header + /// to set. /// /// /// - /// is a restricted header. + /// contains an invalid character. /// /// /// -or- /// /// - /// contains invalid characters. + /// is a restricted header. /// /// /// - /// The length of is greater than 65,535 characters. + /// The length of is greater than 65,535 + /// characters. /// /// - /// The current instance doesn't allow - /// the response . + /// This instance does not allow the response header. /// public void Set (HttpResponseHeader header, string value) { - doWithCheckingState (setWithoutCheckingName, Convert (header), value, true, true); + value = checkValue (value, "value"); + + var key = header.ToString (); + var name = getHeaderName (key); + + checkRestricted (name, HttpHeaderType.Response); + checkAllowed (HttpHeaderType.Response); + + set (name, value, HttpHeaderType.Response); } /// /// Sets the specified header to the specified value. /// /// - /// A that represents the name of the header to set. + /// A that specifies the name of the header to set. /// /// - /// A that represents the value of the header to set. + /// A that specifies the value of the header to set. /// - /// - /// is or empty. - /// /// /// - /// or contains invalid characters. + /// is an empty string. + /// + /// + /// -or- + /// + /// + /// is a string of spaces. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. + /// + /// + /// -or- + /// + /// + /// contains an invalid character. /// /// /// -or- @@ -1511,43 +1807,66 @@ public void Set (HttpResponseHeader header, string value) /// is a restricted header name. /// /// + /// + /// is . + /// /// - /// The length of is greater than 65,535 characters. + /// The length of is greater than 65,535 + /// characters. /// /// - /// The current instance doesn't allow - /// the header . + /// This instance does not allow the header. /// public override void Set (string name, string value) { - doWithCheckingState (setWithoutCheckingName, checkName (name), value, true); + name = checkName (name, "name"); + value = checkValue (value, "value"); + + var headerType = getHeaderType (name); + + checkRestricted (name, headerType); + checkAllowed (headerType); + + set (name, value, headerType); } /// - /// Converts the current to an array of . + /// Converts the current instance to an array of byte. /// /// - /// An array of that receives the converted current - /// . + /// An array of converted from a string that represents + /// the current instance. /// public byte[] ToByteArray () { - return Encoding.UTF8.GetBytes (ToString ()); + var s = ToString (); + + return Encoding.UTF8.GetBytes (s); } /// - /// Returns a that represents the current - /// . + /// Returns a string that represents the current instance. /// /// - /// A that represents the current . + /// A that represents all headers in the collection. /// public override string ToString () { + var cnt = Count; + + if (cnt == 0) + return "\r\n"; + var buff = new StringBuilder (); - Count.Times (i => buff.AppendFormat ("{0}: {1}\r\n", GetKey (i), Get (i))); - return buff.Append ("\r\n").ToString (); + var fmt = "{0}: {1}\r\n"; + + for (var i = 0; i < cnt; i++) + buff.AppendFormat (fmt, GetKey (i), Get (i)); + + buff.Append ("\r\n"); + + return buff.ToString (); } #endregion @@ -1555,24 +1874,30 @@ public override string ToString () #region Explicit Interface Implementations /// - /// Populates the specified with the data needed to serialize - /// the current . + /// Populates the specified instance with + /// the data needed to serialize the current instance. /// /// /// A that holds the serialized object data. /// /// - /// A that specifies the destination for the serialization. + /// A that specifies the destination for + /// the serialization. /// /// /// is . /// - [SecurityPermission ( - SecurityAction.LinkDemand, - Flags = SecurityPermissionFlag.SerializationFormatter, - SerializationFormatter = true)] + [ + SecurityPermission ( + SecurityAction.LinkDemand, + Flags = SecurityPermissionFlag.SerializationFormatter, + SerializationFormatter = true + ) + ] 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 eed49ce1c..3410ff62d 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-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,8 +35,8 @@ 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 +50,8 @@ public class HttpListenerWebSocketContext : WebSocketContext #region Internal Constructors internal HttpListenerWebSocketContext ( - HttpListenerContext context, string protocol + HttpListenerContext context, + string protocol ) { _context = context; @@ -186,7 +187,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 +245,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 +271,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 +296,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 +309,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 +340,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 +350,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 { @@ -370,7 +373,9 @@ internal void Close () internal void Close (HttpStatusCode code) { - _context.Response.Close (code); + _context.Response.StatusCode = (int) code; + + _context.Response.Close (); } #endregion diff --git a/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs b/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs index 519da7896..f95eeb98f 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-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 @@ -45,8 +45,8 @@ 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 { @@ -81,6 +81,7 @@ Logger log _log = log; var netStream = tcpClient.GetStream (); + if (secure) { var sslStream = new SslStream ( netStream, @@ -105,7 +106,7 @@ Logger log _serverEndPoint = sock.LocalEndPoint; _userEndPoint = sock.RemoteEndPoint; - _request = HttpRequest.Read (_stream, 90000); + _request = HttpRequest.ReadRequest (_stream, 90000); _websocket = new WebSocket (this, protocol); } @@ -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; @@ -288,7 +288,7 @@ public override Uri RequestUri { get { if (_requestUri == null) { _requestUri = HttpUtility.CreateRequestUrl ( - _request.RequestUri, + _request.RequestTarget, _request.Headers["Host"], _request.IsWebSocketRequest, _secure @@ -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,8 +377,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 { @@ -406,8 +408,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 { @@ -416,11 +418,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 { @@ -430,71 +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.Write (bytes, 0, bytes.Length); + _stream.Close (); + _tcpClient.Close (); + } + + internal void Close (HttpStatusCode code) + { + HttpResponse.CreateCloseResponse (code).WriteTo (_stream); - return HttpRequest.Read (_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/PayloadData.cs b/websocket-sharp/PayloadData.cs index 9e40b9404..6d17bc2a0 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-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 @@ -54,7 +54,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. /// /// @@ -120,7 +120,7 @@ internal long ExtensionDataLength { internal bool HasReservedCode { get { - return _length >= 2 && Code.IsReserved (); + return _length >= 2 && Code.IsReservedStatusCode (); } } @@ -129,10 +129,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; } diff --git a/websocket-sharp/Server/HttpRequestEventArgs.cs b/websocket-sharp/Server/HttpRequestEventArgs.cs index ee76cbab3..45af3c1ee 100644 --- a/websocket-sharp/Server/HttpRequestEventArgs.cs +++ b/websocket-sharp/Server/HttpRequestEventArgs.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2017 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,21 +35,21 @@ namespace WebSocketSharp.Server { /// - /// Represents the event data for the HTTP request events of - /// the . + /// Represents the event data for the HTTP request events of the + /// class. /// /// /// /// An HTTP request event occurs when the - /// receives an HTTP request. + /// instance receives an HTTP request. /// /// /// You should access the property if you would /// like to get the request data sent from a client. /// /// - /// And you should access the property if you would - /// like to get the response data to return to the client. + /// And you should access the property if you + /// would like to get the response data to return to the client. /// /// public class HttpRequestEventArgs : EventArgs @@ -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 { @@ -127,6 +127,7 @@ public IPrincipal User { private string createFilePath (string childPath) { childPath = childPath.TrimStart ('/', '\\'); + return new StringBuilder (_docRootPath, 32) .AppendFormat ("/{0}", childPath) .ToString () @@ -155,25 +156,22 @@ private static bool tryReadFile (string path, out byte[] contents) #region Public Methods /// - /// Reads the specified file from the document folder of - /// the . + /// Reads the specified file from the document folder of the + /// class. /// /// /// - /// 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 represents 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. @@ -185,6 +183,9 @@ private static bool tryReadFile (string path, out byte[] contents) /// contains "..". /// /// + /// + /// is . + /// public byte[] ReadFile (string path) { if (path == null) @@ -193,38 +194,40 @@ 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; - tryReadFile (createFilePath (path), out contents); + + tryReadFile (path, out contents); return contents; } /// /// Tries to read the specified file from the document folder of - /// the . + /// the class. /// /// - /// true if it succeeds to read; otherwise, false. + /// true if the try has succeeded; otherwise, false. /// /// - /// A that represents 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. /// /// /// - /// 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. @@ -236,6 +239,9 @@ public byte[] ReadFile (string path) /// contains "..". /// /// + /// + /// is . + /// public bool TryReadFile (string path, out byte[] contents) { if (path == null) @@ -244,10 +250,15 @@ 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); - return tryReadFile (createFilePath (path), out contents); + return tryReadFile (path, out contents); } #endregion diff --git a/websocket-sharp/Server/HttpServer.cs b/websocket-sharp/Server/HttpServer.cs index 56925ac6d..209109984 100644 --- a/websocket-sharp/Server/HttpServer.cs +++ b/websocket-sharp/Server/HttpServer.cs @@ -2,11 +2,9 @@ /* * HttpServer.cs * - * A simple HTTP server that allows to accept WebSocket handshake requests. - * * 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 @@ -52,11 +50,18 @@ namespace WebSocketSharp.Server { /// - /// Provides a simple HTTP server that allows to accept - /// WebSocket handshake requests. + /// Provides a simple HTTP server. /// /// - /// This class can provide multiple WebSocket services. + /// + /// The server supports HTTP/1.1 version request and response. + /// + /// + /// Also the server allows to accept WebSocket handshake requests. + /// + /// + /// This class can provide multiple WebSocket services. + /// /// public class HttpServer { @@ -64,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; @@ -92,7 +96,7 @@ public HttpServer () /// /// Initializes a new instance of the class with - /// the specified . + /// the specified port. /// /// /// @@ -104,8 +108,8 @@ public HttpServer () /// /// /// - /// An that represents the number of the port - /// on which to listen. + /// An that specifies the number of the port on which + /// to listen. /// /// /// is less than 1 or greater than 65535. @@ -117,12 +121,12 @@ public HttpServer (int port) /// /// Initializes a new instance of the class with - /// the specified . + /// the specified URL. /// /// /// - /// The new instance listens for incoming requests on the IP address of the - /// host of and the port of . + /// The new instance listens for incoming requests on the IP address and + /// port of . /// /// /// Either port 80 or 443 is used if includes @@ -135,14 +139,11 @@ public HttpServer (int port) /// /// /// - /// A that represents the HTTP URL of the server. + /// A that specifies the HTTP URL of the server. /// - /// - /// is . - /// /// /// - /// is empty. + /// is an empty string. /// /// /// -or- @@ -151,6 +152,9 @@ public HttpServer (int port) /// is invalid. /// /// + /// + /// is . + /// public HttpServer (string url) { if (url == null) @@ -161,19 +165,22 @@ public HttpServer (string url) Uri uri; string msg; + if (!tryCreateUri (url, out uri, out msg)) throw new ArgumentException (msg, "url"); var host = uri.GetDnsSafeHost (true); - var addr = host.ToIPAddress (); + if (addr == null) { msg = "The host part could not be converted to an IP address."; + throw new ArgumentException (msg, "url"); } if (!addr.IsLocal ()) { msg = "The IP address of the host is not a local IP address."; + throw new ArgumentException (msg, "url"); } @@ -182,15 +189,15 @@ public HttpServer (string url) /// /// Initializes a new instance of the class with - /// the specified and . + /// the specified port and boolean if secure or not. /// /// /// The new instance listens for incoming requests on /// and . /// /// - /// An that represents the number of the port - /// on which to listen. + /// An that specifies the number of the port on which + /// to listen. /// /// /// A : true if the new instance provides @@ -203,6 +210,7 @@ public HttpServer (int port, bool secure) { if (!port.IsPortNumber ()) { var msg = "Less than 1 or greater than 65535."; + throw new ArgumentOutOfRangeException ("port", msg); } @@ -211,7 +219,7 @@ public HttpServer (int port, bool secure) /// /// Initializes a new instance of the class with - /// the specified and . + /// the specified IP address and port. /// /// /// @@ -223,19 +231,19 @@ public HttpServer (int port, bool secure) /// /// /// - /// A that represents - /// the local IP address on which to listen. + /// A that specifies the local IP + /// address on which to listen. /// /// - /// An that represents the number of the port - /// on which to listen. + /// 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. /// @@ -246,31 +254,30 @@ public HttpServer (System.Net.IPAddress address, int port) /// /// Initializes a new instance of the class with - /// the specified , , - /// and . + /// the specified IP address, port, and boolean if secure or not. /// /// /// The new instance listens for incoming requests on /// and . /// /// - /// A that represents - /// the local IP address on which to listen. + /// A that specifies the local IP + /// address on which to listen. /// /// - /// An that represents the number of the port - /// on which to listen. + /// An that specifies the number of the port on which + /// to listen. /// /// /// 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. /// @@ -279,11 +286,15 @@ public HttpServer (System.Net.IPAddress address, int port, bool secure) if (address == null) throw new ArgumentNullException ("address"); - if (!address.IsLocal ()) - throw new ArgumentException ("Not a local IP address.", "address"); + if (!address.IsLocal ()) { + var msg = "Not a local IP address."; + + throw new ArgumentException (msg, "address"); + } if (!port.IsPortNumber ()) { var msg = "Less than 1 or greater than 65535."; + throw new ArgumentOutOfRangeException ("port", msg); } @@ -298,8 +309,8 @@ public HttpServer (System.Net.IPAddress address, int port, bool secure) /// Gets the IP address of the server. /// /// - /// A that represents the local - /// IP address on which to listen for incoming requests. + /// A that represents the local IP + /// address on which to listen for incoming requests. /// public System.Net.IPAddress Address { get { @@ -311,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. /// /// /// @@ -333,17 +344,9 @@ public AuthenticationSchemes AuthenticationSchemes { } set { - string msg; - if (!canSet (out msg)) { - _log.Warn (msg); - return; - } - lock (_sync) { - if (!canSet (out msg)) { - _log.Warn (msg); + if (!canSet ()) return; - } _listener.AuthenticationSchemes = value; } @@ -354,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. /// /// /// @@ -368,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. @@ -382,15 +380,18 @@ public AuthenticationSchemes AuthenticationSchemes { /// -or- /// /// - /// The value specified for a set operation is an invalid path string. + /// The value specified for a set operation is an absolute root. /// /// /// -or- /// /// - /// The value specified for a set operation is an absolute root. + /// 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; @@ -405,14 +406,6 @@ public string DocumentRootPath { value = value.TrimSlashOrBackslashFromEnd (); - string full = null; - try { - full = Path.GetFullPath (value); - } - catch (Exception ex) { - throw new ArgumentException ("An invalid path string.", "value", ex); - } - if (value == "/") throw new ArgumentException ("An absolute root.", "value"); @@ -422,24 +415,26 @@ public string DocumentRootPath { if (value.Length == 2 && value[1] == ':') throw new ArgumentException ("An absolute root.", "value"); + string full = null; + + try { + full = Path.GetFullPath (value); + } + catch (Exception ex) { + throw new ArgumentException ("An invalid path string.", "value", ex); + } + if (full == "/") throw new ArgumentException ("An absolute root.", "value"); full = full.TrimSlashOrBackslashFromEnd (); + if (full.Length == 2 && full[1] == ':') throw new ArgumentException ("An absolute root.", "value"); - string msg; - if (!canSet (out msg)) { - _log.Warn (msg); - return; - } - lock (_sync) { - if (!canSet (out msg)) { - _log.Warn (msg); + if (!canSet ()) return; - } _docRootPath = value; } @@ -459,15 +454,15 @@ 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; } } @@ -476,8 +471,8 @@ public bool IsSecure { /// 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. /// /// /// @@ -485,7 +480,7 @@ public bool IsSecure { /// every 60 seconds; otherwise, false. /// /// - /// The default value is true. + /// The default value is false. /// /// public bool KeepClean { @@ -517,8 +512,8 @@ public Logger Log { /// Gets the port of the server. /// /// - /// An that represents the number of the port - /// on which to listen for incoming requests. + /// An that represents the number of the port on which + /// to listen for incoming requests. /// public int Port { get { @@ -527,24 +522,22 @@ public int Port { } /// - /// Gets or sets the realm used for authentication. + /// Gets or sets the name of the realm associated with the server. /// /// - /// - /// "SECRET AREA" is used as 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 or by default. + /// A that represents the name of the realm. /// /// - /// That string 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 . /// /// public string Realm { @@ -553,17 +546,9 @@ public string Realm { } set { - string msg; - if (!canSet (out msg)) { - _log.Warn (msg); - return; - } - lock (_sync) { - if (!canSet (out msg)) { - _log.Warn (msg); + if (!canSet ()) return; - } _listener.Realm = value; } @@ -576,12 +561,12 @@ public string Realm { /// /// /// - /// You should set this property to true if you would - /// like to resolve to wait for socket in TIME_WAIT state. + /// You should set this property to true if you would like to + /// 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. /// /// /// @@ -599,17 +584,9 @@ public bool ReuseAddress { } set { - string msg; - if (!canSet (out msg)) { - _log.Warn (msg); - return; - } - lock (_sync) { - if (!canSet (out msg)) { - _log.Warn (msg); + if (!canSet ()) return; - } _listener.ReuseAddress = value; } @@ -620,20 +597,21 @@ public bool ReuseAddress { /// Gets the configuration for secure connection. /// /// - /// This 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 instance does not provide secure connections. + /// The server does not provide secure connections. /// public ServerSslConfiguration SslConfiguration { get { - if (!_secure) { - var msg = "This instance does not provide secure connections."; + if (!_isSecure) { + var msg = "The server does not provide secure connections."; + throw new InvalidOperationException (msg); } @@ -642,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. /// /// - /// That delegate invokes the method called for finding - /// the credentials used to authenticate a client. + /// if not necessary. /// /// /// The default value is . @@ -676,17 +653,9 @@ public Func UserCredentialsFinder { } set { - string msg; - if (!canSet (out msg)) { - _log.Warn (msg); - return; - } - lock (_sync) { - if (!canSet (out msg)) { - _log.Warn (msg); + if (!canSet ()) return; - } _listener.UserCredentialsFinder = value; } @@ -694,16 +663,17 @@ public Func UserCredentialsFinder { } /// - /// 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. @@ -723,12 +693,12 @@ public TimeSpan WaitTime { } /// - /// Gets the management function for the WebSocket services - /// provided by the server. + /// Gets the management function for the WebSocket services provided by + /// the server. /// /// - /// A that manages - /// the WebSocket services provided by the server. + /// A that manages the WebSocket + /// services provided by the server. /// public WebSocketServiceManager WebSocketServices { get { @@ -794,34 +764,27 @@ private void abort () } try { - try { - _services.Stop (1006, String.Empty); - } - finally { - _listener.Abort (); - } + _services.Stop (1006, String.Empty); + } + catch (Exception ex) { + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); + } + + try { + _listener.Abort (); } - catch { + catch (Exception ex) { + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); } _state = ServerState.Stop; } - private bool canSet (out string message) + private bool canSet () { - message = null; - - if (_state == ServerState.Start) { - message = "The server has already started."; - return false; - } - - if (_state == ServerState.ShuttingDown) { - message = "The server is shutting down."; - return false; - } - - return true; + return _state == ServerState.Ready || _state == ServerState.Stop; } private bool checkCertificate (out string message) @@ -833,50 +796,55 @@ private bool checkCertificate (out string message) var path = _listener.CertificateFolderPath; var withPort = EndPointListener.CertificateExists (_port, path); - if (!(byUser || withPort)) { + var either = byUser || withPort; + + if (!either) { message = "There is no server certificate for secure connection."; + return false; } - if (byUser && withPort) - _log.Warn ("The server certificate associated with the port is used."); + var both = byUser && withPort; - return true; - } + if (both) { + var msg = "The server certificate associated with the port is used."; - private string createFilePath (string childPath) - { - childPath = childPath.TrimStart ('/', '\\'); - return new StringBuilder (_docRootPath, 32) - .AppendFormat ("/{0}", childPath) - .ToString () - .Replace ('\\', '/'); + _log.Warn (msg); + } + + return true; } 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); - lsnr.Prefixes.Add (pref); + var pref = String.Format (fmt, schm, hostname, port); + + 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 (); @@ -903,10 +871,17 @@ private void processRequest (HttpListenerContext context) ? OnTrace : null; - if (evt != null) - evt (this, new HttpRequestEventArgs (context, _docRootPath)); - else - context.Response.StatusCode = 501; // Not Implemented + if (evt == null) { + context.ErrorStatusCode = 501; + + context.SendError (); + + return; + } + + var e = new HttpRequestEventArgs (context, _docRootPath); + + evt (this, e); context.Response.Close (); } @@ -914,18 +889,23 @@ private void processRequest (HttpListenerContext context) private void processRequest (HttpListenerWebSocketContext context) { var uri = context.RequestUri; + if (uri == null) { context.Close (HttpStatusCode.BadRequest); + return; } var path = uri.AbsolutePath; + if (path.IndexOfAny (new[] { '%', '+' }) > -1) path = HttpUtility.UrlDecode (path, Encoding.UTF8); WebSocketServiceHost host; + if (!_services.InternalTryGetServiceHost (path, out host)) { context.Close (HttpStatusCode.NotImplemented); + return; } @@ -936,20 +916,23 @@ private void receiveRequest () { while (true) { HttpListenerContext ctx = null; + try { ctx = _listener.GetContext (); + ThreadPool.QueueUserWorkItem ( state => { try { if (ctx.Request.IsUpgradeRequest ("websocket")) { - processRequest (ctx.AcceptWebSocket (null)); + processRequest (ctx.GetWebSocketContext (null)); + return; } processRequest (ctx); } catch (Exception ex) { - _log.Fatal (ex.Message); + _log.Error (ex.Message); _log.Debug (ex.ToString ()); ctx.Connection.Close (true); @@ -957,12 +940,22 @@ private void receiveRequest () } ); } - catch (HttpListenerException) { - _log.Info ("The underlying listener is stopped."); + catch (HttpListenerException ex) { + if (_state == ServerState.ShuttingDown) + return; + + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); + break; } - catch (InvalidOperationException) { - _log.Info ("The underlying listener is stopped."); + catch (InvalidOperationException ex) { + if (_state == ServerState.ShuttingDown) + return; + + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); + break; } catch (Exception ex) { @@ -972,35 +965,27 @@ private void receiveRequest () if (ctx != null) ctx.Connection.Close (true); + if (_state == ServerState.ShuttingDown) + return; + break; } } - if (_state != ServerState.ShuttingDown) - abort (); + abort (); } private void start () { - if (_state == ServerState.Start) { - _log.Info ("The server has already started."); - return; - } - - if (_state == ServerState.ShuttingDown) { - _log.Warn ("The server is shutting down."); - return; - } - lock (_sync) { - if (_state == ServerState.Start) { - _log.Info ("The server has already started."); + if (_state == ServerState.Start || _state == ServerState.ShuttingDown) return; - } - if (_state == ServerState.ShuttingDown) { - _log.Warn ("The server is shutting down."); - return; + if (_isSecure) { + string msg; + + if (!checkCertificate (out msg)) + throw new InvalidOperationException (msg); } _services.Start (); @@ -1010,6 +995,7 @@ private void start () } catch { _services.Stop (1011, String.Empty); + throw; } @@ -1024,67 +1010,45 @@ private void startReceiving () } catch (Exception ex) { var msg = "The underlying listener has failed to start."; + throw new InvalidOperationException (msg, ex); } - _receiveThread = new Thread (new ThreadStart (receiveRequest)); + var receiver = new ThreadStart (receiveRequest); + _receiveThread = new Thread (receiver); _receiveThread.IsBackground = true; + _receiveThread.Start (); } private void stop (ushort code, string reason) { - if (_state == ServerState.Ready) { - _log.Info ("The server is not started."); - return; - } - - if (_state == ServerState.ShuttingDown) { - _log.Info ("The server is shutting down."); - return; - } - - if (_state == ServerState.Stop) { - _log.Info ("The server has already stopped."); - return; - } - lock (_sync) { - if (_state == ServerState.ShuttingDown) { - _log.Info ("The server is shutting down."); - return; - } - - if (_state == ServerState.Stop) { - _log.Info ("The server has already stopped."); + if (_state != ServerState.Start) return; - } _state = ServerState.ShuttingDown; } try { - var threw = false; - try { - _services.Stop (code, reason); - } - catch { - threw = true; - throw; - } - finally { - try { - stopReceiving (5000); - } - catch { - if (!threw) - throw; - } - } + _services.Stop (code, reason); } - finally { - _state = ServerState.Stop; + catch (Exception ex) { + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); + } + + try { + var timeout = 5000; + + stopReceiving (timeout); + } + catch (Exception ex) { + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); } + + _state = ServerState.Stop; } private void stopReceiving (int millisecondsTimeout) @@ -1094,45 +1058,57 @@ 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; message = null; var uri = uriString.ToUri (); + if (uri == null) { message = "An invalid URI string."; + return false; } if (!uri.IsAbsoluteUri) { message = "A relative URI."; + return false; } var schm = uri.Scheme; - if (!(schm == "http" || schm == "https")) { + var isHttpSchm = schm == "http" || schm == "https"; + + if (!isHttpSchm) { message = "The scheme part is not 'http' or 'https'."; + return false; } if (uri.PathAndQuery != "/") { message = "It includes either or both path and query components."; + return false; } if (uri.Fragment.Length > 0) { message = "It includes the fragment component."; + return false; } if (uri.Port == 0) { message = "The port part is zero."; + return false; } result = uri; + return true; } @@ -1141,31 +1117,17 @@ private static bool tryCreateUri ( #region Public Methods /// - /// Adds a WebSocket service with the specified behavior, path, - /// and delegate. + /// Adds a WebSocket service with the specified behavior and path. /// /// /// - /// A that represents an absolute path to + /// A that specifies an absolute path to /// the service to add. /// /// /// / is trimmed from the end of the string if present. /// /// - /// - /// - /// A Func<TBehavior> delegate. - /// - /// - /// It invokes the method called when creating a new session - /// instance for the service. - /// - /// - /// The method must create a new instance of the specified - /// behavior class and return it. - /// - /// /// /// /// The type of the behavior for the service. @@ -1173,18 +1135,10 @@ private static bool tryCreateUri ( /// /// It must inherit the class. /// - /// - /// /// - /// is . - /// - /// - /// -or- + /// Also it must have a public parameterless constructor. /// - /// - /// is . - /// - /// + /// /// /// /// is an empty string. @@ -1209,95 +1163,22 @@ private static bool tryCreateUri ( /// is already in use. /// /// - [Obsolete ("This method will be removed. Use added one instead.")] - public void AddWebSocketService ( - string path, Func creator - ) - where TBehavior : WebSocketBehavior - { - if (path == null) - throw new ArgumentNullException ("path"); - - if (creator == null) - throw new ArgumentNullException ("creator"); - - if (path.Length == 0) - throw new ArgumentException ("An empty string.", "path"); - - if (path[0] != '/') - throw new ArgumentException ("Not an absolute path.", "path"); - - if (path.IndexOfAny (new[] { '?', '#' }) > -1) { - var msg = "It includes either or both query and fragment components."; - throw new ArgumentException (msg, "path"); - } - - _services.Add (path, creator); - } - - /// - /// Adds a WebSocket service with the specified behavior and path. - /// - /// - /// - /// A that represents an absolute path to - /// the service to add. - /// - /// - /// / is trimmed from the end of the string if present. - /// - /// - /// - /// - /// The type of the behavior for the service. - /// - /// - /// It must inherit the class. - /// - /// - /// And also, it must have a public parameterless constructor. - /// - /// /// /// is . /// - /// - /// - /// is an empty string. - /// - /// - /// -or- - /// - /// - /// is not an absolute path. - /// - /// - /// -or- - /// - /// - /// includes either or both - /// query and fragment components. - /// - /// - /// -or- - /// - /// - /// is already in use. - /// - /// - public void AddWebSocketService (string path) - where TBehaviorWithNew : WebSocketBehavior, new () + public void AddWebSocketService (string path) + where TBehavior : WebSocketBehavior, new () { - _services.AddService (path, null); + _services.AddService (path, null); } /// /// Adds a WebSocket service with the specified behavior, path, - /// and delegate. + /// and initializer. /// /// /// - /// A that represents an absolute path to + /// A that specifies an absolute path to /// the service to add. /// /// @@ -1306,15 +1187,17 @@ public void AddWebSocketService (string path) /// /// /// - /// An Action<TBehaviorWithNew> 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. /// /// - /// + /// /// /// The type of the behavior for the service. /// @@ -1322,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. @@ -1352,59 +1232,16 @@ public void AddWebSocketService (string path) /// is already in use. /// /// - public void AddWebSocketService ( - string path, Action initializer - ) - where TBehaviorWithNew : WebSocketBehavior, new () - { - _services.AddService (path, initializer); - } - - /// - /// Gets the contents of the specified file from the document - /// folder of the server. - /// - /// - /// - /// An array of or - /// if it fails. - /// - /// - /// That array represents the contents of the file. - /// - /// - /// - /// A that represents a virtual path to - /// find the file from the document folder. - /// /// /// is . /// - /// - /// - /// is an empty string. - /// - /// - /// -or- - /// - /// - /// contains "..". - /// - /// - [Obsolete ("This method will be removed.")] - public byte[] GetFile (string path) + public void AddWebSocketService ( + string path, + Action initializer + ) + where TBehavior : WebSocketBehavior, new () { - if (path == null) - throw new ArgumentNullException ("path"); - - if (path.Length == 0) - throw new ArgumentException ("An empty string.", "path"); - - if (path.IndexOf ("..") > -1) - throw new ArgumentException ("It contains '..'.", "path"); - - path = createFilePath (path); - return File.Exists (path) ? File.ReadAllBytes (path) : null; + _services.AddService (path, initializer); } /// @@ -1412,7 +1249,7 @@ public byte[] GetFile (string path) /// /// /// 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; @@ -1420,16 +1257,13 @@ public byte[] GetFile (string path) /// /// /// - /// A that represents an absolute path to + /// A that specifies an absolute path to /// the service to remove. /// /// /// / is trimmed from the end of the string if present. /// /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1448,6 +1282,9 @@ public byte[] GetFile (string path) /// query and fragment components. /// /// + /// + /// is . + /// public bool RemoveWebSocketService (string path) { return _services.RemoveService (path); @@ -1457,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. /// /// /// @@ -1473,11 +1309,8 @@ public bool RemoveWebSocketService (string path) /// public void Start () { - if (_secure) { - string msg; - if (!checkCertificate (out msg)) - throw new InvalidOperationException (msg); - } + if (_state == ServerState.Start || _state == ServerState.ShuttingDown) + return; start (); } @@ -1485,166 +1318,15 @@ public void Start () /// /// Stops receiving incoming requests. /// + /// + /// This method works if the current state of the server is Start. + /// public void Stop () { - stop (1001, String.Empty); - } - - /// - /// Stops receiving incoming requests and closes each connection. - /// - /// - /// - /// A that represents the status code indicating - /// the reason for the WebSocket connection close. - /// - /// - /// The status codes are defined in - /// - /// Section 7.4 of RFC 6455. - /// - /// - /// - /// - /// A that represents the reason for the WebSocket - /// connection close. - /// - /// - /// The size must be 123 bytes or less in UTF-8. - /// - /// - /// - /// - /// is less than 1000 or greater than 4999. - /// - /// - /// -or- - /// - /// - /// The size of is greater than 123 bytes. - /// - /// - /// - /// - /// is 1010 (mandatory extension). - /// - /// - /// -or- - /// - /// - /// is 1005 (no status) and there is reason. - /// - /// - /// -or- - /// - /// - /// could not be UTF-8-encoded. - /// - /// - [Obsolete ("This method will be removed.")] - public void Stop (ushort code, string reason) - { - if (!code.IsCloseStatusCode ()) { - var msg = "Less than 1000 or greater than 4999."; - throw new ArgumentOutOfRangeException ("code", msg); - } - - if (code == 1010) { - var msg = "1010 cannot be used."; - throw new ArgumentException (msg, "code"); - } - - if (!reason.IsNullOrEmpty ()) { - 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); - } - } - - stop (code, reason); - } - - /// - /// Stops receiving incoming requests and closes each connection. - /// - /// - /// - /// One of the enum values. - /// - /// - /// It represents the status code indicating the reason for the WebSocket - /// connection close. - /// - /// - /// - /// - /// A that represents the reason for the WebSocket - /// connection close. - /// - /// - /// The size must be 123 bytes or less in UTF-8. - /// - /// - /// - /// The size of is greater than 123 bytes. - /// - /// - /// - /// is - /// . - /// - /// - /// -or- - /// - /// - /// is - /// and there is reason. - /// - /// - /// -or- - /// - /// - /// could not be UTF-8-encoded. - /// - /// - [Obsolete ("This method will be removed.")] - public void Stop (CloseStatusCode code, string reason) - { - if (code == CloseStatusCode.MandatoryExtension) { - var msg = "MandatoryExtension cannot be used."; - throw new ArgumentException (msg, "code"); - } - - if (!reason.IsNullOrEmpty ()) { - 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); - } - } + if (_state != ServerState.Start) + return; - stop ((ushort) code, reason); + stop (1001, String.Empty); } #endregion 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 b5e8ffeb7..8f3e9b68d 100644 --- a/websocket-sharp/Server/WebSocketBehavior.cs +++ b/websocket-sharp/Server/WebSocketBehavior.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 @@ -29,6 +29,7 @@ using System; using System.Collections.Specialized; using System.IO; +using System.Security.Principal; using WebSocketSharp.Net; using WebSocketSharp.Net.WebSockets; @@ -37,7 +38,7 @@ namespace WebSocketSharp.Server /// /// Exposes a set of methods and properties used to define the behavior of /// a WebSocket service provided by the or - /// . + /// class. /// /// /// This class is an abstract class. @@ -49,6 +50,7 @@ public abstract class WebSocketBehavior : IWebSocketSession private WebSocketContext _context; private Func _cookiesValidator; private bool _emitOnPing; + private Func _hostValidator; private string _id; private bool _ignoreExtensions; private Func _originValidator; @@ -74,147 +76,203 @@ 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 session has not started yet. + /// protected NameValueCollection Headers { get { - return _context != null ? _context.Headers : null; + if (_context == null) { + var msg = "The session has not started yet."; + + throw new InvalidOperationException (msg); + } + + return _context.Headers; } } /// - /// Gets the logging function. + /// Gets a value indicating whether the communication is possible for + /// a session. /// /// - /// - /// A that provides the logging function. - /// - /// - /// if the session has not started yet. - /// + /// true if the communication is possible; otherwise, false. /// - [Obsolete ("This property will be removed.")] - protected Logger Log { + /// + /// The session has not started yet. + /// + protected bool IsAlive { get { - return _websocket != null ? _websocket.Log : null; + if (_websocket == null) { + 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 session has not started yet. + /// protected NameValueCollection QueryString { get { - return _context != null ? _context.QueryString : null; + if (_context == null) { + 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 session has not started yet. + /// + protected WebSocketState ReadyState { get { - return _sessions; + if (_websocket == null) { + 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 session has not started yet. + /// + protected WebSocketSessionManager Sessions { + get { + if (_sessions == null) { + 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 session has not started yet. + /// + protected IPrincipal User { get { - return _websocket != null - ? _websocket.ReadyState - : WebSocketState.Connecting; + if (_context == null) { + 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 session has not started yet. + /// + protected System.Net.IPEndPoint UserEndPoint { get { - return _context; + if (_context == null) { + 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 validate 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 + /// It represents the delegate called when the WebSocket interface /// for a session validates 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 to validate. /// /// - /// 2nd parameter passed to the method + /// 2nd parameter passed to the delegate /// receives the cookies to send to the client. /// /// - /// The method must return true if the cookies are valid. + /// The method invoked by the delegate must return true + /// if the cookies are valid. + /// + /// + /// if not necessary. /// /// /// The default value is . @@ -231,13 +289,13 @@ public Func CookiesValidator { } /// - /// 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 message event is emitted + /// when the WebSocket interface for a session 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 receives + /// a ping; otherwise, false. /// /// /// The default value is false. @@ -251,6 +309,7 @@ public bool EmitOnPing { set { if (_websocket != null) { _websocket.EmitOnPing = value; + return; } @@ -258,6 +317,42 @@ public bool EmitOnPing { } } + /// + /// Gets or sets the delegate used to validate the Host header. + /// + /// + /// + /// A delegate. + /// + /// + /// 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 . + /// + /// + public Func HostValidator { + get { + return _hostValidator; + } + + set { + _hostValidator = value; + } + } + /// /// Gets the unique ID of a session. /// @@ -276,14 +371,13 @@ public string 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. @@ -300,25 +394,27 @@ public bool IgnoreExtensions { } /// - /// Gets or sets the delegate used to validate the Origin header included in - /// a WebSocket handshake request to the service. + /// Gets or sets the delegate used to validate the Origin header. /// /// /// - /// A Func<string, bool> delegate or - /// if not needed. + /// A delegate. /// /// - /// The delegate invokes the method called when the WebSocket instance + /// 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 must return true if the header value is valid. + /// The method invoked by the delegate must return true + /// if the header value is valid. + /// + /// + /// if not necessary. /// /// /// The default value is . @@ -335,14 +431,14 @@ public Func OriginValidator { } /// - /// 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. /// @@ -350,12 +446,12 @@ public Func OriginValidator { /// 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 if the session has already started. + /// public string Protocol { get { return _websocket != null @@ -364,18 +460,23 @@ public string Protocol { } set { - if (ConnectionState != WebSocketState.Connecting) { + if (_websocket != null) { var msg = "The session has already started."; + throw new InvalidOperationException (msg); } if (value == null || value.Length == 0) { _protocol = null; + return; } - if (!value.IsToken ()) - throw new ArgumentException ("Not a token.", "value"); + if (!value.IsToken ()) { + var msg = "Not a token."; + + throw new ArgumentException (msg, "value"); + } _protocol = value; } @@ -405,16 +506,31 @@ public DateTime StartTime { private string checkHandshakeRequest (WebSocketContext context) { + if (_hostValidator != null) { + if (!_hostValidator (context.Host)) { + var msg = "The Host header is invalid."; + + return msg; + } + } + if (_originValidator != null) { - if (!_originValidator (context.Origin)) - return "It includes no Origin header or an invalid one."; + if (!_originValidator (context.Origin)) { + var msg = "The Origin header is non-existent or invalid."; + + return msg; + } } if (_cookiesValidator != null) { var req = context.CookieCollection; var res = context.WebSocket.CookieCollection; - if (!_cookiesValidator (req, res)) - return "It includes no cookie or an invalid one."; + + if (!_cookiesValidator (req, res)) { + var msg = "The Cookie header is non-existent or invalid."; + + return msg; + } } return null; @@ -426,6 +542,7 @@ private void onClose (object sender, CloseEventArgs e) return; _sessions.Remove (_id); + OnClose (e); } @@ -442,12 +559,15 @@ private void onMessage (object sender, MessageEventArgs e) private void onOpen (object sender, EventArgs e) { _id = _sessions.Add (this); + if (_id == null) { _websocket.Close (CloseStatusCode.Away); + return; } _startTime = DateTime.Now; + OnOpen (); } @@ -455,15 +575,11 @@ private void onOpen (object sender, EventArgs e) #region Internal Methods - internal void Start (WebSocketContext context, WebSocketSessionManager sessions) + internal void Start ( + WebSocketContext context, + WebSocketSessionManager sessions + ) { - if (_websocket != null) { - _websocket.Log.Error ("A session instance cannot be reused."); - context.WebSocket.Close (HttpStatusCode.ServiceUnavailable); - - return; - } - _context = context; _sessions = sessions; @@ -474,6 +590,7 @@ internal void Start (WebSocketContext context, WebSocketSessionManager sessions) _websocket.Protocol = _protocol; var waitTime = sessions.WaitTime; + if (waitTime != _websocket.WaitTime) _websocket.WaitTime = waitTime; @@ -482,7 +599,7 @@ internal void Start (WebSocketContext context, WebSocketSessionManager sessions) _websocket.OnError += onError; _websocket.OnClose += onClose; - _websocket.InternalAccept (); + _websocket.Accept (); } #endregion @@ -493,8 +610,8 @@ internal void Start (WebSocketContext context, WebSocketSessionManager sessions) /// 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 if the current state of the WebSocket + /// interface is Closing or Closed. /// /// /// The session has not started yet. @@ -503,6 +620,7 @@ protected void Close () { if (_websocket == null) { var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); } @@ -514,12 +632,12 @@ protected void Close () /// code and reason. /// /// - /// This method does nothing if the current state of the connection is - /// Closing or Closed. + /// This method does nothing if the current state of the WebSocket + /// interface is Closing or Closed. /// /// /// - /// A that represents the status code indicating + /// A that specifies the status code indicating /// the reason for the close. /// /// @@ -530,47 +648,49 @@ protected void 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. /// /// - /// - /// 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. /// /// + /// + /// The session has not started yet. + /// protected void Close (ushort code, string reason) { if (_websocket == null) { var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); } @@ -582,42 +702,35 @@ protected void Close (ushort code, string reason) /// code and reason. /// /// - /// This method does nothing if the current state of the connection is - /// Closing or Closed. + /// This method does nothing if the current state of the WebSocket + /// interface is Closing or Closed. /// /// /// /// 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. /// /// - /// - /// The session has not started yet. - /// - /// - /// The size of is greater than 123 bytes. - /// /// /// - /// is - /// . + /// is . /// /// /// -or- /// /// - /// is - /// and there is reason. + /// is and + /// is specified. /// /// /// -or- @@ -626,10 +739,17 @@ protected void Close (ushort code, string reason) /// could not be UTF-8-encoded. /// /// + /// + /// The size of is greater than 123 bytes. + /// + /// + /// The session has not started yet. + /// protected void Close (CloseStatusCode code, string reason) { if (_websocket == null) { var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); } @@ -644,8 +764,8 @@ 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 if the current state of the WebSocket + /// interface is Closing or Closed. /// /// /// @@ -655,6 +775,7 @@ protected void CloseAsync () { if (_websocket == null) { var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); } @@ -670,13 +791,13 @@ protected void CloseAsync () /// 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 if the current state of the WebSocket + /// interface is Closing or Closed. /// /// /// /// - /// A that represents the status code indicating + /// A that specifies the status code indicating /// the reason for the close. /// /// @@ -687,47 +808,49 @@ protected void CloseAsync () /// /// /// - /// 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. /// /// - /// - /// 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. /// /// + /// + /// The session has not started yet. + /// protected void CloseAsync (ushort code, string reason) { if (_websocket == null) { var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); } @@ -743,8 +866,8 @@ protected void CloseAsync (ushort 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 if the current state of the WebSocket + /// interface is Closing or Closed. /// /// /// @@ -752,31 +875,27 @@ protected 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. /// /// - /// - /// The session has not started yet. - /// /// /// - /// is - /// . + /// is . /// /// /// -or- /// /// - /// is - /// and there is reason. + /// is and + /// is specified. /// /// /// -or- @@ -788,44 +907,20 @@ protected void CloseAsync (ushort code, string reason) /// /// The size of is greater than 123 bytes. /// + /// + /// The session has not started yet. + /// protected void CloseAsync (CloseStatusCode code, string reason) { if (_websocket == null) { var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); } _websocket.CloseAsync (code, reason); } - /// - /// Calls the method with the specified message. - /// - /// - /// A that represents the error message. - /// - /// - /// An instance that represents the cause of - /// the error if present. - /// - /// - /// is . - /// - /// - /// is an empty string. - /// - [Obsolete ("This method will be removed.")] - protected void Error (string message, Exception exception) - { - if (message == null) - throw new ArgumentNullException ("message"); - - if (message.Length == 0) - throw new ArgumentException ("An empty string.", "message"); - - OnError (new ErrorEventArgs (message, exception)); - } - /// /// Called when the WebSocket connection for a session has been closed. /// @@ -838,7 +933,7 @@ 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 @@ -849,7 +944,7 @@ 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 @@ -867,21 +962,86 @@ protected virtual void OnOpen () } /// - /// Sends the specified data to a client using the WebSocket connection. + /// Sends a ping to the client for a session. /// - /// - /// An array of that represents the binary data to send. + /// + /// true if the send has successfully done and a pong has been + /// received within a time; otherwise, false. + /// + /// + /// The session has not started yet. + /// + protected bool Ping () + { + if (_websocket == null) { + var msg = "The session has not started yet."; + + throw new InvalidOperationException (msg); + } + + return _websocket.Ping (); + } + + /// + /// Sends a ping with the specified message to the client for a session. + /// + /// + /// 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. + /// + /// + /// Its size must be 125 bytes or less in UTF-8. + /// /// + /// + /// could not be UTF-8-encoded. + /// + /// + /// The size of is greater than 125 bytes. + /// /// - /// The current state of the connection is not Open. + /// The session has not started yet. /// + protected bool Ping (string message) + { + if (_websocket == null) { + var msg = "The session has not started yet."; + + throw new InvalidOperationException (msg); + } + + return _websocket.Ping (message); + } + + /// + /// Sends the specified data to the client for a session. + /// + /// + /// An array of that specifies the binary data to send. + /// /// /// is . /// + /// + /// + /// The session has not started yet. + /// + /// + /// -or- + /// + /// + /// 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."; + var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); } @@ -889,7 +1049,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. /// /// /// @@ -899,27 +1059,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. + /// The session has not started yet. /// /// /// -or- /// /// - /// The file could not be opened. + /// 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."; + var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); } @@ -927,24 +1096,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 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 session has not started yet. + /// + /// + /// -or- + /// + /// + /// 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."; + var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); } @@ -952,8 +1130,8 @@ protected void Send (string data) } /// - /// Sends the data from the specified stream to a client using - /// the WebSocket connection. + /// Sends the data from the specified stream instance to the client for + /// a session. /// /// /// @@ -966,12 +1144,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. @@ -989,10 +1161,25 @@ protected void Send (string data) /// No data could be read from . /// /// + /// + /// is . + /// + /// + /// + /// The session has not started yet. + /// + /// + /// -or- + /// + /// + /// 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."; + var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); } @@ -1000,38 +1187,48 @@ 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. /// /// - /// 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 session has not started yet. + /// + /// + /// -or- + /// + /// + /// 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."; + var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); } @@ -1039,8 +1236,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. @@ -1055,38 +1251,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. + /// The session has not started yet. /// /// /// -or- /// /// - /// The file could not be opened. + /// 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."; + var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); } @@ -1094,41 +1301,51 @@ 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. /// /// - /// 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 session has not started yet. + /// + /// + /// -or- + /// + /// + /// 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."; + var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); } @@ -1136,8 +1353,8 @@ protected void SendAsync (string data, Action completed) } /// - /// Sends the data from the specified stream 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. @@ -1155,23 +1372,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. @@ -1189,10 +1402,25 @@ protected void SendAsync (string data, Action completed) /// No data could be read from . /// /// + /// + /// is . + /// + /// + /// + /// The session has not started yet. + /// + /// + /// -or- + /// + /// + /// 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."; + var msg = "The session has not started yet."; + throw new InvalidOperationException (msg); } @@ -1200,5 +1428,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. + /// + /// + /// if 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 be7bca768..50f03f020 100644 --- a/websocket-sharp/Server/WebSocketServer.cs +++ b/websocket-sharp/Server/WebSocketServer.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2015 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; @@ -102,12 +101,13 @@ static WebSocketServer () public WebSocketServer () { var addr = System.Net.IPAddress.Any; + init (addr.ToString (), addr, 80, false); } /// /// Initializes a new instance of the class - /// with the specified . + /// with the specified port. /// /// /// @@ -119,8 +119,8 @@ public WebSocketServer () /// /// /// - /// An that represents the number of the port - /// on which to listen. + /// An that specifies the number of the port on which + /// to listen. /// /// /// is less than 1 or greater than 65535. @@ -132,13 +132,12 @@ public WebSocketServer (int port) /// /// Initializes a new instance of the class - /// with the specified . + /// with the specified URL. /// /// /// /// The new instance listens for incoming handshake requests on - /// the IP address of the host of and - /// the port of . + /// the IP address and port of . /// /// /// Either port 80 or 443 is used if includes @@ -151,11 +150,8 @@ public WebSocketServer (int port) /// /// /// - /// A that represents the WebSocket URL of the server. + /// 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) @@ -177,19 +176,22 @@ public WebSocketServer (string url) Uri uri; string msg; + if (!tryCreateUri (url, out uri, out msg)) throw new ArgumentException (msg, "url"); var host = uri.DnsSafeHost; - var addr = host.ToIPAddress (); + if (addr == null) { msg = "The host part could not be converted to an IP address."; + throw new ArgumentException (msg, "url"); } if (!addr.IsLocal ()) { msg = "The IP address of the host is not a local IP address."; + throw new ArgumentException (msg, "url"); } @@ -198,15 +200,15 @@ public WebSocketServer (string url) /// /// Initializes a new instance of the class - /// with the specified and . + /// with the specified port and boolean if secure or not. /// /// /// The new instance listens for incoming handshake requests on /// and . /// /// - /// An that represents the number of the port - /// on which to listen. + /// An that specifies the number of the port on which + /// to listen. /// /// /// A : true if the new instance provides @@ -219,16 +221,18 @@ public WebSocketServer (int port, bool secure) { if (!port.IsPortNumber ()) { var msg = "Less than 1 or greater than 65535."; + throw new ArgumentOutOfRangeException ("port", msg); } var addr = System.Net.IPAddress.Any; + init (addr.ToString (), addr, port, secure); } /// /// Initializes a new instance of the class - /// with the specified and . + /// with the specified IP address and port. /// /// /// @@ -240,19 +244,19 @@ public WebSocketServer (int port, bool secure) /// /// /// - /// A that represents the local - /// IP address on which to listen. + /// A that specifies the local IP + /// address on which to listen. /// /// - /// An that represents the number of the port - /// on which to listen. + /// 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. /// @@ -263,31 +267,30 @@ public WebSocketServer (System.Net.IPAddress address, int port) /// /// Initializes a new instance of the class - /// with the specified , , - /// and . + /// with the specified IP address, port, and boolean if secure or not. /// /// /// The new instance listens for incoming handshake requests on /// and . /// /// - /// A that represents the local - /// IP address on which to listen. + /// A that specifies the local IP + /// address on which to listen. /// /// - /// An that represents the number of the port - /// on which to listen. + /// An that specifies the number of the port on which + /// to listen. /// /// /// 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. /// @@ -296,11 +299,15 @@ public WebSocketServer (System.Net.IPAddress address, int port, bool secure) if (address == null) throw new ArgumentNullException ("address"); - if (!address.IsLocal ()) - throw new ArgumentException ("Not a local IP address.", "address"); + if (!address.IsLocal ()) { + var msg = "Not a local IP address."; + + throw new ArgumentException (msg, "address"); + } if (!port.IsPortNumber ()) { var msg = "Less than 1 or greater than 65535."; + throw new ArgumentOutOfRangeException ("port", msg); } @@ -315,8 +322,8 @@ public WebSocketServer (System.Net.IPAddress address, int port, bool secure) /// Gets the IP address of the server. /// /// - /// A that represents the local - /// IP address on which to listen for incoming handshake requests. + /// A that represents the local IP + /// address on which to listen for incoming handshake requests. /// public System.Net.IPAddress Address { get { @@ -324,52 +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 { - string msg; - if (!canSet (out msg)) { - _log.Warn (msg); - return; - } - - lock (_sync) { - if (!canSet (out msg)) { - _log.Warn (msg); - 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. /// /// /// @@ -390,17 +357,9 @@ public AuthenticationSchemes AuthenticationSchemes { } set { - string msg; - if (!canSet (out msg)) { - _log.Warn (msg); - return; - } - lock (_sync) { - if (!canSet (out msg)) { - _log.Warn (msg); + if (!canSet ()) return; - } _authSchemes = value; } @@ -420,15 +379,15 @@ 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; } } @@ -437,16 +396,16 @@ public bool IsSecure { /// 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 { @@ -478,8 +437,8 @@ public Logger Log { /// Gets the port of the server. /// /// - /// An that represents the number of the port - /// on which to listen for incoming handshake requests. + /// An that represents the number of the port on which + /// to listen for incoming handshake requests. /// public int Port { get { @@ -488,24 +447,22 @@ public int Port { } /// - /// Gets or sets the realm used for authentication. + /// Gets or sets the name of the realm associated with the server. /// /// - /// - /// "SECRET AREA" is used as 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 or by default. + /// 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. /// /// - /// That string represents the name of the realm. + /// The default value is . /// /// public string Realm { @@ -514,17 +471,9 @@ public string Realm { } set { - string msg; - if (!canSet (out msg)) { - _log.Warn (msg); - return; - } - lock (_sync) { - if (!canSet (out msg)) { - _log.Warn (msg); + if (!canSet ()) return; - } _realm = value; } @@ -537,12 +486,12 @@ public string Realm { /// /// /// - /// You should set this property to true if you would - /// like to resolve to wait for socket in TIME_WAIT state. + /// You should set this property to true if you would like to + /// 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. /// /// /// @@ -560,17 +509,9 @@ public bool ReuseAddress { } set { - string msg; - if (!canSet (out msg)) { - _log.Warn (msg); - return; - } - lock (_sync) { - if (!canSet (out msg)) { - _log.Warn (msg); + if (!canSet ()) return; - } _reuseAddress = value; } @@ -581,20 +522,21 @@ public bool ReuseAddress { /// Gets the configuration for secure connection. /// /// - /// This 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 instance does not provide secure connections. + /// The server does not provide secure connections. /// public ServerSslConfiguration SslConfiguration { get { - if (!_secure) { - var msg = "This instance does not provide secure connections."; + if (!_isSecure) { + var msg = "The server does not provide secure connections."; + throw new InvalidOperationException (msg); } @@ -603,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. /// /// - /// That delegate invokes the method called for finding - /// the credentials used to authenticate a client. + /// if not necessary. /// /// /// The default value is . @@ -637,17 +578,9 @@ public Func UserCredentialsFinder { } set { - string msg; - if (!canSet (out msg)) { - _log.Warn (msg); - return; - } - lock (_sync) { - if (!canSet (out msg)) { - _log.Warn (msg); + if (!canSet ()) return; - } _userCredFinder = value; } @@ -655,16 +588,17 @@ public Func UserCredentialsFinder { } /// - /// 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. @@ -684,12 +618,12 @@ public TimeSpan WaitTime { } /// - /// Gets the management function for the WebSocket services - /// provided by the server. + /// Gets the management function for the WebSocket services provided by + /// the server. /// /// - /// A that manages - /// the WebSocket services provided by the server. + /// A that manages the WebSocket + /// services provided by the server. /// public WebSocketServiceManager WebSocketServices { get { @@ -711,14 +645,19 @@ private void abort () } try { - try { - _listener.Stop (); - } - finally { - _services.Stop (1006, String.Empty); - } + _listener.Stop (); + } + catch (Exception ex) { + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); + } + + try { + _services.Stop (1006, String.Empty); } - catch { + catch (Exception ex) { + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); } _state = ServerState.Stop; @@ -732,50 +671,45 @@ private bool authenticateClient (TcpListenerWebSocketContext context) if (_authSchemes == AuthenticationSchemes.None) return false; - return context.Authenticate (_authSchemes, _realmInUse, _userCredFinder); - } + var chal = new AuthenticationChallenge (_authSchemes, _realmInUse) + .ToString (); - private bool canSet (out string message) - { - message = null; + var retry = -1; + Func auth = null; + auth = + () => { + retry++; - if (_state == ServerState.Start) { - message = "The server has already started."; - return false; - } + if (retry > 99) + return false; - if (_state == ServerState.ShuttingDown) { - message = "The server is shutting down."; - return false; - } + if (context.SetUser (_authSchemes, _realmInUse, _userCredFinder)) + return true; - return true; + context.SendAuthenticationChallenge (chal); + + return auth (); + }; + + return auth (); } - private bool checkHostNameForRequest (string name) + private bool canSet () { - return !_dnsStyle - || Uri.CheckHostName (name) != UriHostNameType.Dns - || name == _hostname; + return _state == ServerState.Ready || _state == ServerState.Stop; } - private static bool checkSslConfiguration ( - ServerSslConfiguration configuration, out string message - ) + private bool checkHostNameForRequest (string name) { - message = null; - - if (configuration.ServerCertificate == null) { - message = "There is no server certificate for secure connection."; - return false; - } - - return true; + return !_isDnsStyle + || Uri.CheckHostName (name) != UriHostNameType.Dns + || name == _hostname; } private string getRealm () { var realm = _realm; + return realm != null && realm.Length > 0 ? realm : _defaultRealm; } @@ -788,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); @@ -808,34 +745,36 @@ private void processRequest (TcpListenerWebSocketContext context) { if (!authenticateClient (context)) { context.Close (HttpStatusCode.Forbidden); + return; } var uri = context.RequestUri; + if (uri == null) { context.Close (HttpStatusCode.BadRequest); + return; } - if (!_allowForwardedRequest) { - if (uri.Port != _port) { - context.Close (HttpStatusCode.BadRequest); - return; - } + var name = uri.DnsSafeHost; - if (!checkHostNameForRequest (uri.DnsSafeHost)) { - context.Close (HttpStatusCode.NotFound); - return; - } + if (!checkHostNameForRequest (name)) { + context.Close (HttpStatusCode.NotFound); + + return; } var path = uri.AbsolutePath; + if (path.IndexOfAny (new[] { '%', '+' }) > -1) path = HttpUtility.UrlDecode (path, Encoding.UTF8); WebSocketServiceHost host; + if (!_services.InternalTryGetServiceHost (path, out host)) { context.Close (HttpStatusCode.NotImplemented); + return; } @@ -846,13 +785,19 @@ private void receiveRequest () { while (true) { TcpClient cl = null; + try { cl = _listener.AcceptTcpClient (); + ThreadPool.QueueUserWorkItem ( state => { try { var ctx = new TcpListenerWebSocketContext ( - cl, null, _secure, _sslConfigInUse, _log + cl, + null, + _isSecure, + _sslConfigInUse, + _log ); processRequest (ctx); @@ -867,10 +812,17 @@ private void receiveRequest () ); } catch (SocketException ex) { - if (_state == ServerState.ShuttingDown) { - _log.Info ("The underlying listener is stopped."); - break; - } + if (_state == ServerState.ShuttingDown) + return; + + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); + + break; + } + catch (InvalidOperationException ex) { + if (_state == ServerState.ShuttingDown) + return; _log.Fatal (ex.Message); _log.Debug (ex.ToString ()); @@ -884,46 +836,45 @@ private void receiveRequest () if (cl != null) cl.Close (); + if (_state == ServerState.ShuttingDown) + return; + break; } } - if (_state != ServerState.ShuttingDown) - abort (); + abort (); } - private void start (ServerSslConfiguration sslConfig) + private void start () { - if (_state == ServerState.Start) { - _log.Info ("The server has already started."); - return; - } - - if (_state == ServerState.ShuttingDown) { - _log.Warn ("The server is shutting down."); - return; - } - lock (_sync) { - if (_state == ServerState.Start) { - _log.Info ("The server has already started."); + if (_state == ServerState.Start || _state == ServerState.ShuttingDown) return; - } - if (_state == ServerState.ShuttingDown) { - _log.Warn ("The server is shutting down."); - return; + if (_isSecure) { + var src = getSslConfiguration (); + var conf = new ServerSslConfiguration (src); + + if (conf.ServerCertificate == null) { + var msg = "There is no server certificate for secure connection."; + + throw new InvalidOperationException (msg); + } + + _sslConfigInUse = conf; } - _sslConfigInUse = sslConfig; _realmInUse = getRealm (); _services.Start (); + try { startReceiving (); } catch { _services.Stop (1011, String.Empty); + throw; } @@ -935,7 +886,9 @@ private void startReceiving () { if (_reuseAddress) { _listener.Server.SetSocketOption ( - SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true + SocketOptionLevel.Socket, + SocketOptionName.ReuseAddress, + true ); } @@ -944,84 +897,57 @@ private void startReceiving () } catch (Exception ex) { var msg = "The underlying listener has failed to start."; + throw new InvalidOperationException (msg, ex); } - _receiveThread = new Thread (new ThreadStart (receiveRequest)); + var receiver = new ThreadStart (receiveRequest); + _receiveThread = new Thread (receiver); _receiveThread.IsBackground = true; + _receiveThread.Start (); } private void stop (ushort code, string reason) { - if (_state == ServerState.Ready) { - _log.Info ("The server is not started."); - return; - } - - if (_state == ServerState.ShuttingDown) { - _log.Info ("The server is shutting down."); - return; - } - - if (_state == ServerState.Stop) { - _log.Info ("The server has already stopped."); - return; - } - lock (_sync) { - if (_state == ServerState.ShuttingDown) { - _log.Info ("The server is shutting down."); - return; - } - - if (_state == ServerState.Stop) { - _log.Info ("The server has already stopped."); + if (_state != ServerState.Start) return; - } _state = ServerState.ShuttingDown; } try { - var threw = false; - try { - stopReceiving (5000); - } - catch { - threw = true; - throw; - } - finally { - try { - _services.Stop (code, reason); - } - catch { - if (!threw) - throw; - } - } + var timeout = 5000; + + stopReceiving (timeout); } - finally { - _state = ServerState.Stop; + catch (Exception ex) { + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); } - } - private void stopReceiving (int millisecondsTimeout) - { try { - _listener.Stop (); + _services.Stop (code, reason); } catch (Exception ex) { - var msg = "The underlying listener has failed to stop."; - throw new InvalidOperationException (msg, ex); + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); } + _state = ServerState.Stop; + } + + private void stopReceiving (int millisecondsTimeout) + { + _listener.Stop (); _receiveThread.Join (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)) @@ -1042,31 +968,17 @@ private static bool tryCreateUri ( #region Public Methods /// - /// Adds a WebSocket service with the specified behavior, path, - /// and delegate. + /// Adds a WebSocket service with the specified behavior and path. /// /// /// - /// A that represents an absolute path to + /// A that specifies an absolute path to /// the service to add. /// /// /// / is trimmed from the end of the string if present. /// /// - /// - /// - /// A Func<TBehavior> delegate. - /// - /// - /// It invokes the method called when creating a new session - /// instance for the service. - /// - /// - /// The method must create a new instance of the specified - /// behavior class and return it. - /// - /// /// /// /// The type of the behavior for the service. @@ -1074,18 +986,10 @@ private static bool tryCreateUri ( /// /// It must inherit the class. /// - /// - /// /// - /// is . + /// Also it must have a public parameterless constructor. /// - /// - /// -or- - /// - /// - /// is . - /// - /// + /// /// /// /// is an empty string. @@ -1110,95 +1014,22 @@ private static bool tryCreateUri ( /// is already in use. /// /// - [Obsolete ("This method will be removed. Use added one instead.")] - public void AddWebSocketService ( - string path, Func creator - ) - where TBehavior : WebSocketBehavior - { - if (path == null) - throw new ArgumentNullException ("path"); - - if (creator == null) - throw new ArgumentNullException ("creator"); - - if (path.Length == 0) - throw new ArgumentException ("An empty string.", "path"); - - if (path[0] != '/') - throw new ArgumentException ("Not an absolute path.", "path"); - - if (path.IndexOfAny (new[] { '?', '#' }) > -1) { - var msg = "It includes either or both query and fragment components."; - throw new ArgumentException (msg, "path"); - } - - _services.Add (path, creator); - } - - /// - /// Adds a WebSocket service with the specified behavior and path. - /// - /// - /// - /// A that represents an absolute path to - /// the service to add. - /// - /// - /// / is trimmed from the end of the string if present. - /// - /// - /// - /// - /// The type of the behavior for the service. - /// - /// - /// It must inherit the class. - /// - /// - /// And also, it must have a public parameterless constructor. - /// - /// /// /// is . /// - /// - /// - /// is an empty string. - /// - /// - /// -or- - /// - /// - /// is not an absolute path. - /// - /// - /// -or- - /// - /// - /// includes either or both - /// query and fragment components. - /// - /// - /// -or- - /// - /// - /// is already in use. - /// - /// - public void AddWebSocketService (string path) - where TBehaviorWithNew : WebSocketBehavior, new () + public void AddWebSocketService (string path) + where TBehavior : WebSocketBehavior, new () { - _services.AddService (path, null); + _services.AddService (path, null); } /// /// Adds a WebSocket service with the specified behavior, path, - /// and delegate. + /// and initializer. /// /// /// - /// A that represents an absolute path to + /// A that specifies an absolute path to /// the service to add. /// /// @@ -1207,15 +1038,17 @@ public void AddWebSocketService (string path) /// /// /// - /// An Action<TBehaviorWithNew> 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. /// /// - /// + /// /// /// The type of the behavior for the service. /// @@ -1223,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. @@ -1253,12 +1083,16 @@ public void AddWebSocketService (string path) /// is already in use. /// /// - public void AddWebSocketService ( - string path, Action initializer + /// + /// is . + /// + public void AddWebSocketService ( + string path, + Action initializer ) - where TBehaviorWithNew : WebSocketBehavior, new () + where TBehavior : WebSocketBehavior, new () { - _services.AddService (path, initializer); + _services.AddService (path, initializer); } /// @@ -1266,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; @@ -1274,16 +1108,13 @@ public void AddWebSocketService ( /// /// /// - /// A that represents an absolute path to + /// A that specifies an absolute path to /// the service to remove. /// /// /// / is trimmed from the end of the string if present. /// /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1302,6 +1133,9 @@ public void AddWebSocketService ( /// query and fragment components. /// /// + /// + /// is . + /// public bool RemoveWebSocketService (string path) { return _services.RemoveService (path); @@ -1311,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. /// /// /// @@ -1327,190 +1160,24 @@ public bool RemoveWebSocketService (string path) /// public void Start () { - ServerSslConfiguration sslConfig = null; - - if (_secure) { - sslConfig = new ServerSslConfiguration (getSslConfiguration ()); - - string msg; - if (!checkSslConfiguration (sslConfig, out msg)) - throw new InvalidOperationException (msg); - } + if (_state == ServerState.Start || _state == ServerState.ShuttingDown) + return; - start (sslConfig); + start (); } /// /// Stops receiving incoming handshake requests. /// - /// - /// The underlying has failed to stop. - /// + /// + /// This method works if the current state of the server is Start. + /// public void Stop () { - stop (1001, String.Empty); - } - - /// - /// Stops receiving incoming handshake requests and closes each connection - /// with the specified code and reason. - /// - /// - /// - /// A that represents the status code indicating - /// the reason for the close. - /// - /// - /// The status codes are defined in - /// - /// Section 7.4 of RFC 6455. - /// - /// - /// - /// - /// A that represents the reason for the close. - /// - /// - /// The size must be 123 bytes or less in UTF-8. - /// - /// - /// - /// - /// is less than 1000 or greater than 4999. - /// - /// - /// -or- - /// - /// - /// The size of is greater than 123 bytes. - /// - /// - /// - /// - /// is 1010 (mandatory extension). - /// - /// - /// -or- - /// - /// - /// is 1005 (no status) and there is reason. - /// - /// - /// -or- - /// - /// - /// could not be UTF-8-encoded. - /// - /// - /// - /// The underlying has failed to stop. - /// - [Obsolete ("This method will be removed.")] - public void Stop (ushort code, string reason) - { - if (!code.IsCloseStatusCode ()) { - var msg = "Less than 1000 or greater than 4999."; - throw new ArgumentOutOfRangeException ("code", msg); - } - - if (code == 1010) { - var msg = "1010 cannot be used."; - throw new ArgumentException (msg, "code"); - } - - if (!reason.IsNullOrEmpty ()) { - 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); - } - } - - stop (code, reason); - } - - /// - /// Stops receiving incoming handshake requests and closes each connection - /// with the specified code and reason. - /// - /// - /// - /// One of the enum values. - /// - /// - /// It represents the status code indicating the reason for the close. - /// - /// - /// - /// - /// A that represents the reason for the close. - /// - /// - /// The size must be 123 bytes or less in UTF-8. - /// - /// - /// - /// - /// is - /// . - /// - /// - /// -or- - /// - /// - /// is - /// and there is reason. - /// - /// - /// -or- - /// - /// - /// could not be UTF-8-encoded. - /// - /// - /// - /// The size of is greater than 123 bytes. - /// - /// - /// The underlying has failed to stop. - /// - [Obsolete ("This method will be removed.")] - public void Stop (CloseStatusCode code, string reason) - { - if (code == CloseStatusCode.MandatoryExtension) { - var msg = "MandatoryExtension cannot be used."; - throw new ArgumentException (msg, "code"); - } - - if (!reason.IsNullOrEmpty ()) { - 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); - } - } + if (_state != ServerState.Start) + return; - stop ((ushort) code, reason); + stop (1001, String.Empty); } #endregion diff --git a/websocket-sharp/Server/WebSocketServiceHost.cs b/websocket-sharp/Server/WebSocketServiceHost.cs index 1da76427a..ee20d92a9 100644 --- a/websocket-sharp/Server/WebSocketServiceHost.cs +++ b/websocket-sharp/Server/WebSocketServiceHost.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2017 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 @@ -41,7 +41,7 @@ namespace WebSocketSharp.Server /// /// Exposes the methods and properties used to access the information in /// a WebSocket service provided by the or - /// . + /// class. /// /// /// This class is an abstract class. @@ -59,14 +59,16 @@ public abstract class WebSocketServiceHost #region Protected Constructors /// - /// Initializes a new instance of the class - /// with the specified and . + /// Initializes a new instance of the + /// class with the specified path and logging function. /// /// - /// A that represents the absolute path to the service. + /// A that specifies the absolute path to + /// the service. /// /// - /// A that represents the logging function for the service. + /// A that specifies the logging function for + /// the service. /// protected WebSocketServiceHost (string path, Logger log) { @@ -111,8 +113,8 @@ protected Logger Log { /// 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 @@ -155,7 +157,7 @@ public WebSocketSessionManager Sessions { } /// - /// Gets the of the behavior of the service. + /// Gets the type of the behavior of the service. /// /// /// A that represents the type of the behavior of @@ -164,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 d4ca6a2d1..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-2017 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 @@ -31,7 +31,7 @@ namespace WebSocketSharp.Server { internal class WebSocketServiceHost : WebSocketServiceHost - where TBehavior : WebSocketBehavior + where TBehavior : WebSocketBehavior, new () { #region Private Fields @@ -41,22 +41,14 @@ internal class WebSocketServiceHost : WebSocketServiceHost #region Internal Constructors - internal WebSocketServiceHost ( - string path, Func creator, Logger log - ) - : this (path, creator, null, log) - { - } - internal WebSocketServiceHost ( string path, - Func creator, Action initializer, Logger log ) : base (path, log) { - _creator = createCreator (creator, initializer); + _creator = createSessionCreator (initializer); } #endregion @@ -73,15 +65,16 @@ public override Type BehaviorType { #region Private Methods - private Func createCreator ( - Func creator, Action initializer + private static Func createSessionCreator ( + Action initializer ) { if (initializer == null) - return creator; + return () => new TBehavior (); return () => { - var ret = creator (); + var ret = new TBehavior (); + initializer (ret); return ret; diff --git a/websocket-sharp/Server/WebSocketServiceManager.cs b/websocket-sharp/Server/WebSocketServiceManager.cs index ee1256fcf..29021062a 100644 --- a/websocket-sharp/Server/WebSocketServiceManager.cs +++ b/websocket-sharp/Server/WebSocketServiceManager.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2015 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,15 +36,15 @@ namespace WebSocketSharp.Server /// Provides the management function for the WebSocket services. /// /// - /// This class manages the WebSocket services provided by - /// the or . + /// This class manages the WebSocket services provided by the + /// or class. /// public class WebSocketServiceManager { #region Private Fields - private volatile bool _clean; private Dictionary _hosts; + private volatile bool _keepClean; private Logger _log; private volatile ServerState _state; private object _sync; @@ -62,7 +58,6 @@ internal WebSocketServiceManager (Logger log) { _log = log; - _clean = true; _hosts = new Dictionary (); _state = ServerState.Ready; _sync = ((ICollection) _hosts).SyncRoot; @@ -87,15 +82,16 @@ public int Count { } /// - /// Gets the host instances for the WebSocket services. + /// Gets the service host instances for the WebSocket services. /// /// /// - /// An IEnumerable<WebSocketServiceHost> instance. + /// An + /// instance. /// /// /// It provides an enumerator which supports the iteration over - /// the collection of the host instances. + /// the collection of the service host instances. /// /// public IEnumerable Hosts { @@ -106,33 +102,33 @@ public IEnumerable Hosts { } /// - /// Gets the host instance for a WebSocket service with the specified path. + /// Gets the service host instance for a WebSocket service with + /// the specified path. /// /// /// - /// A instance or - /// if not found. + /// A instance that represents + /// the service host instance. /// /// - /// The 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 represents an absolute path to - /// the service to find. + /// A that specifies an absolute path to + /// the service to get. /// /// /// / is trimmed from the end of the string if present. /// /// - /// - /// is . - /// /// /// - /// is empty. + /// is an empty string. /// /// /// -or- @@ -148,6 +144,9 @@ public IEnumerable Hosts { /// query and fragment components. /// /// + /// + /// is . + /// public WebSocketServiceHost this[string path] { get { if (path == null) @@ -156,15 +155,20 @@ public WebSocketServiceHost this[string path] { if (path.Length == 0) throw new ArgumentException ("An empty string.", "path"); - if (path[0] != '/') - throw new ArgumentException ("Not an absolute path.", "path"); + if (path[0] != '/') { + var msg = "Not an absolute path."; + + throw new ArgumentException (msg, "path"); + } if (path.IndexOfAny (new[] { '?', '#' }) > -1) { var msg = "It includes either or both query and fragment components."; + throw new ArgumentException (msg, "path"); } WebSocketServiceHost host; + InternalTryGetServiceHost (path, out host); return host; @@ -176,35 +180,32 @@ 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. /// /// - /// true if the inactive sessions are cleaned up every 60 seconds; - /// otherwise, false. + /// + /// true if the inactive sessions are cleaned up every 60 + /// seconds; otherwise, false. + /// + /// + /// The default value is false. + /// /// public bool KeepClean { get { - return _clean; + return _keepClean; } set { - string msg; - if (!canSet (out msg)) { - _log.Warn (msg); - return; - } - lock (_sync) { - if (!canSet (out msg)) { - _log.Warn (msg); + if (!canSet ()) return; - } foreach (var host in _hosts.Values) host.KeepClean = value; - _clean = value; + _keepClean = value; } } } @@ -214,7 +215,8 @@ public bool KeepClean { /// /// /// - /// An IEnumerable<string> instance. + /// An + /// instance. /// /// /// It provides an enumerator which supports the iteration over @@ -229,37 +231,21 @@ public IEnumerable Paths { } /// - /// Gets the total number of the sessions in the WebSocket services. - /// - /// - /// An that represents the total number of - /// the sessions in the services. - /// - [Obsolete ("This property will be removed.")] - public int SessionCount { - get { - var cnt = 0; - foreach (var host in Hosts) { - if (_state != ServerState.Start) - break; - - cnt += host.Sessions.Count; - } - - return cnt; - } - } - - /// - /// 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. + /// /// /// /// The value specified for a set operation is zero or less. @@ -270,20 +256,15 @@ public TimeSpan 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)) { - _log.Warn (msg); - return; + throw new ArgumentOutOfRangeException ("value", msg); } lock (_sync) { - if (!canSet (out msg)) { - _log.Warn (msg); + if (!canSet ()) return; - } foreach (var host in _hosts.Values) host.WaitTime = value; @@ -297,144 +278,18 @@ public TimeSpan WaitTime { #region Private Methods - private void broadcast (Opcode opcode, byte[] data, Action completed) + private bool canSet () { - var cache = new Dictionary (); - - try { - foreach (var host in Hosts) { - if (_state != ServerState.Start) { - _log.Error ("The server is shutting down."); - break; - } - - host.Sessions.Broadcast (opcode, data, cache); - } - - if (completed != null) - completed (); - } - catch (Exception ex) { - _log.Error (ex.Message); - _log.Debug (ex.ToString ()); - } - finally { - cache.Clear (); - } - } - - private void broadcast (Opcode opcode, Stream stream, Action completed) - { - var cache = new Dictionary (); - - try { - foreach (var host in Hosts) { - if (_state != ServerState.Start) { - _log.Error ("The server is shutting down."); - break; - } - - host.Sessions.Broadcast (opcode, stream, cache); - } - - if (completed != null) - completed (); - } - catch (Exception ex) { - _log.Error (ex.Message); - _log.Debug (ex.ToString ()); - } - finally { - foreach (var cached in cache.Values) - cached.Dispose (); - - cache.Clear (); - } - } - - private void broadcastAsync (Opcode opcode, byte[] data, Action completed) - { - ThreadPool.QueueUserWorkItem ( - state => broadcast (opcode, data, completed) - ); - } - - private void broadcastAsync (Opcode opcode, Stream stream, Action completed) - { - ThreadPool.QueueUserWorkItem ( - state => broadcast (opcode, stream, completed) - ); - } - - private Dictionary> broadping ( - byte[] frameAsBytes, TimeSpan timeout - ) - { - var ret = new Dictionary> (); - - foreach (var host in Hosts) { - if (_state != ServerState.Start) { - _log.Error ("The server is shutting down."); - break; - } - - var res = host.Sessions.Broadping (frameAsBytes, timeout); - ret.Add (host.Path, res); - } - - return ret; - } - - private bool canSet (out string message) - { - message = null; - - if (_state == ServerState.Start) { - message = "The server has already started."; - return false; - } - - if (_state == ServerState.ShuttingDown) { - message = "The server is shutting down."; - return false; - } - - return true; + return _state == ServerState.Ready || _state == ServerState.Stop; } #endregion #region Internal Methods - internal void Add (string path, Func creator) - where TBehavior : WebSocketBehavior - { - path = path.TrimSlashFromEnd (); - - lock (_sync) { - WebSocketServiceHost host; - if (_hosts.TryGetValue (path, out host)) - throw new ArgumentException ("Already in use.", "path"); - - host = new WebSocketServiceHost ( - path, creator, null, _log - ); - - if (!_clean) - host.KeepClean = false; - - if (_waitTime != host.WaitTime) - host.WaitTime = _waitTime; - - if (_state == ServerState.Start) - host.Start (); - - _hosts.Add (path, host); - } - } - internal bool InternalTryGetServiceHost ( - string path, out WebSocketServiceHost host + string path, + out WebSocketServiceHost host ) { path = path.TrimSlashFromEnd (); @@ -471,11 +326,11 @@ internal void Stop (ushort code, string reason) /// /// Adds a WebSocket service with the specified behavior, path, - /// and delegate. + /// and initializer. /// /// /// - /// A that represents an absolute path to + /// A that specifies an absolute path to /// the service to add. /// /// @@ -484,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. /// /// /// @@ -500,15 +357,12 @@ 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 empty. + /// is an empty string. /// /// /// -or- @@ -530,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 () { @@ -541,11 +399,15 @@ public void AddService ( if (path.Length == 0) throw new ArgumentException ("An empty string.", "path"); - if (path[0] != '/') - throw new ArgumentException ("Not an absolute path.", "path"); + if (path[0] != '/') { + var msg = "Not an absolute path."; + + throw new ArgumentException (msg, "path"); + } if (path.IndexOfAny (new[] { '?', '#' }) > -1) { var msg = "It includes either or both query and fragment components."; + throw new ArgumentException (msg, "path"); } @@ -553,15 +415,17 @@ public void AddService ( lock (_sync) { WebSocketServiceHost host; - if (_hosts.TryGetValue (path, out host)) - throw new ArgumentException ("Already in use.", "path"); - host = new WebSocketServiceHost ( - path, () => new TBehavior (), initializer, _log - ); + if (_hosts.TryGetValue (path, out host)) { + var msg = "It is already in use."; + + throw new ArgumentException (msg, "path"); + } - if (!_clean) - host.KeepClean = false; + host = new WebSocketServiceHost (path, initializer, _log); + + if (_keepClean) + host.KeepClean = true; if (_waitTime != host.WaitTime) host.WaitTime = _waitTime; @@ -573,350 +437,12 @@ public void AddService ( } } - /// - /// Sends to every client in the WebSocket services. - /// - /// - /// An array of that represents the binary data to send. - /// - /// - /// The current state of the manager is not Start. - /// - /// - /// is . - /// - [Obsolete ("This method will be removed.")] - public void Broadcast (byte[] data) - { - if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; - throw new InvalidOperationException (msg); - } - - if (data == null) - throw new ArgumentNullException ("data"); - - if (data.LongLength <= WebSocket.FragmentLength) - broadcast (Opcode.Binary, data, null); - else - broadcast (Opcode.Binary, new MemoryStream (data), null); - } - - /// - /// Sends to every client in the WebSocket services. - /// - /// - /// A that represents the text data to send. - /// - /// - /// The current state of the manager is not Start. - /// - /// - /// is . - /// - /// - /// could not be UTF-8-encoded. - /// - [Obsolete ("This method will be removed.")] - public void Broadcast (string data) - { - if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; - throw new InvalidOperationException (msg); - } - - if (data == null) - 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"); - } - - if (bytes.LongLength <= WebSocket.FragmentLength) - broadcast (Opcode.Text, bytes, null); - else - broadcast (Opcode.Text, new MemoryStream (bytes), null); - } - - /// - /// Sends asynchronously to every client in - /// the WebSocket services. - /// - /// - /// This method does not wait for the send to be complete. - /// - /// - /// An array of that represents the binary data to send. - /// - /// - /// - /// An delegate or - /// if not needed. - /// - /// - /// The delegate invokes the method called when the send is complete. - /// - /// - /// - /// The current state of the manager is not Start. - /// - /// - /// is . - /// - [Obsolete ("This method will be removed.")] - public void BroadcastAsync (byte[] data, Action completed) - { - if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; - throw new InvalidOperationException (msg); - } - - if (data == null) - throw new ArgumentNullException ("data"); - - if (data.LongLength <= WebSocket.FragmentLength) - broadcastAsync (Opcode.Binary, data, completed); - else - broadcastAsync (Opcode.Binary, new MemoryStream (data), completed); - } - - /// - /// Sends asynchronously to every client in - /// the WebSocket services. - /// - /// - /// This method does not wait for the send to be complete. - /// - /// - /// A that represents the text data to send. - /// - /// - /// - /// An delegate or - /// if not needed. - /// - /// - /// The delegate invokes the method called when the send is complete. - /// - /// - /// - /// The current state of the manager is not Start. - /// - /// - /// is . - /// - /// - /// could not be UTF-8-encoded. - /// - [Obsolete ("This method will be removed.")] - public void BroadcastAsync (string data, Action completed) - { - if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; - throw new InvalidOperationException (msg); - } - - if (data == null) - 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"); - } - - if (bytes.LongLength <= WebSocket.FragmentLength) - broadcastAsync (Opcode.Text, bytes, completed); - else - broadcastAsync (Opcode.Text, new MemoryStream (bytes), completed); - } - - /// - /// Sends the data from asynchronously to - /// every client in the WebSocket services. - /// - /// - /// - /// The data is sent as the binary data. - /// - /// - /// This method does not wait for the send to be complete. - /// - /// - /// - /// A instance from which to read the data to send. - /// - /// - /// An that specifies the number of bytes to send. - /// - /// - /// - /// An delegate or - /// if not needed. - /// - /// - /// The delegate invokes the method called when the send is complete. - /// - /// - /// - /// The current state of the manager is not Start. - /// - /// - /// is . - /// - /// - /// - /// cannot be read. - /// - /// - /// -or- - /// - /// - /// is less than 1. - /// - /// - /// -or- - /// - /// - /// No data could be read from . - /// - /// - [Obsolete ("This method will be removed.")] - public void BroadcastAsync (Stream stream, int length, Action completed) - { - if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; - throw new InvalidOperationException (msg); - } - - if (stream == null) - throw new ArgumentNullException ("stream"); - - 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) { - _log.Warn ( - String.Format ( - "Only {0} byte(s) of data could be read from the stream.", - len - ) - ); - } - - if (len <= WebSocket.FragmentLength) - broadcastAsync (Opcode.Binary, bytes, completed); - else - broadcastAsync (Opcode.Binary, new MemoryStream (bytes), completed); - } - - /// - /// Sends a ping to every client in the WebSocket services. - /// - /// - /// - /// A Dictionary<string, Dictionary<string, bool>>. - /// - /// - /// It represents a collection of pairs of a service path and another - /// collection of pairs of a session ID and a value indicating whether - /// a pong has been received from the client within a time. - /// - /// - /// - /// The current state of the manager is not Start. - /// - [Obsolete ("This method will be removed.")] - public Dictionary> Broadping () - { - if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; - throw new InvalidOperationException (msg); - } - - return broadping (WebSocketFrame.EmptyPingBytes, _waitTime); - } - - /// - /// Sends a ping with to every client in - /// the WebSocket services. - /// - /// - /// - /// A Dictionary<string, Dictionary<string, bool>>. - /// - /// - /// It represents a collection of pairs of a service path and another - /// collection of pairs of a session ID and a value indicating whether - /// a pong has been received from the client within a time. - /// - /// - /// - /// - /// A that represents the message to send. - /// - /// - /// The size must be 125 bytes or less in UTF-8. - /// - /// - /// - /// The current state of the manager is not Start. - /// - /// - /// could not be UTF-8-encoded. - /// - /// - /// The size of is greater than 125 bytes. - /// - [Obsolete ("This method will be removed.")] - public Dictionary> Broadping (string message) - { - if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; - throw new InvalidOperationException (msg); - } - - if (message.IsNullOrEmpty ()) - return broadping (WebSocketFrame.EmptyPingBytes, _waitTime); - - 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); - } - - var frame = WebSocketFrame.CreatePingFrame (bytes, false); - return broadping (frame.ToArray (), _waitTime); - } - /// /// 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 () { @@ -924,6 +450,7 @@ public void Clear () lock (_sync) { hosts = _hosts.Values.ToList (); + _hosts.Clear (); } @@ -938,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; @@ -946,19 +473,16 @@ public void Clear () /// /// /// - /// A that represents an absolute path to + /// A that specifies an absolute path to /// the service to remove. /// /// /// / is trimmed from the end of the string if present. /// /// - /// - /// is . - /// /// /// - /// is empty. + /// is an empty string. /// /// /// -or- @@ -974,6 +498,9 @@ public void Clear () /// query and fragment components. /// /// + /// + /// is . + /// public bool RemoveService (string path) { if (path == null) @@ -982,17 +509,21 @@ public bool RemoveService (string path) if (path.Length == 0) throw new ArgumentException ("An empty string.", "path"); - if (path[0] != '/') - throw new ArgumentException ("Not an absolute path.", "path"); + if (path[0] != '/') { + var msg = "Not an absolute path."; + + throw new ArgumentException (msg, "path"); + } if (path.IndexOfAny (new[] { '?', '#' }) > -1) { var msg = "It includes either or both query and fragment components."; + throw new ArgumentException (msg, "path"); } path = path.TrimSlashFromEnd (); - WebSocketServiceHost host; + lock (_sync) { if (!_hosts.TryGetValue (path, out host)) return false; @@ -1007,17 +538,16 @@ public bool RemoveService (string path) } /// - /// Tries to get the host instance for a WebSocket service with + /// Tries to get the service host instance for a WebSocket service with /// the specified path. /// /// - /// true if the service is successfully found; otherwise, - /// false. + /// true if the try has succeeded; otherwise, false. /// /// /// - /// A that represents an absolute path to - /// the service to find. + /// A that specifies an absolute path to + /// the service to get. /// /// /// / is trimmed from the end of the string if present. @@ -1026,19 +556,18 @@ public bool RemoveService (string path) /// /// /// When this method returns, a - /// instance or if not found. + /// instance that receives the service host instance. /// /// - /// The 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. /// /// - /// - /// is . - /// /// /// - /// is empty. + /// is an empty string. /// /// /// -or- @@ -1054,6 +583,9 @@ public bool RemoveService (string path) /// query and fragment components. /// /// + /// + /// is . + /// public bool TryGetServiceHost (string path, out WebSocketServiceHost host) { if (path == null) @@ -1062,11 +594,15 @@ public bool TryGetServiceHost (string path, out WebSocketServiceHost host) if (path.Length == 0) throw new ArgumentException ("An empty string.", "path"); - if (path[0] != '/') - throw new ArgumentException ("Not an absolute path.", "path"); + if (path[0] != '/') { + var msg = "Not an absolute path."; + + throw new ArgumentException (msg, "path"); + } if (path.IndexOfAny (new[] { '?', '#' }) > -1) { var msg = "It includes either or both query and fragment components."; + throw new ArgumentException (msg, "path"); } diff --git a/websocket-sharp/Server/WebSocketSessionManager.cs b/websocket-sharp/Server/WebSocketSessionManager.cs index f7144b0ce..df2a83649 100644 --- a/websocket-sharp/Server/WebSocketSessionManager.cs +++ b/websocket-sharp/Server/WebSocketSessionManager.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2015 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 @@ -41,16 +41,17 @@ namespace WebSocketSharp.Server /// Provides the management function for the sessions in a WebSocket service. /// /// - /// This class manages the sessions in a WebSocket service provided by - /// the or . + /// This class manages the sessions in a WebSocket service provided by the + /// or class. /// public class WebSocketSessionManager { #region Private Fields - private volatile bool _clean; 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; @@ -60,13 +61,21 @@ public class WebSocketSessionManager #endregion + #region Static Constructor + + static WebSocketSessionManager () + { + _rawEmptyPingFrame = WebSocketFrame.CreatePingFrame (false).ToArray (); + } + + #endregion + #region Internal Constructors internal WebSocketSessionManager (Logger log) { _log = log; - _clean = true; _forSweep = new object (); _sessions = new Dictionary (); _state = ServerState.Ready; @@ -95,7 +104,8 @@ internal ServerState State { /// /// /// - /// An IEnumerable<string> instance. + /// An + /// instance. /// /// /// It provides an enumerator which supports the iteration over @@ -104,7 +114,7 @@ internal ServerState State { /// public IEnumerable ActiveIDs { get { - foreach (var res in broadping (WebSocketFrame.EmptyPingBytes)) { + foreach (var res in broadping (_rawEmptyPingFrame)) { if (res.Value) yield return res.Key; } @@ -129,7 +139,8 @@ public int Count { /// /// /// - /// An IEnumerable<string> instance. + /// An + /// instance. /// /// /// It provides an enumerator which supports the iteration over @@ -155,7 +166,8 @@ public IEnumerable IDs { /// /// /// - /// An IEnumerable<string> instance. + /// An + /// instance. /// /// /// It provides an enumerator which supports the iteration over @@ -164,7 +176,7 @@ public IEnumerable IDs { /// public IEnumerable InactiveIDs { get { - foreach (var res in broadping (WebSocketFrame.EmptyPingBytes)) { + foreach (var res in broadping (_rawEmptyPingFrame)) { if (!res.Value) yield return res.Key; } @@ -172,27 +184,26 @@ public IEnumerable InactiveIDs { } /// - /// Gets the session instance with . + /// Gets the session instance with the specified ID. /// /// /// - /// 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 represents 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) @@ -202,6 +213,7 @@ public IWebSocketSession this[string id] { throw new ArgumentException ("An empty string.", "id"); IWebSocketSession session; + tryGetSession (id, out session); return session; @@ -213,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; @@ -222,23 +234,15 @@ public IWebSocketSession this[string id] { /// public bool KeepClean { get { - return _clean; + return _keepClean; } set { - string msg; - if (!canSet (out msg)) { - _log.Warn (msg); - return; - } - lock (_sync) { - if (!canSet (out msg)) { - _log.Warn (msg); + if (!canSet ()) return; - } - _clean = value; + _keepClean = value; } } } @@ -248,7 +252,8 @@ public bool KeepClean { /// /// /// - /// An IEnumerable<IWebSocketSession> instance. + /// An + /// instance. /// /// /// It provides an enumerator which supports the iteration over @@ -270,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. @@ -289,20 +295,15 @@ public TimeSpan 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)) { - _log.Warn (msg); - return; + throw new ArgumentOutOfRangeException ("value", msg); } lock (_sync) { - if (!canSet (out msg)) { - _log.Warn (msg); + if (!canSet ()) return; - } _waitTime = value; } @@ -320,11 +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) @@ -339,18 +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) @@ -375,45 +382,39 @@ 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); } return ret; } - private bool canSet (out string message) + private bool canSet () { - message = null; - - if (_state == ServerState.Start) { - message = "The service has already started."; - return false; - } - - if (_state == ServerState.ShuttingDown) { - message = "The service is shutting down."; - return false; - } - - return true; + return _state == ServerState.Ready || _state == ServerState.Stop; } private static string createID () @@ -429,16 +430,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; } @@ -470,59 +473,13 @@ internal string Add (IWebSocketSession session) return null; var id = createID (); + _sessions.Add (id, session); return id; } } - internal void Broadcast ( - Opcode opcode, byte[] data, Dictionary cache - ) - { - foreach (var session in Sessions) { - if (_state != ServerState.Start) { - _log.Error ("The service is shutting down."); - break; - } - - session.Context.WebSocket.Send (opcode, data, cache); - } - } - - internal void Broadcast ( - Opcode opcode, Stream stream, Dictionary cache - ) - { - foreach (var session in Sessions) { - if (_state != ServerState.Start) { - _log.Error ("The service is shutting down."); - break; - } - - session.Context.WebSocket.Send (opcode, stream, cache); - } - } - - internal Dictionary Broadping ( - byte[] frameAsBytes, TimeSpan timeout - ) - { - var ret = new Dictionary (); - - foreach (var session in Sessions) { - if (_state != ServerState.Start) { - _log.Error ("The service is shutting down."); - break; - } - - var res = session.Context.WebSocket.Ping (frameAsBytes, timeout); - ret.Add (session.ID, res); - } - - return ret; - } - internal bool Remove (string id) { lock (_sync) @@ -532,19 +489,23 @@ internal bool Remove (string id) internal void Start () { lock (_sync) { - _sweepTimer.Enabled = _clean; + _sweepTimer.Enabled = _keepClean; _state = ServerState.Start; } } internal void Stop (ushort code, string reason) { - if (code == 1005) { // == no status + if (code == 1005) { stop (PayloadData.Empty, true); + return; } - stop (new PayloadData (code, reason), !code.IsReserved ()); + var payloadData = new PayloadData (code, reason); + var send = !code.IsReservedStatusCode (); + + stop (payloadData, send); } #endregion @@ -552,21 +513,22 @@ internal void Stop (ushort code, string reason) #region Public Methods /// - /// Sends to every client in the WebSocket service. + /// Sends the specified data to every client in the WebSocket service. /// /// - /// 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 manager is not Start. - /// /// /// is . /// + /// + /// The current state of the service is not Start. + /// public void Broadcast (byte[] data) { if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; + var msg = "The current state of the service is not Start."; + throw new InvalidOperationException (msg); } @@ -580,24 +542,25 @@ public void Broadcast (byte[] data) } /// - /// Sends to every client in the WebSocket service. + /// Sends the specified data to every client in the WebSocket service. /// /// - /// A that represents the text data to send. + /// A that specifies the text data to send. /// - /// - /// The current state of the manager 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) { if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; + var msg = "The current state of the service is not Start."; + throw new InvalidOperationException (msg); } @@ -605,8 +568,10 @@ public void Broadcast (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"); } @@ -617,24 +582,20 @@ public void Broadcast (string data) } /// - /// Sends the data from to every client in + /// Sends the data from the specified stream instance to every client in /// the WebSocket service. /// - /// - /// The data is sent as the binary data. - /// /// - /// A instance from which to read the data to send. + /// + /// A instance from which to read the data to send. + /// + /// + /// The data is sent as the binary data. + /// /// /// /// An that specifies the number of bytes to send. /// - /// - /// The current state of the manager is not Start. - /// - /// - /// is . - /// /// /// /// cannot be read. @@ -652,10 +613,17 @@ 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) { - var msg = "The current state of the manager is not Start."; + var msg = "The current state of the service is not Start."; + throw new InvalidOperationException (msg); } @@ -664,29 +632,30 @@ public void Broadcast (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) { - _log.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); } if (len <= WebSocket.FragmentLength) @@ -696,34 +665,37 @@ public void Broadcast (Stream stream, int length) } /// - /// Sends 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. /// /// - /// An array of that represents the binary data to send. + /// An array of that specifies the binary data to send. /// /// /// - /// 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 manager is not Start. - /// /// /// is . /// + /// + /// The current state of the service is not Start. + /// public void BroadcastAsync (byte[] data, Action completed) { if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; + var msg = "The current state of the service is not Start."; + throw new InvalidOperationException (msg); } @@ -737,37 +709,40 @@ public void BroadcastAsync (byte[] data, Action completed) } /// - /// Sends 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. /// /// - /// A that represents the text data to send. + /// A that specifies the text data to send. /// /// /// - /// 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 manager 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) { if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; + var msg = "The current state of the service is not Start."; + throw new InvalidOperationException (msg); } @@ -775,8 +750,10 @@ public void BroadcastAsync (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"); } @@ -787,38 +764,34 @@ public void BroadcastAsync (string data, Action completed) } /// - /// Sends the data from 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. + /// + /// /// - /// The data is sent as the binary data. + /// A instance from which to read the data to send. /// /// - /// This method does not wait for the send to be complete. + /// The data is sent as the binary data. /// - /// - /// - /// A instance from which to read the data to send. /// /// /// An that specifies the number of bytes to send. /// /// /// - /// 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 manager is not Start. - /// - /// - /// is . - /// /// /// /// cannot be read. @@ -836,10 +809,17 @@ 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) { - var msg = "The current state of the manager is not Start."; + var msg = "The current state of the service is not Start."; + throw new InvalidOperationException (msg); } @@ -848,29 +828,30 @@ public void BroadcastAsync (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) { - _log.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); } if (len <= WebSocket.FragmentLength) @@ -880,125 +861,42 @@ public void BroadcastAsync (Stream stream, int length, Action completed) } /// - /// Sends a ping to every client in the WebSocket service. - /// - /// - /// - /// A Dictionary<string, bool>. - /// - /// - /// It represents a collection of pairs of a session ID and - /// a value indicating whether a pong has been received from - /// the client within a time. - /// - /// - /// - /// The current state of the manager is not Start. - /// - [Obsolete ("This method will be removed.")] - public Dictionary Broadping () - { - if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; - throw new InvalidOperationException (msg); - } - - return Broadping (WebSocketFrame.EmptyPingBytes, _waitTime); - } - - /// - /// Sends a ping with to every client in - /// the WebSocket service. + /// Closes the session with the specified ID. /// - /// - /// - /// A Dictionary<string, bool>. - /// - /// - /// It represents a collection of pairs of a session ID and - /// a value indicating whether a pong has been received from - /// the client within a time. - /// - /// - /// - /// - /// A that represents the message to send. - /// - /// - /// The size must be 125 bytes or less in UTF-8. - /// + /// + /// A that specifies the ID of the session to close. /// - /// - /// The current state of the manager is not Start. - /// /// - /// could not be UTF-8-encoded. - /// - /// - /// The size of is greater than 125 bytes. + /// is an empty string. /// - [Obsolete ("This method will be removed.")] - public Dictionary Broadping (string message) - { - if (_state != ServerState.Start) { - var msg = "The current state of the manager is not Start."; - throw new InvalidOperationException (msg); - } - - if (message.IsNullOrEmpty ()) - return Broadping (WebSocketFrame.EmptyPingBytes, _waitTime); - - 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); - } - - var frame = WebSocketFrame.CreatePingFrame (bytes, false); - return Broadping (frame.ToArray (), _waitTime); - } - - /// - /// Closes the specified session. - /// - /// - /// A that represents the ID of the session to close. - /// /// /// is . /// - /// - /// is an empty string. - /// /// /// The session could not be found. /// public void CloseSession (string id) { IWebSocketSession session; + if (!TryGetSession (id, out session)) { var msg = "The session could not be found."; + throw new InvalidOperationException (msg); } - session.Context.WebSocket.Close (); + session.WebSocket.Close (); } /// - /// Closes the specified session with and - /// . + /// Closes the session with the specified ID, code, and reason. /// /// - /// A that represents the ID of the session to close. + /// A that specifies the ID of the session to close. /// /// /// - /// A that represents the status code indicating + /// A that specifies the status code indicating /// the reason for the close. /// /// @@ -1009,15 +907,12 @@ public void CloseSession (string id) /// /// /// - /// 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 . - /// /// /// /// is an empty string. @@ -1032,8 +927,8 @@ public void CloseSession (string id) /// -or- /// /// - /// is 1005 (no status) and there is - /// . + /// is 1005 (no status) and + /// is specified. /// /// /// -or- @@ -1042,8 +937,8 @@ public void CloseSession (string id) /// could not be UTF-8-encoded. /// /// - /// - /// The session could not be found. + /// + /// is . /// /// /// @@ -1056,43 +951,44 @@ 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; + if (!TryGetSession (id, out session)) { var msg = "The session could not be found."; + throw new InvalidOperationException (msg); } - session.Context.WebSocket.Close (code, reason); + session.WebSocket.Close (code, reason); } /// - /// Closes the specified session with and - /// . + /// Closes the session with the specified ID, code, and reason. /// /// - /// A that represents the ID of the session to close. + /// A that specifies the ID of the session to close. /// /// /// /// 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 . - /// /// /// /// is an empty string. @@ -1101,16 +997,14 @@ public void CloseSession (string id, ushort code, string reason) /// -or- /// /// - /// is - /// . + /// is . /// /// /// -or- /// /// - /// is - /// and there is - /// . + /// is and + /// is specified. /// /// /// -or- @@ -1119,75 +1013,79 @@ 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; + if (!TryGetSession (id, out session)) { var msg = "The session could not be found."; + 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 represents the ID of the session. + /// A that specifies the ID of the session. /// - /// - /// is . - /// /// /// is an empty string. /// + /// + /// is . + /// /// /// The session could not be found. /// public bool PingTo (string id) { IWebSocketSession session; + if (!TryGetSession (id, out session)) { var msg = "The session could not be found."; + throw new InvalidOperationException (msg); } - return session.Context.WebSocket.Ping (); + return session.WebSocket.Ping (); } /// - /// Sends a ping with to the client using + /// Sends a ping with the specified message 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 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. /// /// /// - /// A that represents the ID of the session. + /// A that specifies the ID of the session. /// - /// - /// is . - /// /// /// /// is an empty string. @@ -1199,32 +1097,40 @@ 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; + if (!TryGetSession (id, out session)) { var msg = "The session could not be found."; + throw new InvalidOperationException (msg); } - return session.Context.WebSocket.Ping (message); + return session.WebSocket.Ping (message); } /// - /// Sends to the client using the specified session. + /// Sends the specified data to the client using the specified session. /// /// - /// An array of that represents the binary data to send. + /// An array of that specifies the binary data to send. /// /// - /// A that represents the ID of the session. + /// A that specifies the ID of the session. /// + /// + /// is an empty string. + /// /// /// /// is . @@ -1236,9 +1142,6 @@ public bool PingTo (string message, string id) /// is . /// /// - /// - /// is an empty string. - /// /// /// /// The session could not be found. @@ -1247,49 +1150,51 @@ 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) { IWebSocketSession session; + if (!TryGetSession (id, out session)) { var msg = "The session could not be found."; + throw new InvalidOperationException (msg); } - session.Context.WebSocket.Send (data); + session.WebSocket.Send (data); } /// - /// Sends to the client using the specified session. + /// Sends the specified data to the client using the specified session. /// /// - /// A that represents the text data to send. + /// A that specifies the text data to send. /// /// - /// A that represents the ID of the session. + /// 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 . /// /// /// @@ -1300,47 +1205,40 @@ 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) { IWebSocketSession session; + if (!TryGetSession (id, out session)) { var msg = "The session could not be found."; + throw new InvalidOperationException (msg); } - session.Context.WebSocket.Send (data); + session.WebSocket.Send (data); } /// - /// Sends the data from to the client using + /// Sends the data from the specified stream instance to the client using /// the specified session. /// - /// - /// The data is sent as the binary data. - /// /// - /// A instance from which to read the data to send. + /// + /// A instance from which to read the data to send. + /// + /// + /// The data is sent as the binary data. + /// /// /// /// An that specifies the number of bytes to send. /// /// - /// A that represents the ID of the session. + /// A that specifies the ID of the session. /// - /// - /// - /// is . - /// - /// - /// -or- - /// - /// - /// is . - /// - /// /// /// /// is an empty string. @@ -1364,6 +1262,17 @@ public void SendTo (string data, string id) /// No data could be read from . /// /// + /// + /// + /// is . + /// + /// + /// -or- + /// + /// + /// is . + /// + /// /// /// /// The session could not be found. @@ -1372,46 +1281,54 @@ 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) { IWebSocketSession session; + if (!TryGetSession (id, out session)) { var msg = "The session could not be found."; + throw new InvalidOperationException (msg); } - session.Context.WebSocket.Send (stream, length); + session.WebSocket.Send (stream, length); } /// - /// Sends 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. /// /// - /// An array of that represents the binary data to send. + /// An array of that specifies the binary data to send. /// /// - /// A that represents the ID of the session. + /// A that specifies the ID of the session. /// /// /// - /// 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 . @@ -1423,9 +1340,6 @@ public void SendTo (Stream stream, int length, string id) /// is . /// /// - /// - /// is an empty string. - /// /// /// /// The session could not be found. @@ -1434,66 +1348,71 @@ 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) { IWebSocketSession session; + if (!TryGetSession (id, out session)) { var msg = "The session could not be found."; + throw new InvalidOperationException (msg); } - session.Context.WebSocket.SendAsync (data, completed); + session.WebSocket.SendAsync (data, completed); } /// - /// Sends 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. /// /// - /// A that represents the text data to send. + /// A that specifies the text data to send. /// /// - /// A that represents the ID of the session. + /// A that specifies the ID of the session. /// /// /// - /// 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 . /// /// /// @@ -1504,65 +1423,59 @@ 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) { IWebSocketSession session; + if (!TryGetSession (id, out session)) { var msg = "The session could not be found."; + throw new InvalidOperationException (msg); } - session.Context.WebSocket.SendAsync (data, completed); + session.WebSocket.SendAsync (data, completed); } /// - /// Sends the data from 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. + /// + /// /// - /// The data is sent as the binary data. + /// A instance from which to read the data to send. /// /// - /// This method does not wait for the send to be complete. + /// The data is sent as the binary data. /// - /// - /// - /// A instance from which to read the data to send. /// /// /// An that specifies the number of bytes to send. /// /// - /// A that represents the ID of the session. + /// A that specifies the ID of the session. /// /// /// - /// An Action<bool> delegate or - /// if not needed. - /// - /// - /// The delegate invokes the method called when the send is complete. - /// - /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. + /// An delegate. /// - /// - /// /// - /// is . + /// It specifies the delegate called when the send is complete. /// /// - /// -or- + /// The parameter passed to the delegate is + /// true if the send has successfully done; otherwise, + /// false. /// /// - /// is . + /// if not necessary. /// - /// + /// /// /// /// is an empty string. @@ -1586,6 +1499,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. @@ -1594,20 +1518,25 @@ 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; + if (!TryGetSession (id, out session)) { var msg = "The session could not be found."; + throw new InvalidOperationException (msg); } - session.Context.WebSocket.SendAsync (stream, length, completed); + session.WebSocket.SendAsync (stream, length, completed); } /// @@ -1616,13 +1545,15 @@ 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; } @@ -1638,47 +1569,56 @@ public void Sweep () break; IWebSocketSession session; - if (_sessions.TryGetValue (id, out session)) { - var state = session.ConnectionState; - if (state == WebSocketState.Open) - session.Context.WebSocket.Close (CloseStatusCode.Abnormal); - else if (state == WebSocketState.Closing) - continue; - else - _sessions.Remove (id); + + if (!_sessions.TryGetValue (id, out session)) + continue; + + var state = session.WebSocket.ReadyState; + + if (state == WebSocketState.Open) { + session.WebSocket.Close (CloseStatusCode.Abnormal); + + continue; } + + if (state == WebSocketState.Closing) + continue; + + _sessions.Remove (id); } } - _sweeping = false; + lock (_forSweep) + _sweeping = false; } /// - /// Tries to get the session instance with . + /// 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 represents 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 011dee00d..a9636e6a2 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-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 @@ -96,7 +96,7 @@ public class WebSocket : IDisposable private bool _ignoreExtensions; private bool _inContinuation; private volatile bool _inMessage; - private volatile Logger _logger; + private volatile Logger _log; private static readonly int _maxRetryCountForConnect; private Action _message; private Queue _messageEventQueue; @@ -130,15 +130,17 @@ public class WebSocket : IDisposable 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; @@ -171,7 +173,7 @@ internal WebSocket (HttpListenerWebSocketContext context, string protocol) _protocol = protocol; _closeContext = context.Close; - _logger = context.Log; + _log = context.Log; _message = messages; _secure = context.IsSecureConnection; _stream = context.Stream; @@ -187,7 +189,7 @@ internal WebSocket (TcpListenerWebSocketContext context, string protocol) _protocol = protocol; _closeContext = context.Close; - _logger = context.Log; + _log = context.Log; _message = messages; _secure = context.IsSecureConnection; _stream = context.Stream; @@ -202,7 +204,7 @@ internal WebSocket (TcpListenerWebSocketContext context, string protocol) /// /// Initializes a new instance of the class with - /// and optionally . + /// the specified URL and optionally subprotocols. /// /// /// @@ -261,6 +263,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"); @@ -273,8 +276,9 @@ public WebSocket (string url, params string[] protocols) _base64Key = CreateBase64Key (); _client = true; - _logger = new Logger (); + _log = new Logger (); _message = messagec; + _retryCountForConnect = -1; _secure = _uri.Scheme == "wss"; _waitTime = TimeSpan.FromSeconds (5); @@ -302,13 +306,6 @@ internal Func CustomHandshakeRequestChecker { } } - internal bool HasMessage { - get { - lock (_forMessageEventQueue) - return _messageEventQueue.Count > 0; - } - } - // As server internal bool IgnoreExtensions { get { @@ -320,12 +317,6 @@ internal bool IgnoreExtensions { } } - internal bool IsConnected { - get { - return _readyState == WebSocketState.Open || _readyState == WebSocketState.Closing; - } - } - #endregion #region Public Properties @@ -334,22 +325,23 @@ 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. + /// The set operation works if the current state of the interface is + /// New or Closed. /// /// /// /// 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. /// public CompressionMethod Compression { get { @@ -357,23 +349,15 @@ public CompressionMethod Compression { } set { - string msg = null; - if (!_client) { - msg = "This instance is not a client."; - throw new InvalidOperationException (msg); - } + var msg = "The interface is not for the client."; - if (!canSet (out msg)) { - _logger.Warn (msg); - return; + throw new InvalidOperationException (msg); } lock (_forState) { - if (!canSet (out msg)) { - _logger.Warn (msg); + if (!canSet ()) return; - } _compression = value; } @@ -396,7 +380,7 @@ public CompressionMethod Compression { public IEnumerable Cookies { get { lock (_cookies.SyncRoot) { - foreach (Cookie cookie in _cookies) + foreach (var cookie in _cookies) yield return cookie; } } @@ -421,13 +405,13 @@ 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 message event is + /// emitted when the interface 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 + /// receives a ping; otherwise, false. /// /// /// The default value is false. @@ -448,12 +432,12 @@ public bool EmitOnPing { /// the handshake request is allowed. /// /// - /// The set operation does nothing if the connection has already been - /// established or it is closing. + /// The set operation works if the current state of the interface is + /// New or Closed. /// /// /// - /// 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 +445,8 @@ 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. /// public bool EnableRedirection { get { @@ -469,23 +454,15 @@ public bool EnableRedirection { } set { - string msg = null; - if (!_client) { - msg = "This instance is not a client."; - throw new InvalidOperationException (msg); - } + var msg = "The interface is not for the client."; - if (!canSet (out msg)) { - _logger.Warn (msg); - return; + throw new InvalidOperationException (msg); } lock (_forState) { - if (!canSet (out msg)) { - _logger.Warn (msg); + if (!canSet ()) return; - } _enableRedirection = value; } @@ -493,12 +470,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,14 +488,10 @@ public string Extensions { } /// - /// Gets a value indicating whether the connection is alive. + /// 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 { @@ -523,11 +500,10 @@ public bool IsAlive { } /// - /// 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 { @@ -546,11 +522,11 @@ public bool IsSecure { /// public Logger Log { get { - return _logger; + return _log; } internal set { - _logger = value; + _log = value; } } @@ -565,11 +541,11 @@ internal set { /// Section 7 of RFC 6454. /// /// - /// This instance sends the Origin header if this property has any. + /// The interface 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 set operation works if the current state of the interface is + /// New or Closed. /// /// /// @@ -585,7 +561,8 @@ internal set { /// /// /// - /// 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. /// /// /// @@ -604,36 +581,31 @@ public string Origin { } set { - string msg = null; - if (!_client) { - msg = "This instance is not a client."; + var msg = "The interface is not for the 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); + if (!canSet ()) return; - } _origin = !value.IsNullOrEmpty () ? value.TrimEnd ('/') : value; } @@ -663,17 +635,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 +658,35 @@ 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 interface is not for the client. + /// + /// + /// -or- /// /// - /// This instance does not use a secure connection. + /// The interface does not use a secure connection. /// /// public ClientSslConfiguration SslConfiguration { get { if (!_client) { - var msg = "This instance is not a client."; + var msg = "The interface is not for the client."; + throw new InvalidOperationException (msg); } if (!_secure) { - var msg = "This instance does not use a secure connection."; + var msg = "The interface does not use a secure connection."; + throw new InvalidOperationException (msg); } @@ -721,7 +698,13 @@ 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 { @@ -733,16 +716,17 @@ public Uri Url { /// 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. + /// The set operation works if the current state of the interface is + /// New or Closed. /// /// /// - /// 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. /// /// /// @@ -754,20 +738,15 @@ public TimeSpan 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); + if (!canSet ()) return; - } _waitTime = value; } @@ -779,22 +758,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 +781,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,31 +849,22 @@ private bool accept () // As server private bool acceptHandshake () { - _logger.Debug ( - String.Format ( - "A handshake request from {0}:\n{1}", _context.UserEndPoint, _context - ) - ); - string msg; + if (!checkHandshakeRequest (_context, out msg)) { - _logger.Error (msg); + _log.Error (msg); + _log.Debug (_context.ToString ()); - refuseHandshake ( - CloseStatusCode.ProtocolError, - "A handshake error has occurred while attempting to accept." - ); + refuseHandshake (1002, "A handshake error has occurred."); return false; } if (!customCheckHandshakeRequest (_context, out msg)) { - _logger.Error (msg); + _log.Error (msg); + _log.Debug (_context.ToString ()); - refuseHandshake ( - CloseStatusCode.PolicyViolation, - "A handshake error has occurred while attempting to accept." - ); + refuseHandshake (1002, "A handshake error has occurred."); return false; } @@ -894,33 +872,29 @@ private bool acceptHandshake () _base64Key = _context.Headers["Sec-WebSocket-Key"]; if (_protocol != null) { - var vals = _context.SecWebSocketProtocols; - processSecWebSocketProtocolClientHeader (vals); + var matched = _context + .SecWebSocketProtocols + .Contains (p => p == _protocol); + + if (!matched) + _protocol = null; } if (!_ignoreExtensions) { var val = _context.Headers["Sec-WebSocket-Extensions"]; + processSecWebSocketExtensionsClientHeader (val); } - return sendHttpResponse (createHandshakeResponse ()); + createHandshakeResponse ().WriteTo (_stream); + + return true; } - private bool canSet (out string message) + private bool canSet () { - message = null; - - if (_readyState == WebSocketState.Open) { - message = "The connection has already been established."; - return false; - } - - if (_readyState == WebSocketState.Closing) { - message = "The connection is closing."; - return false; - } - - return true; + return _readyState == WebSocketState.New + || _readyState == WebSocketState.Closed; } // As server @@ -931,50 +905,60 @@ private bool checkHandshakeRequest ( message = null; if (!context.IsWebSocketRequest) { - message = "Not a handshake request."; - return false; - } + message = "Not a WebSocket handshake request."; - if (context.RequestUri == null) { - message = "It specifies an invalid Request-URI."; return false; } var headers = context.Headers; var key = headers["Sec-WebSocket-Key"]; + if (key == null) { - message = "It includes no Sec-WebSocket-Key header."; + message = "The Sec-WebSocket-Key header is non-existent."; + return false; } if (key.Length == 0) { - message = "It includes an invalid Sec-WebSocket-Key header."; + message = "The Sec-WebSocket-Key header is invalid."; + return false; } - var version = headers["Sec-WebSocket-Version"]; - if (version == null) { - message = "It includes no Sec-WebSocket-Version header."; + var ver = headers["Sec-WebSocket-Version"]; + + if (ver == null) { + message = "The Sec-WebSocket-Version header is non-existent."; + return false; } - if (version != _version) { - message = "It includes an invalid Sec-WebSocket-Version header."; + if (ver != _version) { + message = "The Sec-WebSocket-Version header is invalid."; + return false; } - var protocol = headers["Sec-WebSocket-Protocol"]; - if (protocol != null && protocol.Length == 0) { - message = "It includes an invalid Sec-WebSocket-Protocol header."; - return false; + var subps = headers["Sec-WebSocket-Protocol"]; + + if (subps != null) { + if (subps.Length == 0) { + message = "The Sec-WebSocket-Protocol header is invalid."; + + return false; + } } if (!_ignoreExtensions) { - var extensions = headers["Sec-WebSocket-Extensions"]; - if (extensions != null && extensions.Length == 0) { - message = "It includes an invalid Sec-WebSocket-Extensions header."; - return false; + var exts = headers["Sec-WebSocket-Extensions"]; + + if (exts != null) { + if (exts.Length == 0) { + message = "The Sec-WebSocket-Extensions header is invalid."; + + return false; + } } } @@ -982,44 +966,85 @@ private bool checkHandshakeRequest ( } // As client - private bool checkHandshakeResponse (HttpResponse response, out string message) + private bool checkHandshakeResponse ( + HttpResponse response, out string message + ) { message = null; if (response.IsRedirect) { - message = "Indicates the redirection."; + message = "The redirection is indicated."; + return false; } if (response.IsUnauthorized) { - message = "Requires the authentication."; + message = "The authentication is required."; + return false; } if (!response.IsWebSocketResponse) { message = "Not a WebSocket handshake response."; + return false; } var headers = response.Headers; - if (!validateSecWebSocketAcceptHeader (headers["Sec-WebSocket-Accept"])) { - message = "Includes no Sec-WebSocket-Accept header, or it has an invalid value."; + + var key = headers["Sec-WebSocket-Accept"]; + + if (key == null) { + message = "The Sec-WebSocket-Accept header is non-existent."; + return false; } - if (!validateSecWebSocketProtocolServerHeader (headers["Sec-WebSocket-Protocol"])) { - message = "Includes no Sec-WebSocket-Protocol header, or it has an invalid value."; + if (key != CreateResponseKey (_base64Key)) { + message = "The Sec-WebSocket-Accept header is invalid."; + return false; } - if (!validateSecWebSocketExtensionsServerHeader (headers["Sec-WebSocket-Extensions"])) { - message = "Includes an invalid Sec-WebSocket-Extensions header."; - return false; + var ver = headers["Sec-WebSocket-Version"]; + + if (ver != null) { + if (ver != _version) { + message = "The Sec-WebSocket-Version header is invalid."; + + return false; + } } - if (!validateSecWebSocketVersionServerHeader (headers["Sec-WebSocket-Version"])) { - message = "Includes an invalid Sec-WebSocket-Version header."; - return false; + var subp = headers["Sec-WebSocket-Protocol"]; + + if (subp == null) { + if (_protocolsRequested) { + message = "The Sec-WebSocket-Protocol header is non-existent."; + + return false; + } + } + else { + var valid = _protocolsRequested + && subp.Length > 0 + && _protocols.Contains (p => p == subp); + + if (!valid) { + message = "The Sec-WebSocket-Protocol header is invalid."; + + return false; + } + } + + var exts = headers["Sec-WebSocket-Extensions"]; + + if (exts != null) { + if (!validateSecWebSocketExtensionsServerHeader (exts)) { + message = "The Sec-WebSocket-Extensions header is invalid."; + + return false; + } } return true; @@ -1029,54 +1054,109 @@ 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 (_client) { + message = "A frame from the server is masked."; + + return false; + } } + else { + if (!_client) { + 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; } @@ -1086,51 +1166,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; @@ -1140,85 +1225,77 @@ 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 bytes = frame.ToArray (); + + sent = sendBytes (bytes); if (_client) 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; } @@ -1226,64 +1303,80 @@ 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; } + _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; } } + + // As client + private AuthenticationResponse createAuthenticationResponse () + { + if (_credentials == null) + return null; + + if (_authChallenge != null) { + var ret = new AuthenticationResponse ( + _authChallenge, _credentials, _nonceCount + ); + + _nonceCount = ret.NonceCount; + + return ret; + } + + return _preAuth ? new AuthenticationResponse (_credentials) : null; + } // As client private string createExtensions () @@ -1292,24 +1385,27 @@ 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); } var len = buff.Length; - if (len > 2) { - buff.Length = len - 2; - return buff.ToString (); - } - return null; + if (len <= 2) + return null; + + buff.Length = len - 2; + + return buff.ToString (); } // 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; return ret; @@ -1318,35 +1414,34 @@ private HttpResponse createHandshakeFailureResponse (HttpStatusCode code) // As client private HttpRequest createHandshakeRequest () { - var ret = HttpRequest.CreateWebSocketRequest (_uri); + var ret = HttpRequest.CreateWebSocketHandshakeRequest (_uri); var headers = ret.Headers; - if (!_origin.IsNullOrEmpty ()) - headers["Origin"] = _origin; headers["Sec-WebSocket-Key"] = _base64Key; + headers["Sec-WebSocket-Version"] = _version; - _protocolsRequested = _protocols != null; - if (_protocolsRequested) + if (!_origin.IsNullOrEmpty ()) + headers["Origin"] = _origin; + + if (_protocols != null) { headers["Sec-WebSocket-Protocol"] = _protocols.ToString (", "); - _extensionsRequested = _compression != CompressionMethod.None; - if (_extensionsRequested) - headers["Sec-WebSocket-Extensions"] = createExtensions (); + _protocolsRequested = true; + } - headers["Sec-WebSocket-Version"] = _version; + var exts = createExtensions (); - AuthenticationResponse authRes = null; - if (_authChallenge != null && _credentials != null) { - authRes = new AuthenticationResponse (_authChallenge, _credentials, _nonceCount); - _nonceCount = authRes.NonceCount; - } - else if (_preAuth) { - authRes = new AuthenticationResponse (_credentials); + if (exts != null) { + headers["Sec-WebSocket-Extensions"] = exts; + + _extensionsRequested = true; } - if (authRes != null) - headers["Authorization"] = authRes.ToString (); + var ares = createAuthenticationResponse (); + + if (ares != null) + headers["Authorization"] = ares.ToString (); if (_cookies.Count > 0) ret.SetCookies (_cookies); @@ -1357,9 +1452,10 @@ private HttpRequest createHandshakeRequest () // As server private HttpResponse createHandshakeResponse () { - var ret = HttpResponse.CreateWebSocketResponse (); + var ret = HttpResponse.CreateWebSocketHandshakeResponse (); var headers = ret.Headers; + headers["Sec-WebSocket-Accept"] = CreateResponseKey (_base64Key); if (_protocol != null) @@ -1385,33 +1481,56 @@ private bool customCheckHandshakeRequest ( return true; message = _handshakeRequestChecker (context); + return message == null; } private MessageEventArgs dequeueFromMessageEventQueue () { - lock (_forMessageEventQueue) - return _messageEventQueue.Count > 0 ? _messageEventQueue.Dequeue () : null; + lock (_forMessageEventQueue) { + return _messageEventQueue.Count > 0 + ? _messageEventQueue.Dequeue () + : null; + } } // As client - private void doHandshake () + private bool doHandshake () { setClientStream (); + var res = sendHandshakeRequest (); string msg; - if (!checkHandshakeResponse (res, out msg)) - throw new WebSocketException (CloseStatusCode.ProtocolError, msg); - if (_protocolsRequested) - _protocol = res.Headers["Sec-WebSocket-Protocol"]; + if (!checkHandshakeResponse (res, out msg)) { + _log.Error (msg); + _log.Debug (res.ToString ()); - if (_extensionsRequested) - processSecWebSocketExtensionsServerHeader (res.Headers["Sec-WebSocket-Extensions"]); + abort (1002, "A handshake error has occurred."); - processCookies (res.Cookies); - } + return false; + } + + if (_protocolsRequested) + _protocol = res.Headers["Sec-WebSocket-Protocol"]; + + if (_extensionsRequested) { + var exts = res.Headers["Sec-WebSocket-Extensions"]; + + if (exts == null) + _compression = CompressionMethod.None; + else + _extensions = exts; + } + + var cookies = res.Cookies; + + if (cookies.Count > 0) + _cookies.SetOrRemove (cookies); + + return true; + } private void enqueueToMessageEventQueue (MessageEventArgs e) { @@ -1421,35 +1540,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) @@ -1467,18 +1568,26 @@ private void init () _forState = new object (); _messageEventQueue = new Queue (); _forMessageEventQueue = ((ICollection) _messageEventQueue).SyncRoot; - _readyState = WebSocketState.Connecting; + _readyState = WebSocketState.New; } 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); @@ -1491,13 +1600,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; } @@ -1513,13 +1631,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; } @@ -1532,19 +1659,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; } @@ -1559,17 +1698,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; @@ -1579,28 +1722,24 @@ private bool ping (byte[] data) private bool processCloseFrame (WebSocketFrame frame) { - var payload = frame.PayloadData; - close (payload, !payload.HasReservedCode, false, true); + var data = frame.PayloadData; + var send = !data.HasReservedCode; - return false; - } + close (data, send, true); - // As client - private void processCookies (CookieCollection cookies) - { - if (cookies.Count == 0) - return; - - _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; } @@ -1608,7 +1747,6 @@ private bool processDataFrame (WebSocketFrame frame) private bool processFragmentFrame (WebSocketFrame frame) { if (!_inContinuation) { - // Must process first fragment. if (frame.IsContinuation) return true; @@ -1619,13 +1757,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; @@ -1637,27 +1778,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); 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) pong.Unmask (); - enqueueToMessageEventQueue (new MessageEventArgs (frame)); + var e = new MessageEventArgs (frame); + + enqueueToMessageEventQueue (e); } return true; @@ -1665,25 +1812,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; } @@ -1691,10 +1832,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 @@ -1715,23 +1864,25 @@ private void processSecWebSocketExtensionsClientHeader (string value) return; var buff = new StringBuilder (80); + var comp = false; foreach (var elm in value.SplitHeaderValue (',')) { - var extension = elm.Trim (); - if (extension.Length == 0) + var ext = elm.Trim (); + + if (ext.Length == 0) continue; if (!comp) { - if (extension.IsCompressionExtension (CompressionMethod.Deflate)) { + if (ext.IsCompressionExtension (CompressionMethod.Deflate)) { _compression = CompressionMethod.Deflate; - buff.AppendFormat ( - "{0}, ", - _compression.ToExtensionString ( - "client_no_context_takeover", "server_no_context_takeover" - ) - ); + var str = _compression.ToExtensionString ( + "client_no_context_takeover", + "server_no_context_takeover" + ); + + buff.AppendFormat ("{0}, ", str); comp = true; } @@ -1739,64 +1890,31 @@ private void processSecWebSocketExtensionsClientHeader (string value) } var len = buff.Length; + if (len <= 2) return; buff.Length = len - 2; - _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; + _extensions = buff.ToString (); } 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; - - var res = createHandshakeFailureResponse (HttpStatusCode.BadRequest); - sendHttpResponse (res); - - releaseServerResources (); + createHandshakeFailureResponse ().WriteTo (_stream); - _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 @@ -1804,11 +1922,13 @@ private void releaseClientResources () { if (_stream != null) { _stream.Dispose (); + _stream = null; } if (_tcpClient != null) { _tcpClient.Close (); + _tcpClient = null; } } @@ -1817,17 +1937,20 @@ 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; } } @@ -1845,49 +1968,68 @@ 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); @@ -1895,31 +2037,39 @@ private bool send (Opcode opcode, Stream stream, bool compressed) 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) @@ -1927,44 +2077,46 @@ 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, _client); + 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 ); } @@ -1979,8 +2131,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; } @@ -1992,122 +2144,127 @@ private bool sendBytes (byte[] bytes) private HttpResponse sendHandshakeRequest () { var req = createHandshakeRequest (); - var res = sendHttpRequest (req, 90000); + + var timeout = 90000; + var res = req.GetResponse (_stream, timeout); + if (res.IsUnauthorized) { - var chal = res.Headers["WWW-Authenticate"]; - _logger.Warn (String.Format ("Received an authentication requirement for '{0}'.", chal)); - if (chal.IsNullOrEmpty ()) { - _logger.Error ("No authentication challenge is specified."); + var val = res.Headers["WWW-Authenticate"]; + + if (val.IsNullOrEmpty ()) { + _log.Debug ("No authentication challenge is specified."); + return res; } - _authChallenge = AuthenticationChallenge.Parse (chal); - if (_authChallenge == null) { - _logger.Error ("An invalid authentication challenge is specified."); + var achal = AuthenticationChallenge.Parse (val); + + if (achal == null) { + _log.Debug ("An invalid authentication challenge is specified."); + return res; } - if (_credentials != null && - (!_preAuth || _authChallenge.Scheme == AuthenticationSchemes.Digest)) { - if (res.HasConnectionClose) { - releaseClientResources (); - setClientStream (); - } + _authChallenge = achal; + + if (_credentials == null) + return res; + + var ares = new AuthenticationResponse ( + _authChallenge, _credentials, _nonceCount + ); - var authRes = new AuthenticationResponse (_authChallenge, _credentials, _nonceCount); - _nonceCount = authRes.NonceCount; - req.Headers["Authorization"] = authRes.ToString (); - res = sendHttpRequest (req, 15000); + _nonceCount = ares.NonceCount; + + req.Headers["Authorization"] = ares.ToString (); + + if (res.CloseConnection) { + releaseClientResources (); + setClientStream (); } + + timeout = 15000; + res = req.GetResponse (_stream, timeout); } if (res.IsRedirect) { - var url = res.Headers["Location"]; - _logger.Warn (String.Format ("Received a redirection to '{0}'.", url)); - if (_enableRedirection) { - if (url.IsNullOrEmpty ()) { - _logger.Error ("No url to redirect is located."); - return res; - } + if (!_enableRedirection) + return res; - Uri uri; - string msg; - if (!url.TryCreateWebSocketUri (out uri, out msg)) { - _logger.Error ("An invalid url to redirect is located: " + msg); - return res; - } + var val = res.Headers["Location"]; - releaseClientResources (); + if (val.IsNullOrEmpty ()) { + _log.Debug ("No URL to redirect is located."); - _uri = uri; - _secure = uri.Scheme == "wss"; + return res; + } - setClientStream (); - return sendHandshakeRequest (); + Uri uri; + string msg; + + if (!val.TryCreateWebSocketUri (out uri, out msg)) { + _log.Debug ("An invalid URL to redirect is located."); + + return res; } - } - return res; - } + releaseClientResources (); - // As client - private HttpResponse sendHttpRequest (HttpRequest request, int millisecondsTimeout) - { - _logger.Debug ("A request to the server:\n" + request.ToString ()); - var res = request.GetResponse (_stream, millisecondsTimeout); - _logger.Debug ("A response to this request:\n" + res.ToString ()); + _uri = uri; + _secure = uri.Scheme == "wss"; - return res; - } + setClientStream (); - // As server - private bool sendHttpResponse (HttpResponse response) - { - _logger.Debug ( - String.Format ( - "A response to {0}:\n{1}", _context.UserEndPoint, response - ) - ); + return sendHandshakeRequest (); + } - return sendBytes (response.ToByteArray ()); + return res; } // As client - private void sendProxyConnectRequest () + private HttpResponse sendProxyConnectRequest () { var req = HttpRequest.CreateConnectRequest (_uri); - var res = sendHttpRequest (req, 90000); + + var timeout = 90000; + var res = req.GetResponse (_stream, timeout); + if (res.IsProxyAuthenticationRequired) { - var chal = res.Headers["Proxy-Authenticate"]; - _logger.Warn ( - String.Format ("Received a proxy authentication requirement for '{0}'.", chal)); - - if (chal.IsNullOrEmpty ()) - throw new WebSocketException ("No proxy authentication challenge is specified."); - - var authChal = AuthenticationChallenge.Parse (chal); - if (authChal == null) - throw new WebSocketException ("An invalid proxy authentication challenge is specified."); - - if (_proxyCredentials != null) { - if (res.HasConnectionClose) { - releaseClientResources (); - _tcpClient = new TcpClient (_proxyUri.DnsSafeHost, _proxyUri.Port); - _stream = _tcpClient.GetStream (); - } + if (_proxyCredentials == null) + return res; + + var val = res.Headers["Proxy-Authenticate"]; + + if (val.IsNullOrEmpty ()) { + _log.Debug ("No proxy authentication challenge is specified."); + + return res; + } + + var achal = AuthenticationChallenge.Parse (val); + + if (achal == null) { + _log.Debug ("An invalid proxy authentication challenge is specified."); + + return res; + } + + var ares = new AuthenticationResponse (achal, _proxyCredentials, 0); + + req.Headers["Proxy-Authorization"] = ares.ToString (); + + if (res.CloseConnection) { + releaseClientResources (); - var authRes = new AuthenticationResponse (authChal, _proxyCredentials, 0); - req.Headers["Proxy-Authorization"] = authRes.ToString (); - res = sendHttpRequest (req, 15000); + _tcpClient = new TcpClient (_proxyUri.DnsSafeHost, _proxyUri.Port); + _stream = _tcpClient.GetStream (); } - if (res.IsProxyAuthenticationRequired) - throw new WebSocketException ("A proxy authentication is required."); + timeout = 15000; + res = req.GetResponse (_stream, timeout); } - if (res.StatusCode[0] != '2') - throw new WebSocketException ( - "The proxy has failed a connection to the requested host and port."); + return res; } // As client @@ -2116,7 +2273,13 @@ private void setClientStream () if (_proxyUri != null) { _tcpClient = new TcpClient (_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); @@ -2126,27 +2289,36 @@ private void setClientStream () if (_secure) { var conf = getSslConfiguration (); var host = conf.TargetHost; - if (host != _uri.DnsSafeHost) + + if (host != _uri.DnsSafeHost) { + var msg = "An invalid host name is specified."; + throw new WebSocketException ( - CloseStatusCode.TlsHandshakeFailure, "An invalid host name is specified."); + CloseStatusCode.TlsHandshakeFailure, msg + ); + } try { var sslStream = new SslStream ( - _stream, - false, - conf.ServerCertificateValidationCallback, - conf.ClientCertificateSelectionCallback); + _stream, + false, + conf.ServerCertificateValidationCallback, + conf.ClientCertificateSelectionCallback + ); sslStream.AuthenticateAsClient ( host, conf.ClientCertificates, conf.EnabledSslProtocols, - conf.CheckCertificateRevocation); + conf.CheckCertificateRevocation + ); _stream = sslStream; } catch (Exception ex) { - throw new WebSocketException (CloseStatusCode.TlsHandshakeFailure, ex); + throw new WebSocketException ( + CloseStatusCode.TlsHandshakeFailure, ex + ); } } } @@ -2166,74 +2338,77 @@ private void startReceiving () _stream, false, frame => { - if (!processReceivedFrame (frame) || _readyState == WebSocketState.Closed) { + var cont = processReceivedFrame (frame) + && _readyState != WebSocketState.Closed; + + if (!cont) { 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) + if (_inMessage) return; message (); }, ex => { - _logger.Fatal (ex.ToString ()); - fatal ("An exception has occurred while receiving.", ex); + _log.Fatal (ex.Message); + _log.Debug (ex.ToString ()); + + abort ("An exception has occurred while receiving.", ex); } ); receive (); } - // As client - private bool validateSecWebSocketAcceptHeader (string value) - { - return value != null && value == CreateResponseKey (_base64Key); - } - // As client private bool validateSecWebSocketExtensionsServerHeader (string value) { - if (value == null) - return true; - - if (value.Length == 0) + if (!_extensionsRequested) return false; - if (!_extensionsRequested) + if (value.Length == 0) return false; var comp = _compression != CompressionMethod.None; - foreach (var e in value.SplitHeaderValue (',')) { - var ext = e.Trim (); + + foreach (var elm in value.SplitHeaderValue (',')) { + var ext = elm.Trim (); + if (comp && ext.IsCompressionExtension (_compression)) { - if (!ext.Contains ("server_no_context_takeover")) { - _logger.Error ("The server hasn't sent back 'server_no_context_takeover'."); + var param1 = "server_no_context_takeover"; + var param2 = "client_no_context_takeover"; + + if (!ext.Contains (param1)) { + // The server did not send back "server_no_context_takeover". + return false; } - if (!ext.Contains ("client_no_context_takeover")) - _logger.Warn ("The server hasn't sent back 'client_no_context_takeover'."); - - var method = _compression.ToExtensionString (); - var invalid = - ext.SplitHeaderValue (';').Contains ( - t => { - t = t.Trim (); - return t != method - && t != "server_no_context_takeover" - && t != "client_no_context_takeover"; - } - ); + var name = _compression.ToExtensionString (); + var invalid = ext.SplitHeaderValue (';').Contains ( + t => { + t = t.Trim (); + + var valid = t == name + || t == param1 + || t == param2; + + return !valid; + } + ); if (invalid) return false; + + comp = false; } else { return false; @@ -2243,81 +2418,80 @@ private bool validateSecWebSocketExtensionsServerHeader (string value) return true; } - // As client - private bool validateSecWebSocketProtocolServerHeader (string value) - { - if (value == null) - return !_protocolsRequested; - - if (value.Length == 0) - return false; - - return _protocolsRequested && _protocols.Contains (p => p == value); - } - - // As client - private bool validateSecWebSocketVersionServerHeader (string value) - { - return value == null || value == _version; - } - #endregion #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; @@ -2327,75 +2501,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; @@ -2409,40 +2561,38 @@ internal void Send ( ) { 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 { @@ -2457,112 +2607,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 () @@ -2574,12 +2623,12 @@ public void Close () /// Closes the connection with the specified 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. /// /// @@ -2594,41 +2643,26 @@ public void Close () /// /// /// 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. /// /// 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. /// /// - /// 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. /// /// @@ -2636,49 +2670,37 @@ 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 . + /// 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. /// /// - /// 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. /// /// @@ -2689,10 +2711,10 @@ 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. /// /// /// @@ -2709,20 +2731,21 @@ public void Close (CloseStatusCode code) /// /// /// 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. /// /// /// -or- /// /// - /// is 1005 (no status) and there is reason. + /// is 1005 (no status) and + /// is specified. /// /// /// -or- @@ -2735,37 +2758,45 @@ 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 (!_client && code == 1010) { var msg = "1010 cannot be used."; + 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); } @@ -2776,7 +2807,7 @@ public void Close (ushort code, string reason) /// Closes the connection with the specified 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. /// /// @@ -2784,37 +2815,35 @@ 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 . + /// 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- @@ -2830,32 +2859,39 @@ public void Close (CloseStatusCode code, string reason) { 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"); } 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); } @@ -2870,7 +2906,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. /// /// @@ -2887,13 +2923,13 @@ public void CloseAsync () /// 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. /// /// @@ -2908,34 +2944,19 @@ public void CloseAsync () /// /// /// 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. /// /// 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); } /// @@ -2946,7 +2967,7 @@ public void CloseAsync (ushort 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. /// /// @@ -2955,37 +2976,25 @@ 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 . + /// 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 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); } /// @@ -2996,13 +3005,13 @@ public void CloseAsync (CloseStatusCode 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. /// /// @@ -3013,10 +3022,10 @@ 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. /// /// /// @@ -3033,20 +3042,21 @@ public void CloseAsync (CloseStatusCode code) /// /// /// 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. /// /// /// -or- /// /// - /// is 1005 (no status) and there is reason. + /// is 1005 (no status) and + /// is specified. /// /// /// -or- @@ -3059,37 +3069,45 @@ 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 (!_client && code == 1010) { var msg = "1010 cannot be used."; + 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); } @@ -3104,7 +3122,7 @@ public void CloseAsync (ushort 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. /// /// @@ -3113,37 +3131,35 @@ 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 . + /// 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- @@ -3159,32 +3175,39 @@ public void CloseAsync (CloseStatusCode code, string reason) { 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"); } 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); } @@ -3195,17 +3218,12 @@ 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 if the current state of the interface is + /// Connecting or Open. /// /// /// - /// This instance is not a client. - /// - /// - /// -or- - /// - /// - /// The close process is in progress. + /// The interface is not for the client. /// /// /// -or- @@ -3217,22 +3235,23 @@ public void CloseAsync (CloseStatusCode code, string reason) public void Connect () { if (!_client) { - var msg = "This instance is not a client."; - throw new InvalidOperationException (msg); - } + var msg = "The interface is not for the client."; - if (_readyState == WebSocketState.Closing) { - var msg = "The close process is in progress."; throw new InvalidOperationException (msg); } - if (_retryCountForConnect > _maxRetryCountForConnect) { + if (_retryCountForConnect >= _maxRetryCountForConnect) { var msg = "A series of reconnecting has failed."; + throw new InvalidOperationException (msg); } - if (connect ()) - open (); + var connected = connect (); + + if (!connected) + return; + + open (); } /// @@ -3243,19 +3262,13 @@ 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 if the current state of the interface is + /// Connecting or Open. /// /// /// /// - /// This instance is not a client. - /// - /// - /// -or- - /// - /// - /// The close process is in progress. + /// The interface is not for the client. /// /// /// -or- @@ -3267,35 +3280,37 @@ public void Connect () public void ConnectAsync () { if (!_client) { - var msg = "This instance is not a client."; - throw new InvalidOperationException (msg); - } + var msg = "The interface is not for the client."; - if (_readyState == WebSocketState.Closing) { - var msg = "The close process is in progress."; throw new InvalidOperationException (msg); } - if (_retryCountForConnect > _maxRetryCountForConnect) { + if (_retryCountForConnect >= _maxRetryCountForConnect) { var msg = "A series of reconnecting has failed."; + 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 () @@ -3304,19 +3319,18 @@ public bool Ping () } /// - /// 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. /// /// /// @@ -3331,13 +3345,16 @@ public bool Ping (string message) 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); } @@ -3345,13 +3362,13 @@ 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. + /// The current state of the interface is not Open. /// /// /// is . @@ -3359,7 +3376,8 @@ public bool Ping (string message) public void Send (byte[] data) { if (_readyState != WebSocketState.Open) { - var msg = "The current state of the connection is not Open."; + var msg = "The current state of the interface is not Open."; + throw new InvalidOperationException (msg); } @@ -3370,7 +3388,7 @@ public void Send (byte[] data) } /// - /// Sends the specified file using the WebSocket connection. + /// Sends the specified file to the remote endpoint. /// /// /// @@ -3381,7 +3399,7 @@ public void Send (byte[] data) /// /// /// - /// The current state of the connection is not Open. + /// The current state of the interface is not Open. /// /// /// is . @@ -3400,7 +3418,8 @@ public void Send (byte[] data) public void Send (FileInfo fileInfo) { if (_readyState != WebSocketState.Open) { - var msg = "The current state of the connection is not Open."; + var msg = "The current state of the interface is not Open."; + throw new InvalidOperationException (msg); } @@ -3409,12 +3428,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"); } @@ -3422,13 +3444,13 @@ 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. + /// The current state of the interface is not Open. /// /// /// is . @@ -3439,7 +3461,8 @@ public void Send (FileInfo fileInfo) public void Send (string data) { if (_readyState != WebSocketState.Open) { - var msg = "The current state of the connection is not Open."; + var msg = "The current state of the interface is not Open."; + throw new InvalidOperationException (msg); } @@ -3447,8 +3470,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"); } @@ -3456,7 +3481,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. /// /// /// @@ -3470,7 +3495,7 @@ public void Send (string data) /// An that specifies the number of bytes to send. /// /// - /// The current state of the connection is not Open. + /// The current state of the interface is not Open. /// /// /// is . @@ -3495,7 +3520,8 @@ public void Send (string data) 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 current state of the interface is not Open."; + throw new InvalidOperationException (msg); } @@ -3504,58 +3530,61 @@ 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. /// /// - /// true is passed to the method if the send has done with - /// no error; otherwise, false. + /// The parameter passed to the method is true + /// if the send has successfully done; otherwise, false. + /// + /// + /// if not necessary. /// /// /// - /// The current state of the connection is not Open. + /// The current state of the interface is not Open. /// /// /// is . @@ -3563,7 +3592,8 @@ public void Send (Stream stream, int length) 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 current state of the interface is not Open."; + throw new InvalidOperationException (msg); } @@ -3574,7 +3604,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. @@ -3589,19 +3619,21 @@ public void SendAsync (byte[] data, Action completed) /// /// /// - /// An Action<bool> delegate or - /// if not needed. + /// An delegate. /// /// /// The delegate invokes the method 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 method is true + /// if the send has successfully done; otherwise, false. + /// + /// + /// if not necessary. /// /// /// - /// The current state of the connection is not Open. + /// The current state of the interface is not Open. /// /// /// is . @@ -3620,7 +3652,8 @@ public void SendAsync (byte[] data, Action completed) 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 current state of the interface is not Open."; + throw new InvalidOperationException (msg); } @@ -3629,12 +3662,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"); } @@ -3642,29 +3678,31 @@ 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. /// /// /// The delegate invokes the method 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 method is true + /// if the send has successfully done; otherwise, false. + /// + /// + /// if not necessary. /// /// /// - /// The current state of the connection is not Open. + /// The current state of the interface is not Open. /// /// /// is . @@ -3675,7 +3713,8 @@ public void SendAsync (FileInfo fileInfo, Action completed) 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 current state of the interface is not Open."; + throw new InvalidOperationException (msg); } @@ -3683,8 +3722,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"); } @@ -3692,8 +3733,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. @@ -3711,19 +3752,21 @@ public void SendAsync (string data, Action completed) /// /// /// - /// An Action<bool> delegate or - /// if not needed. + /// An delegate. /// /// /// The delegate invokes the method 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 method is true + /// if the send has successfully done; otherwise, false. + /// + /// + /// if not necessary. /// /// /// - /// The current state of the connection is not Open. + /// The current state of the interface is not Open. /// /// /// is . @@ -3748,7 +3791,8 @@ public void SendAsync (string data, Action completed) 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 current state of the interface is not Open."; + throw new InvalidOperationException (msg); } @@ -3757,29 +3801,30 @@ 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); @@ -3789,40 +3834,32 @@ public void SendAsync (Stream stream, int length, Action completed) /// Sets an HTTP cookie to send with the handshake request. /// /// - /// This method does nothing if the connection has already been - /// established or it is closing. + /// This method works if the current state of the interface is + /// New or Closed. /// /// - /// A that represents the cookie to send. + /// A that specifies the cookie to send. /// /// - /// This instance is not a client. + /// The interface is not for the client. /// /// /// is . /// public void SetCookie (Cookie cookie) { - string msg = null; - if (!_client) { - msg = "This instance is not a client."; + var msg = "The interface is not for the 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); + if (!canSet ()) return; - } lock (_cookies.SyncRoot) _cookies.SetOrRemove (cookie); @@ -3833,13 +3870,13 @@ public void SetCookie (Cookie 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. + /// This method works if the current state of the interface is + /// New or Closed. /// /// /// - /// A that represents the username associated with - /// the credentials. + /// A that specifies the username associated + /// with the credentials. /// /// /// or an empty string if initializes @@ -3848,19 +3885,20 @@ 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. + /// The interface is not for the client. /// /// /// @@ -3875,37 +3913,31 @@ public void SetCookie (Cookie cookie) /// public void SetCredentials (string username, string password, bool preAuth) { - string msg = null; - if (!_client) { - msg = "This instance is not a client."; + var msg = "The interface is not for the 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); + if (!canSet ()) return; - } if (username.IsNullOrEmpty ()) { _credentials = null; @@ -3927,43 +3959,43 @@ public void SetCredentials (string username, string password, bool preAuth) /// the credentials for the HTTP proxy authentication (Basic/Digest). /// /// - /// This method does nothing if the connection has already been - /// established or it is closing. + /// This method works if the current state of the interface is + /// New or Closed. /// /// /// - /// 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. + /// The interface is not for the client. /// /// /// @@ -3996,10 +4028,9 @@ public void SetCredentials (string username, string password, bool preAuth) /// public void SetProxy (string url, string username, string password) { - string msg = null; - if (!_client) { - msg = "This instance is not a client."; + var msg = "The interface is not for the client."; + throw new InvalidOperationException (msg); } @@ -4007,45 +4038,43 @@ 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); + if (!canSet ()) return; - } if (url.IsNullOrEmpty ()) { _proxyUri = null; @@ -4079,7 +4108,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..6dfe0b126 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-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 @@ -38,7 +38,19 @@ 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 @@ -82,9 +94,8 @@ internal WebSocketException (CloseStatusCode code, string message) internal WebSocketException ( CloseStatusCode code, string message, Exception innerException ) - : base (message ?? code.GetMessage (), innerException) + : this ((ushort) code, message, innerException) { - _code = code; } #endregion @@ -95,10 +106,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 ba0de3cd8..874947e52 100644 --- a/websocket-sharp/WebSocketFrame.cs +++ b/websocket-sharp/WebSocketFrame.cs @@ -4,7 +4,7 @@ * * The MIT License * - * Copyright (c) 2012-2019 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 @@ -45,30 +45,18 @@ 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; - - #endregion - - #region Internal Fields - - /// - /// Represents the ping frame without the payload data as an array of - /// . - /// - /// - /// The value of this field is created from a non masked ping frame, - /// so it can only be used to send a ping from the server. - /// - internal static readonly byte[] EmptyPingBytes; + private static readonly int _defaultHeaderLength; + private static readonly int _defaultMaskingKeyLength; + 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; #endregion @@ -76,7 +64,8 @@ internal class WebSocketFrame : IEnumerable static WebSocketFrame () { - EmptyPingBytes = CreatePingFrame (false).ToArray (); + _defaultHeaderLength = 2; + _defaultMaskingKeyLength = 4; } #endregion @@ -91,11 +80,6 @@ private WebSocketFrame () #region Internal Constructors - internal WebSocketFrame (Opcode opcode, PayloadData payloadData, bool mask) - : this (Fin.Final, opcode, payloadData, false, mask) - { - } - internal WebSocketFrame ( Fin fin, Opcode opcode, byte[] data, bool compressed, bool mask ) @@ -114,27 +98,29 @@ 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; } else if (len < 0x010000) { _payloadLength = (byte) 126; - _extPayloadLength = ((ushort) len).InternalToByteArray (ByteOrder.Big); + _extPayloadLength = ((ushort) len).ToByteArray (ByteOrder.Big); } else { _payloadLength = (byte) 127; - _extPayloadLength = len.InternalToByteArray (ByteOrder.Big); + _extPayloadLength = len.ToByteArray (ByteOrder.Big); } if (mask) { _mask = Mask.On; _maskingKey = createMaskingKey (); + payloadData.Mask (_maskingKey); } else { @@ -259,8 +245,11 @@ public bool IsText { public ulong Length { get { - return 2 - + (ulong) (_extPayloadLength.Length + _maskingKey.Length) + return (ulong) ( + _defaultHeaderLength + + _extPayloadLength.Length + + _maskingKey.Length + ) + _payloadData.Length; } } @@ -319,159 +308,18 @@ 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); } @@ -496,26 +344,10 @@ private static WebSocketFrame processHeader (byte[] header) // Payload Length var payloadLen = (byte) (header[1] & 0x7f); - if (!opcode.IsSupported ()) { - var msg = "A frame has an unsupported opcode."; - 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.IsSupportedOpcode ()) { + var msg = "The opcode of a frame is not supported."; - 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 (); @@ -535,18 +367,23 @@ private static WebSocketFrame readExtendedPayloadLength ( ) { var len = frame.ExtendedPayloadLengthWidth; + if (len == 0) { frame._extPayloadLength = WebSocket.EmptyBytes; + return frame; } var bytes = stream.ReadBytes (len); + if (bytes.Length != len) { var msg = "The extended payload length of a frame could not be read."; + throw new WebSocketException (msg); } frame._extPayloadLength = bytes; + return frame; } @@ -558,8 +395,10 @@ Action error ) { var len = frame.ExtendedPayloadLengthWidth; + if (len == 0) { frame._extPayloadLength = WebSocket.EmptyBytes; + completed (frame); return; @@ -570,10 +409,12 @@ Action error bytes => { if (bytes.Length != len) { var msg = "The extended payload length of a frame could not be read."; + throw new WebSocketException (msg); } frame._extPayloadLength = bytes; + completed (frame); }, error @@ -582,7 +423,9 @@ Action error private static WebSocketFrame readHeader (Stream stream) { - return processHeader (stream.ReadBytes (2)); + var bytes = stream.ReadBytes (_defaultHeaderLength); + + return processHeader (bytes); } private static void readHeaderAsync ( @@ -590,7 +433,13 @@ private static void readHeaderAsync ( ) { stream.ReadBytesAsync ( - 2, bytes => completed (processHeader (bytes)), error + _defaultHeaderLength, + bytes => { + var frame = processHeader (bytes); + + completed (frame); + }, + error ); } @@ -600,18 +449,20 @@ private static WebSocketFrame readMaskingKey ( { if (!frame.IsMasked) { frame._maskingKey = WebSocket.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); } frame._maskingKey = bytes; + return frame; } @@ -624,22 +475,23 @@ Action error { if (!frame.IsMasked) { frame._maskingKey = WebSocket.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); } frame._maskingKey = bytes; + completed (frame); }, error @@ -650,28 +502,33 @@ private static WebSocketFrame readPayloadData ( Stream stream, WebSocketFrame frame ) { - var exactLen = frame.ExactPayloadLength; - if (exactLen > PayloadData.MaxLength) { - var msg = "A frame has too long payload length."; + var exactPayloadLen = frame.ExactPayloadLength; + + 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."; + throw new WebSocketException (msg); } frame._payloadData = new PayloadData (bytes, len); + return frame; } @@ -682,47 +539,174 @@ private static void readPayloadDataAsync ( Action error ) { - var exactLen = frame.ExactPayloadLength; - if (exactLen > PayloadData.MaxLength) { - var msg = "A frame has too long payload length."; + var exactPayloadLen = frame.ExactPayloadLength; + + 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); return; } - var len = (long) exactLen; + var len = (long) exactPayloadLen; + Action comp = bytes => { if (bytes.LongLength != len) { var msg = "The payload data of a frame could not be read."; + throw new WebSocketException (msg); } frame._payloadData = new PayloadData (bytes, len); + 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}"; + } + else if (cnt < 0x010000) { + spFmt = "{0,4}"; + cntFmt = "{0,4:X}"; + } + else if (cnt < 0x0100000000) { + spFmt = "{0,8}"; + cntFmt = "{0,8:X}"; } - catch { - return null; + 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 @@ -764,6 +748,7 @@ internal static WebSocketFrame CreatePongFrame ( internal static WebSocketFrame ReadFrame (Stream stream, bool unmask) { var frame = readHeader (stream); + readExtendedPayloadLength (stream, frame); readMaskingKey (stream, frame); readPayloadData (stream, frame); @@ -811,14 +796,20 @@ Action error ); } + internal string ToString (bool dump) + { + return dump ? toDumpString () : toString (); + } + internal void Unmask () { if (_mask == Mask.Off) return; - _mask = Mask.Off; _payloadData.Mask (_maskingKey); + _maskingKey = WebSocket.EmptyBytes; + _mask = Mask.Off; } #endregion @@ -831,16 +822,6 @@ public IEnumerator GetEnumerator () yield return b; } - public void Print (bool dumped) - { - Console.WriteLine (dumped ? dump (this) : print (this)); - } - - public string PrintToString (bool dumped) - { - return dumped ? dump (this) : print (this); - } - public byte[] ToArray () { using (var buff = new MemoryStream ()) { @@ -852,33 +833,37 @@ public byte[] ToArray () header = (header << 1) + (int) _mask; header = (header << 7) + (int) _payloadLength; - buff.Write ( - ((ushort) header).InternalToByteArray (ByteOrder.Big), 0, 2 - ); + var uint16Header = (ushort) header; + var rawHeader = uint16Header.ToByteArray (ByteOrder.Big); - if (_payloadLength > 125) - buff.Write (_extPayloadLength, 0, _payloadLength == 126 ? 2 : 8); + buff.Write (rawHeader, 0, _defaultHeaderLength); + + 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 (); + return buff.ToArray (); } } public override string ToString () { - return BitConverter.ToString (ToArray ()); + var val = ToArray (); + + return BitConverter.ToString (val); } #endregion 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 @@ -