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..8d16c0794 100644
--- a/Example/Program.cs
+++ b/Example/Program.cs
@@ -18,92 +18,109 @@ 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 ("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 ("ws://localhost:4649/Chat"))
//using (var ws = new WebSocket ("wss://localhost:5963/Chat"))
//using (var ws = new WebSocket ("ws://localhost:4649/Chat?name=nobita"))
//using (var ws = new WebSocket ("wss://localhost:5963/Chat?name=nobita"))
{
- // Set the WebSocket events.
-
- 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"
- }
- );
#if DEBUG
// To change the logging level.
ws.Log.Level = LogLevel.Trace;
- // To change the wait time for the response to the Ping or Close.
- //ws.WaitTime = TimeSpan.FromSeconds (10);
+ // To enable the Per-message Compression extension.
+ //ws.Compression = CompressionMethod.Deflate;
// To emit a WebSocket.OnMessage event when receives a ping.
//ws.EmitOnPing = true;
-#endif
- // To enable the Per-message Compression extension.
- //ws.Compression = CompressionMethod.Deflate;
+
+ // To enable the redirection.
+ //ws.EnableRedirection = true;
+
+ // To disable a delay when send or receive buffer of the underlying
+ // TCP socket is not full.
+ ws.NoDelay = true;
+
+ // To send the Origin header.
+ //ws.Origin = "http://localhost:4649/";
+
+ // To send the cookies.
+ //ws.SetCookie (new Cookie ("name", "nobita"));
+ //ws.SetCookie (new Cookie ("roles", "\"idiot, gunfighter\""));
+
+ // To send the credentials for the HTTP Authentication (Basic/Digest).
+ //ws.SetCredentials ("nobita", "password", false);
+
+ // To connect through the HTTP Proxy server.
+ //ws.SetProxy ("http://localhost:3128/", "nobita", "password");
+
+ // To send a user header.
+
+ var reqHeader = "RequestForID";
+ var resHeader = "ID";
+
+ ws.SetUserHeader (reqHeader, resHeader);
+
// To validate the server certificate.
/*
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.
};
*/
- // To send the credentials for the HTTP Authentication (Basic/Digest).
- //ws.SetCredentials ("nobita", "password", false);
+ // To change the wait time for the response to the Ping or Close.
+ //ws.WaitTime = TimeSpan.FromSeconds (10);
+#endif
+ // Set the WebSocket events.
- // To send the Origin header.
- //ws.Origin = "http://localhost:4649/";
+ ws.OnClose +=
+ (sender, e) => {
+ var fmt = "[WebSocket Close ({0})] {1}";
- // To send the cookies.
- //ws.SetCookie (new Cookie ("name", "nobita"));
- //ws.SetCookie (new Cookie ("roles", "\"idiot, gunfighter\""));
+ Console.WriteLine (fmt, e.Code, e.Reason);
+ };
- // To connect through the HTTP Proxy server.
- //ws.SetProxy ("http://localhost:3128/", "nobita", "password");
+ ws.OnError +=
+ (sender, e) => {
+ var fmt = "[WebSocket Error] {0}";
- // To enable the redirection.
- //ws.EnableRedirection = true;
+ Console.WriteLine (fmt, e.Message);
+ };
+
+ ws.OnMessage +=
+ (sender, e) => {
+ var fmt = e.IsPing
+ ? "[WebSocket Ping] {0}"
+ : "[WebSocket Message] {0}";
+
+ Console.WriteLine (fmt, e.Data);
+ };
+
+ ws.OnOpen +=
+ (sender, e) => {
+#if DEBUG
+ var val = ws.HandshakeResponseHeaders[resHeader];
+
+ if (!val.IsNullOrEmpty ()) {
+ var fmt = "[WebSocket Open] {0}: {1}";
+
+ Console.WriteLine (fmt, resHeader, val);
+ }
+#endif
+ ws.Send ("Hi, there!");
+ };
// Connect to the server.
ws.Connect ();
@@ -111,11 +128,15 @@ public static void Main (string[] args)
// Connect to the server asynchronously.
//ws.ConnectAsync ();
- Console.WriteLine ("\nType 'exit' to exit.\n");
+ Console.WriteLine ("\nType \"exit\" to exit.\n");
+
while (true) {
Thread.Sleep (1000);
+
Console.Write ("> ");
+
var msg = Console.ReadLine ();
+
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..4a2e8c0bf 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);
@@ -50,85 +50,122 @@ public static void Main (string[] args)
// To change the logging level.
wssv.Log.Level = LogLevel.Trace;
- // To change the wait time for the response to the WebSocket Ping or Close.
- //wssv.WaitTime = TimeSpan.FromSeconds (2);
-
- // Not to remove the inactive sessions periodically.
- //wssv.KeepClean = false;
-#endif
- // To provide the secure connection.
- /*
- var cert = ConfigurationManager.AppSettings["ServerCertFile"];
- var passwd = ConfigurationManager.AppSettings["CertFilePassword"];
- wssv.SslConfiguration.ServerCertificate = new X509Certificate2 (cert, passwd);
- */
-
// To provide the HTTP Authentication (Basic/Digest).
/*
wssv.AuthenticationSchemes = AuthenticationSchemes.Basic;
wssv.Realm = "WebSocket Test";
- wssv.UserCredentialsFinder = id => {
+ wssv.UserCredentialsFinder =
+ id => {
var name = id.Name;
// Return user name, password, and roles.
return name == "nobita"
? new NetworkCredential (name, "password", "gunfighter")
- : null; // If the user credentials aren't found.
+ : null; // If the user credentials are not found.
};
*/
+ // To remove the inactive sessions periodically.
+ //wssv.KeepClean = true;
+
// To resolve to wait for socket in TIME_WAIT state.
//wssv.ReuseAddress = true;
+ // To provide the secure connection.
+ /*
+ var cert = ConfigurationManager.AppSettings["ServerCertFile"];
+ var passwd = ConfigurationManager.AppSettings["CertFilePassword"];
+
+ wssv.SslConfiguration.ServerCertificate = new X509Certificate2 (
+ cert,
+ passwd
+ );
+ */
+
+ // To change the wait time for the response to the WebSocket Ping or Close.
+ //wssv.WaitTime = TimeSpan.FromSeconds (2);
+#endif
// Add the WebSocket services.
+
wssv.AddWebSocketService ("/Echo");
- wssv.AddWebSocketService ("/Chat");
- // Add the WebSocket service with initializing.
- /*
+ // With initializing.
wssv.AddWebSocketService (
"/Chat",
- () =>
- 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#";
+#if DEBUG
+ // To respond to the cookies.
+ /*
+ s.CookiesResponder =
+ (reqCookies, resCookies) => {
+ foreach (var cookie in reqCookies) {
+ cookie.Expired = true;
+
+ resCookies.Add (cookie);
}
- }
+ };
+ */
+
+ // To emit a WebSocket.OnMessage event when receives a ping.
+ //s.EmitOnPing = true;
+
+ // To ignore the Sec-WebSocket-Extensions header.
+ //s.IgnoreExtensions = true;
+
+ // To disable a delay when send or receive buffer of the underlying
+ // TCP socket is not full.
+ s.NoDelay = true;
+
+ // To validate the Origin header.
+ /*
+ s.OriginValidator =
+ val => {
+ // Check the value of the Origin header, and return true if valid.
+
+ Uri origin;
+
+ return !val.IsNullOrEmpty ()
+ && Uri.TryCreate (val, UriKind.Absolute, out origin)
+ && origin.Host == "localhost";
+ };
+ */
+
+ // To send the Sec-WebSocket-Protocol header that has a subprotocol
+ // name.
+ //s.Protocol = "chat";
+
+ // To respond to the user headers.
+
+ s.UserHeadersResponder =
+ (reqHeaders, userHeaders) => {
+ var val = reqHeaders["RequestForID"];
+
+ if (!val.IsNullOrEmpty ())
+ userHeaders[val] = s.ID;
+ };
+
+#endif
+ }
);
- */
+ // Start the server.
wssv.Start ();
+
if (wssv.IsListening) {
- Console.WriteLine ("Listening on port {0}, and providing WebSocket services:", wssv.Port);
+ var fmt = "Listening on port {0}, and providing WebSocket services:";
+
+ Console.WriteLine (fmt, wssv.Port);
+
foreach (var path in wssv.WebSocketServices.Paths)
Console.WriteLine ("- {0}", path);
}
Console.WriteLine ("\nPress Enter key to stop the server...");
+
Console.ReadLine ();
+ // Stop the server.
wssv.Stop ();
}
}
diff --git a/Example3/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..259b5164b 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);
@@ -51,51 +51,61 @@ public static void Main (string[] args)
// To change the logging level.
httpsv.Log.Level = LogLevel.Trace;
- // To change the wait time for the response to the WebSocket Ping or Close.
- //httpsv.WaitTime = TimeSpan.FromSeconds (2);
-
- // Not to remove the inactive WebSocket sessions periodically.
- //httpsv.KeepClean = false;
-#endif
- // To provide the secure connection.
- /*
- var cert = ConfigurationManager.AppSettings["ServerCertFile"];
- var passwd = ConfigurationManager.AppSettings["CertFilePassword"];
- httpsv.SslConfiguration.ServerCertificate = new X509Certificate2 (cert, passwd);
- */
-
// To provide the HTTP Authentication (Basic/Digest).
/*
httpsv.AuthenticationSchemes = AuthenticationSchemes.Basic;
httpsv.Realm = "WebSocket Test";
- httpsv.UserCredentialsFinder = id => {
+ httpsv.UserCredentialsFinder =
+ id => {
var name = id.Name;
// Return user name, password, and roles.
return name == "nobita"
? new NetworkCredential (name, "password", "gunfighter")
- : null; // If the user credentials aren't found.
+ : null; // If the user credentials are not found.
};
*/
+ // To remove the inactive WebSocket sessions periodically.
+ //httpsv.KeepClean = true;
+
// To resolve to wait for socket in TIME_WAIT state.
//httpsv.ReuseAddress = true;
+ // To provide the secure connection.
+ /*
+ var cert = ConfigurationManager.AppSettings["ServerCertFile"];
+ var passwd = ConfigurationManager.AppSettings["CertFilePassword"];
+
+ httpsv.SslConfiguration.ServerCertificate = new X509Certificate2 (
+ cert,
+ passwd
+ );
+ */
+
+ // To change the wait time for the response to the WebSocket Ping or Close.
+ //httpsv.WaitTime = TimeSpan.FromSeconds (2);
+#endif
// Set the document root path.
- httpsv.DocumentRootPath = ConfigurationManager.AppSettings["DocumentRootPath"];
+ httpsv.DocumentRootPath = ConfigurationManager
+ .AppSettings["DocumentRootPath"];
// Set the HTTP GET request event.
- httpsv.OnGet += (sender, e) => {
+ httpsv.OnGet +=
+ (sender, e) => {
var req = e.Request;
var res = e.Response;
var path = req.RawUrl;
+
if (path == "/")
path += "index.html";
byte[] contents;
+
if (!e.TryReadFile (path, out contents)) {
res.StatusCode = (int) HttpStatusCode.NotFound;
+
return;
}
@@ -109,58 +119,91 @@ public static void Main (string[] args)
}
res.ContentLength64 = contents.LongLength;
+
res.Close (contents, true);
};
// Add the WebSocket services.
+
httpsv.AddWebSocketService ("/Echo");
- httpsv.AddWebSocketService ("/Chat");
- // Add the WebSocket service with initializing.
- /*
+ // With initializing.
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) {
+ s => {
+ s.Prefix = "Anon#";
+#if DEBUG
+ // To respond to the cookies.
+ /*
+ s.CookiesResponder =
+ (reqCookies, resCookies) => {
+ foreach (var cookie in reqCookies) {
cookie.Expired = true;
- res.Add (cookie);
- }
- return true; // If valid.
- }
- }
+ resCookies.Add (cookie);
+ }
+ };
+ */
+
+ // To emit a WebSocket.OnMessage event when receives a ping.
+ //s.EmitOnPing = true;
+
+ // To ignore the Sec-WebSocket-Extensions header.
+ //s.IgnoreExtensions = true;
+
+ // To disable a delay when send or receive buffer of the underlying
+ // TCP socket is not full.
+ s.NoDelay = true;
+
+ // To validate the Origin header.
+ /*
+ s.OriginValidator =
+ val => {
+ // Check the value of the Origin header, and return true if valid.
+
+ Uri origin;
+
+ return !val.IsNullOrEmpty ()
+ && Uri.TryCreate (val, UriKind.Absolute, out origin)
+ && origin.Host == "localhost";
+ };
+ */
+
+ // To send the Sec-WebSocket-Protocol header that has a subprotocol
+ // name.
+ //s.Protocol = "chat";
+
+ // To respond to the user headers.
+
+ s.UserHeadersResponder =
+ (reqHeaders, userHeaders) => {
+ var val = reqHeaders["RequestForID"];
+
+ if (!val.IsNullOrEmpty ())
+ userHeaders[val] = s.ID;
+ };
+
+#endif
+ }
);
- */
+ // Start the server.
httpsv.Start ();
+
if (httpsv.IsListening) {
- Console.WriteLine ("Listening on port {0}, and providing WebSocket services:", httpsv.Port);
+ var fmt = "Listening on port {0}, and providing WebSocket services:";
+
+ Console.WriteLine (fmt, httpsv.Port);
+
foreach (var path in httpsv.WebSocketServices.Paths)
Console.WriteLine ("- {0}", path);
}
Console.WriteLine ("\nPress Enter key to stop the server...");
+
Console.ReadLine ();
+ // Stop the server.
httpsv.Stop ();
}
}
diff --git a/LICENSE.txt b/LICENSE.txt
index 4d4e56322..b8fa987a2 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2010-2021 sta.blockhead
+Copyright (c) 2010-2025 sta.blockhead
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index e7049b224..35f94d454 100644
--- a/README.md
+++ b/README.md
@@ -9,9 +9,9 @@ websocket-sharp supports:
- [Per-message Compression](#per-message-compression) extension
- [Secure Connection](#secure-connection)
- [HTTP Authentication](#http-authentication)
-- [Query string, Origin header, and Cookies](#query-string-origin-header-and-cookies)
+- [Query string, Origin header, Cookies, and User headers](#query-string-origin-header-cookies-and-user-headers)
- [Connecting through the HTTP proxy server](#connecting-through-the-http-proxy-server)
-- .NET Framework **3.5** or later (includes compatible environment such as [Mono])
+- .NET Framework **3.5** or later versions of .NET Framework (includes compatible environment such as [Mono])
## Branches ##
@@ -43,25 +43,6 @@ You can add websocket-sharp to your project with the NuGet Package Manager, by u
PM> Install-Package WebSocketSharp -Pre
-### Unity Asset Store ###
-
-websocket-sharp is available on the Unity Asset Store (Sorry, Not available now).
-
-- [WebSocket-Sharp for Unity]
-
-It works with **Unity Free**, but there are some limitations:
-
-- [Security Sandbox of the Webplayer] (The server is not available in Web Player)
-- [WebGL Networking] (Not available in WebGL)
-- Incompatible platform (Not available for such UWP)
-- Lack of dll for the System.IO.Compression (The compression extension is not available on Windows)
-- .NET Socket Support for iOS/Android (iOS/Android Pro is required if your Unity is earlier than Unity 5)
-- .NET API 2.0 compatibility level for iOS/Android
-
-.NET API 2.0 compatibility level for iOS/Android may require to fix lack of some features for later than .NET Framework 2.0, such as the `System.Func<...>` delegates (so i have added them in the asset package).
-
-And it is priced at **US$15**. I believe your $15 makes this project more better, **Thank you!**
-
## Usage ##
### WebSocket Client ###
@@ -78,7 +59,7 @@ namespace Example
{
using (var ws = new WebSocket ("ws://dragonsnest.far/Laputa")) {
ws.OnMessage += (sender, e) =>
- Console.WriteLine ("Laputa says: " + e.Data);
+ Console.WriteLine ("Laputa says: " + e.Data);
ws.Connect ();
ws.Send ("BALUS");
@@ -127,20 +108,20 @@ This event occurs when the WebSocket connection has been established.
```csharp
ws.OnOpen += (sender, e) => {
- ...
- };
+ ...
+ };
```
`System.EventArgs.Empty` is passed as `e`, so you do not need to use it.
##### WebSocket.OnMessage Event #####
-This event occurs when the `WebSocket` receives a message.
+This event occurs when the `WebSocket` instance receives a message.
```csharp
ws.OnMessage += (sender, e) => {
- ...
- };
+ ...
+ };
```
A `WebSocketSharp.MessageEventArgs` instance is passed as `e`.
@@ -172,23 +153,23 @@ And if you would like to notify that a **ping** has been received, via this even
```csharp
ws.EmitOnPing = true;
ws.OnMessage += (sender, e) => {
- if (e.IsPing) {
- // Do something to notify that a ping has been received.
- ...
+ if (e.IsPing) {
+ // Do something to notify that a ping has been received.
+ ...
- return;
- }
- };
+ return;
+ }
+ };
```
##### WebSocket.OnError Event #####
-This event occurs when the `WebSocket` gets an error.
+This event occurs when the `WebSocket` instance gets an error.
```csharp
ws.OnError += (sender, e) => {
- ...
- };
+ ...
+ };
```
A `WebSocketSharp.ErrorEventArgs` instance is passed as `e`.
@@ -205,8 +186,8 @@ This event occurs when the WebSocket connection has been closed.
```csharp
ws.OnClose += (sender, e) => {
- ...
- };
+ ...
+ };
```
A `WebSocketSharp.CloseEventArgs` instance is passed as `e`.
@@ -237,7 +218,7 @@ ws.Send (data);
The `WebSocket.Send` method is overloaded.
-You can use the `WebSocket.Send (string)`, `WebSocket.Send (byte[])`, or `WebSocket.Send (System.IO.FileInfo)` method to send the data.
+You can use the `WebSocket.Send (string)`, `WebSocket.Send (byte[])`, `WebSocket.Send (System.IO.FileInfo)`, or `WebSocket.Send (System.IO.Stream, int)` method to send the data.
If you would like to send the data asynchronously, you should use the `WebSocket.SendAsync` method.
@@ -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.
+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 `TBehaviorWithNew` must inherit the `WebSocketBehavior` class, and must have a public parameterless constructor.
-
-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 ###
@@ -534,7 +514,9 @@ If you would like to provide the Digest authentication, you should set such as t
wssv.AuthenticationSchemes = AuthenticationSchemes.Digest;
```
-### Query string, Origin header, and Cookies ###
+### Query string, Origin header, Cookies, and User headers ###
+
+#### Query string ####
As a WebSocket client, if you would like to send the query string in the handshake request, you should create a new instance of the `WebSocket` class with a WebSocket URL that includes the [Query] string parameters.
@@ -542,64 +524,105 @@ 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.
+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
+{
+ private string _name;
+ ...
+
+ protected override void OnOpen ()
+ {
+ _name = QueryString["name"];
+ }
+
+ ...
+}
+```
+
+#### Origin header ####
+
+As a WebSocket client, if you would like to send the Origin header in the handshake request, you should set the `WebSocket.Origin` property to an allowable value as the [Origin] header before calling the connect method.
```csharp
ws.Origin = "http://example.com/";
```
-And if you would like to 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.
+As a WebSocket server, if you would like to validate the Origin header, you should set a validation for it with your `WebSocketBehavior`, for example, by using the `WebSocketServer.AddWebSocketService (string, Action)` method with initializing, such as the following.
+
+```csharp
+wssv.AddWebSocketService (
+ "/Chat",
+ s => {
+ s.OriginValidator =
+ val => {
+ // Check the value of the Origin header, and return true if valid.
+
+ Uri origin;
+
+ return !val.IsNullOrEmpty ()
+ && Uri.TryCreate (val, UriKind.Absolute, out origin)
+ && origin.Host == "example.com";
+ };
+ }
+);
+```
+
+#### Cookies ####
+
+As a WebSocket client, if you would like to send the cookies in the handshake request, you should set any cookie by using the `WebSocket.SetCookie (WebSocketSharp.Net.Cookie)` method before calling the connect method.
```csharp
ws.SetCookie (new Cookie ("name", "nobita"));
```
-As a WebSocket server, if you would like to 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 respond to the cookies, you should set a response action for it with your `WebSocketBehavior`, for example, by using the `WebSocketServer.AddWebSocketService (string, Action)` method with initializing, such as the following.
```csharp
-public class Chat : WebSocketBehavior
-{
- private string _name;
- ...
+wssv.AddWebSocketService (
+ "/Chat",
+ s => {
+ s.CookiesResponder =
+ (reqCookies, resCookies) => {
+ foreach (var cookie in reqCookies) {
+ cookie.Expired = true;
- protected override void OnOpen ()
- {
- _name = Context.QueryString["name"];
+ resCookies.Add (cookie);
+ }
+ };
}
+);
+```
- ...
-}
+#### User headers ####
+
+As a WebSocket client, if you would like to send the user headers in the handshake request, you should set any user defined header by using the `WebSocket.SetUserHeader (string, string)` method before calling the connect method.
+
+```csharp
+ws.SetUserHeader ("RequestForID", "ID");
```
-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.
+And if you would like to get the user headers included in the handshake response, you should access the `WebSocket.HandshakeResponseHeaders` property after the handshake is done.
-If you would like to get the cookies included in a handshake request, you should access the `WebSocketBehavior.Context.CookieCollection` property.
+```csharp
+var id = ws.HandshakeResponseHeaders["ID"];
+```
-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.
+As a WebSocket server, if you would like to respond to the user headers, you should set a response action for it with your `WebSocketBehavior`, for example, by using the `WebSocketServer.AddWebSocketService (string, Action)` method with initializing, such as the following.
```csharp
wssv.AddWebSocketService (
"/Chat",
- () =>
- 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.UserHeadersResponder =
+ (reqHeaders, userHeaders) => {
+ var val = reqHeaders["RequestForID"];
+
+ if (!val.IsNullOrEmpty ())
+ userHeaders[val] = s.ID;
+ };
+ }
);
```
@@ -649,7 +672,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 +690,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 +702,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 +711,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 +725,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..8aa46e2d2 100644
--- a/websocket-sharp/CloseEventArgs.cs
+++ b/websocket-sharp/CloseEventArgs.cs
@@ -4,7 +4,7 @@
*
* The MIT License
*
- * Copyright (c) 2012-2019 sta.blockhead
+ * Copyright (c) 2012-2024 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -35,19 +35,20 @@ namespace WebSocketSharp
///
///
///
- /// That event occurs when the WebSocket connection has been closed.
+ /// The close event occurs when the WebSocket connection has been closed.
///
///
- /// If you would like to get the reason for the connection close, you should
- /// access the or property.
+ /// If you would like to get the reason for the connection close,
+ /// you should access the or
+ /// property.
///
///
public class CloseEventArgs : EventArgs
{
#region Private Fields
- private bool _clean;
private PayloadData _payloadData;
+ private bool _wasClean;
#endregion
@@ -56,13 +57,7 @@ public class CloseEventArgs : EventArgs
internal CloseEventArgs (PayloadData payloadData, bool clean)
{
_payloadData = payloadData;
- _clean = clean;
- }
-
- internal CloseEventArgs (ushort code, string reason, bool clean)
- {
- _payloadData = new PayloadData (code, reason);
- _clean = clean;
+ _wasClean = clean;
}
#endregion
@@ -73,8 +68,13 @@ internal CloseEventArgs (ushort code, string reason, bool clean)
/// Gets the status code for the connection close.
///
///
- /// A that represents the status code for
- /// the connection close if present.
+ ///
+ /// A that represents the status code for
+ /// the connection close.
+ ///
+ ///
+ /// 1005 (no status) if not present.
+ ///
///
public ushort Code {
get {
@@ -86,8 +86,13 @@ public ushort Code {
/// Gets the reason for the connection close.
///
///
- /// A that represents the reason for
- /// the connection close if present.
+ ///
+ /// A that represents the reason for
+ /// the connection close.
+ ///
+ ///
+ /// An empty string if not present.
+ ///
///
public string Reason {
get {
@@ -104,7 +109,7 @@ public string Reason {
///
public bool WasClean {
get {
- return _clean;
+ return _wasClean;
}
}
diff --git a/websocket-sharp/ErrorEventArgs.cs b/websocket-sharp/ErrorEventArgs.cs
index 41502ab08..c02d0e000 100644
--- a/websocket-sharp/ErrorEventArgs.cs
+++ b/websocket-sharp/ErrorEventArgs.cs
@@ -4,7 +4,7 @@
*
* The MIT License
*
- * Copyright (c) 2012-2016 sta.blockhead
+ * Copyright (c) 2012-2022 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -42,14 +42,15 @@ namespace WebSocketSharp
///
///
///
- /// That event occurs when the gets an error.
+ /// The error event occurs when the interface
+ /// gets an error.
///
///
/// If you would like to get the error message, you should access
/// the property.
///
///
- /// And if the error is due to an exception, you can get it by accessing
+ /// If the error is due to an exception, you can get it by accessing
/// the property.
///
///
@@ -83,8 +84,13 @@ internal ErrorEventArgs (string message, Exception exception)
/// Gets the exception that caused the error.
///
///
- /// An instance that represents the cause of
- /// the error if it is due to an exception; otherwise, .
+ ///
+ /// An instance that represents
+ /// the cause of the error.
+ ///
+ ///
+ /// if not present.
+ ///
///
public Exception Exception {
get {
diff --git a/websocket-sharp/Ext.cs b/websocket-sharp/Ext.cs
index 00745ddc0..2540f803e 100644
--- a/websocket-sharp/Ext.cs
+++ b/websocket-sharp/Ext.cs
@@ -3,9 +3,9 @@
* Ext.cs
*
* Some parts of this code are derived from Mono (http://www.mono-project.com):
- * - GetStatusDescription is derived from HttpListenerResponse.cs (System.Net)
- * - 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-2025 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -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,53 +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 (bytes);
- buff.AddRange (Encoding.UTF8.GetBytes (reason));
+ var buff = new List (codeAsBytes);
+ var reasonAsBytes = Encoding.UTF8.GetBytes (reason);
- return buff.ToArray ();
- }
+ buff.AddRange (reasonAsBytes);
- internal static byte[] Compress (this byte[] data, CompressionMethod method)
- {
- return method == CompressionMethod.Deflate
- ? data.compress ()
- : data;
+ return buff.ToArray ();
}
- internal static Stream Compress (this Stream stream, CompressionMethod method)
+ internal static byte[] Compress (
+ this byte[] data,
+ CompressionMethod method
+ )
{
- return method == CompressionMethod.Deflate
- ? stream.compress ()
- : stream;
+ return method == CompressionMethod.Deflate ? data.compress () : data;
}
- internal static byte[] CompressToArray (this Stream stream, CompressionMethod method)
+ internal static Stream Compress (
+ 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
@@ -221,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;
@@ -235,6 +240,7 @@ StringComparison comparisonTypeForValue
)
{
var val = collection[name];
+
if (val == null)
return false;
@@ -247,7 +253,8 @@ StringComparison comparisonTypeForValue
}
internal static bool Contains (
- this IEnumerable source, Func condition
+ this IEnumerable source,
+ Func condition
)
{
foreach (T elm in source) {
@@ -269,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;
@@ -280,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
@@ -326,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 ();
@@ -334,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)
@@ -344,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)
@@ -352,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 ()
@@ -374,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)
@@ -384,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
{
@@ -394,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 ();
@@ -459,184 +454,135 @@ 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
+ internal static bool IsCompressionExtension (
+ this string value,
+ CompressionMethod method
)
{
- var ret = BitConverter.GetBytes (value);
+ var extStr = method.ToExtensionString ();
+ var compType = StringComparison.Ordinal;
- if (!order.IsHostOrder ())
- Array.Reverse (ret);
-
- return ret;
+ return value.StartsWith (extStr, compType);
}
- internal static byte[] InternalToByteArray (
- this ulong value, ByteOrder order
- )
+ internal static bool IsDefined (this CloseStatusCode code)
{
- var ret = BitConverter.GetBytes (value);
-
- if (!order.IsHostOrder ())
- Array.Reverse (ret);
-
- return ret;
+ return Enum.IsDefined (typeof (CloseStatusCode), code);
}
- internal static bool IsCompressionExtension (
- this string value, CompressionMethod method
+ internal static bool IsEqualTo (
+ this int value,
+ char c,
+ Action beforeComparing
)
{
- return value.StartsWith (method.ToExtensionString ());
- }
-
- internal static bool IsControl (this byte opcode)
- {
- return opcode > 0x7 && opcode < 0x10;
- }
-
- internal static bool IsControl (this Opcode opcode)
- {
- 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
@@ -644,15 +590,7 @@ internal static bool IsReserved (this ushort code)
|| code == 1015;
}
- internal static bool IsReserved (this CloseStatusCode code)
- {
- return code == CloseStatusCode.Undefined
- || code == CloseStatusCode.NoStatus
- || code == CloseStatusCode.Abnormal
- || code == CloseStatusCode.TlsHandshakeFailure;
- }
-
- internal static bool IsSupported (this byte opcode)
+ internal static bool IsSupportedOpcode (this int opcode)
{
return Enum.IsDefined (typeof (Opcode), opcode);
}
@@ -663,16 +601,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;
}
@@ -704,36 +645,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;
@@ -742,26 +702,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;
}
@@ -771,10 +734,12 @@ internal static byte[] ReadBytes (
retry = 0;
dest.Write (buff, 0, nread);
+
length -= nread;
}
dest.Close ();
+
return dest.ToArray ();
}
}
@@ -786,7 +751,8 @@ internal static void ReadBytesAsync (
Action error
)
{
- var buff = new byte[length];
+ var ret = new byte[length];
+
var offset = 0;
var retry = 0;
@@ -795,23 +761,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;
}
@@ -821,7 +789,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)
@@ -830,7 +798,7 @@ Action error
};
try {
- stream.BeginRead (buff, offset, length, callback, null);
+ stream.BeginRead (ret, offset, length, callback, null);
}
catch (Exception ex) {
if (error != null)
@@ -847,6 +815,7 @@ Action error
)
{
var dest = new MemoryStream ();
+
var buff = new byte[bufferLength];
var retry = 0;
@@ -863,9 +832,11 @@ Action error
ar => {
try {
var nread = stream.EndRead (ar);
+
if (nread <= 0) {
- if (retry < _retry) {
+ if (retry < _maxRetry) {
retry++;
+
read (len);
return;
@@ -873,10 +844,14 @@ Action error
if (completed != null) {
dest.Close ();
- completed (dest.ToArray ());
+
+ var ret = dest.ToArray ();
+
+ completed (ret);
}
dest.Dispose ();
+
return;
}
@@ -885,10 +860,14 @@ Action error
if (nread == len) {
if (completed != null) {
dest.Close ();
- completed (dest.ToArray ());
+
+ var ret = dest.ToArray ();
+
+ completed (ret);
}
dest.Dispose ();
+
return;
}
@@ -898,6 +877,7 @@ Action error
}
catch (Exception ex) {
dest.Dispose ();
+
if (error != null)
error (ex);
}
@@ -911,6 +891,7 @@ Action error
}
catch (Exception ex) {
dest.Dispose ();
+
if (error != null)
error (ex);
}
@@ -918,38 +899,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;
}
@@ -968,9 +954,11 @@ internal static IEnumerable SplitHeaderValue (
continue;
buff.Length -= 1;
+
yield return buff.ToString ();
buff.Length = 0;
+
continue;
}
}
@@ -980,40 +968,70 @@ 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 CompressionMethod ToCompressionMethod (this string value)
+ internal static byte[] ToByteArray (this ushort value, ByteOrder order)
{
- var methods = Enum.GetValues (typeof (CompressionMethod));
- foreach (CompressionMethod method in methods) {
- if (method.ToExtensionString () == value)
- return method;
- }
+ 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;
+ }
return CompressionMethod.None;
}
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;
+
+ var eparams = parameters.ToString ("; ");
- return parameters != null && parameters.Length > 0
- ? String.Format ("{0}; {1}", name, parameters.ToString ("; "))
- : name;
+ 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)
@@ -1022,11 +1040,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 {
@@ -1042,26 +1062,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 Version ToVersion (this string versionString)
+ {
+ return new Version (versionString);
}
- internal static IEnumerable Trim (this IEnumerable source)
+ internal static IEnumerable TrimEach (
+ this IEnumerable source
+ )
{
foreach (var elm in source)
yield return elm.Trim ();
@@ -1070,17 +1102,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;
@@ -1095,78 +1130,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;
@@ -1180,7 +1212,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;
@@ -1195,7 +1230,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;
@@ -1212,32 +1248,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)
@@ -1246,7 +1289,9 @@ internal static string UrlEncode (this string value, Encoding encoding)
}
internal static void WriteBytes (
- this Stream stream, byte[] bytes, int bufferLength
+ this Stream stream,
+ byte[] bytes,
+ int bufferLength
)
{
using (var src = new MemoryStream (bytes))
@@ -1262,6 +1307,7 @@ Action error
)
{
var src = new MemoryStream (bytes);
+
src.CopyToAsync (
stream,
bufferLength,
@@ -1273,6 +1319,7 @@ Action error
},
ex => {
src.Dispose ();
+
if (error != null)
error (ex);
}
@@ -1432,10 +1479,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;
}
///
@@ -1491,8 +1536,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;
@@ -1517,76 +1563,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.
@@ -1598,11 +1574,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.
@@ -1640,6 +1616,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");
@@ -1662,10 +1639,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;
}
///
@@ -1679,11 +1657,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.
@@ -1721,6 +1699,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");
@@ -1743,94 +1722,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);
-
- return subArray;
- }
-
- ///
- /// 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;
+ var ret = new T[length];
- if (action == null)
- return;
+ Array.Copy (array, startIndex, ret, 0, length);
- for (ulong i = 0; i < n; i++)
- action ();
+ return ret;
}
///
@@ -1885,195 +1781,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.
@@ -2148,19 +1855,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 ();
}
@@ -2180,62 +1886,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/Fin.cs b/websocket-sharp/Fin.cs
index 8965c378e..36622d7e5 100644
--- a/websocket-sharp/Fin.cs
+++ b/websocket-sharp/Fin.cs
@@ -4,7 +4,7 @@
*
* The MIT License
*
- * Copyright (c) 2012-2015 sta.blockhead
+ * Copyright (c) 2012-2025 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -35,9 +35,10 @@ namespace WebSocketSharp
///
///
/// The values of this enumeration are defined in
- /// Section 5.2 of RFC 6455.
+ ///
+ /// Section 5.2 of RFC 6455.
///
- internal enum Fin : byte
+ internal enum Fin
{
///
/// Equivalent to numeric value 0. Indicates more frames of a message follow.
diff --git a/websocket-sharp/HttpBase.cs b/websocket-sharp/HttpBase.cs
index 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..946024d6a 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.SortedList) {
+ 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..d511c94fe 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.SortedList) {
+ 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..bb3492a9b 100644
--- a/websocket-sharp/LogData.cs
+++ b/websocket-sharp/LogData.cs
@@ -4,7 +4,7 @@
*
* The MIT License
*
- * Copyright (c) 2013-2015 sta.blockhead
+ * Copyright (c) 2013-2024 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -53,6 +53,7 @@ internal LogData (LogLevel level, StackFrame caller, string message)
_level = level;
_caller = caller;
_message = message ?? String.Empty;
+
_date = DateTime.Now;
}
@@ -64,7 +65,8 @@ internal LogData (LogLevel level, StackFrame caller, string message)
/// Gets the information of the logging method caller.
///
///
- /// A that provides the information of the logging method caller.
+ /// A that provides the information of
+ /// the logging method caller.
///
public StackFrame Caller {
get {
@@ -76,7 +78,8 @@ public StackFrame Caller {
/// Gets the date and time when the log data was created.
///
///
- /// A that represents the date and time when the log data was created.
+ /// A that represents the date and time when
+ /// the log data was created.
///
public DateTime Date {
get {
@@ -88,7 +91,12 @@ public DateTime Date {
/// Gets the logging level of the log data.
///
///
- /// One of the enum values, indicates the logging level of the log data.
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It represents the logging level of the log data.
+ ///
///
public LogLevel Level {
get {
@@ -113,34 +121,36 @@ public string Message {
#region Public Methods
///
- /// Returns a that represents the current .
+ /// Returns a string that represents the current instance.
///
///
- /// A that represents the current .
+ /// A that represents the current instance.
///
public override string ToString ()
{
- var header = String.Format ("{0}|{1,-5}|", _date, _level);
+ var date = String.Format ("[{0}]", _date);
+ var level = String.Format ("{0,-5}", _level.ToString ().ToUpper ());
+
var method = _caller.GetMethod ();
var type = method.DeclaringType;
#if DEBUG
- var lineNum = _caller.GetFileLineNumber ();
- var headerAndCaller =
- String.Format ("{0}{1}.{2}:{3}|", header, type.Name, method.Name, lineNum);
+ var num = _caller.GetFileLineNumber ();
+ var caller = String.Format ("{0}.{1}:{2}", type.Name, method.Name, num);
#else
- var headerAndCaller = String.Format ("{0}{1}.{2}|", header, type.Name, method.Name);
+ var caller = String.Format ("{0}.{1}", type.Name, method.Name);
#endif
var msgs = _message.Replace ("\r\n", "\n").TrimEnd ('\n').Split ('\n');
+
if (msgs.Length <= 1)
- return String.Format ("{0}{1}", headerAndCaller, _message);
+ return String.Format ("{0} {1} {2} {3}", date, level, caller, _message);
+
+ var buff = new StringBuilder (64);
- var buff = new StringBuilder (String.Format ("{0}{1}\n", headerAndCaller, msgs[0]), 64);
+ buff.AppendFormat ("{0} {1} {2}\n\n", date, level, caller);
- var fmt = String.Format ("{{0,{0}}}{{1}}\n", header.Length);
- for (var i = 1; i < msgs.Length; i++)
- buff.AppendFormat (fmt, "", msgs[i]);
+ foreach (var msg in msgs)
+ buff.AppendFormat (" {0}\n", msg);
- buff.Length--;
return buff.ToString ();
}
diff --git a/websocket-sharp/LogLevel.cs b/websocket-sharp/LogLevel.cs
index ef9967728..5ff1d8fed 100644
--- a/websocket-sharp/LogLevel.cs
+++ b/websocket-sharp/LogLevel.cs
@@ -4,7 +4,7 @@
*
* The MIT License
*
- * Copyright (c) 2013-2015 sta.blockhead
+ * Copyright (c) 2013-2022 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -58,6 +58,10 @@ public enum LogLevel
///
/// Specifies the top logging level.
///
- Fatal
+ Fatal,
+ ///
+ /// Specifies not to output logs.
+ ///
+ None
}
}
diff --git a/websocket-sharp/Logger.cs b/websocket-sharp/Logger.cs
index 17850e67e..a280ce43f 100644
--- a/websocket-sharp/Logger.cs
+++ b/websocket-sharp/Logger.cs
@@ -4,7 +4,7 @@
*
* The MIT License
*
- * Copyright (c) 2013-2015 sta.blockhead
+ * Copyright (c) 2013-2024 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -37,17 +37,17 @@ namespace WebSocketSharp
///
///
///
- /// If you output a log with lower than the value of the property,
+ /// If you output a log with lower than the current logging level,
/// it cannot be outputted.
///
///
- /// The default output action writes a log to the standard output stream and the log file
- /// if the property has a valid path to it.
+ /// The default output method writes a log to the standard output
+ /// stream and the text file if it has a valid path.
///
///
- /// If you would like to use the custom output action, you should set
- /// the property to any Action<LogData, string>
- /// delegate.
+ /// If you would like to use the custom output method, you should
+ /// specify it with the constructor or the
+ /// property.
///
///
public class Logger
@@ -67,7 +67,7 @@ public class Logger
/// Initializes a new instance of the class.
///
///
- /// This constructor initializes the current logging level with .
+ /// This constructor initializes the logging level with the Error level.
///
public Logger ()
: this (LogLevel.Error, null, null)
@@ -76,10 +76,11 @@ public Logger ()
///
/// Initializes a new instance of the class with
- /// the specified logging .
+ /// the specified logging level.
///
///
- /// One of the enum values.
+ /// One of the enum values that specifies
+ /// the logging level.
///
public Logger (LogLevel level)
: this (level, null, null)
@@ -88,25 +89,26 @@ public Logger (LogLevel level)
///
/// Initializes a new instance of the class with
- /// the specified logging , path to the log ,
- /// and action.
+ /// the specified logging level, path to the log file, and delegate
+ /// used to output a log.
///
///
- /// One of the enum values.
+ /// One of the enum values that specifies
+ /// the logging level.
///
///
- /// A that represents the path to the log file.
+ /// A that specifies the path to the log file.
///
///
- /// An Action<LogData, string> delegate that references the method(s) used to
- /// output a log. A parameter passed to this delegate is
- /// .
+ /// An that specifies
+ /// the delegate used to output a log.
///
public Logger (LogLevel level, string file, Action output)
{
_level = level;
_file = file;
_output = output ?? defaultOutput;
+
_sync = new object ();
}
@@ -115,10 +117,10 @@ public Logger (LogLevel level, string file, Action output)
#region Public Properties
///
- /// Gets or sets the current path to the log file.
+ /// Gets or sets the path to the log file.
///
///
- /// A that represents the current path to the log file if any.
+ /// A that represents the path to the log file if any.
///
public string File {
get {
@@ -126,11 +128,8 @@ public string File {
}
set {
- lock (_sync) {
+ lock (_sync)
_file = value;
- Warn (
- String.Format ("The current path to the log file has been changed to {0}.", _file));
- }
}
}
@@ -141,7 +140,12 @@ public string File {
/// A log with lower than the value of this property cannot be outputted.
///
///
- /// One of the enum values, specifies the current logging level.
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It represents the current logging level.
+ ///
///
public LogLevel Level {
get {
@@ -149,25 +153,28 @@ public LogLevel Level {
}
set {
- lock (_sync) {
+ lock (_sync)
_level = value;
- Warn (String.Format ("The current logging level has been changed to {0}.", _level));
- }
}
}
///
- /// Gets or sets the current output action used to output a log.
+ /// Gets or sets the delegate used to output a log.
///
///
///
- /// An Action<LogData, string> delegate that references the method(s) used to
- /// output a log. A parameter passed to this delegate is the value of
+ /// An delegate.
+ ///
+ ///
+ /// It represents the delegate called when the logger outputs a log.
+ ///
+ ///
+ /// The string parameter passed to the delegate is the value of
/// the property.
///
///
- /// If the value to set is , the current output action is changed to
- /// the default output action.
+ /// If the value to set is , the default
+ /// output method is set.
///
///
public Action Output {
@@ -176,10 +183,8 @@ public Action Output {
}
set {
- lock (_sync) {
+ lock (_sync)
_output = value ?? defaultOutput;
- Warn ("The current output action has been changed.");
- }
}
}
@@ -189,10 +194,12 @@ public Action Output {
private static void defaultOutput (LogData data, string path)
{
- var log = data.ToString ();
- Console.WriteLine (log);
+ var val = data.ToString ();
+
+ Console.WriteLine (val);
+
if (path != null && path.Length > 0)
- writeToFile (log, path);
+ writeToFile (val, path);
}
private void output (string message, LogLevel level)
@@ -201,13 +208,18 @@ private void output (string message, LogLevel level)
if (_level > level)
return;
- LogData data = null;
try {
- data = new LogData (level, new StackFrame (2, true), message);
+ var data = new LogData (level, new StackFrame (2, true), message);
+
_output (data, _file);
}
catch (Exception ex) {
- data = new LogData (LogLevel.Fatal, new StackFrame (0, true), ex.Message);
+ var data = new LogData (
+ LogLevel.Fatal,
+ new StackFrame (0, true),
+ ex.Message
+ );
+
Console.WriteLine (data.ToString ());
}
}
@@ -225,14 +237,14 @@ private static void writeToFile (string value, string path)
#region Public Methods
///
- /// Outputs as a log with .
+ /// Outputs the specified message as a log with the Debug level.
///
///
- /// If the current logging level is higher than ,
- /// this method doesn't output as a log.
+ /// If the current logging level is higher than the Debug level,
+ /// this method does not output the message as a log.
///
///
- /// A that represents the message to output as a log.
+ /// A that specifies the message to output.
///
public void Debug (string message)
{
@@ -243,14 +255,14 @@ public void Debug (string message)
}
///
- /// Outputs as a log with .
+ /// Outputs the specified message as a log with the Error level.
///
///
- /// If the current logging level is higher than ,
- /// this method doesn't output as a log.
+ /// If the current logging level is higher than the Error level,
+ /// this method does not output the message as a log.
///
///
- /// A that represents the message to output as a log.
+ /// A that specifies the message to output.
///
public void Error (string message)
{
@@ -261,25 +273,28 @@ public void Error (string message)
}
///
- /// Outputs as a log with .
+ /// Outputs the specified message as a log with the Fatal level.
///
///
- /// A that represents the message to output as a log.
+ /// A that specifies the message to output.
///
public void Fatal (string message)
{
+ if (_level > LogLevel.Fatal)
+ return;
+
output (message, LogLevel.Fatal);
}
///
- /// Outputs as a log with .
+ /// Outputs the specified message as a log with the Info level.
///
///
- /// If the current logging level is higher than ,
- /// this method doesn't output as a log.
+ /// If the current logging level is higher than the Info level,
+ /// this method does not output the message as a log.
///
///
- /// A that represents the message to output as a log.
+ /// A that specifies the message to output.
///
public void Info (string message)
{
@@ -290,14 +305,14 @@ public void Info (string message)
}
///
- /// Outputs as a log with .
+ /// Outputs the specified message as a log with the Trace level.
///
///
- /// If the current logging level is higher than ,
- /// this method doesn't output as a log.
+ /// If the current logging level is higher than the Trace level,
+ /// this method does not output the message as a log.
///
///
- /// A that represents the message to output as a log.
+ /// A that specifies the message to output.
///
public void Trace (string message)
{
@@ -308,14 +323,14 @@ public void Trace (string message)
}
///
- /// Outputs as a log with .
+ /// Outputs the specified message as a log with the Warn level.
///
///
- /// If the current logging level is higher than ,
- /// this method doesn't output as a log.
+ /// If the current logging level is higher than the Warn level,
+ /// this method does not output the message as a log.
///
///
- /// A that represents the message to output as a log.
+ /// A that specifies the message to output.
///
public void Warn (string message)
{
diff --git a/websocket-sharp/Mask.cs b/websocket-sharp/Mask.cs
index fcafac80c..2958f5a83 100644
--- a/websocket-sharp/Mask.cs
+++ b/websocket-sharp/Mask.cs
@@ -4,7 +4,7 @@
*
* The MIT License
*
- * Copyright (c) 2012-2015 sta.blockhead
+ * Copyright (c) 2012-2025 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -35,9 +35,10 @@ namespace WebSocketSharp
///
///
/// The values of this enumeration are defined in
- /// Section 5.2 of RFC 6455.
+ ///
+ /// Section 5.2 of RFC 6455.
///
- internal enum Mask : byte
+ internal enum Mask
{
///
/// Equivalent to numeric value 0. Indicates not masked.
diff --git a/websocket-sharp/MessageEventArgs.cs b/websocket-sharp/MessageEventArgs.cs
index 7940f98b7..63add90f7 100644
--- a/websocket-sharp/MessageEventArgs.cs
+++ b/websocket-sharp/MessageEventArgs.cs
@@ -4,7 +4,7 @@
*
* The MIT License
*
- * Copyright (c) 2012-2016 sta.blockhead
+ * Copyright (c) 2012-2022 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -35,8 +35,8 @@ namespace WebSocketSharp
///
///
///
- /// That event occurs when the receives
- /// a message or a ping if the
+ /// The message event occurs when the interface
+ /// receives a message or a ping if the
/// property is set to true.
///
///
@@ -97,13 +97,19 @@ internal Opcode Opcode {
/// Gets the message data as a .
///
///
- /// A that represents the message data if its type is
- /// text or ping and if decoding it to a string has successfully done;
- /// otherwise, .
+ ///
+ /// A that represents the message data
+ /// if the message type is text or ping.
+ ///
+ ///
+ /// if the message type is binary or
+ /// the message data could not be UTF-8-decoded.
+ ///
///
public string Data {
get {
setData ();
+
return _data;
}
}
@@ -153,6 +159,7 @@ public bool IsText {
public byte[] RawData {
get {
setData ();
+
return _rawData;
}
}
@@ -168,10 +175,12 @@ private void setData ()
if (_opcode == Opcode.Binary) {
_dataSet = true;
+
return;
}
string data;
+
if (_rawData.TryGetUTF8DecodedString (out data))
_data = data;
diff --git a/websocket-sharp/Net/AuthenticationBase.cs b/websocket-sharp/Net/AuthenticationBase.cs
deleted file mode 100644
index 107750499..000000000
--- a/websocket-sharp/Net/AuthenticationBase.cs
+++ /dev/null
@@ -1,151 +0,0 @@
-#region License
-/*
- * AuthenticationBase.cs
- *
- * The MIT License
- *
- * Copyright (c) 2014 sta.blockhead
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-#endregion
-
-using System;
-using System.Collections.Specialized;
-using System.Text;
-
-namespace WebSocketSharp.Net
-{
- internal abstract class AuthenticationBase
- {
- #region Private Fields
-
- private AuthenticationSchemes _scheme;
-
- #endregion
-
- #region Internal Fields
-
- internal NameValueCollection Parameters;
-
- #endregion
-
- #region Protected Constructors
-
- protected AuthenticationBase (AuthenticationSchemes scheme, NameValueCollection parameters)
- {
- _scheme = scheme;
- Parameters = parameters;
- }
-
- #endregion
-
- #region Public Properties
-
- public string Algorithm {
- get {
- return Parameters["algorithm"];
- }
- }
-
- public string Nonce {
- get {
- return Parameters["nonce"];
- }
- }
-
- public string Opaque {
- get {
- return Parameters["opaque"];
- }
- }
-
- public string Qop {
- get {
- return Parameters["qop"];
- }
- }
-
- public string Realm {
- get {
- return Parameters["realm"];
- }
- }
-
- public AuthenticationSchemes Scheme {
- get {
- return _scheme;
- }
- }
-
- #endregion
-
- #region Internal Methods
-
- internal static string CreateNonceValue ()
- {
- var src = new byte[16];
- var rand = new Random ();
- rand.NextBytes (src);
-
- var res = new StringBuilder (32);
- foreach (var b in src)
- res.Append (b.ToString ("x2"));
-
- return res.ToString ();
- }
-
- internal static NameValueCollection ParseParameters (string value)
- {
- var res = new NameValueCollection ();
- foreach (var param in value.SplitHeaderValue (',')) {
- var i = param.IndexOf ('=');
- var name = i > 0 ? param.Substring (0, i).Trim () : null;
- var val = i < 0
- ? param.Trim ().Trim ('"')
- : i < param.Length - 1
- ? param.Substring (i + 1).Trim ().Trim ('"')
- : String.Empty;
-
- res.Add (name, val);
- }
-
- return res;
- }
-
- internal abstract string ToBasicString ();
-
- internal abstract string ToDigestString ();
-
- #endregion
-
- #region Public Methods
-
- public override string ToString ()
- {
- return _scheme == AuthenticationSchemes.Basic
- ? ToBasicString ()
- : _scheme == AuthenticationSchemes.Digest
- ? ToDigestString ()
- : String.Empty;
- }
-
- #endregion
- }
-}
diff --git a/websocket-sharp/Net/AuthenticationChallenge.cs b/websocket-sharp/Net/AuthenticationChallenge.cs
index 3472204b9..726740801 100644
--- a/websocket-sharp/Net/AuthenticationChallenge.cs
+++ b/websocket-sharp/Net/AuthenticationChallenge.cs
@@ -4,7 +4,7 @@
*
* The MIT License
*
- * Copyright (c) 2013-2014 sta.blockhead
+ * Copyright (c) 2013-2024 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -32,27 +32,52 @@
namespace WebSocketSharp.Net
{
- internal class AuthenticationChallenge : AuthenticationBase
+ internal class AuthenticationChallenge
{
+ #region Private Fields
+
+ private NameValueCollection _parameters;
+ private AuthenticationSchemes _scheme;
+
+ #endregion
+
#region Private Constructors
- private AuthenticationChallenge (AuthenticationSchemes scheme, NameValueCollection parameters)
- : base (scheme, parameters)
+ private AuthenticationChallenge (
+ AuthenticationSchemes scheme,
+ NameValueCollection parameters
+ )
{
+ _scheme = scheme;
+ _parameters = parameters;
}
#endregion
#region Internal Constructors
- internal AuthenticationChallenge (AuthenticationSchemes scheme, string realm)
- : base (scheme, new NameValueCollection ())
+ internal AuthenticationChallenge (
+ AuthenticationSchemes scheme,
+ string realm
+ )
+ : this (scheme, new NameValueCollection ())
{
- Parameters["realm"] = realm;
+ _parameters["realm"] = realm;
+
if (scheme == AuthenticationSchemes.Digest) {
- Parameters["nonce"] = CreateNonceValue ();
- Parameters["algorithm"] = "MD5";
- Parameters["qop"] = "auth";
+ _parameters["nonce"] = CreateNonceValue ();
+ _parameters["algorithm"] = "MD5";
+ _parameters["qop"] = "auth";
+ }
+ }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal NameValueCollection Parameters {
+ get {
+ return _parameters;
}
}
@@ -60,15 +85,51 @@ internal AuthenticationChallenge (AuthenticationSchemes scheme, string realm)
#region Public Properties
+ public string Algorithm {
+ get {
+ return _parameters["algorithm"];
+ }
+ }
+
public string Domain {
get {
- return Parameters["domain"];
+ return _parameters["domain"];
+ }
+ }
+
+ public string Nonce {
+ get {
+ return _parameters["nonce"];
+ }
+ }
+
+ public string Opaque {
+ get {
+ return _parameters["opaque"];
+ }
+ }
+
+ public string Qop {
+ get {
+ return _parameters["qop"];
+ }
+ }
+
+ public string Realm {
+ get {
+ return _parameters["realm"];
+ }
+ }
+
+ public AuthenticationSchemes Scheme {
+ get {
+ return _scheme;
}
}
public string Stale {
get {
- return Parameters["stale"];
+ return _parameters["stale"];
}
}
@@ -86,59 +147,132 @@ internal static AuthenticationChallenge CreateDigestChallenge (string realm)
return new AuthenticationChallenge (AuthenticationSchemes.Digest, realm);
}
+ internal static string CreateNonceValue ()
+ {
+ var rand = new Random ();
+ var bytes = new byte[16];
+
+ rand.NextBytes (bytes);
+
+ var buff = new StringBuilder (32);
+
+ foreach (var b in bytes)
+ buff.Append (b.ToString ("x2"));
+
+ return buff.ToString ();
+ }
+
internal static AuthenticationChallenge Parse (string value)
{
var chal = value.Split (new[] { ' ' }, 2);
+
if (chal.Length != 2)
return null;
var schm = chal[0].ToLower ();
- return schm == "basic"
- ? new AuthenticationChallenge (
- AuthenticationSchemes.Basic, ParseParameters (chal[1]))
- : schm == "digest"
- ? new AuthenticationChallenge (
- AuthenticationSchemes.Digest, ParseParameters (chal[1]))
- : null;
+
+ if (schm == "basic") {
+ var parameters = ParseParameters (chal[1]);
+
+ return new AuthenticationChallenge (
+ AuthenticationSchemes.Basic,
+ parameters
+ );
+ }
+
+ if (schm == "digest") {
+ var parameters = ParseParameters (chal[1]);
+
+ return new AuthenticationChallenge (
+ AuthenticationSchemes.Digest,
+ parameters
+ );
+ }
+
+ return null;
}
- internal override string ToBasicString ()
+ internal static NameValueCollection ParseParameters (string value)
{
- return String.Format ("Basic realm=\"{0}\"", Parameters["realm"]);
+ var ret = new NameValueCollection ();
+
+ foreach (var param in value.SplitHeaderValue (',')) {
+ var i = param.IndexOf ('=');
+
+ var name = i > 0 ? param.Substring (0, i).Trim () : null;
+ var val = i < 0
+ ? param.Trim ().Trim ('"')
+ : i < param.Length - 1
+ ? param.Substring (i + 1).Trim ().Trim ('"')
+ : String.Empty;
+
+ ret.Add (name, val);
+ }
+
+ return ret;
}
- internal override string ToDigestString ()
+ internal string ToBasicString ()
{
- var output = new StringBuilder (128);
+ return String.Format ("Basic realm=\"{0}\"", _parameters["realm"]);
+ }
+
+ internal string ToDigestString ()
+ {
+ var buff = new StringBuilder (128);
- var domain = Parameters["domain"];
- if (domain != null)
- output.AppendFormat (
+ var domain = _parameters["domain"];
+ var realm = _parameters["realm"];
+ var nonce = _parameters["nonce"];
+
+ if (domain != null) {
+ buff.AppendFormat (
"Digest realm=\"{0}\", domain=\"{1}\", nonce=\"{2}\"",
- Parameters["realm"],
+ realm,
domain,
- Parameters["nonce"]);
- else
- output.AppendFormat (
- "Digest realm=\"{0}\", nonce=\"{1}\"", Parameters["realm"], Parameters["nonce"]);
+ nonce
+ );
+ }
+ else {
+ buff.AppendFormat ("Digest realm=\"{0}\", nonce=\"{1}\"", realm, nonce);
+ }
+
+ var opaque = _parameters["opaque"];
- var opaque = Parameters["opaque"];
if (opaque != null)
- output.AppendFormat (", opaque=\"{0}\"", opaque);
+ buff.AppendFormat (", opaque=\"{0}\"", opaque);
+
+ var stale = _parameters["stale"];
- var stale = Parameters["stale"];
if (stale != null)
- output.AppendFormat (", stale={0}", stale);
+ buff.AppendFormat (", stale={0}", stale);
+
+ var algo = _parameters["algorithm"];
- var algo = Parameters["algorithm"];
if (algo != null)
- output.AppendFormat (", algorithm={0}", algo);
+ buff.AppendFormat (", algorithm={0}", algo);
+
+ var qop = _parameters["qop"];
- var qop = Parameters["qop"];
if (qop != null)
- output.AppendFormat (", qop=\"{0}\"", qop);
+ buff.AppendFormat (", qop=\"{0}\"", qop);
+
+ return buff.ToString ();
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public override string ToString ()
+ {
+ if (_scheme == AuthenticationSchemes.Basic)
+ return ToBasicString ();
+
+ if (_scheme == AuthenticationSchemes.Digest)
+ return ToDigestString ();
- return output.ToString ();
+ return String.Empty;
}
#endregion
diff --git a/websocket-sharp/Net/AuthenticationResponse.cs b/websocket-sharp/Net/AuthenticationResponse.cs
index 0257d85b2..28fbd2236 100644
--- a/websocket-sharp/Net/AuthenticationResponse.cs
+++ b/websocket-sharp/Net/AuthenticationResponse.cs
@@ -2,13 +2,13 @@
/*
* AuthenticationResponse.cs
*
- * ParseBasicCredentials is derived from System.Net.HttpListenerContext.cs of Mono
- * (http://www.mono-project.com).
+ * The ParseBasicCredentials method is derived from HttpListenerContext.cs
+ * (System.Net) of Mono (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
- * Copyright (c) 2013-2014 sta.blockhead
+ * Copyright (c) 2013-2024 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -38,19 +38,25 @@
namespace WebSocketSharp.Net
{
- internal class AuthenticationResponse : AuthenticationBase
+ internal class AuthenticationResponse
{
#region Private Fields
- private uint _nonceCount;
+ private uint _nonceCount;
+ private NameValueCollection _parameters;
+ private AuthenticationSchemes _scheme;
#endregion
#region Private Constructors
- private AuthenticationResponse (AuthenticationSchemes scheme, NameValueCollection parameters)
- : base (scheme, parameters)
+ private AuthenticationResponse (
+ AuthenticationSchemes scheme,
+ NameValueCollection parameters
+ )
{
+ _scheme = scheme;
+ _parameters = parameters;
}
#endregion
@@ -58,12 +64,20 @@ private AuthenticationResponse (AuthenticationSchemes scheme, NameValueCollectio
#region Internal Constructors
internal AuthenticationResponse (NetworkCredential credentials)
- : this (AuthenticationSchemes.Basic, new NameValueCollection (), credentials, 0)
+ : this (
+ AuthenticationSchemes.Basic,
+ new NameValueCollection (),
+ credentials,
+ 0
+ )
{
}
internal AuthenticationResponse (
- AuthenticationChallenge challenge, NetworkCredential credentials, uint nonceCount)
+ AuthenticationChallenge challenge,
+ NetworkCredential credentials,
+ uint nonceCount
+ )
: this (challenge.Scheme, challenge.Parameters, credentials, nonceCount)
{
}
@@ -72,13 +86,15 @@ internal AuthenticationResponse (
AuthenticationSchemes scheme,
NameValueCollection parameters,
NetworkCredential credentials,
- uint nonceCount)
- : base (scheme, parameters)
+ uint nonceCount
+ )
+ : this (scheme, parameters)
{
- Parameters["username"] = credentials.Username;
- Parameters["password"] = credentials.Password;
- Parameters["uri"] = credentials.Domain;
+ _parameters["username"] = credentials.Username;
+ _parameters["password"] = credentials.Password;
+ _parameters["uri"] = credentials.Domain;
_nonceCount = nonceCount;
+
if (scheme == AuthenticationSchemes.Digest)
initAsDigest ();
}
@@ -89,9 +105,13 @@ internal AuthenticationResponse (
internal uint NonceCount {
get {
- return _nonceCount < UInt32.MaxValue
- ? _nonceCount
- : 0;
+ return _nonceCount < UInt32.MaxValue ? _nonceCount : 0;
+ }
+ }
+
+ internal NameValueCollection Parameters {
+ get {
+ return _parameters;
}
}
@@ -99,39 +119,75 @@ internal uint NonceCount {
#region Public Properties
+ public string Algorithm {
+ get {
+ return _parameters["algorithm"];
+ }
+ }
+
public string Cnonce {
get {
- return Parameters["cnonce"];
+ return _parameters["cnonce"];
}
}
public string Nc {
get {
- return Parameters["nc"];
+ return _parameters["nc"];
+ }
+ }
+
+ public string Nonce {
+ get {
+ return _parameters["nonce"];
+ }
+ }
+
+ public string Opaque {
+ get {
+ return _parameters["opaque"];
}
}
public string Password {
get {
- return Parameters["password"];
+ return _parameters["password"];
+ }
+ }
+
+ public string Qop {
+ get {
+ return _parameters["qop"];
+ }
+ }
+
+ public string Realm {
+ get {
+ return _parameters["realm"];
}
}
public string Response {
get {
- return Parameters["response"];
+ return _parameters["response"];
+ }
+ }
+
+ public AuthenticationSchemes Scheme {
+ get {
+ return _scheme;
}
}
public string Uri {
get {
- return Parameters["uri"];
+ return _parameters["uri"];
}
}
public string UserName {
get {
- return Parameters["username"];
+ return _parameters["username"];
}
}
@@ -139,16 +195,26 @@ public string UserName {
#region Private Methods
- private static string createA1 (string username, string password, string realm)
+ private static string createA1 (
+ string username,
+ string password,
+ string realm
+ )
{
return String.Format ("{0}:{1}:{2}", username, realm, password);
}
private static string createA1 (
- string username, string password, string realm, string nonce, string cnonce)
+ string username,
+ string password,
+ string realm,
+ string nonce,
+ string cnonce
+ )
{
- return String.Format (
- "{0}:{1}:{2}", hash (createA1 (username, password, realm)), nonce, cnonce);
+ var a1 = createA1 (username, password, realm);
+
+ return String.Format ("{0}:{1}:{2}", hash (a1), nonce, cnonce);
}
private static string createA2 (string method, string uri)
@@ -163,33 +229,39 @@ private static string createA2 (string method, string uri, string entity)
private static string hash (string value)
{
- var src = Encoding.UTF8.GetBytes (value);
+ var buff = new StringBuilder (64);
+
var md5 = MD5.Create ();
- var hashed = md5.ComputeHash (src);
+ var bytes = Encoding.UTF8.GetBytes (value);
+ var res = md5.ComputeHash (bytes);
- var res = new StringBuilder (64);
- foreach (var b in hashed)
- res.Append (b.ToString ("x2"));
+ foreach (var b in res)
+ buff.Append (b.ToString ("x2"));
- return res.ToString ();
+ return buff.ToString ();
}
private void initAsDigest ()
{
- var qops = Parameters["qop"];
+ var qops = _parameters["qop"];
+
if (qops != null) {
- if (qops.Split (',').Contains (qop => qop.Trim ().ToLower () == "auth")) {
- Parameters["qop"] = "auth";
- Parameters["cnonce"] = CreateNonceValue ();
- Parameters["nc"] = String.Format ("{0:x8}", ++_nonceCount);
+ var hasAuth = qops.Split (',').Contains (
+ qop => qop.Trim ().ToLower () == "auth"
+ );
+
+ if (hasAuth) {
+ _parameters["qop"] = "auth";
+ _parameters["cnonce"] = AuthenticationChallenge.CreateNonceValue ();
+ _parameters["nc"] = String.Format ("{0:x8}", ++_nonceCount);
}
else {
- Parameters["qop"] = null;
+ _parameters["qop"] = null;
}
}
- Parameters["method"] = "GET";
- Parameters["response"] = CreateRequestDigest (Parameters);
+ _parameters["method"] = "GET";
+ _parameters["response"] = CreateRequestDigest (_parameters);
}
#endregion
@@ -198,8 +270,8 @@ private void initAsDigest ()
internal static string CreateRequestDigest (NameValueCollection parameters)
{
- var user = parameters["username"];
- var pass = parameters["password"];
+ var uname = parameters["username"];
+ var passwd = parameters["password"];
var realm = parameters["realm"];
var nonce = parameters["nonce"];
var uri = parameters["uri"];
@@ -210,8 +282,8 @@ internal static string CreateRequestDigest (NameValueCollection parameters)
var method = parameters["method"];
var a1 = algo != null && algo.ToLower () == "md5-sess"
- ? createA1 (user, pass, realm, nonce, cnonce)
- : createA1 (user, pass, realm);
+ ? createA1 (uname, passwd, realm, nonce, cnonce)
+ : createA1 (uname, passwd, realm);
var a2 = qop != null && qop.ToLower () == "auth-int"
? createA2 (method, uri, parameters["entity"])
@@ -219,89 +291,142 @@ internal static string CreateRequestDigest (NameValueCollection parameters)
var secret = hash (a1);
var data = qop != null
- ? String.Format ("{0}:{1}:{2}:{3}:{4}", nonce, nc, cnonce, qop, hash (a2))
+ ? String.Format (
+ "{0}:{1}:{2}:{3}:{4}",
+ nonce,
+ nc,
+ cnonce,
+ qop,
+ hash (a2)
+ )
: String.Format ("{0}:{1}", nonce, hash (a2));
- return hash (String.Format ("{0}:{1}", secret, data));
+ var keyed = String.Format ("{0}:{1}", secret, data);
+
+ return hash (keyed);
}
internal static AuthenticationResponse Parse (string value)
{
try {
var cred = value.Split (new[] { ' ' }, 2);
+
if (cred.Length != 2)
return null;
var schm = cred[0].ToLower ();
- return schm == "basic"
- ? new AuthenticationResponse (
- AuthenticationSchemes.Basic, ParseBasicCredentials (cred[1]))
- : schm == "digest"
- ? new AuthenticationResponse (
- AuthenticationSchemes.Digest, ParseParameters (cred[1]))
- : null;
+
+ if (schm == "basic") {
+ var parameters = ParseBasicCredentials (cred[1]);
+
+ return new AuthenticationResponse (
+ AuthenticationSchemes.Basic,
+ parameters
+ );
+ }
+
+ if (schm == "digest") {
+ var parameters = AuthenticationChallenge.ParseParameters (cred[1]);
+
+ return new AuthenticationResponse (
+ AuthenticationSchemes.Digest,
+ parameters
+ );
+ }
+
+ return null;
}
catch {
+ return null;
}
-
- return null;
}
internal static NameValueCollection ParseBasicCredentials (string value)
{
+ var ret = new NameValueCollection ();
+
// Decode the basic-credentials (a Base64 encoded string).
- var userPass = Encoding.Default.GetString (Convert.FromBase64String (value));
+
+ var bytes = Convert.FromBase64String (value);
+ var userPass = Encoding.UTF8.GetString (bytes);
// The format is [\]:.
- var i = userPass.IndexOf (':');
- var user = userPass.Substring (0, i);
- var pass = i < userPass.Length - 1 ? userPass.Substring (i + 1) : String.Empty;
- // Check if 'domain' exists.
- i = user.IndexOf ('\\');
- if (i > -1)
- user = user.Substring (i + 1);
+ var idx = userPass.IndexOf (':');
+ var uname = userPass.Substring (0, idx);
+ var passwd = idx < userPass.Length - 1
+ ? userPass.Substring (idx + 1)
+ : String.Empty;
- var res = new NameValueCollection ();
- res["username"] = user;
- res["password"] = pass;
+ // Check if exists.
- return res;
+ idx = uname.IndexOf ('\\');
+
+ if (idx > -1)
+ uname = uname.Substring (idx + 1);
+
+ ret["username"] = uname;
+ ret["password"] = passwd;
+
+ return ret;
}
- internal override string ToBasicString ()
+ internal string ToBasicString ()
{
- var userPass = String.Format ("{0}:{1}", Parameters["username"], Parameters["password"]);
- var cred = Convert.ToBase64String (Encoding.UTF8.GetBytes (userPass));
+ var uname = _parameters["username"];
+ var passwd = _parameters["password"];
+ var userPass = String.Format ("{0}:{1}", uname, passwd);
+
+ var bytes = Encoding.UTF8.GetBytes (userPass);
+ var cred = Convert.ToBase64String (bytes);
return "Basic " + cred;
}
- internal override string ToDigestString ()
+ internal string ToDigestString ()
{
- var output = new StringBuilder (256);
- output.AppendFormat (
+ var buff = new StringBuilder (256);
+
+ var uname = _parameters["username"];
+ var realm = _parameters["realm"];
+ var nonce = _parameters["nonce"];
+ var uri = _parameters["uri"];
+ var res = _parameters["response"];
+
+ buff.AppendFormat (
"Digest username=\"{0}\", realm=\"{1}\", nonce=\"{2}\", uri=\"{3}\", response=\"{4}\"",
- Parameters["username"],
- Parameters["realm"],
- Parameters["nonce"],
- Parameters["uri"],
- Parameters["response"]);
+ uname,
+ realm,
+ nonce,
+ uri,
+ res
+ );
+
+ var opaque = _parameters["opaque"];
- var opaque = Parameters["opaque"];
if (opaque != null)
- output.AppendFormat (", opaque=\"{0}\"", opaque);
+ buff.AppendFormat (", opaque=\"{0}\"", opaque);
+
+ var algo = _parameters["algorithm"];
- var algo = Parameters["algorithm"];
if (algo != null)
- output.AppendFormat (", algorithm={0}", algo);
+ buff.AppendFormat (", algorithm={0}", algo);
+
+ var qop = _parameters["qop"];
- var qop = Parameters["qop"];
- if (qop != null)
- output.AppendFormat (
- ", qop={0}, cnonce=\"{1}\", nc={2}", qop, Parameters["cnonce"], Parameters["nc"]);
+ if (qop != null) {
+ var cnonce = _parameters["cnonce"];
+ var nc = _parameters["nc"];
- return output.ToString ();
+ buff.AppendFormat (
+ ", qop={0}, cnonce=\"{1}\", nc={2}",
+ qop,
+ cnonce,
+ nc
+ );
+ }
+
+ return buff.ToString ();
}
#endregion
@@ -310,12 +435,28 @@ internal override string ToDigestString ()
public IIdentity ToIdentity ()
{
- var schm = Scheme;
- return schm == AuthenticationSchemes.Basic
- ? new HttpBasicIdentity (Parameters["username"], Parameters["password"]) as IIdentity
- : schm == AuthenticationSchemes.Digest
- ? new HttpDigestIdentity (Parameters)
- : null;
+ if (_scheme == AuthenticationSchemes.Basic) {
+ var uname = _parameters["username"];
+ var passwd = _parameters["password"];
+
+ return new HttpBasicIdentity (uname, passwd);
+ }
+
+ if (_scheme == AuthenticationSchemes.Digest)
+ return new HttpDigestIdentity (_parameters);
+
+ return null;
+ }
+
+ public override string ToString ()
+ {
+ if (_scheme == AuthenticationSchemes.Basic)
+ return ToBasicString ();
+
+ if (_scheme == AuthenticationSchemes.Digest)
+ return ToDigestString ();
+
+ return String.Empty;
}
#endregion
diff --git a/websocket-sharp/Net/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 0eb366a5e..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,32 @@ 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.ErrorMessage = ex.Message;
+ _context.ErrorMessage = "I/O operation aborted";
+
_context.SendError ();
ares.Complete (ex);
@@ -117,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;
@@ -166,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;
@@ -177,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;
}
@@ -205,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 7816e0301..33438a93c 100644
--- a/websocket-sharp/Net/ClientSslConfiguration.cs
+++ b/websocket-sharp/Net/ClientSslConfiguration.cs
@@ -5,7 +5,7 @@
* The MIT License
*
* Copyright (c) 2014 liryna
- * Copyright (c) 2014-2020 sta.blockhead
+ * Copyright (c) 2014-2024 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -42,7 +42,8 @@
namespace WebSocketSharp.Net
{
///
- /// Stores the parameters for the used by clients.
+ /// Stores the parameters for an instance used by
+ /// a client.
///
public class ClientSslConfiguration
{
@@ -61,17 +62,18 @@ public class ClientSslConfiguration
///
/// Initializes a new instance of the
- /// class with the specified target host server name.
+ /// class with the specified target host name.
///
///
- /// A that specifies the target host server name.
+ /// A that specifies the name of the server that
+ /// will share a secure connection with the client.
///
- ///
- /// is .
- ///
///
/// is an empty string.
///
+ ///
+ /// is .
+ ///
public ClientSslConfiguration (string targetHost)
{
if (targetHost == null)
@@ -87,7 +89,7 @@ public ClientSslConfiguration (string targetHost)
///
/// Initializes a new instance of the
- /// class that stores the parameters copied from the specified configuration.
+ /// class copying from the specified configuration.
///
///
/// A from which to copy.
@@ -136,15 +138,16 @@ public bool CheckCertificateRevocation {
}
///
- /// Gets or sets the collection of client certificates from which to select
+ /// Gets or sets the collection of the certificates from which to select
/// one to supply to the server.
///
///
///
- /// A or .
+ /// A that contains
+ /// the certificates from which to select.
///
///
- /// The collection contains client certificates from which to select.
+ /// if not present.
///
///
/// The default value is .
@@ -169,12 +172,15 @@ public X509CertificateCollection ClientCertificates {
///
///
///
- /// A delegate that
- /// invokes the method called for selecting the certificate.
+ /// A delegate.
+ ///
+ ///
+ /// It represents the delegate called when the client selects
+ /// the certificate.
///
///
- /// The default value is a delegate that invokes a method that only
- /// returns .
+ /// The default value invokes a method that only returns
+ /// .
///
///
public LocalCertificateSelectionCallback ClientCertificateSelectionCallback {
@@ -191,14 +197,14 @@ public LocalCertificateSelectionCallback ClientCertificateSelectionCallback {
}
///
- /// Gets or sets the protocols used for authentication.
+ /// Gets or sets the enabled versions of the SSL/TLS protocols.
///
///
///
/// Any of the enum values.
///
///
- /// It represents the protocols used for authentication.
+ /// It represents the enabled versions of the SSL/TLS protocols.
///
///
/// The default value is .
@@ -223,12 +229,14 @@ public SslProtocols EnabledSslProtocols {
///
///
///
- /// A delegate that
- /// invokes the method called for validating the certificate.
+ /// A delegate.
///
///
- /// The default value is a delegate that invokes a method that only
- /// returns true.
+ /// It represents the delegate called when the client validates
+ /// the certificate.
+ ///
+ ///
+ /// The default value invokes a method that only returns true.
///
///
public RemoteCertificateValidationCallback ServerCertificateValidationCallback {
@@ -245,18 +253,18 @@ public RemoteCertificateValidationCallback ServerCertificateValidationCallback {
}
///
- /// Gets or sets the target host server name.
+ /// Gets or sets the target host name.
///
///
/// A that represents the name of the server that
- /// will share a secure connection with a client.
+ /// will share a secure connection with the client.
///
- ///
- /// The value specified for a set operation is .
- ///
///
/// The value specified for a set operation is an empty string.
///
+ ///
+ /// The value specified for a set operation is .
+ ///
public string TargetHost {
get {
return _targetHost;
diff --git a/websocket-sharp/Net/Cookie.cs b/websocket-sharp/Net/Cookie.cs
index 1c5a4bf2d..149b5041e 100644
--- a/websocket-sharp/Net/Cookie.cs
+++ b/websocket-sharp/Net/Cookie.cs
@@ -8,7 +8,7 @@
* The MIT License
*
* Copyright (c) 2004,2009 Novell, Inc. (http://www.novell.com)
- * Copyright (c) 2012-2019 sta.blockhead
+ * Copyright (c) 2012-2024 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -56,7 +56,8 @@ namespace WebSocketSharp.Net
///
/// -
///
- /// Netscape specification
+ ///
+ /// Netscape specification
///
///
/// -
@@ -116,9 +117,6 @@ static Cookie ()
#region Internal Constructors
- ///
- /// Initializes a new instance of the class.
- ///
internal Cookie ()
{
init (String.Empty, String.Empty, String.Empty, String.Empty);
@@ -145,33 +143,33 @@ internal Cookie ()
///
/// A that specifies the value of the cookie.
///
- ///
- /// is .
- ///
///
///
/// is an empty string.
///
///
- /// - or -
+ /// -or-
///
///
/// starts with a dollar sign.
///
///
- /// - or -
+ /// -or-
///
///
/// contains an invalid character.
///
///
- /// - or -
+ /// -or-
///
///
/// is a string not enclosed in double quotes
- /// that contains an invalid character.
+ /// although it contains a reserved character.
///
///
+ ///
+ /// is .
+ ///
public Cookie (string name, string value)
: this (name, value, String.Empty, String.Empty)
{
@@ -198,33 +196,33 @@ public Cookie (string name, string value)
/// A that specifies the value of the Path
/// attribute of the cookie.
///
- ///
- /// is .
- ///
///
///
/// is an empty string.
///
///
- /// - or -
+ /// -or-
///
///
/// starts with a dollar sign.
///
///
- /// - or -
+ /// -or-
///
///
/// contains an invalid character.
///
///
- /// - or -
+ /// -or-
///
///
/// is a string not enclosed in double quotes
- /// that contains an invalid character.
+ /// although it contains a reserved character.
///
///
+ ///
+ /// is .
+ ///
public Cookie (string name, string value, string path)
: this (name, value, path, String.Empty)
{
@@ -255,33 +253,33 @@ public Cookie (string name, string value, string path)
/// A that specifies the value of the Domain
/// attribute of the cookie.
///
- ///
- /// is .
- ///
///
///
/// is an empty string.
///
///
- /// - or -
+ /// -or-
///
///
/// starts with a dollar sign.
///
///
- /// - or -
+ /// -or-
///
///
/// contains an invalid character.
///
///
- /// - or -
+ /// -or-
///
///
/// is a string not enclosed in double quotes
- /// that contains an invalid character.
+ /// although it contains a reserved character.
///
///
+ ///
+ /// is .
+ ///
public Cookie (string name, string value, string path, string domain)
{
if (name == null)
@@ -292,11 +290,13 @@ public Cookie (string name, string value, string path, string domain)
if (name[0] == '$') {
var msg = "It starts with a dollar sign.";
+
throw new ArgumentException (msg, "name");
}
if (!name.IsToken ()) {
var msg = "It contains an invalid character.";
+
throw new ArgumentException (msg, "name");
}
@@ -306,6 +306,7 @@ public Cookie (string name, string value, string path, string domain)
if (value.Contains (_reservedCharsForValue)) {
if (!value.IsEnclosedIn ('"')) {
var msg = "A string not enclosed in double quotes.";
+
throw new ArgumentException (msg, "value");
}
}
@@ -333,6 +334,7 @@ internal int MaxAge {
: _expires;
var span = expires - DateTime.Now;
+
return span > TimeSpan.Zero
? (int) span.TotalSeconds
: 0;
@@ -447,7 +449,7 @@ internal set {
/// the cookie is valid for.
///
///
- /// An empty string if this attribute is not needed.
+ /// An empty string if not necessary.
///
///
public string Domain {
@@ -490,7 +492,7 @@ public bool Expired {
/// the cookie expires on.
///
///
- /// if this attribute is not needed.
+ /// if not necessary.
///
///
/// The default value is .
@@ -542,26 +544,26 @@ public bool HttpOnly {
/// RFC 2616.
///
///
- ///
- /// The value specified for a set operation is .
- ///
///
///
/// The value specified for a set operation is an empty string.
///
///
- /// - or -
+ /// -or-
///
///
/// The value specified for a set operation starts with a dollar sign.
///
///
- /// - or -
+ /// -or-
///
///
/// The value specified for a set operation contains an invalid character.
///
///
+ ///
+ /// The value specified for a set operation is .
+ ///
public string Name {
get {
return _name;
@@ -576,11 +578,13 @@ public string Name {
if (value[0] == '$') {
var msg = "It starts with a dollar sign.";
+
throw new ArgumentException (msg, "value");
}
if (!value.IsToken ()) {
var msg = "It contains an invalid character.";
+
throw new ArgumentException (msg, "value");
}
@@ -627,11 +631,12 @@ public string Port {
internal set {
int[] ports;
+
if (!tryCreatePorts (value, out ports))
return;
- _port = value;
_ports = ports;
+ _port = value;
}
}
@@ -683,7 +688,7 @@ public DateTime TimeStamp {
///
///
/// The value specified for a set operation is a string not enclosed in
- /// double quotes that contains an invalid character.
+ /// double quotes although it contains a reserved character.
///
public string Value {
get {
@@ -697,6 +702,7 @@ public string Value {
if (value.Contains (_reservedCharsForValue)) {
if (!value.IsEnclosedIn ('"')) {
var msg = "A string not enclosed in double quotes.";
+
throw new ArgumentException (msg, "value");
}
}
@@ -714,7 +720,10 @@ public string Value {
/// management that the cookie conforms to.
///
///
- /// 0 or 1. 0 if not present.
+ /// 0 or 1.
+ ///
+ ///
+ /// 0 if not present.
///
///
/// The default value is 0.
@@ -764,13 +773,14 @@ private string toResponseStringVersion0 ()
buff.AppendFormat ("{0}={1}", _name, _value);
if (_expires != DateTime.MinValue) {
- buff.AppendFormat (
- "; Expires={0}",
- _expires.ToUniversalTime ().ToString (
- "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'",
- CultureInfo.CreateSpecificCulture ("en-US")
- )
- );
+ var expires = _expires
+ .ToUniversalTime ()
+ .ToString (
+ "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'",
+ CultureInfo.CreateSpecificCulture ("en-US")
+ );
+
+ buff.AppendFormat ("; Expires={0}", expires);
}
if (!_path.IsNullOrEmpty ())
@@ -813,13 +823,18 @@ private string toResponseStringVersion1 ()
buff.Append ("; Port");
}
- if (_comment != null)
- buff.AppendFormat ("; Comment={0}", HttpUtility.UrlEncode (_comment));
+ if (_comment != null) {
+ var comment = HttpUtility.UrlEncode (_comment);
+
+ buff.AppendFormat ("; Comment={0}", comment);
+ }
if (_commentUri != null) {
var url = _commentUri.OriginalString;
+
buff.AppendFormat (
- "; CommentURL={0}", !url.IsToken () ? url.Quote () : url
+ "; CommentURL={0}",
+ !url.IsToken () ? url.Quote () : url
);
}
@@ -842,8 +857,10 @@ private static bool tryCreatePorts (string value, out int[] result)
for (var i = 0; i < len; i++) {
var s = arr[i].Trim ();
+
if (s.Length == 0) {
res[i] = Int32.MinValue;
+
continue;
}
@@ -852,6 +869,7 @@ private static bool tryCreatePorts (string value, out int[] result)
}
result = res;
+
return true;
}
@@ -914,24 +932,21 @@ internal string ToRequestString (Uri uri)
return buff.ToString ();
}
- ///
- /// Returns a string that represents the current cookie instance.
- ///
- ///
- /// A that is suitable for the Set-Cookie response
- /// header.
- ///
internal string ToResponseString ()
{
- return _name.Length == 0
- ? String.Empty
- : _version == 0
- ? toResponseStringVersion0 ()
- : toResponseStringVersion1 ();
+ if (_name.Length == 0)
+ return String.Empty;
+
+ if (_version == 0)
+ return toResponseStringVersion0 ();
+
+ return toResponseStringVersion1 ();
}
internal static bool TryCreate (
- string name, string value, out Cookie result
+ string name,
+ string value,
+ out Cookie result
)
{
result = null;
@@ -970,6 +985,7 @@ internal static bool TryCreate (
public override bool Equals (object comparand)
{
var cookie = comparand as Cookie;
+
if (cookie == null)
return false;
@@ -991,13 +1007,13 @@ public override bool Equals (object comparand)
///
public override int GetHashCode ()
{
- return hash (
- StringComparer.InvariantCultureIgnoreCase.GetHashCode (_name),
- _value.GetHashCode (),
- _path.GetHashCode (),
- StringComparer.InvariantCultureIgnoreCase.GetHashCode (_domain),
- _version
- );
+ var i = StringComparer.InvariantCultureIgnoreCase.GetHashCode (_name);
+ var j = _value.GetHashCode ();
+ var k = _path.GetHashCode ();
+ var l = StringComparer.InvariantCultureIgnoreCase.GetHashCode (_domain);
+ var m = _version;
+
+ return hash (i, j, k, l, m);
}
///
diff --git a/websocket-sharp/Net/CookieCollection.cs b/websocket-sharp/Net/CookieCollection.cs
index 8c0322bda..02e065580 100644
--- a/websocket-sharp/Net/CookieCollection.cs
+++ b/websocket-sharp/Net/CookieCollection.cs
@@ -8,7 +8,7 @@
* The MIT License
*
* Copyright (c) 2004,2009 Novell, Inc. (http://www.novell.com)
- * Copyright (c) 2012-2019 sta.blockhead
+ * Copyright (c) 2012-2025 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -57,7 +57,7 @@ public class CookieCollection : ICollection
private List _list;
private bool _readOnly;
- private object _sync;
+ private object _syncRoot;
#endregion
@@ -69,7 +69,7 @@ public class CookieCollection : ICollection
public CookieCollection ()
{
_list = new List ();
- _sync = ((ICollection) _list).SyncRoot;
+ _syncRoot = ((ICollection) _list).SyncRoot;
}
#endregion
@@ -82,11 +82,22 @@ internal IList List {
}
}
- internal IEnumerable Sorted {
+ internal bool ReadOnly {
+ get {
+ return _readOnly;
+ }
+
+ set {
+ _readOnly = value;
+ }
+ }
+
+ internal ICollection SortedList {
get {
var list = new List (_list);
+
if (list.Count > 1)
- list.Sort (compareForSorted);
+ list.Sort (compareForSortedList);
return list;
}
@@ -124,10 +135,6 @@ public bool IsReadOnly {
get {
return _readOnly;
}
-
- internal set {
- _readOnly = value;
- }
}
///
@@ -195,7 +202,7 @@ public Cookie this[string name] {
var caseInsensitive = StringComparison.InvariantCultureIgnoreCase;
- foreach (var cookie in Sorted) {
+ foreach (var cookie in SortedList) {
if (cookie.Name.Equals (name, caseInsensitive))
return cookie;
}
@@ -212,7 +219,7 @@ public Cookie this[string name] {
///
public object SyncRoot {
get {
- return _sync;
+ return _syncRoot;
}
}
@@ -223,8 +230,10 @@ public object SyncRoot {
private void add (Cookie cookie)
{
var idx = search (cookie);
+
if (idx == -1) {
_list.Add (cookie);
+
return;
}
@@ -237,14 +246,19 @@ private static int compareForSort (Cookie x, Cookie y)
- (y.Name.Length + y.Value.Length);
}
- private static int compareForSorted (Cookie x, Cookie y)
+ private static int compareForSortedList (Cookie x, Cookie y)
{
var ret = x.Version - y.Version;
- return ret != 0
- ? ret
- : (ret = x.Name.CompareTo (y.Name)) != 0
- ? ret
- : y.Path.Length - x.Path.Length;
+
+ if (ret != 0)
+ return ret;
+
+ ret = x.Name.CompareTo (y.Name);
+
+ if (ret != 0)
+ return ret;
+
+ return y.Path.Length - x.Path.Length;
}
private static CookieCollection parseRequest (string value)
@@ -253,22 +267,25 @@ private static CookieCollection parseRequest (string value)
Cookie cookie = null;
var ver = 0;
-
var caseInsensitive = StringComparison.InvariantCultureIgnoreCase;
+
var pairs = value.SplitHeaderValue (',', ';').ToList ();
for (var i = 0; i < pairs.Count; i++) {
var pair = pairs[i].Trim ();
+
if (pair.Length == 0)
continue;
var idx = pair.IndexOf ('=');
+
if (idx == -1) {
if (cookie == null)
continue;
if (pair.Equals ("$port", caseInsensitive)) {
cookie.Port = "\"\"";
+
continue;
}
@@ -278,6 +295,7 @@ private static CookieCollection parseRequest (string value)
if (idx == 0) {
if (cookie != null) {
ret.add (cookie);
+
cookie = null;
}
@@ -293,11 +311,15 @@ private static CookieCollection parseRequest (string value)
if (val.Length == 0)
continue;
+ var s = val.Unquote ();
+
int num;
- if (!Int32.TryParse (val.Unquote (), out num))
+
+ if (!Int32.TryParse (s, out num))
continue;
ver = num;
+
continue;
}
@@ -309,6 +331,7 @@ private static CookieCollection parseRequest (string value)
continue;
cookie.Path = val;
+
continue;
}
@@ -320,6 +343,7 @@ private static CookieCollection parseRequest (string value)
continue;
cookie.Domain = val;
+
continue;
}
@@ -331,6 +355,7 @@ private static CookieCollection parseRequest (string value)
continue;
cookie.Port = val;
+
continue;
}
@@ -355,37 +380,43 @@ private static CookieCollection parseResponse (string value)
var ret = new CookieCollection ();
Cookie cookie = null;
-
var caseInsensitive = StringComparison.InvariantCultureIgnoreCase;
+
var pairs = value.SplitHeaderValue (',', ';').ToList ();
for (var i = 0; i < pairs.Count; i++) {
var pair = pairs[i].Trim ();
+
if (pair.Length == 0)
continue;
var idx = pair.IndexOf ('=');
+
if (idx == -1) {
if (cookie == null)
continue;
if (pair.Equals ("port", caseInsensitive)) {
cookie.Port = "\"\"";
+
continue;
}
if (pair.Equals ("discard", caseInsensitive)) {
cookie.Discard = true;
+
continue;
}
if (pair.Equals ("secure", caseInsensitive)) {
cookie.Secure = true;
+
continue;
}
if (pair.Equals ("httponly", caseInsensitive)) {
cookie.HttpOnly = true;
+
continue;
}
@@ -395,6 +426,7 @@ private static CookieCollection parseResponse (string value)
if (idx == 0) {
if (cookie != null) {
ret.add (cookie);
+
cookie = null;
}
@@ -413,11 +445,15 @@ private static CookieCollection parseResponse (string value)
if (val.Length == 0)
continue;
+ var s = val.Unquote ();
+
int num;
- if (!Int32.TryParse (val.Unquote (), out num))
+
+ if (!Int32.TryParse (s, out num))
continue;
cookie.Version = num;
+
continue;
}
@@ -437,22 +473,30 @@ private static CookieCollection parseResponse (string value)
continue;
var buff = new StringBuilder (val, 32);
+
buff.AppendFormat (", {0}", pairs[i].Trim ());
+ var s = buff.ToString ();
+ var fmts = new[] { "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", "r" };
+ var provider = CultureInfo.CreateSpecificCulture ("en-US");
+ var style = DateTimeStyles.AdjustToUniversal
+ | DateTimeStyles.AssumeUniversal;
+
DateTime expires;
- if (
- !DateTime.TryParseExact (
- buff.ToString (),
- new[] { "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", "r" },
- CultureInfo.CreateSpecificCulture ("en-US"),
- DateTimeStyles.AdjustToUniversal
- | DateTimeStyles.AssumeUniversal,
- out expires
- )
- )
+
+ var done = DateTime.TryParseExact (
+ s,
+ fmts,
+ provider,
+ style,
+ out expires
+ );
+
+ if (!done)
continue;
cookie.Expires = expires.ToLocalTime ();
+
continue;
}
@@ -463,11 +507,15 @@ out expires
if (val.Length == 0)
continue;
- int num;
- if (!Int32.TryParse (val.Unquote (), out num))
+ var s = val.Unquote ();
+
+ int maxAge;
+
+ if (!Int32.TryParse (s, out maxAge))
continue;
- cookie.MaxAge = num;
+ cookie.MaxAge = maxAge;
+
continue;
}
@@ -479,6 +527,7 @@ out expires
continue;
cookie.Path = val;
+
continue;
}
@@ -490,6 +539,7 @@ out expires
continue;
cookie.Domain = val;
+
continue;
}
@@ -501,6 +551,7 @@ out expires
continue;
cookie.Port = val;
+
continue;
}
@@ -512,6 +563,7 @@ out expires
continue;
cookie.Comment = urlDecode (val, Encoding.UTF8);
+
continue;
}
@@ -523,6 +575,7 @@ out expires
continue;
cookie.CommentUri = val.Unquote ().ToUri ();
+
continue;
}
@@ -534,6 +587,7 @@ out expires
continue;
cookie.SameSite = val.Unquote ();
+
continue;
}
@@ -579,9 +633,7 @@ private static string urlDecode (string s, Encoding encoding)
internal static CookieCollection Parse (string value, bool response)
{
try {
- return response
- ? parseResponse (value)
- : parseRequest (value);
+ return response ? parseResponse (value) : parseRequest (value);
}
catch (Exception ex) {
throw new CookieException ("It could not be parsed.", ex);
@@ -591,16 +643,19 @@ internal static CookieCollection Parse (string value, bool response)
internal void SetOrRemove (Cookie cookie)
{
var idx = search (cookie);
+
if (idx == -1) {
if (cookie.Expired)
return;
_list.Add (cookie);
+
return;
}
if (cookie.Expired) {
_list.RemoveAt (idx);
+
return;
}
@@ -615,8 +670,10 @@ internal void SetOrRemove (CookieCollection cookies)
internal void Sort ()
{
- if (_list.Count > 1)
- _list.Sort (compareForSort);
+ if (_list.Count < 2)
+ return;
+
+ _list.Sort (compareForSort);
}
#endregion
@@ -629,16 +686,17 @@ internal void Sort ()
///
/// A to add.
///
- ///
- /// The collection is read-only.
- ///
///
/// is .
///
+ ///
+ /// This method is not available if the collection is read-only.
+ ///
public void Add (Cookie cookie)
{
if (_readOnly) {
var msg = "The collection is read-only.";
+
throw new InvalidOperationException (msg);
}
@@ -654,16 +712,17 @@ public void Add (Cookie cookie)
///
/// A that contains the cookies to add.
///
- ///
- /// The collection is read-only.
- ///
///
/// is .
///
+ ///
+ /// This method is not available if the collection is read-only.
+ ///
public void Add (CookieCollection cookies)
{
if (_readOnly) {
var msg = "The collection is read-only.";
+
throw new InvalidOperationException (msg);
}
@@ -678,12 +737,13 @@ public void Add (CookieCollection cookies)
/// Removes all cookies from the collection.
///
///
- /// The collection is read-only.
+ /// This method is not available if the collection is read-only.
///
public void Clear ()
{
if (_readOnly) {
var msg = "The collection is read-only.";
+
throw new InvalidOperationException (msg);
}
@@ -723,26 +783,30 @@ public bool Contains (Cookie cookie)
/// An that specifies the zero-based index in
/// the array at which copying starts.
///
+ ///
+ /// The space from to the end of
+ /// is not enough to copy to.
+ ///
///
/// is .
///
///
/// is less than zero.
///
- ///
- /// The space from to the end of
- /// is not enough to copy to.
- ///
public void CopyTo (Cookie[] array, int index)
{
if (array == null)
throw new ArgumentNullException ("array");
- if (index < 0)
- throw new ArgumentOutOfRangeException ("index", "Less than zero.");
+ if (index < 0) {
+ var msg = "Less than zero.";
+
+ throw new ArgumentOutOfRangeException ("index", msg);
+ }
if (array.Length - index < _list.Count) {
var msg = "The available space of the array is not enough to copy to.";
+
throw new ArgumentException (msg);
}
@@ -765,27 +829,23 @@ public IEnumerator GetEnumerator ()
/// Removes the specified cookie from the collection.
///
///
- ///
- /// true if the cookie is successfully removed; otherwise,
- /// false.
- ///
- ///
- /// false if the cookie is not found in the collection.
- ///
+ /// true if the cookie is successfully found and removed;
+ /// otherwise, false.
///
///
/// A to remove.
///
- ///
- /// The collection is read-only.
- ///
///
/// is .
///
+ ///
+ /// This method is not available if the collection is read-only.
+ ///
public bool Remove (Cookie cookie)
{
if (_readOnly) {
var msg = "The collection is read-only.";
+
throw new InvalidOperationException (msg);
}
@@ -793,10 +853,12 @@ public bool Remove (Cookie cookie)
throw new ArgumentNullException ("cookie");
var idx = search (cookie);
+
if (idx == -1)
return false;
_list.RemoveAt (idx);
+
return true;
}
diff --git a/websocket-sharp/Net/CookieException.cs b/websocket-sharp/Net/CookieException.cs
index 2a5abe98a..3c9ab3f9e 100644
--- a/websocket-sharp/Net/CookieException.cs
+++ b/websocket-sharp/Net/CookieException.cs
@@ -7,7 +7,7 @@
*
* The MIT License
*
- * Copyright (c) 2012-2019 sta.blockhead
+ * Copyright (c) 2012-2024 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -66,10 +66,11 @@ internal CookieException (string message, Exception innerException)
///
/// Initializes a new instance of the class
- /// with the serialized data.
+ /// with the specified serialized data.
///
///
- /// A that holds the serialized object data.
+ /// A that contains the serialized
+ /// object data.
///
///
/// A that specifies the source for
@@ -79,7 +80,8 @@ internal CookieException (string message, Exception innerException)
/// is .
///
protected CookieException (
- SerializationInfo serializationInfo, StreamingContext streamingContext
+ SerializationInfo serializationInfo,
+ StreamingContext streamingContext
)
: base (serializationInfo, streamingContext)
{
@@ -122,7 +124,8 @@ public CookieException ()
)
]
public override void GetObjectData (
- SerializationInfo serializationInfo, StreamingContext streamingContext
+ SerializationInfo serializationInfo,
+ StreamingContext streamingContext
)
{
base.GetObjectData (serializationInfo, streamingContext);
@@ -154,7 +157,8 @@ public override void GetObjectData (
)
]
void ISerializable.GetObjectData (
- SerializationInfo serializationInfo, StreamingContext streamingContext
+ SerializationInfo serializationInfo,
+ StreamingContext streamingContext
)
{
base.GetObjectData (serializationInfo, streamingContext);
diff --git a/websocket-sharp/Net/EndPointListener.cs b/websocket-sharp/Net/EndPointListener.cs
index 86402f72a..3c8b3653a 100644
--- a/websocket-sharp/Net/EndPointListener.cs
+++ b/websocket-sharp/Net/EndPointListener.cs
@@ -8,7 +8,7 @@
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
- * Copyright (c) 2012-2020 sta.blockhead
+ * Copyright (c) 2012-2023 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -61,16 +61,16 @@ internal sealed class EndPointListener
{
#region Private Fields
- private List _all; // host == '+'
- private static readonly string _defaultCertFolderPath;
- private IPEndPoint _endpoint;
- private List _prefixes;
- private bool _secure;
- private Socket _socket;
- private ServerSslConfiguration _sslConfig;
- private List _unhandled; // host == '*'
- private List _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
@@ -116,8 +116,8 @@ bool reuseAddress
}
_prefixes = new List ();
- _unregistered = new List ();
- _unregisteredSync = ((ICollection) _unregistered).SyncRoot;
+ _connections = new Dictionary ();
+ _connectionsSync = ((ICollection) _connections).SyncRoot;
_socket = new Socket (
endpoint.Address.AddressFamily,
@@ -171,7 +171,8 @@ public ServerSslConfiguration SslConfiguration {
#region Private Methods
private static void addSpecial (
- List prefixes, HttpListenerPrefix prefix
+ List prefixes,
+ HttpListenerPrefix prefix
)
{
var path = prefix.Path;
@@ -191,22 +192,20 @@ private void clearConnections ()
{
HttpConnection[] conns = null;
- var cnt = 0;
-
- lock (_unregisteredSync) {
- cnt = _unregistered.Count;
+ lock (_connectionsSync) {
+ var cnt = _connections.Count;
if (cnt == 0)
return;
conns = new HttpConnection[cnt];
- _unregistered.CopyTo (conns, 0);
- _unregistered.Clear ();
+ _connections.Values.CopyTo (conns, 0);
+ _connections.Clear ();
}
- for (var i = cnt - 1; i >= 0; i--)
- conns[i].Close (true);
+ foreach (var conn in conns)
+ conn.Close (true);
}
private static RSACryptoServiceProvider createRSAFromFile (string path)
@@ -214,13 +213,16 @@ private static RSACryptoServiceProvider createRSAFromFile (string path)
var rsa = new RSACryptoServiceProvider ();
var key = File.ReadAllBytes (path);
+
rsa.ImportCspBlob (key);
return rsa;
}
private static X509Certificate2 getCertificate (
- int port, string folderPath, X509Certificate2 defaultCertificate
+ int port,
+ string folderPath,
+ X509Certificate2 defaultCertificate
)
{
if (folderPath == null || folderPath.Length == 0)
@@ -230,17 +232,20 @@ private static X509Certificate2 getCertificate (
var cer = Path.Combine (folderPath, String.Format ("{0}.cer", port));
var key = Path.Combine (folderPath, String.Format ("{0}.key", port));
- if (File.Exists (cer) && File.Exists (key)) {
- var cert = new X509Certificate2 (cer);
- cert.PrivateKey = createRSAFromFile (key);
+ var exists = File.Exists (cer) && File.Exists (key);
- return cert;
- }
+ if (!exists)
+ return defaultCertificate;
+
+ var cert = new X509Certificate2 (cer);
+
+ cert.PrivateKey = createRSAFromFile (key);
+
+ return cert;
}
catch {
+ return defaultCertificate;
}
-
- return defaultCertificate;
}
private void leaveIfNoPrefix ()
@@ -296,7 +301,8 @@ private static void onAccept (IAsyncResult asyncResult)
}
private static void processAccepted (
- Socket socket, EndPointListener listener
+ Socket socket,
+ EndPointListener listener
)
{
HttpConnection conn = null;
@@ -312,14 +318,15 @@ private static void processAccepted (
return;
}
- lock (listener._unregisteredSync)
- listener._unregistered.Add (conn);
+ lock (listener._connectionsSync)
+ listener._connections.Add (conn, conn);
conn.BeginReadRequest ();
}
private static bool removeSpecial (
- List prefixes, HttpListenerPrefix prefix
+ List prefixes,
+ HttpListenerPrefix prefix
)
{
var path = prefix.Path;
@@ -337,7 +344,8 @@ private static bool removeSpecial (
}
private static HttpListener searchHttpListenerFromSpecial (
- string path, List prefixes
+ string path,
+ List prefixes
)
{
if (prefixes == null)
@@ -354,10 +362,13 @@ private static HttpListener searchHttpListenerFromSpecial (
if (len < bestLen)
continue;
- if (path.StartsWith (prefPath, StringComparison.Ordinal)) {
- bestLen = len;
- ret = pref.Listener;
- }
+ var match = path.StartsWith (prefPath, StringComparison.Ordinal);
+
+ if (!match)
+ continue;
+
+ bestLen = len;
+ ret = pref.Listener;
}
return ret;
@@ -380,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)
@@ -423,10 +434,13 @@ internal bool TrySearchHttpListener (Uri uri, out HttpListener listener)
if (len < bestLen)
continue;
- if (path.StartsWith (prefPath, StringComparison.Ordinal)) {
- bestLen = len;
- listener = pref.Listener;
- }
+ var match = path.StartsWith (prefPath, StringComparison.Ordinal);
+
+ if (!match)
+ continue;
+
+ bestLen = len;
+ listener = pref.Listener;
}
if (bestLen != -1)
@@ -461,7 +475,8 @@ public void AddPrefix (HttpListenerPrefix prefix)
addSpecial (future, prefix);
}
while (
- Interlocked.CompareExchange (ref _unhandled, future, current) != current
+ Interlocked.CompareExchange (ref _unhandled, future, current)
+ != current
);
return;
@@ -477,7 +492,8 @@ public void AddPrefix (HttpListenerPrefix prefix)
addSpecial (future, prefix);
}
while (
- Interlocked.CompareExchange (ref _all, future, current) != current
+ Interlocked.CompareExchange (ref _all, future, current)
+ != current
);
return;
@@ -485,13 +501,13 @@ public void AddPrefix (HttpListenerPrefix prefix)
do {
current = _prefixes;
+
var idx = current.IndexOf (prefix);
if (idx > -1) {
if (current[idx].Listener != prefix.Listener) {
- var msg = String.Format (
- "There is another listener for {0}.", prefix
- );
+ var fmt = "There is another listener for {0}.";
+ var msg = String.Format (fmt, prefix);
throw new HttpListenerException (87, msg);
}
@@ -500,10 +516,12 @@ public void AddPrefix (HttpListenerPrefix prefix)
}
future = new List (current);
+
future.Add (prefix);
}
while (
- Interlocked.CompareExchange (ref _prefixes, future, current) != current
+ Interlocked.CompareExchange (ref _prefixes, future, current)
+ != current
);
}
@@ -532,7 +550,8 @@ public void RemovePrefix (HttpListenerPrefix prefix)
break;
}
while (
- Interlocked.CompareExchange (ref _unhandled, future, current) != current
+ Interlocked.CompareExchange (ref _unhandled, future, current)
+ != current
);
leaveIfNoPrefix ();
@@ -553,7 +572,8 @@ public void RemovePrefix (HttpListenerPrefix prefix)
break;
}
while (
- Interlocked.CompareExchange (ref _all, future, current) != current
+ Interlocked.CompareExchange (ref _all, future, current)
+ != current
);
leaveIfNoPrefix ();
@@ -568,10 +588,12 @@ public void RemovePrefix (HttpListenerPrefix prefix)
break;
future = new List (current);
+
future.Remove (prefix);
}
while (
- Interlocked.CompareExchange (ref _prefixes, future, current) != current
+ Interlocked.CompareExchange (ref _prefixes, future, current)
+ != current
);
leaveIfNoPrefix ();
diff --git a/websocket-sharp/Net/HttpConnection.cs b/websocket-sharp/Net/HttpConnection.cs
index e9ee9e46d..e51efb11c 100644
--- a/websocket-sharp/Net/HttpConnection.cs
+++ b/websocket-sharp/Net/HttpConnection.cs
@@ -8,7 +8,7 @@
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
- * Copyright (c) 2012-2020 sta.blockhead
+ * Copyright (c) 2012-2025 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -60,16 +60,16 @@ internal sealed class HttpConnection
{
#region Private Fields
+ private int _attempts;
private byte[] _buffer;
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 bool _isSecure;
private LineState _lineState;
- private EndPointListener _listener;
private EndPoint _localEndPoint;
private static readonly int _maxInputLength;
private ResponseStream _outputStream;
@@ -77,7 +77,6 @@ internal sealed class HttpConnection
private EndPoint _remoteEndPoint;
private MemoryStream _requestBuffer;
private int _reuses;
- private bool _secure;
private Socket _socket;
private Stream _stream;
private object _sync;
@@ -102,7 +101,7 @@ static HttpConnection ()
internal HttpConnection (Socket socket, EndPointListener listener)
{
_socket = socket;
- _listener = listener;
+ _endPointListener = listener;
var netStream = new NetworkStream (socket, false);
@@ -121,7 +120,7 @@ internal HttpConnection (Socket socket, EndPointListener listener)
sslConf.CheckCertificateRevocation
);
- _secure = true;
+ _isSecure = true;
_stream = sslStream;
}
else {
@@ -135,7 +134,8 @@ internal HttpConnection (Socket socket, EndPointListener listener)
_timeoutCanceled = new Dictionary ();
_timer = new Timer (onTimeout, this, Timeout.Infinite, Timeout.Infinite);
- init (90000); // 90k ms for first request, 15k ms from then on.
+ // 90k ms for first request, 15k ms from then on.
+ init (new MemoryStream (), 90000);
}
#endregion
@@ -156,7 +156,7 @@ public bool IsLocal {
public bool IsSecure {
get {
- return _secure;
+ return _isSecure;
}
}
@@ -178,6 +178,12 @@ public int Reuses {
}
}
+ public Socket Socket {
+ get {
+ return _socket;
+ }
+ }
+
public Stream Stream {
get {
return _stream;
@@ -200,8 +206,8 @@ private void close ()
closeSocket ();
}
- unregisterContext ();
- removeConnection ();
+ _context.Unregister ();
+ _endPointListener.RemoveConnection (this);
}
private void closeSocket ()
@@ -217,6 +223,32 @@ private void closeSocket ()
_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)
@@ -253,8 +285,9 @@ private void disposeTimer ()
_timer = null;
}
- private void init (int timeout)
+ private void init (MemoryStream requestBuffer, int timeout)
{
+ _requestBuffer = requestBuffer;
_timeout = timeout;
_context = new HttpListenerContext (this);
@@ -264,13 +297,12 @@ private void init (int timeout)
_lineState = LineState.None;
_outputStream = null;
_position = 0;
- _requestBuffer = new MemoryStream ();
}
private static void onRead (IAsyncResult asyncResult)
{
var conn = (HttpConnection) asyncResult.AsyncState;
- var current = conn._reuses;
+ var current = conn._attempts;
if (conn._socket == null)
return;
@@ -279,10 +311,8 @@ private static void onRead (IAsyncResult asyncResult)
if (conn._socket == null)
return;
- 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;
@@ -304,48 +334,18 @@ private static void onRead (IAsyncResult asyncResult)
}
conn._requestBuffer.Write (conn._buffer, 0, nread);
- var len = (int) conn._requestBuffer.Length;
-
- if (conn.processInput (conn._requestBuffer.GetBuffer (), len)) {
- if (!conn._context.HasErrorMessage)
- conn._context.Request.FinishInitialization ();
-
- if (conn._context.HasErrorMessage) {
- conn._context.SendError ();
-
- return;
- }
-
- var url = conn._context.Request.Url;
- HttpListener lsnr;
-
- if (conn._listener.TrySearchHttpListener (url, out lsnr)) {
- conn.registerContext (lsnr);
-
- return;
- }
-
- conn._context.ErrorStatusCode = 404;
- conn._context.SendError ();
+ if (conn.processRequestBuffer ())
return;
- }
- try {
- conn._stream.BeginRead (conn._buffer, 0, _bufferLength, onRead, conn);
- }
- catch (Exception) {
- // TODO: Logging.
-
- conn.close ();
- }
+ 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;
@@ -357,8 +357,7 @@ private static void onTimeout (object state)
if (conn._timeoutCanceled[current])
return;
- conn._context.ErrorStatusCode = 408;
- conn._context.SendError ();
+ conn._context.SendError (408);
}
}
@@ -368,6 +367,8 @@ private bool processInput (byte[] data, int length)
// - true Done processing
// - false Need more input
+ var req = _context.Request;
+
try {
while (true) {
int nread;
@@ -389,19 +390,22 @@ private bool processInput (byte[] data, int length)
}
if (_inputState == InputState.RequestLine) {
- _context.Request.SetRequestLine (line);
+ req.SetRequestLine (line);
+
_inputState = InputState.Headers;
}
else {
- _context.Request.AddHeader (line);
+ req.AddHeader (line);
}
if (_context.HasErrorMessage)
return true;
}
}
- catch (Exception ex) {
- _context.ErrorMessage = ex.Message;
+ catch (Exception) {
+ // TODO: Logging.
+
+ _context.ErrorMessage = "Processing failure";
return true;
}
@@ -415,8 +419,48 @@ private bool processInput (byte[] data, int length)
return false;
}
+ private bool processRequestBuffer ()
+ {
+ // 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 ();
+
+ 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
+ byte[] buffer,
+ int offset,
+ int length,
+ out int nread
)
{
nread = 0;
@@ -452,50 +496,23 @@ private string readLineFrom (
return ret;
}
- private void registerContext (HttpListener listener)
+ private MemoryStream takeOverRequestBuffer ()
{
- if (_lastListener != listener) {
- removeConnection ();
-
- if (!listener.AddConnection (this)) {
- close ();
-
- return;
- }
+ if (_inputStream != null)
+ return createRequestBuffer (_inputStream);
- _lastListener = listener;
- }
-
- _context.Listener = listener;
+ var ret = new MemoryStream ();
- if (!_context.Authenticate ())
- return;
+ var buff = _requestBuffer.GetBuffer ();
+ var len = (int) _requestBuffer.Length;
+ var cnt = len - _position;
- if (!_context.Register ())
- return;
+ if (cnt > 0)
+ ret.Write (buff, _position, cnt);
- _contextRegistered = true;
- }
+ disposeRequestBuffer ();
- private void removeConnection ()
- {
- if (_lastListener == null) {
- _listener.RemoveConnection (this);
-
- return;
- }
-
- _lastListener.RemoveConnection (this);
- }
-
- private void unregisterContext ()
- {
- if (!_contextRegistered)
- return;
-
- _context.Unregister ();
-
- _contextRegistered = false;
+ return ret;
}
#endregion
@@ -504,7 +521,9 @@ private void unregisterContext ()
internal void BeginReadRequest ()
{
- _timeoutCanceled.Add (_reuses, false);
+ _attempts++;
+
+ _timeoutCanceled.Add (_attempts, false);
_timer.Change (_timeout, Timeout.Infinite);
try {
@@ -549,12 +568,20 @@ internal void Close (bool force)
return;
}
- disposeRequestBuffer ();
- unregisterContext ();
+ _context.Unregister ();
_reuses++;
- init (15000);
+ var buff = takeOverRequestBuffer ();
+ var len = buff.Length;
+
+ init (buff, 15000);
+
+ if (len > 0) {
+ if (processRequestBuffer ())
+ return;
+ }
+
BeginReadRequest ();
}
}
@@ -581,16 +608,24 @@ public RequestStream GetRequestStream (long contentLength, bool chunked)
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;
}
}
@@ -606,6 +641,7 @@ public ResponseStream GetResponseStream ()
var lsnr = _context.Listener;
var ignore = lsnr != null ? lsnr.IgnoreWriteExceptions : true;
+
_outputStream = new ResponseStream (_stream, _context.Response, ignore);
return _outputStream;
diff --git a/websocket-sharp/Net/HttpDigestIdentity.cs b/websocket-sharp/Net/HttpDigestIdentity.cs
index 68ec86d9f..e2863aa9c 100644
--- a/websocket-sharp/Net/HttpDigestIdentity.cs
+++ b/websocket-sharp/Net/HttpDigestIdentity.cs
@@ -4,7 +4,7 @@
*
* The MIT License
*
- * Copyright (c) 2014-2017 sta.blockhead
+ * Copyright (c) 2014-2024 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -33,8 +33,8 @@
namespace WebSocketSharp.Net
{
///
- /// Holds the username and other parameters from
- /// an HTTP Digest authentication attempt.
+ /// Holds the username and other parameters from an HTTP Digest
+ /// authentication attempt.
///
public class HttpDigestIdentity : GenericIdentity
{
@@ -169,17 +169,22 @@ public string Uri {
#region Internal Methods
internal bool IsValid (
- string password, string realm, string method, string entity
+ string password,
+ string realm,
+ string method,
+ string entity
)
{
- var copied = new NameValueCollection (_parameters);
- copied["password"] = password;
- copied["realm"] = realm;
- copied["method"] = method;
- copied["entity"] = entity;
-
- var expected = AuthenticationResponse.CreateRequestDigest (copied);
- return _parameters["response"] == expected;
+ var parameters = new NameValueCollection (_parameters);
+
+ parameters["password"] = password;
+ parameters["realm"] = realm;
+ parameters["method"] = method;
+ parameters["entity"] = entity;
+
+ var expectedDigest = AuthenticationResponse.CreateRequestDigest (parameters);
+
+ return _parameters["response"] == expectedDigest;
}
#endregion
diff --git a/websocket-sharp/Net/HttpListener.cs b/websocket-sharp/Net/HttpListener.cs
index 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 3de938d28..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-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
@@ -153,15 +153,15 @@ public HttpListenerResponse Response {
}
///
- /// Gets the client information (identity, authentication, and security roles).
+ /// Gets the client information.
///
///
///
- /// A instance or if not
- /// authenticated.
+ /// A instance that represents identity,
+ /// authentication, and security roles for the client.
///
///
- /// The instance describes the client.
+ /// if the client is not authenticated.
///
///
public IPrincipal User {
@@ -175,7 +175,9 @@ public IPrincipal User {
#region Private Methods
private static string createErrorContent (
- int statusCode, string statusDescription, string message
+ int statusCode,
+ string statusDescription,
+ string message
)
{
return message != null && message.Length > 0
@@ -192,54 +194,10 @@ private static string createErrorContent (
);
}
- private void sendAuthenticationChallenge (string challenge)
- {
- _response.StatusCode = 401;
- _response.Headers.InternalSet ("WWW-Authenticate", challenge, true);
-
- _response.Close ();
- }
-
#endregion
#region Internal Methods
- internal bool Authenticate ()
- {
- var schm = _listener.SelectAuthenticationScheme (_request);
-
- if (schm == AuthenticationSchemes.Anonymous)
- return true;
-
- if (schm == AuthenticationSchemes.None) {
- _errorStatusCode = 403;
- _errorMessage = "Authentication not allowed";
- SendError ();
-
- return false;
- }
-
- var realm = _listener.GetRealm ();
- var user = HttpUtility.CreateUser (
- _request.Headers["Authorization"],
- schm,
- realm,
- _request.HttpMethod,
- _listener.GetUserCredentialsFinder ()
- );
-
- if (user == null || !user.Identity.IsAuthenticated) {
- var chal = new AuthenticationChallenge (schm, realm).ToString ();
- sendAuthenticationChallenge (chal);
-
- return false;
- }
-
- _user = user;
-
- return true;
- }
-
internal HttpListenerWebSocketContext GetWebSocketContext (string protocol)
{
_websocketContext = new HttpListenerWebSocketContext (this, protocol);
@@ -247,9 +205,18 @@ internal HttpListenerWebSocketContext GetWebSocketContext (string protocol)
return _websocketContext;
}
- internal bool Register ()
+ internal void SendAuthenticationChallenge (
+ AuthenticationSchemes scheme,
+ string realm
+ )
{
- return _listener.RegisterContext (this);
+ _response.StatusCode = 401;
+
+ var val = new AuthenticationChallenge (scheme, realm).ToString ();
+
+ _response.Headers.InternalSet ("WWW-Authenticate", val, true);
+
+ _response.Close ();
}
internal void SendError ()
@@ -266,6 +233,7 @@ internal void SendError ()
var enc = Encoding.UTF8;
var entity = enc.GetBytes (content);
+
_response.ContentEncoding = enc;
_response.ContentLength64 = entity.LongLength;
@@ -276,8 +244,51 @@ internal void SendError ()
}
}
+ internal void SendError (int statusCode)
+ {
+ _errorStatusCode = statusCode;
+
+ SendError ();
+ }
+
+ internal void SendError (int statusCode, string message)
+ {
+ _errorStatusCode = statusCode;
+ _errorMessage = message;
+
+ SendError ();
+ }
+
+ internal bool SetUser (
+ AuthenticationSchemes scheme,
+ string realm,
+ Func credentialsFinder
+ )
+ {
+ var user = HttpUtility.CreateUser (
+ _request.Headers["Authorization"],
+ scheme,
+ realm,
+ _request.HttpMethod,
+ credentialsFinder
+ );
+
+ if (user == null)
+ return false;
+
+ if (!user.Identity.IsAuthenticated)
+ return false;
+
+ _user = user;
+
+ return true;
+ }
+
internal void Unregister ()
{
+ if (_listener == null)
+ return;
+
_listener.UnregisterContext (this);
}
@@ -286,19 +297,24 @@ internal void Unregister ()
#region Public Methods
///
- /// Accepts a WebSocket handshake request.
+ /// Accepts a WebSocket connection.
///
///
/// A that represents
/// the WebSocket handshake request.
///
///
- /// A that specifies the subprotocol supported on
- /// the WebSocket connection.
+ ///
+ /// A that specifies the name of the subprotocol
+ /// supported on the WebSocket connection.
+ ///
+ ///
+ /// if not necessary.
+ ///
///
///
///
- /// is empty.
+ /// is an empty string.
///
///
/// -or-
@@ -308,12 +324,88 @@ internal void Unregister ()
///
///
///
- /// This method has already been called.
+ ///
+ /// This method has already been done.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The client request is not a WebSocket handshake request.
+ ///
///
public HttpListenerWebSocketContext AcceptWebSocket (string protocol)
+ {
+ return AcceptWebSocket (protocol, null);
+ }
+
+ ///
+ /// Accepts a WebSocket connection with initializing the WebSocket
+ /// interface.
+ ///
+ ///
+ /// A that represents
+ /// the WebSocket handshake request.
+ ///
+ ///
+ ///
+ /// A that specifies the name of the subprotocol
+ /// supported on the WebSocket connection.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ ///
+ ///
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when a new WebSocket instance is
+ /// initialized.
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// caused an exception.
+ ///
+ ///
+ ///
+ ///
+ /// This method has already been done.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The client request is not a WebSocket handshake request.
+ ///
+ ///
+ public HttpListenerWebSocketContext AcceptWebSocket (
+ string protocol,
+ Action initializer
+ )
{
if (_websocketContext != null) {
- var msg = "The accepting is already in progress.";
+ var msg = "The method has already been done.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ if (!_request.IsWebSocketRequest) {
+ var msg = "The request is not a WebSocket handshake request.";
throw new InvalidOperationException (msg);
}
@@ -332,7 +424,27 @@ public HttpListenerWebSocketContext AcceptWebSocket (string protocol)
}
}
- return GetWebSocketContext (protocol);
+ var ret = GetWebSocketContext (protocol);
+
+ var ws = ret.WebSocket;
+
+ if (initializer != null) {
+ try {
+ initializer (ws);
+ }
+ catch (Exception ex) {
+ if (ws.ReadyState == WebSocketState.New)
+ _websocketContext = null;
+
+ var msg = "It caused an exception.";
+
+ throw new ArgumentException (msg, "initializer", ex);
+ }
+ }
+
+ ws.Accept ();
+
+ return ret;
}
#endregion
diff --git a/websocket-sharp/Net/HttpListenerException.cs b/websocket-sharp/Net/HttpListenerException.cs
index 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 3c9b0f8ae..aba9d1bfd 100644
--- a/websocket-sharp/Net/HttpListenerPrefix.cs
+++ b/websocket-sharp/Net/HttpListenerPrefix.cs
@@ -8,7 +8,7 @@
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
- * Copyright (c) 2012-2020 sta.blockhead
+ * Copyright (c) 2012-2024 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -47,30 +47,18 @@ internal sealed class HttpListenerPrefix
#region Private Fields
private string _host;
+ private bool _isSecure;
private HttpListener _listener;
private string _original;
private string _path;
private string _port;
private string _prefix;
- private bool _secure;
+ private string _scheme;
#endregion
#region Internal Constructors
- ///
- /// Initializes a new instance of the class
- /// with the specified URI prefix and HTTP listener.
- ///
- ///
- /// This constructor must be called after calling the CheckPrefix method.
- ///
- ///
- /// A that specifies the URI prefix.
- ///
- ///
- /// A that specifies the HTTP listener.
- ///
internal HttpListenerPrefix (string uriPrefix, HttpListener listener)
{
_original = uriPrefix;
@@ -91,7 +79,7 @@ public string Host {
public bool IsSecure {
get {
- return _secure;
+ return _isSecure;
}
}
@@ -119,39 +107,48 @@ public string Port {
}
}
+ public string Scheme {
+ get {
+ return _scheme;
+ }
+ }
+
#endregion
#region Private Methods
private void parse (string uriPrefix)
{
- if (uriPrefix.StartsWith ("https"))
- _secure = true;
+ var compType = StringComparison.Ordinal;
+
+ _isSecure = uriPrefix.StartsWith ("https", compType);
+ _scheme = _isSecure ? "https" : "http";
+
+ var hostStartIdx = uriPrefix.IndexOf (':') + 3;
var len = uriPrefix.Length;
- var host = uriPrefix.IndexOf (':') + 3;
- var root = uriPrefix.IndexOf ('/', host + 1, len - host - 1);
+ var rootIdx = uriPrefix
+ .IndexOf ('/', hostStartIdx + 1, len - hostStartIdx - 1);
- var colon = uriPrefix.LastIndexOf (':', root - 1, root - host - 1);
+ var colonIdx = uriPrefix
+ .LastIndexOf (':', rootIdx - 1, rootIdx - hostStartIdx - 1);
- if (uriPrefix[root - 1] != ']' && colon > host) {
- _host = uriPrefix.Substring (host, colon - host);
- _port = uriPrefix.Substring (colon + 1, root - colon - 1);
+ var hasPort = uriPrefix[rootIdx - 1] != ']' && colonIdx > hostStartIdx;
+
+ if (hasPort) {
+ _host = uriPrefix.Substring (hostStartIdx, colonIdx - hostStartIdx);
+ _port = uriPrefix.Substring (colonIdx + 1, rootIdx - colonIdx - 1);
}
else {
- _host = uriPrefix.Substring (host, root - host);
- _port = _secure ? "443" : "80";
+ _host = uriPrefix.Substring (hostStartIdx, rootIdx - hostStartIdx);
+ _port = _isSecure ? "443" : "80";
}
- _path = uriPrefix.Substring (root);
+ _path = uriPrefix.Substring (rootIdx);
+
+ var fmt = "{0}://{1}:{2}{3}";
- _prefix = String.Format (
- "{0}://{1}:{2}{3}",
- _secure ? "https" : "http",
- _host,
- _port,
- _path
- );
+ _prefix = String.Format (fmt, _scheme, _host, _port, _path);
}
#endregion
@@ -171,77 +168,59 @@ public static void CheckPrefix (string uriPrefix)
throw new ArgumentException (msg, "uriPrefix");
}
- var schm = uriPrefix.StartsWith ("http://")
- || uriPrefix.StartsWith ("https://");
+ var compType = StringComparison.Ordinal;
+ var isHttpSchm = uriPrefix.StartsWith ("http://", compType)
+ || uriPrefix.StartsWith ("https://", compType);
- if (!schm) {
- var msg = "The scheme is not 'http' or 'https'.";
+ if (!isHttpSchm) {
+ var msg = "The scheme is not http or https.";
throw new ArgumentException (msg, "uriPrefix");
}
- var end = len - 1;
+ var endIdx = len - 1;
- if (uriPrefix[end] != '/') {
- var msg = "It ends without '/'.";
+ if (uriPrefix[endIdx] != '/') {
+ var msg = "It ends without a forward slash.";
throw new ArgumentException (msg, "uriPrefix");
}
- var host = uriPrefix.IndexOf (':') + 3;
+ var hostStartIdx = uriPrefix.IndexOf (':') + 3;
- if (host >= end) {
+ if (hostStartIdx >= endIdx) {
var msg = "No host is specified.";
throw new ArgumentException (msg, "uriPrefix");
}
- if (uriPrefix[host] == ':') {
+ if (uriPrefix[hostStartIdx] == ':') {
var msg = "No host is specified.";
throw new ArgumentException (msg, "uriPrefix");
}
- var root = uriPrefix.IndexOf ('/', host, len - host);
+ var rootIdx = uriPrefix.IndexOf ('/', hostStartIdx, len - hostStartIdx);
- if (root == host) {
+ if (rootIdx == hostStartIdx) {
var msg = "No host is specified.";
throw new ArgumentException (msg, "uriPrefix");
}
- if (uriPrefix[root - 1] == ':') {
+ if (uriPrefix[rootIdx - 1] == ':') {
var msg = "No port is specified.";
throw new ArgumentException (msg, "uriPrefix");
}
- if (root == end - 1) {
+ if (rootIdx == endIdx - 1) {
var msg = "No path is specified.";
throw new ArgumentException (msg, "uriPrefix");
}
}
- ///
- /// Determines whether the current instance is equal to the specified
- /// instance.
- ///
- ///
- /// This method will be required to detect duplicates in any collection.
- ///
- ///
- ///
- /// An instance to compare to the current instance.
- ///
- ///
- /// An reference to a instance.
- ///
- ///
- ///
- /// true if the current instance and have
- /// the same URI prefix; otherwise, false.
- ///
public override bool Equals (object obj)
{
var pref = obj as HttpListenerPrefix;
@@ -249,15 +228,6 @@ public override bool Equals (object obj)
return pref != null && _prefix.Equals (pref._prefix);
}
- ///
- /// Gets the hash code for the current instance.
- ///
- ///
- /// This method will be required to detect duplicates in any collection.
- ///
- ///
- /// An that represents the hash code.
- ///
public override int GetHashCode ()
{
return _prefix.GetHashCode ();
diff --git a/websocket-sharp/Net/HttpListenerPrefixCollection.cs b/websocket-sharp/Net/HttpListenerPrefixCollection.cs
index 765213de0..3f5c44a2c 100644
--- a/websocket-sharp/Net/HttpListenerPrefixCollection.cs
+++ b/websocket-sharp/Net/HttpListenerPrefixCollection.cs
@@ -8,7 +8,7 @@
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
- * Copyright (c) 2012-2020 sta.blockhead
+ * Copyright (c) 2012-2024 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -48,8 +48,8 @@ namespace WebSocketSharp.Net
/// the class.
///
///
- /// The instance responds to the request which has
- /// a requested URI that the prefixes most closely match.
+ /// The instance responds to the request which
+ /// has a requested URI that the prefixes most closely match.
///
public class HttpListenerPrefixCollection : ICollection
{
@@ -65,6 +65,7 @@ public class HttpListenerPrefixCollection : ICollection
internal HttpListenerPrefixCollection (HttpListener listener)
{
_listener = listener;
+
_prefixes = new List ();
}
@@ -123,23 +124,22 @@ public bool IsSynchronized {
///
///
/// It must be a well-formed URI prefix with http or https scheme,
- /// and must end with a '/'.
+ /// and must end with a forward slash (/).
///
///
- ///
- /// is .
- ///
///
/// is invalid.
///
+ ///
+ /// is .
+ ///
///
/// The instance associated with this
/// collection is closed.
///
public void Add (string uriPrefix)
{
- if (_listener.IsDisposed)
- throw new ObjectDisposedException (_listener.GetType ().ToString ());
+ _listener.CheckDisposed ();
HttpListenerPrefix.CheckPrefix (uriPrefix);
@@ -161,8 +161,7 @@ public void Add (string uriPrefix)
///
public void Clear ()
{
- if (_listener.IsDisposed)
- throw new ObjectDisposedException (_listener.GetType ().ToString ());
+ _listener.CheckDisposed ();
if (_listener.IsListening)
EndPointManager.RemoveListener (_listener);
@@ -190,8 +189,7 @@ public void Clear ()
///
public bool Contains (string uriPrefix)
{
- if (_listener.IsDisposed)
- throw new ObjectDisposedException (_listener.GetType ().ToString ());
+ _listener.CheckDisposed ();
if (uriPrefix == null)
throw new ArgumentNullException ("uriPrefix");
@@ -210,24 +208,23 @@ public bool Contains (string uriPrefix)
/// An that specifies the zero-based index in
/// the array at which copying begins.
///
+ ///
+ /// The space from to the end of
+ /// is not enough to copy to.
+ ///
///
/// is .
///
///
/// is less than zero.
///
- ///
- /// The space from to the end of
- /// is not enough to copy to.
- ///
///
/// The instance associated with this
/// collection is closed.
///
public void CopyTo (string[] array, int offset)
{
- if (_listener.IsDisposed)
- throw new ObjectDisposedException (_listener.GetType ().ToString ());
+ _listener.CheckDisposed ();
_prefixes.CopyTo (array, offset);
}
@@ -263,8 +260,7 @@ public IEnumerator GetEnumerator ()
///
public bool Remove (string uriPrefix)
{
- if (_listener.IsDisposed)
- throw new ObjectDisposedException (_listener.GetType ().ToString ());
+ _listener.CheckDisposed ();
if (uriPrefix == null)
throw new ArgumentNullException ("uriPrefix");
diff --git a/websocket-sharp/Net/HttpListenerRequest.cs b/websocket-sharp/Net/HttpListenerRequest.cs
index 372d26974..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,48 +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
@@ -631,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;
}
@@ -655,51 +638,54 @@ 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";
@@ -709,11 +695,11 @@ internal void FinishInitialization ()
var transferEnc = _headers["Transfer-Encoding"];
if (transferEnc != null) {
- var comparison = StringComparison.OrdinalIgnoreCase;
+ var compType = StringComparison.OrdinalIgnoreCase;
- if (!transferEnc.Equals ("chunked", comparison)) {
- _context.ErrorMessage = String.Empty;
+ if (!transferEnc.Equals ("chunked", compType)) {
_context.ErrorStatusCode = 501;
+ _context.ErrorMessage = "Invalid Transfer-Encoding header";
return;
}
@@ -722,9 +708,16 @@ internal void FinishInitialization ()
}
if (_httpMethod == "POST" || _httpMethod == "PUT") {
- if (_contentLength <= 0 && !_chunked) {
- _context.ErrorMessage = String.Empty;
+ 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;
}
@@ -733,15 +726,17 @@ internal void FinishInitialization ()
var expect = _headers["Expect"];
if (expect != null) {
- var comparison = StringComparison.OrdinalIgnoreCase;
+ var compType = StringComparison.OrdinalIgnoreCase;
- if (!expect.Equals ("100-continue", comparison)) {
+ 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);
}
}
@@ -749,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;
@@ -761,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;
}
@@ -799,6 +798,13 @@ internal void SetRequestLine (string requestLine)
return;
}
+ if (!method.IsHttpMethod ()) {
+ _context.ErrorStatusCode = 501;
+ _context.ErrorMessage = "Invalid request line (method)";
+
+ return;
+ }
+
var target = parts[1];
if (target.Length == 0) {
@@ -815,7 +821,7 @@ internal void SetRequestLine (string requestLine)
return;
}
- if (rawVer.IndexOf ("HTTP/") != 0) {
+ if (!rawVer.StartsWith ("HTTP/", StringComparison.Ordinal)) {
_context.ErrorMessage = "Invalid request line (version)";
return;
@@ -829,18 +835,13 @@ internal void SetRequestLine (string requestLine)
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;
- }
-
_httpMethod = method;
_rawUrl = target;
_protocolVersion = ver;
@@ -854,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.
@@ -921,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 da1789fef..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.
@@ -934,9 +893,6 @@ public void AppendCookie (Cookie cookie)
/// A that specifies the value of the header to
/// append.
///
- ///
- /// is .
- ///
///
///
/// is an empty string.
@@ -966,6 +922,9 @@ public void AppendCookie (Cookie cookie)
/// is a restricted header name.
///
///
+ ///
+ /// is .
+ ///
///
/// The length of is greater than 65,535
/// characters.
@@ -1009,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");
@@ -1021,6 +978,7 @@ public void Close (byte[] responseEntity, bool willBlock)
if (len > Int32.MaxValue) {
close (responseEntity, 1024, willBlock);
+
return;
}
@@ -1092,9 +1050,6 @@ public void CopyFrom (HttpListenerResponse templateResponse)
/// A that specifies the absolute URL to which
/// the client is redirected to locate a requested resource.
///
- ///
- /// is .
- ///
///
///
/// is an empty string.
@@ -1106,6 +1061,9 @@ public void CopyFrom (HttpListenerResponse templateResponse)
/// is not an absolute URL.
///
///
+ ///
+ /// is .
+ ///
///
/// The response is already being sent.
///
@@ -1114,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");
}
@@ -1144,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)
@@ -1163,6 +1120,7 @@ public void SetCookie (Cookie cookie)
if (!canSetCookie (cookie)) {
var msg = "It cannot be updated.";
+
throw new ArgumentException (msg, "cookie");
}
@@ -1179,9 +1137,6 @@ public void SetCookie (Cookie cookie)
///
/// A that specifies the value of the header to set.
///
- ///
- /// is .
- ///
///
///
/// is an empty string.
@@ -1211,6 +1166,9 @@ public void SetCookie (Cookie cookie)
/// is a restricted header name.
///
///
+ ///
+ /// is .
+ ///
///
/// The length of is greater than 65,535
/// characters.
diff --git a/websocket-sharp/Net/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 47541f435..b4de5d64c 100644
--- a/websocket-sharp/Net/ServerSslConfiguration.cs
+++ b/websocket-sharp/Net/ServerSslConfiguration.cs
@@ -5,7 +5,7 @@
* The MIT License
*
* Copyright (c) 2014 liryna
- * Copyright (c) 2014-2020 sta.blockhead
+ * Copyright (c) 2014-2024 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -42,7 +42,8 @@
namespace WebSocketSharp.Net
{
///
- /// Stores the parameters for the used by servers.
+ /// Stores the parameters for instances used by
+ /// a server.
///
public class ServerSslConfiguration
{
@@ -69,7 +70,7 @@ public ServerSslConfiguration ()
///
/// Initializes a new instance of the
- /// class that stores the parameters copied from the specified configuration.
+ /// class copying from the specified configuration.
///
///
/// A from which to copy.
@@ -117,12 +118,12 @@ public bool CheckCertificateRevocation {
}
///
- /// Gets or sets a value indicating whether the client is asked for
+ /// Gets or sets a value indicating whether each client is asked for
/// a certificate for authentication.
///
///
///
- /// true if the client is asked for a certificate for
+ /// true if each client is asked for a certificate for
/// authentication; otherwise, false.
///
///
@@ -141,19 +142,21 @@ public bool ClientCertificateRequired {
///
/// Gets or sets the callback used to validate the certificate supplied by
- /// the client.
+ /// each client.
///
///
/// The certificate is valid if the callback returns true.
///
///
///
- /// A delegate that
- /// invokes the method called for validating the certificate.
+ /// A delegate.
///
///
- /// The default value is a delegate that invokes a method that only
- /// returns true.
+ /// It represents the delegate called when the server validates
+ /// the certificate.
+ ///
+ ///
+ /// The default value invokes a method that only returns true.
///
///
public RemoteCertificateValidationCallback ClientCertificateValidationCallback {
@@ -170,14 +173,14 @@ public RemoteCertificateValidationCallback ClientCertificateValidationCallback {
}
///
- /// Gets or sets the protocols used for authentication.
+ /// Gets or sets the enabled versions of the SSL/TLS protocols.
///
///
///
/// Any of the enum values.
///
///
- /// It represents the protocols used for authentication.
+ /// It represents the enabled versions of the SSL/TLS protocols.
///
///
/// The default value is .
@@ -198,10 +201,10 @@ public SslProtocols EnabledSslProtocols {
///
///
///
- /// A or .
+ /// A that represents an X.509 certificate.
///
///
- /// The certificate represents an X.509 certificate.
+ /// if not present.
///
///
/// The default value is .
diff --git a/websocket-sharp/Net/WebHeaderCollection.cs b/websocket-sharp/Net/WebHeaderCollection.cs
index f92fc2915..da4a79d94 100644
--- a/websocket-sharp/Net/WebHeaderCollection.cs
+++ b/websocket-sharp/Net/WebHeaderCollection.cs
@@ -9,7 +9,7 @@
*
* Copyright (c) 2003 Ximian, Inc. (http://www.ximian.com)
* Copyright (c) 2007 Novell, Inc. (http://www.novell.com)
- * Copyright (c) 2012-2020 sta.blockhead
+ * Copyright (c) 2012-2024 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -133,7 +133,9 @@ static WebHeaderCollection ()
"Authorization",
new HttpHeaderInfo (
"Authorization",
- HttpHeaderType.Request | HttpHeaderType.MultiValue
+ HttpHeaderType.Request
+ | HttpHeaderType.Restricted
+ | HttpHeaderType.MultiValue
)
},
{
@@ -216,14 +218,14 @@ static WebHeaderCollection ()
"Cookie",
new HttpHeaderInfo (
"Cookie",
- HttpHeaderType.Request
+ HttpHeaderType.Request | HttpHeaderType.Restricted
)
},
{
"Cookie2",
new HttpHeaderInfo (
"Cookie2",
- HttpHeaderType.Request
+ HttpHeaderType.Request | HttpHeaderType.Restricted
)
},
{
@@ -327,7 +329,7 @@ static WebHeaderCollection ()
"Location",
new HttpHeaderInfo (
"Location",
- HttpHeaderType.Response
+ HttpHeaderType.Response | HttpHeaderType.Restricted
)
},
{
@@ -348,14 +350,16 @@ static WebHeaderCollection ()
"ProxyAuthenticate",
new HttpHeaderInfo (
"Proxy-Authenticate",
- HttpHeaderType.Response | HttpHeaderType.MultiValue
+ HttpHeaderType.Response
+ | HttpHeaderType.Restricted
+ | HttpHeaderType.MultiValue
)
},
{
"ProxyAuthorization",
new HttpHeaderInfo (
"Proxy-Authorization",
- HttpHeaderType.Request
+ HttpHeaderType.Request | HttpHeaderType.Restricted
)
},
{
@@ -427,6 +431,7 @@ static WebHeaderCollection ()
"Sec-WebSocket-Protocol",
HttpHeaderType.Request
| HttpHeaderType.Response
+ | HttpHeaderType.Restricted
| HttpHeaderType.MultiValueInRequest
)
},
@@ -451,14 +456,18 @@ static WebHeaderCollection ()
"SetCookie",
new HttpHeaderInfo (
"Set-Cookie",
- HttpHeaderType.Response | HttpHeaderType.MultiValue
+ HttpHeaderType.Response
+ | HttpHeaderType.Restricted
+ | HttpHeaderType.MultiValue
)
},
{
"SetCookie2",
new HttpHeaderInfo (
"Set-Cookie2",
- HttpHeaderType.Response | HttpHeaderType.MultiValue
+ HttpHeaderType.Response
+ | HttpHeaderType.Restricted
+ | HttpHeaderType.MultiValue
)
},
{
@@ -498,6 +507,7 @@ static WebHeaderCollection ()
"Upgrade",
HttpHeaderType.Request
| HttpHeaderType.Response
+ | HttpHeaderType.Restricted
| HttpHeaderType.MultiValue
)
},
@@ -560,9 +570,8 @@ internal WebHeaderCollection (HttpHeaderType state, bool internallyUsed)
#region Protected Constructors
///
- /// Initializes a new instance of the class
- /// from the specified instances of the and
- /// classes.
+ /// Initializes a new instance of the
+ /// class with the specified serialized data.
///
///
/// A that contains the serialized
@@ -572,15 +581,16 @@ internal WebHeaderCollection (HttpHeaderType state, bool internallyUsed)
/// A that specifies the source for
/// the deserialization.
///
- ///
- /// is .
- ///
///
/// An element with the specified name is not found in
/// .
///
+ ///
+ /// is .
+ ///
protected WebHeaderCollection (
- SerializationInfo serializationInfo, StreamingContext streamingContext
+ SerializationInfo serializationInfo,
+ StreamingContext streamingContext
)
{
if (serializationInfo == null)
@@ -865,10 +875,10 @@ private static string checkValue (string value, string paramName)
private static HttpHeaderInfo getHeaderInfo (string name)
{
- var comparison = StringComparison.InvariantCultureIgnoreCase;
+ var compType = StringComparison.InvariantCultureIgnoreCase;
foreach (var headerInfo in _headers.Values) {
- if (headerInfo.HeaderName.Equals (name, comparison))
+ if (headerInfo.HeaderName.Equals (name, compType))
return headerInfo;
}
@@ -987,17 +997,19 @@ internal string ToStringMultiValue (bool response)
var buff = new StringBuilder ();
+ var fmt = "{0}: {1}\r\n";
+
for (var i = 0; i < cnt; i++) {
var name = GetKey (i);
if (isMultiValue (name, response)) {
foreach (var val in GetValues (i))
- buff.AppendFormat ("{0}: {1}\r\n", name, val);
+ buff.AppendFormat (fmt, name, val);
continue;
}
- buff.AppendFormat ("{0}: {1}\r\n", name, Get (i));
+ buff.AppendFormat (fmt, name, Get (i));
}
buff.Append ("\r\n");
@@ -1019,9 +1031,6 @@ internal string ToStringMultiValue (bool response)
///
/// A that specifies the value of the header to add.
///
- ///
- /// is .
- ///
///
///
/// is an empty string.
@@ -1045,6 +1054,9 @@ internal string ToStringMultiValue (bool response)
/// contains an invalid character.
///
///
+ ///
+ /// is .
+ ///
///
/// The length of is greater than 65,535
/// characters.
@@ -1060,6 +1072,7 @@ protected void AddWithoutValidate (string headerName, string headerValue)
var headerType = getHeaderType (headerName);
checkAllowed (headerType);
+
add (headerName, headerValue, headerType);
}
@@ -1074,9 +1087,6 @@ protected void AddWithoutValidate (string headerName, string headerValue)
/// A that specifies the header to add,
/// with the name and value separated by a colon character (':').
///
- ///
- /// is .
- ///
///
///
/// is an empty string.
@@ -1120,6 +1130,9 @@ protected void AddWithoutValidate (string headerName, string headerValue)
/// is a restricted header.
///
///
+ ///
+ /// is .
+ ///
///
/// The length of the value part of is greater
/// than 65,535 characters.
@@ -1149,9 +1162,7 @@ public void Add (string header)
}
var name = header.Substring (0, idx);
- var val = idx < len - 1
- ? header.Substring (idx + 1)
- : String.Empty;
+ var val = idx < len - 1 ? header.Substring (idx + 1) : String.Empty;
name = checkName (name, "header");
val = checkValue (val, "header");
@@ -1160,6 +1171,7 @@ public void Add (string header)
checkRestricted (name, headerType);
checkAllowed (headerType);
+
add (name, val, headerType);
}
@@ -1205,6 +1217,7 @@ public void Add (HttpRequestHeader header, string value)
checkRestricted (name, HttpHeaderType.Request);
checkAllowed (HttpHeaderType.Request);
+
add (name, value, HttpHeaderType.Request);
}
@@ -1250,6 +1263,7 @@ public void Add (HttpResponseHeader header, string value)
checkRestricted (name, HttpHeaderType.Response);
checkAllowed (HttpHeaderType.Response);
+
add (name, value, HttpHeaderType.Response);
}
@@ -1262,9 +1276,6 @@ public void Add (HttpResponseHeader header, string value)
///
/// A that specifies the value of the header to add.
///
- ///
- /// is .
- ///
///
///
/// is an empty string.
@@ -1294,6 +1305,9 @@ public void Add (HttpResponseHeader header, string value)
/// is a restricted header name.
///
///
+ ///
+ /// is .
+ ///
///
/// The length of is greater than 65,535
/// characters.
@@ -1310,6 +1324,7 @@ public override void Add (string name, string value)
checkRestricted (name, headerType);
checkAllowed (headerType);
+
add (name, value, headerType);
}
@@ -1319,6 +1334,7 @@ public override void Add (string name, string value)
public override void Clear ()
{
base.Clear ();
+
_state = HttpHeaderType.Unspecified;
}
@@ -1330,7 +1346,7 @@ public override void Clear ()
///
///
/// An that specifies the zero-based index of the header
- /// to find.
+ /// to get.
///
///
/// is out of allowable range of indexes for
@@ -1353,7 +1369,7 @@ public override string Get (int index)
///
///
///
- /// A that specifies the name of the header to find.
+ /// A that specifies the name of the header to get.
///
public override string Get (string name)
{
@@ -1380,7 +1396,7 @@ public override IEnumerator GetEnumerator ()
///
///
/// An that specifies the zero-based index of the header
- /// to find.
+ /// to get.
///
///
/// is out of allowable range of indexes for
@@ -1405,7 +1421,7 @@ public override string GetKey (int index)
///
///
/// An that specifies the zero-based index of the header
- /// to find.
+ /// to get.
///
///
/// is out of allowable range of indexes for
@@ -1431,7 +1447,7 @@ public override string[] GetValues (int index)
///
///
///
- /// A that specifies the name of the header to find.
+ /// A that specifies the name of the header to get.
///
public override string[] GetValues (string name)
{
@@ -1441,11 +1457,11 @@ public override string[] GetValues (string name)
}
///
- /// Populates a instance with the data
- /// needed to serialize this instance.
+ /// Populates the specified instance with
+ /// the data needed to serialize the current instance.
///
///
- /// A to populate with the data.
+ /// A that holds the serialized object data.
///
///
/// A that specifies the destination for
@@ -1461,7 +1477,8 @@ public override string[] GetValues (string name)
)
]
public override void GetObjectData (
- SerializationInfo serializationInfo, StreamingContext streamingContext
+ SerializationInfo serializationInfo,
+ StreamingContext streamingContext
)
{
if (serializationInfo == null)
@@ -1481,7 +1498,7 @@ public override void GetObjectData (
}
///
- /// Determines whether the specified HTTP header can be set for the request.
+ /// Determines whether the specified header can be set for the request.
///
///
/// true if the header cannot be set; otherwise, false.
@@ -1489,9 +1506,6 @@ public override void GetObjectData (
///
/// A that specifies the name of the header to test.
///
- ///
- /// is .
- ///
///
///
/// is an empty string.
@@ -1509,14 +1523,17 @@ public override void GetObjectData (
/// contains an invalid character.
///
///
+ ///
+ /// is .
+ ///
public static bool IsRestricted (string headerName)
{
return IsRestricted (headerName, false);
}
///
- /// Determines whether the specified HTTP header can be set for the request
- /// or the response.
+ /// Determines whether the specified header can be set for the request or
+ /// the response.
///
///
/// true if the header cannot be set; otherwise, false.
@@ -1528,9 +1545,6 @@ public static bool IsRestricted (string headerName)
/// A : true if the test is for the response;
/// otherwise, false.
///
- ///
- /// is .
- ///
///
///
/// is an empty string.
@@ -1548,6 +1562,9 @@ public static bool IsRestricted (string headerName)
/// contains an invalid character.
///
///
+ ///
+ /// is .
+ ///
public static bool IsRestricted (string headerName, bool response)
{
headerName = checkName (headerName, "headerName");
@@ -1591,6 +1608,7 @@ public void Remove (HttpRequestHeader header)
checkRestricted (name, HttpHeaderType.Request);
checkAllowed (HttpHeaderType.Request);
+
base.Remove (name);
}
@@ -1618,6 +1636,7 @@ public void Remove (HttpResponseHeader header)
checkRestricted (name, HttpHeaderType.Response);
checkAllowed (HttpHeaderType.Response);
+
base.Remove (name);
}
@@ -1627,9 +1646,6 @@ public void Remove (HttpResponseHeader header)
///
/// A that specifies the name of the header to remove.
///
- ///
- /// is .
- ///
///
///
/// is an empty string.
@@ -1653,6 +1669,9 @@ public void Remove (HttpResponseHeader header)
/// is a restricted header name.
///
///
+ ///
+ /// is .
+ ///
///
/// This instance does not allow the header.
///
@@ -1664,6 +1683,7 @@ public override void Remove (string name)
checkRestricted (name, headerType);
checkAllowed (headerType);
+
base.Remove (name);
}
@@ -1709,6 +1729,7 @@ public void Set (HttpRequestHeader header, string value)
checkRestricted (name, HttpHeaderType.Request);
checkAllowed (HttpHeaderType.Request);
+
set (name, value, HttpHeaderType.Request);
}
@@ -1754,6 +1775,7 @@ public void Set (HttpResponseHeader header, string value)
checkRestricted (name, HttpHeaderType.Response);
checkAllowed (HttpHeaderType.Response);
+
set (name, value, HttpHeaderType.Response);
}
@@ -1766,9 +1788,6 @@ public void Set (HttpResponseHeader header, string value)
///
/// A that specifies the value of the header to set.
///
- ///
- /// is .
- ///
///
///
/// is an empty string.
@@ -1798,6 +1817,9 @@ public void Set (HttpResponseHeader header, string value)
/// is a restricted header name.
///
///
+ ///
+ /// is .
+ ///
///
/// The length of is greater than 65,535
/// characters.
@@ -1814,6 +1836,7 @@ public override void Set (string name, string value)
checkRestricted (name, headerType);
checkAllowed (headerType);
+
set (name, value, headerType);
}
@@ -1826,7 +1849,9 @@ public override void Set (string name, string value)
///
public byte[] ToByteArray ()
{
- return Encoding.UTF8.GetBytes (ToString ());
+ var s = ToString ();
+
+ return Encoding.UTF8.GetBytes (s);
}
///
@@ -1844,8 +1869,10 @@ public override string ToString ()
var buff = new StringBuilder ();
+ var fmt = "{0}: {1}\r\n";
+
for (var i = 0; i < cnt; i++)
- buff.AppendFormat ("{0}: {1}\r\n", GetKey (i), Get (i));
+ buff.AppendFormat (fmt, GetKey (i), Get (i));
buff.Append ("\r\n");
@@ -1857,11 +1884,11 @@ public override string ToString ()
#region Explicit Interface Implementations
///
- /// Populates a instance with the data
- /// needed to serialize this instance.
+ /// Populates the specified instance with
+ /// the data needed to serialize the current instance.
///
///
- /// A to populate with the data.
+ /// A that holds the serialized object data.
///
///
/// A that specifies the destination for
@@ -1878,7 +1905,8 @@ public override string ToString ()
)
]
void ISerializable.GetObjectData (
- SerializationInfo serializationInfo, StreamingContext streamingContext
+ SerializationInfo serializationInfo,
+ StreamingContext streamingContext
)
{
GetObjectData (serializationInfo, streamingContext);
diff --git a/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs b/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs
index ac963c785..7e0358d1f 100644
--- a/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs
+++ b/websocket-sharp/Net/WebSockets/HttpListenerWebSocketContext.cs
@@ -4,7 +4,7 @@
*
* The MIT License
*
- * Copyright (c) 2012-2018 sta.blockhead
+ * Copyright (c) 2012-2025 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -30,13 +30,14 @@
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
+using System.Net.Sockets;
using System.Security.Principal;
namespace WebSocketSharp.Net.WebSockets
{
///
- /// Provides the access to the information in a WebSocket handshake request to
- /// a instance.
+ /// Provides the access to the information in a WebSocket handshake request
+ /// to a instance.
///
public class HttpListenerWebSocketContext : WebSocketContext
{
@@ -50,7 +51,8 @@ public class HttpListenerWebSocketContext : WebSocketContext
#region Internal Constructors
internal HttpListenerWebSocketContext (
- HttpListenerContext context, string protocol
+ HttpListenerContext context,
+ string protocol
)
{
_context = context;
@@ -67,6 +69,12 @@ internal Logger Log {
}
}
+ internal Socket Socket {
+ get {
+ return _context.Connection.Socket;
+ }
+ }
+
internal Stream Stream {
get {
return _context.Connection.Stream;
@@ -186,7 +194,7 @@ public override bool IsWebSocketRequest {
/// A that represents the value of the Origin header.
///
///
- /// if the header is not present.
+ /// if not included.
///
///
public override string Origin {
@@ -244,7 +252,7 @@ public override Uri RequestUri {
/// a valid WebSocket handshake request.
///
///
- /// if the header is not present.
+ /// if not included.
///
///
public override string SecWebSocketKey {
@@ -270,11 +278,13 @@ public override string SecWebSocketKey {
public override IEnumerable SecWebSocketProtocols {
get {
var val = _context.Request.Headers["Sec-WebSocket-Protocol"];
+
if (val == null || val.Length == 0)
yield break;
foreach (var elm in val.Split (',')) {
var protocol = elm.Trim ();
+
if (protocol.Length == 0)
continue;
@@ -293,7 +303,7 @@ public override IEnumerable SecWebSocketProtocols {
/// version specified by the client.
///
///
- /// if the header is not present.
+ /// if not included.
///
///
public override string SecWebSocketVersion {
@@ -306,8 +316,8 @@ public override string SecWebSocketVersion {
/// Gets the endpoint to which the handshake request is sent.
///
///
- /// A that represents the server IP
- /// address and port number.
+ /// A that represents the server
+ /// IP address and port number.
///
public override System.Net.IPEndPoint ServerEndPoint {
get {
@@ -337,8 +347,8 @@ public override IPrincipal User {
/// Gets the endpoint from which the handshake request is sent.
///
///
- /// A that represents the client IP
- /// address and port number.
+ /// A that represents the client
+ /// IP address and port number.
///
public override System.Net.IPEndPoint UserEndPoint {
get {
@@ -347,11 +357,11 @@ public override System.Net.IPEndPoint UserEndPoint {
}
///
- /// Gets the WebSocket instance used for two-way communication between
+ /// Gets the WebSocket interface used for two-way communication between
/// the client and server.
///
///
- /// A .
+ /// A that represents the interface.
///
public override WebSocket WebSocket {
get {
@@ -371,6 +381,7 @@ internal void Close ()
internal void Close (HttpStatusCode code)
{
_context.Response.StatusCode = (int) code;
+
_context.Response.Close ();
}
diff --git a/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs b/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs
index 519da7896..3e8d12777 100644
--- a/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs
+++ b/websocket-sharp/Net/WebSockets/TcpListenerWebSocketContext.cs
@@ -4,7 +4,7 @@
*
* The MIT License
*
- * Copyright (c) 2012-2018 sta.blockhead
+ * Copyright (c) 2012-2025 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -45,23 +45,21 @@
namespace WebSocketSharp.Net.WebSockets
{
///
- /// Provides the access to the information in a WebSocket handshake request to
- /// a instance.
+ /// Provides the access to the information in a WebSocket handshake request
+ /// to a instance.
///
internal class TcpListenerWebSocketContext : WebSocketContext
{
#region Private Fields
+ private bool _isSecureConnection;
private Logger _log;
private NameValueCollection _queryString;
private HttpRequest _request;
private Uri _requestUri;
- private bool _secure;
- private System.Net.EndPoint _serverEndPoint;
private Stream _stream;
private TcpClient _tcpClient;
private IPrincipal _user;
- private System.Net.EndPoint _userEndPoint;
private WebSocket _websocket;
#endregion
@@ -77,10 +75,10 @@ Logger log
)
{
_tcpClient = tcpClient;
- _secure = secure;
_log = log;
var netStream = tcpClient.GetStream ();
+
if (secure) {
var sslStream = new SslStream (
netStream,
@@ -95,17 +93,14 @@ Logger log
sslConfig.CheckCertificateRevocation
);
+ _isSecureConnection = true;
_stream = sslStream;
}
else {
_stream = netStream;
}
- var sock = tcpClient.Client;
- _serverEndPoint = sock.LocalEndPoint;
- _userEndPoint = sock.RemoteEndPoint;
-
- _request = HttpRequest.Read (_stream, 90000);
+ _request = HttpRequest.ReadRequest (_stream, 90000);
_websocket = new WebSocket (this, protocol);
}
@@ -119,6 +114,12 @@ internal Logger Log {
}
}
+ internal Socket Socket {
+ get {
+ return _tcpClient.Client;
+ }
+ }
+
internal Stream Stream {
get {
return _stream;
@@ -212,7 +213,7 @@ public override bool IsLocal {
///
public override bool IsSecureConnection {
get {
- return _secure;
+ return _isSecureConnection;
}
}
@@ -238,7 +239,7 @@ public override bool IsWebSocketRequest {
/// A that represents the value of the Origin header.
///
///
- /// if the header is not present.
+ /// if not included.
///
///
public override string Origin {
@@ -263,10 +264,9 @@ public override NameValueCollection QueryString {
get {
if (_queryString == null) {
var uri = RequestUri;
- _queryString = QueryStringCollection.Parse (
- uri != null ? uri.Query : null,
- Encoding.UTF8
- );
+ var query = uri != null ? uri.Query : null;
+
+ _queryString = QueryStringCollection.Parse (query, Encoding.UTF8);
}
return _queryString;
@@ -288,10 +288,10 @@ public override Uri RequestUri {
get {
if (_requestUri == null) {
_requestUri = HttpUtility.CreateRequestUrl (
- _request.RequestUri,
+ _request.RequestTarget,
_request.Headers["Host"],
_request.IsWebSocketRequest,
- _secure
+ _isSecureConnection
);
}
@@ -313,7 +313,7 @@ public override Uri RequestUri {
/// a valid WebSocket handshake request.
///
///
- /// if the header is not present.
+ /// if not included.
///
///
public override string SecWebSocketKey {
@@ -339,11 +339,13 @@ public override string SecWebSocketKey {
public override IEnumerable SecWebSocketProtocols {
get {
var val = _request.Headers["Sec-WebSocket-Protocol"];
+
if (val == null || val.Length == 0)
yield break;
foreach (var elm in val.Split (',')) {
var protocol = elm.Trim ();
+
if (protocol.Length == 0)
continue;
@@ -362,7 +364,7 @@ public override IEnumerable SecWebSocketProtocols {
/// version specified by the client.
///
///
- /// if the header is not present.
+ /// if not included.
///
///
public override string SecWebSocketVersion {
@@ -375,12 +377,12 @@ public override string SecWebSocketVersion {
/// Gets the endpoint to which the handshake request is sent.
///
///
- /// A that represents the server IP
- /// address and port number.
+ /// A that represents the server
+ /// IP address and port number.
///
public override System.Net.IPEndPoint ServerEndPoint {
get {
- return (System.Net.IPEndPoint) _serverEndPoint;
+ return (System.Net.IPEndPoint) _tcpClient.Client.LocalEndPoint;
}
}
@@ -406,21 +408,21 @@ public override IPrincipal User {
/// Gets the endpoint from which the handshake request is sent.
///
///
- /// A that represents the client IP
- /// address and port number.
+ /// A that represents the client
+ /// IP address and port number.
///
public override System.Net.IPEndPoint UserEndPoint {
get {
- return (System.Net.IPEndPoint) _userEndPoint;
+ return (System.Net.IPEndPoint) _tcpClient.Client.RemoteEndPoint;
}
}
///
- /// Gets the WebSocket instance used for two-way communication between
+ /// Gets the WebSocket interface used for two-way communication between
/// the client and server.
///
///
- /// A .
+ /// A that represents the interface.
///
public override WebSocket WebSocket {
get {
@@ -430,71 +432,52 @@ public override WebSocket WebSocket {
#endregion
- #region Private Methods
+ #region Internal Methods
+
+ internal void Close ()
+ {
+ _stream.Close ();
+ _tcpClient.Close ();
+ }
- private HttpRequest sendAuthenticationChallenge (string challenge)
+ internal void Close (HttpStatusCode code)
{
- var res = HttpResponse.CreateUnauthorizedResponse (challenge);
- var bytes = res.ToByteArray ();
- _stream.Write (bytes, 0, bytes.Length);
+ 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/Opcode.cs b/websocket-sharp/Opcode.cs
index 5a8c632e0..06da1b5e1 100644
--- a/websocket-sharp/Opcode.cs
+++ b/websocket-sharp/Opcode.cs
@@ -4,7 +4,7 @@
*
* The MIT License
*
- * Copyright (c) 2012-2016 sta.blockhead
+ * Copyright (c) 2012-2025 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -38,7 +38,7 @@ namespace WebSocketSharp
///
/// Section 5.2 of RFC 6455.
///
- internal enum Opcode : byte
+ internal enum Opcode
{
///
/// Equivalent to numeric value 0. Indicates continuation frame.
diff --git a/websocket-sharp/PayloadData.cs b/websocket-sharp/PayloadData.cs
index 9e40b9404..e01fcd27e 100644
--- a/websocket-sharp/PayloadData.cs
+++ b/websocket-sharp/PayloadData.cs
@@ -4,7 +4,7 @@
*
* The MIT License
*
- * Copyright (c) 2012-2019 sta.blockhead
+ * Copyright (c) 2012-2025 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -36,9 +36,10 @@ internal class PayloadData : IEnumerable
{
#region Private Fields
- private byte[] _data;
- private long _extDataLength;
- private long _length;
+ private byte[] _data;
+ private static readonly byte[] _emptyBytes;
+ private long _extDataLength;
+ private long _length;
#endregion
@@ -54,7 +55,7 @@ internal class PayloadData : IEnumerable
///
///
///
- /// A will occur when the length of
+ /// A is thrown when the length of
/// incoming payload data is greater than the value of this field.
///
///
@@ -71,7 +72,9 @@ internal class PayloadData : IEnumerable
static PayloadData ()
{
- Empty = new PayloadData (WebSocket.EmptyBytes, 0);
+ _emptyBytes = new byte[0];
+
+ Empty = new PayloadData (_emptyBytes, 0);
MaxLength = Int64.MaxValue;
}
@@ -120,7 +123,7 @@ internal long ExtensionDataLength {
internal bool HasReservedCode {
get {
- return _length >= 2 && Code.IsReserved ();
+ return _length >= 2 && Code.IsReservedStatusCode ();
}
}
@@ -129,10 +132,11 @@ internal string Reason {
if (_length <= 2)
return String.Empty;
- var raw = _data.SubArray (2, _length - 2);
+ var bytes = _data.SubArray (2, _length - 2);
string reason;
- return raw.TryGetUTF8DecodedString (out reason)
+
+ return bytes.TryGetUTF8DecodedString (out reason)
? reason
: String.Empty;
}
@@ -154,7 +158,7 @@ public byte[] ExtensionData {
get {
return _extDataLength > 0
? _data.SubArray (0, _extDataLength)
- : WebSocket.EmptyBytes;
+ : _emptyBytes;
}
}
diff --git a/websocket-sharp/Rsv.cs b/websocket-sharp/Rsv.cs
index 8a10567c5..c2a4dea7a 100644
--- a/websocket-sharp/Rsv.cs
+++ b/websocket-sharp/Rsv.cs
@@ -4,7 +4,7 @@
*
* The MIT License
*
- * Copyright (c) 2012-2015 sta.blockhead
+ * Copyright (c) 2012-2025 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -31,13 +31,15 @@
namespace WebSocketSharp
{
///
- /// Indicates whether each RSV (RSV1, RSV2, and RSV3) of a WebSocket frame is non-zero.
+ /// Indicates whether each RSV (RSV1, RSV2, and RSV3) of a WebSocket
+ /// frame is non-zero.
///
///
/// The values of this enumeration are defined in
- /// Section 5.2 of RFC 6455.
+ ///
+ /// Section 5.2 of RFC 6455.
///
- internal enum Rsv : byte
+ internal enum Rsv
{
///
/// Equivalent to numeric value 0. Indicates zero.
diff --git a/websocket-sharp/Server/HttpRequestEventArgs.cs b/websocket-sharp/Server/HttpRequestEventArgs.cs
index 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 a243a6f3f..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.
+ ///
+ ///
+ /// "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 {
@@ -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 {
+ catch (Exception ex) {
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+ }
+
+ try {
+ _listener.Abort ();
+ }
+ 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 ();
}
@@ -957,7 +932,7 @@ private void receiveRequest ()
processRequest (ctx);
}
catch (Exception ex) {
- _log.Fatal (ex.Message);
+ _log.Error (ex.Message);
_log.Debug (ex.ToString ());
ctx.Connection.Close (true);
@@ -965,13 +940,21 @@ 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;
}
@@ -982,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 ();
@@ -1020,6 +995,7 @@ private void start ()
}
catch {
_services.Stop (1011, String.Empty);
+
throw;
}
@@ -1034,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)
@@ -1104,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;
}
@@ -1151,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.
@@ -1183,18 +1135,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.
@@ -1219,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.
///
///
@@ -1316,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.
///
@@ -1332,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.
@@ -1362,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);
}
///
@@ -1422,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;
@@ -1430,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.
@@ -1458,6 +1282,9 @@ public byte[] GetFile (string path)
/// query and fragment components.
///
///
+ ///
+ /// is .
+ ///
public bool RemoveWebSocketService (string path)
{
return _services.RemoveService (path);
@@ -1467,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.
///
///
///
@@ -1483,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 ();
}
@@ -1495,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..a0befe74d 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-2025 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -29,6 +29,7 @@
using System;
using System.Collections.Specialized;
using System.IO;
+using System.Security.Principal;
using WebSocketSharp.Net;
using WebSocketSharp.Net.WebSockets;
@@ -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.
@@ -46,16 +47,20 @@ public abstract class WebSocketBehavior : IWebSocketSession
{
#region Private Fields
- private WebSocketContext _context;
- private Func _cookiesValidator;
- private bool _emitOnPing;
- private string _id;
- private bool _ignoreExtensions;
- private Func _originValidator;
- private string _protocol;
- private WebSocketSessionManager _sessions;
- private DateTime _startTime;
- private WebSocket _websocket;
+ private WebSocketContext _context;
+ private Action _cookiesResponder;
+ private bool _emitOnPing;
+ private Func _hostValidator;
+ private string _id;
+ private bool _ignoreExtensions;
+ private bool _noDelay;
+ private Func _originValidator;
+ private string _protocol;
+ private bool _registered;
+ private WebSocketSessionManager _sessions;
+ private DateTime _startTime;
+ private Action _userHeadersResponder;
+ private WebSocket _websocket;
#endregion
@@ -74,184 +79,249 @@ protected WebSocketBehavior ()
#region Protected Properties
///
- /// Gets the HTTP headers included in a WebSocket handshake request.
+ /// Gets the HTTP headers for a session.
///
///
- ///
- /// A that contains the headers.
- ///
- ///
- /// if the session has not started yet.
- ///
+ /// A that contains the headers
+ /// included in the WebSocket handshake request.
///
+ ///
+ /// The get operation is not available when the session has not started yet.
+ ///
protected NameValueCollection Headers {
get {
- return _context != null ? _context.Headers : null;
+ if (!_registered) {
+ var msg = "The session has not started yet.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _context.Headers;
}
}
///
- /// Gets 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 get operation is not available when the session has not started yet.
+ ///
+ protected bool IsAlive {
get {
- return _websocket != null ? _websocket.Log : null;
+ if (!_registered) {
+ var msg = "The session has not started yet.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _websocket.IsAlive;
}
}
///
- /// Gets the query string included in a WebSocket handshake request.
+ /// Gets the query string for a session.
///
///
///
/// A that contains the query
- /// parameters.
+ /// parameters included in the WebSocket handshake request.
///
///
/// An empty collection if not included.
///
- ///
- /// if the session has not started yet.
- ///
///
+ ///
+ /// The get operation is not available when the session has not started yet.
+ ///
protected NameValueCollection QueryString {
get {
- return _context != null ? _context.QueryString : null;
+ if (!_registered) {
+ var msg = "The session has not started yet.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _context.QueryString;
}
}
///
- /// Gets the management function for the sessions in the service.
+ /// Gets the current state of the WebSocket interface for a session.
///
///
///
- /// A that manages the sessions in
- /// the service.
+ /// One of the enum values.
///
///
- /// if the session has not started yet.
+ /// It indicates the current state of the interface.
///
///
- protected WebSocketSessionManager Sessions {
+ ///
+ /// The get operation is not available when the session has not started yet.
+ ///
+ protected WebSocketState ReadyState {
get {
- return _sessions;
+ if (!_registered) {
+ var msg = "The session has not started yet.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _websocket.ReadyState;
}
}
- #endregion
+ ///
+ /// Gets the management function for the sessions in the service.
+ ///
+ ///
+ /// A that manages the sessions in
+ /// the service.
+ ///
+ ///
+ /// The get operation is not available when the session has not started yet.
+ ///
+ protected WebSocketSessionManager Sessions {
+ get {
+ if (!_registered) {
+ var msg = "The session has not started yet.";
- #region Public Properties
+ throw new InvalidOperationException (msg);
+ }
+
+ return _sessions;
+ }
+ }
///
- /// Gets the current state of the WebSocket connection for a session.
+ /// Gets the client information for a session.
///
///
///
- /// One of the enum values.
+ /// A instance that represents identity,
+ /// authentication, and security roles for the client.
///
///
- /// It indicates the current state of the connection.
- ///
- ///
- /// if the session has not
- /// started yet.
+ /// if the client is not authenticated.
///
///
- public WebSocketState ConnectionState {
+ ///
+ /// The get operation is not available when the session has not started yet.
+ ///
+ protected IPrincipal User {
get {
- return _websocket != null
- ? _websocket.ReadyState
- : WebSocketState.Connecting;
+ if (!_registered) {
+ var msg = "The session has not started yet.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _context.User;
}
}
///
- /// Gets the information in a WebSocket handshake request to the service.
+ /// Gets the client endpoint for a session.
///
///
- ///
- /// A instance that provides the access to
- /// the information in the handshake request.
- ///
- ///
- /// if the session has not started yet.
- ///
+ /// A that represents the client
+ /// IP address and port number.
///
- public WebSocketContext Context {
+ ///
+ /// The get operation is not available when the session has not started yet.
+ ///
+ protected System.Net.IPEndPoint UserEndPoint {
get {
- return _context;
+ if (!_registered) {
+ var msg = "The session has not started yet.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _context.UserEndPoint;
}
}
+ #endregion
+
+ #region Public Properties
+
///
- /// Gets or sets the delegate used to validate the HTTP cookies included in
- /// a WebSocket handshake request to the service.
+ /// Gets or sets the delegate used to respond to the HTTP cookies.
///
///
///
- /// A Func<CookieCollection, CookieCollection, bool> delegate
- /// or if not needed.
+ /// A
+ /// delegate.
///
///
- /// The delegate invokes the method called when the WebSocket instance
- /// for a session validates the handshake request.
+ /// It represents the delegate called when the WebSocket interface
+ /// for a session respond to the handshake request.
///
///
- /// 1st parameter passed to the method
- /// contains the cookies to validate if present.
+ /// 1st parameter passed to the delegate
+ /// contains the cookies included in the handshake request if any.
///
///
- /// 2nd parameter passed to the method
- /// receives the cookies to send to the client.
+ /// 2nd parameter passed to the delegate
+ /// holds the cookies to send to the client.
///
///
- /// The method must return true if the cookies are valid.
+ /// if not necessary.
///
///
/// The default value is .
///
///
- public Func CookiesValidator {
+ ///
+ /// The set operation is not available when the session has already started.
+ ///
+ public Action CookiesResponder {
get {
- return _cookiesValidator;
+ return _cookiesResponder;
}
set {
- _cookiesValidator = value;
+ if (_registered) {
+ var msg = "The session has already started.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _cookiesResponder = value;
}
}
///
- /// Gets or sets a value indicating whether the WebSocket instance for
- /// a session emits the message event when receives a ping.
+ /// Gets or sets a value indicating whether the WebSocket interface for
+ /// a session emits the message event when it receives a ping.
///
///
///
- /// true if the WebSocket instance emits the message event
- /// when receives a ping; otherwise, false.
+ /// true if the interface emits the message event when it receives
+ /// a ping; otherwise, false.
///
///
/// The default value is false.
///
///
+ ///
+ /// The set operation is not available when the session has already started.
+ ///
public bool EmitOnPing {
get {
- return _websocket != null ? _websocket.EmitOnPing : _emitOnPing;
+ return _emitOnPing;
}
set {
- if (_websocket != null) {
- _websocket.EmitOnPing = value;
- return;
+ if (_registered) {
+ var msg = "The session has already started.";
+
+ throw new InvalidOperationException (msg);
}
_emitOnPing = value;
@@ -259,123 +329,226 @@ public bool EmitOnPing {
}
///
- /// Gets the unique ID of a session.
+ /// Gets or sets the delegate used to validate the Host header.
///
///
///
- /// A that represents the unique ID of the session.
+ /// A delegate.
///
///
- /// if the session has not started yet.
+ /// It represents the delegate called when the WebSocket interface
+ /// for a session validates the handshake request.
+ ///
+ ///
+ /// The parameter passed to the delegate is
+ /// the value of the Host header.
+ ///
+ ///
+ /// The method invoked by the delegate must return true
+ /// if the header value is valid.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ /// The default value is .
///
///
+ ///
+ /// The set operation is not available when the session has already started.
+ ///
+ public Func HostValidator {
+ get {
+ return _hostValidator;
+ }
+
+ set {
+ if (_registered) {
+ var msg = "The session has already started.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _hostValidator = value;
+ }
+ }
+
+ ///
+ /// Gets the unique ID of a session.
+ ///
+ ///
+ /// A that represents the unique ID of the session.
+ ///
public string ID {
get {
+ if (_id == null)
+ _id = WebSocketSessionManager.CreateID ();
+
return _id;
}
}
///
- /// Gets or sets a value indicating whether the service ignores
- /// the Sec-WebSocket-Extensions header included in a WebSocket
- /// handshake request.
+ /// Gets or sets a value indicating whether the WebSocket interface for
+ /// a session ignores the Sec-WebSocket-Extensions header.
///
///
///
- /// true if the service ignores the extensions requested
- /// from a client; otherwise, false.
+ /// true if the interface ignores the extensions requested
+ /// from the client; otherwise, false.
///
///
/// The default value is false.
///
///
+ ///
+ /// The set operation is not available when the session has already started.
+ ///
public bool IgnoreExtensions {
get {
return _ignoreExtensions;
}
set {
+ if (_registered) {
+ var msg = "The session has already started.";
+
+ throw new InvalidOperationException (msg);
+ }
+
_ignoreExtensions = value;
}
}
///
- /// Gets or sets the delegate used to validate the Origin header included in
- /// a WebSocket handshake request to the service.
+ /// Gets or sets a value indicating whether the underlying TCP socket of
+ /// the WebSocket interface for a session disables a delay when send or
+ /// receive buffer is not full.
+ ///
+ ///
+ ///
+ /// true if the delay is disabled; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ ///
+ ///
+ /// The set operation is not available when the session has already started.
+ ///
+ public bool NoDelay {
+ get {
+ return _noDelay;
+ }
+
+ set {
+ if (_registered) {
+ var msg = "The session has already started.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _noDelay = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the delegate used to validate the Origin header.
///
///
///
- /// A 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 invoked by the delegate must return true
+ /// if the header value is valid.
///
///
- /// The method must return true if the header value is valid.
+ /// if not necessary.
///
///
/// The default value is .
///
///
+ ///
+ /// The set operation is not available when the session has already started.
+ ///
public Func OriginValidator {
get {
return _originValidator;
}
set {
+ if (_registered) {
+ var msg = "The session has already started.";
+
+ throw new InvalidOperationException (msg);
+ }
+
_originValidator = value;
}
}
///
- /// Gets or sets the name of the WebSocket subprotocol for the service.
+ /// Gets or sets the name of the WebSocket subprotocol for a session.
///
///
///
/// A that represents the name of the subprotocol.
///
///
- /// The value specified for a set must be a token defined in
+ /// The value specified for a set operation must be a token defined in
///
/// RFC 2616.
///
///
+ /// The value is initialized if not requested.
+ ///
+ ///
/// The default value is an empty string.
///
///
- ///
- /// The set operation is not available if the session has already started.
- ///
///
/// The value specified for a set operation is not a token.
///
+ ///
+ /// The set operation is not available when the session has already started.
+ ///
public string Protocol {
get {
- return _websocket != null
+ return _registered
? _websocket.Protocol
: (_protocol ?? String.Empty);
}
set {
- if (ConnectionState != WebSocketState.Connecting) {
+ if (_registered) {
var msg = "The session has already started.";
+
throw new InvalidOperationException (msg);
}
- if (value == null || value.Length == 0) {
+ if (value.IsNullOrEmpty ()) {
_protocol = null;
+
return;
}
- if (!value.IsToken ())
- throw new ArgumentException ("Not a token.", "value");
+ if (!value.IsToken ()) {
+ var msg = "Not a token.";
+
+ throw new ArgumentException (msg, "value");
+ }
_protocol = value;
}
@@ -390,7 +563,7 @@ public string Protocol {
/// has started.
///
///
- /// if the session has not started yet.
+ /// when the session has not started yet.
///
///
public DateTime StartTime {
@@ -399,22 +572,72 @@ public DateTime StartTime {
}
}
+ ///
+ /// Gets or sets the delegate used to respond to the user headers.
+ ///
+ ///
+ ///
+ /// A
+ /// delegate.
+ ///
+ ///
+ /// It represents the delegate called when the WebSocket interface
+ /// for a session respond to the handshake request.
+ ///
+ ///
+ /// 1st parameter passed to the delegate
+ /// contains the HTTP headers included in the handshake request.
+ ///
+ ///
+ /// 2nd parameter passed to the delegate
+ /// holds the user headers to send to the client.
+ ///
+ ///
+ /// if not necessary.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ ///
+ /// The set operation is not available when the session has already started.
+ ///
+ public Action UserHeadersResponder {
+ get {
+ return _userHeadersResponder;
+ }
+
+ set {
+ if (_registered) {
+ var msg = "The session has already started.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _userHeadersResponder = value;
+ }
+ }
+
#endregion
#region Private Methods
private string checkHandshakeRequest (WebSocketContext context)
{
- if (_originValidator != null) {
- if (!_originValidator (context.Origin))
- return "It includes no Origin header or an invalid one.";
+ if (_hostValidator != null) {
+ if (!_hostValidator (context.Host)) {
+ var msg = "The Host header is invalid.";
+
+ return msg;
+ }
}
- if (_cookiesValidator != null) {
- var req = context.CookieCollection;
- var res = context.WebSocket.CookieCollection;
- if (!_cookiesValidator (req, res))
- return "It includes no cookie or an invalid one.";
+ if (_originValidator != null) {
+ if (!_originValidator (context.Origin)) {
+ var msg = "The Origin header is non-existent or invalid.";
+
+ return msg;
+ }
}
return null;
@@ -422,10 +645,11 @@ private string checkHandshakeRequest (WebSocketContext context)
private void onClose (object sender, CloseEventArgs e)
{
- if (_id == null)
+ if (!_registered)
return;
_sessions.Remove (_id);
+
OnClose (e);
}
@@ -441,48 +665,75 @@ private void onMessage (object sender, MessageEventArgs e)
private void onOpen (object sender, EventArgs e)
{
- _id = _sessions.Add (this);
- if (_id == null) {
+ _registered = _sessions.Add (this);
+
+ if (!_registered) {
_websocket.Close (CloseStatusCode.Away);
+
return;
}
_startTime = DateTime.Now;
+
OnOpen ();
}
- #endregion
+ private void respondToHandshakeRequest (WebSocketContext context)
+ {
+ if (_cookiesResponder != null) {
+ var reqCookies = context.CookieCollection;
+ var resCookies = context.WebSocket.Cookies;
- #region Internal Methods
+ _cookiesResponder (reqCookies, resCookies);
+ }
- internal void Start (WebSocketContext context, WebSocketSessionManager sessions)
- {
- if (_websocket != null) {
- _websocket.Log.Error ("A session instance cannot be reused.");
- context.WebSocket.Close (HttpStatusCode.ServiceUnavailable);
+ if (_userHeadersResponder != null) {
+ var reqHeaders = context.Headers;
+ var userHeaders = context.WebSocket.UserHeaders;
- return;
+ _userHeadersResponder (reqHeaders, userHeaders);
}
+ }
+ #endregion
+
+ #region Internal Methods
+
+ internal void Start (
+ WebSocketContext context,
+ WebSocketSessionManager sessions
+ )
+ {
_context = context;
_sessions = sessions;
_websocket = context.WebSocket;
_websocket.CustomHandshakeRequestChecker = checkHandshakeRequest;
- _websocket.EmitOnPing = _emitOnPing;
- _websocket.IgnoreExtensions = _ignoreExtensions;
- _websocket.Protocol = _protocol;
+ _websocket.CustomHandshakeRequestResponder = respondToHandshakeRequest;
+
+ if (_emitOnPing)
+ _websocket.EmitOnPing = true;
+
+ if (_ignoreExtensions)
+ _websocket.IgnoreExtensions = true;
+
+ if (_noDelay)
+ _websocket.NoDelay = true;
+
+ if (_protocol != null)
+ _websocket.Protocol = _protocol;
var waitTime = sessions.WaitTime;
+
if (waitTime != _websocket.WaitTime)
_websocket.WaitTime = waitTime;
- _websocket.OnOpen += onOpen;
- _websocket.OnMessage += onMessage;
- _websocket.OnError += onError;
_websocket.OnClose += onClose;
+ _websocket.OnError += onError;
+ _websocket.OnMessage += onMessage;
+ _websocket.OnOpen += onOpen;
- _websocket.InternalAccept ();
+ _websocket.Accept ();
}
#endregion
@@ -493,16 +744,17 @@ 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 when the current state of the WebSocket
+ /// interface is Closing or Closed.
///
///
- /// The session has not started yet.
+ /// This method is not available when the session has not started yet.
///
protected void Close ()
{
- if (_websocket == null) {
+ if (!_registered) {
var msg = "The session has not started yet.";
+
throw new InvalidOperationException (msg);
}
@@ -511,15 +763,15 @@ protected void Close ()
///
/// Closes the WebSocket connection for a session with the specified
- /// code and reason.
+ /// status code and reason.
///
///
- /// This method does nothing if the current state of the connection is
- /// Closing or Closed.
+ /// This method does nothing when the current state of the WebSocket
+ /// interface is Closing or Closed.
///
///
///
- /// A that represents the status code indicating
+ /// A that specifies the status code indicating
/// the reason for the close.
///
///
@@ -530,47 +782,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.
///
///
+ ///
+ /// This method is not available when the session has not started yet.
+ ///
protected void Close (ushort code, string reason)
{
- if (_websocket == null) {
+ if (!_registered) {
var msg = "The session has not started yet.";
+
throw new InvalidOperationException (msg);
}
@@ -579,45 +833,44 @@ protected void Close (ushort code, string reason)
///
/// Closes the WebSocket connection for a session with the specified
- /// code and reason.
+ /// status code and reason.
///
///
- /// This method does nothing if the current state of the connection is
- /// Closing or Closed.
+ /// This method does nothing when the current state of the WebSocket
+ /// interface is Closing or Closed.
///
///
///
/// 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 an undefined enum value.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
///
///
/// -or-
///
///
- /// is
- /// and there is reason.
+ /// is and
+ /// is specified.
///
///
/// -or-
@@ -626,10 +879,17 @@ protected void Close (ushort code, string reason)
/// could not be UTF-8-encoded.
///
///
+ ///
+ /// The size of is greater than 123 bytes.
+ ///
+ ///
+ /// This method is not available when the session has not started yet.
+ ///
protected void Close (CloseStatusCode code, string reason)
{
- if (_websocket == null) {
+ if (!_registered) {
var msg = "The session has not started yet.";
+
throw new InvalidOperationException (msg);
}
@@ -644,17 +904,18 @@ protected void Close (CloseStatusCode code, string reason)
/// This method does not wait for the close to be complete.
///
///
- /// This method does nothing if the current state of the connection is
- /// Closing or Closed.
+ /// This method does nothing when the current state of the WebSocket
+ /// interface is Closing or Closed.
///
///
///
- /// The session has not started yet.
+ /// This method is not available when the session has not started yet.
///
protected void CloseAsync ()
{
- if (_websocket == null) {
+ if (!_registered) {
var msg = "The session has not started yet.";
+
throw new InvalidOperationException (msg);
}
@@ -663,20 +924,20 @@ protected void CloseAsync ()
///
/// Closes the WebSocket connection for a session asynchronously with
- /// the specified code and reason.
+ /// the specified status code and reason.
///
///
///
/// This method does not wait for the close to be complete.
///
///
- /// This method does nothing if the current state of the connection is
- /// Closing or Closed.
+ /// This method does nothing when the current state of the WebSocket
+ /// interface is Closing or Closed.
///
///
///
///
- /// A that represents the status code indicating
+ /// A that specifies the status code indicating
/// the reason for the close.
///
///
@@ -687,47 +948,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.
///
///
+ ///
+ /// This method is not available when the session has not started yet.
+ ///
protected void CloseAsync (ushort code, string reason)
{
- if (_websocket == null) {
+ if (!_registered) {
var msg = "The session has not started yet.";
+
throw new InvalidOperationException (msg);
}
@@ -736,15 +999,15 @@ protected void CloseAsync (ushort code, string reason)
///
/// Closes the WebSocket connection for a session asynchronously with
- /// the specified code and reason.
+ /// the specified status code and reason.
///
///
///
/// This method does not wait for the close to be complete.
///
///
- /// This method does nothing if the current state of the connection is
- /// Closing or Closed.
+ /// This method does nothing when the current state of the WebSocket
+ /// interface is Closing or Closed.
///
///
///
@@ -752,31 +1015,33 @@ 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 an undefined enum value.
///
///
/// -or-
///
///
- /// is
- /// and there is reason.
+ /// is .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is and
+ /// is specified.
///
///
/// -or-
@@ -788,72 +1053,48 @@ protected void CloseAsync (ushort code, string reason)
///
/// The size of is greater than 123 bytes.
///
+ ///
+ /// This method is not available when the session has not started yet.
+ ///
protected void CloseAsync (CloseStatusCode code, string reason)
{
- if (_websocket == null) {
+ if (!_registered) {
var msg = "The session has not started yet.";
+
throw new InvalidOperationException (msg);
}
_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.
///
///
- /// A that represents the event data passed
- /// from a event.
+ /// A that represents the close event data
+ /// passed from the WebSocket interface.
///
protected virtual void OnClose (CloseEventArgs e)
{
}
///
- /// Called when the WebSocket instance for a session gets an error.
+ /// Called when the WebSocket interface for a session gets an error.
///
///
- /// A that represents the event data passed
- /// from a event.
+ /// A that represents the error event data
+ /// passed from the interface.
///
protected virtual void OnError (ErrorEventArgs e)
{
}
///
- /// Called when the WebSocket instance for a session receives a message.
+ /// Called when the WebSocket interface for a session receives a message.
///
///
- /// A that represents the event data passed
- /// from a event.
+ /// A that represents the message event data
+ /// passed from the interface.
///
protected virtual void OnMessage (MessageEventArgs e)
{
@@ -867,21 +1108,87 @@ 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.
+ ///
+ ///
+ /// This method is not available when the session has not started yet.
+ ///
+ protected bool Ping ()
+ {
+ if (!_registered) {
+ 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.
+ /// This method is not available when the session has not started yet.
///
+ protected bool Ping (string message)
+ {
+ if (!_registered) {
+ 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 .
///
+ ///
+ ///
+ /// This method is not available when the session has not started yet.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// This method is not available when the current state of the WebSocket
+ /// interface is not Open.
+ ///
+ ///
protected void Send (byte[] data)
{
- if (_websocket == null) {
- var msg = "The current state of the connection is not Open.";
+ if (!_registered) {
+ var msg = "The session has not started yet.";
+
throw new InvalidOperationException (msg);
}
@@ -889,7 +1196,7 @@ protected void Send (byte[] data)
}
///
- /// Sends the specified file to a client using the WebSocket connection.
+ /// Sends the specified file to the client for a session.
///
///
///
@@ -899,27 +1206,37 @@ protected void Send (byte[] data)
/// The file is sent as the binary data.
///
///
- ///
- /// The current state of the connection is not Open.
+ ///
+ ///
+ /// The file does not exist.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The file could not be opened.
+ ///
///
///
/// is .
///
- ///
+ ///
///
- /// The file does not exist.
+ /// This method is not available when the session has not started yet.
///
///
/// -or-
///
///
- /// The file could not be opened.
+ /// This method is not available when the current state of the WebSocket
+ /// interface is not Open.
///
///
protected void Send (FileInfo fileInfo)
{
- if (_websocket == null) {
- var msg = "The current state of the connection is not Open.";
+ if (!_registered) {
+ var msg = "The session has not started yet.";
+
throw new InvalidOperationException (msg);
}
@@ -927,24 +1244,34 @@ 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.
+ ///
+ ///
+ /// This method is not available when the session has not started yet.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// This method is not available when the current state of the WebSocket
+ /// interface is not Open.
+ ///
///
protected void Send (string data)
{
- if (_websocket == null) {
- var msg = "The current state of the connection is not Open.";
+ if (!_registered) {
+ var msg = "The session has not started yet.";
+
throw new InvalidOperationException (msg);
}
@@ -952,8 +1279,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 +1293,6 @@ protected void Send (string data)
///
/// An that specifies the number of bytes to send.
///
- ///
- /// The current state of the connection is not Open.
- ///
- ///
- /// is .
- ///
///
///
/// cannot be read.
@@ -989,10 +1310,26 @@ protected void Send (string data)
/// No data could be read from .
///
///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// This method is not available when the session has not started yet.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// This method is not available when the current state of the WebSocket
+ /// interface is not Open.
+ ///
+ ///
protected void Send (Stream stream, int length)
{
- if (_websocket == null) {
- var msg = "The current state of the connection is not Open.";
+ if (!_registered) {
+ var msg = "The session has not started yet.";
+
throw new InvalidOperationException (msg);
}
@@ -1000,38 +1337,49 @@ 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.
+ ///
+ ///
+ /// It specifies the delegate called when the send is complete.
///
///
- /// The delegate invokes the method called when the send is complete.
+ /// The parameter passed to the delegate is true
+ /// if the send has successfully done; otherwise, false.
///
///
- /// true is passed to the method if the send has done with
- /// no error; otherwise, false.
+ /// if not necessary.
///
///
- ///
- /// The current state of the connection is not Open.
- ///
///
/// is .
///
+ ///
+ ///
+ /// This method is not available when the session has not started yet.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// This method is not available when the current state of the WebSocket
+ /// interface is not Open.
+ ///
+ ///
protected void SendAsync (byte[] data, Action completed)
{
- if (_websocket == null) {
- var msg = "The current state of the connection is not Open.";
+ if (!_registered) {
+ var msg = "The session has not started yet.";
+
throw new InvalidOperationException (msg);
}
@@ -1039,8 +1387,7 @@ protected void SendAsync (byte[] data, Action completed)
}
///
- /// Sends the specified file to a client asynchronously using
- /// the WebSocket connection.
+ /// Sends the specified file to the client for a session asynchronously.
///
///
/// This method does not wait for the send to be complete.
@@ -1055,38 +1402,50 @@ protected void SendAsync (byte[] data, Action completed)
///
///
///
- /// An Action<bool> delegate or
- /// if not needed.
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the send is complete.
///
///
- /// The delegate invokes the method called when the send is complete.
+ /// The parameter passed to the delegate is true
+ /// if the send has successfully done; otherwise, false.
///
///
- /// true is passed to the method if the send has done with
- /// no error; otherwise, false.
+ /// if not necessary.
///
///
- ///
- /// The current state of the connection is not Open.
+ ///
+ ///
+ /// The file does not exist.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The file could not be opened.
+ ///
///
///
/// is .
///
- ///
+ ///
///
- /// The file does not exist.
+ /// This method is not available when the session has not started yet.
///
///
/// -or-
///
///
- /// The file could not be opened.
+ /// This method is not available when the current state of the WebSocket
+ /// interface is not Open.
///
///
protected void SendAsync (FileInfo fileInfo, Action completed)
{
- if (_websocket == null) {
- var msg = "The current state of the connection is not Open.";
+ if (!_registered) {
+ var msg = "The session has not started yet.";
+
throw new InvalidOperationException (msg);
}
@@ -1094,41 +1453,52 @@ 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.
+ ///
+ ///
+ /// This method is not available when the session has not started yet.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// This method is not available when the current state of the WebSocket
+ /// interface is not Open.
+ ///
///
protected void SendAsync (string data, Action completed)
{
- if (_websocket == null) {
- var msg = "The current state of the connection is not Open.";
+ if (!_registered) {
+ var msg = "The session has not started yet.";
+
throw new InvalidOperationException (msg);
}
@@ -1136,8 +1506,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 +1525,19 @@ protected void SendAsync (string data, Action completed)
///
///
///
- /// An Action<bool> delegate or
- /// if not needed.
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the send is complete.
///
///
- /// The delegate invokes the method called when the send is complete.
+ /// The parameter passed to the delegate is true
+ /// if the send has successfully done; otherwise, false.
///
///
- /// true is passed to the method if the send has done with
- /// no error; otherwise, false.
+ /// if not necessary.
///
///
- ///
- /// The current state of the connection is not Open.
- ///
- ///
- /// is .
- ///
///
///
/// cannot be read.
@@ -1189,10 +1555,26 @@ protected void SendAsync (string data, Action completed)
/// No data could be read from .
///
///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// This method is not available when the session has not started yet.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// This method is not available when the current state of the WebSocket
+ /// interface is not Open.
+ ///
+ ///
protected void SendAsync (Stream stream, int length, Action completed)
{
- if (_websocket == null) {
- var msg = "The current state of the connection is not Open.";
+ if (!_registered) {
+ var msg = "The session has not started yet.";
+
throw new InvalidOperationException (msg);
}
@@ -1200,5 +1582,27 @@ protected void SendAsync (Stream stream, int length, Action completed)
}
#endregion
+
+ #region Explicit Interface Implementations
+
+ ///
+ /// Gets the WebSocket interface for a session.
+ ///
+ ///
+ ///
+ /// A that represents
+ /// the WebSocket interface.
+ ///
+ ///
+ /// when the session has not started yet.
+ ///
+ ///
+ WebSocket IWebSocketSession.WebSocket {
+ get {
+ return _websocket;
+ }
+ }
+
+ #endregion
}
}
diff --git a/websocket-sharp/Server/WebSocketServer.cs b/websocket-sharp/Server/WebSocketServer.cs
index 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..e1d1a2001 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-2025 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -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,50 +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;
- }
-
- private static string createID ()
- {
- return Guid.NewGuid ().ToString ("N");
+ return _state == ServerState.Ready || _state == ServerState.Stop;
}
private void setSweepTimer (double interval)
@@ -429,16 +425,18 @@ private void setSweepTimer (double interval)
private void stop (PayloadData payloadData, bool send)
{
- var bytes = send
- ? WebSocketFrame.CreateCloseFrame (payloadData, false).ToArray ()
- : null;
+ var rawFrame = send
+ ? WebSocketFrame
+ .CreateCloseFrame (payloadData, false)
+ .ToArray ()
+ : null;
lock (_sync) {
_state = ServerState.ShuttingDown;
-
_sweepTimer.Enabled = false;
+
foreach (var session in _sessions.Values.ToList ())
- session.Context.WebSocket.Close (payloadData, bytes);
+ session.WebSocket.Close (payloadData, rawFrame);
_state = ServerState.Stop;
}
@@ -463,64 +461,21 @@ private bool tryGetSession (string id, out IWebSocketSession session)
#region Internal Methods
- internal string Add (IWebSocketSession session)
+ internal bool Add (IWebSocketSession session)
{
lock (_sync) {
if (_state != ServerState.Start)
- return null;
-
- var id = createID ();
- _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);
- }
- }
+ return false;
- 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;
- }
+ _sessions.Add (session.ID, session);
- session.Context.WebSocket.Send (opcode, stream, cache);
+ return true;
}
}
- internal Dictionary Broadping (
- byte[] frameAsBytes, TimeSpan timeout
- )
+ internal static string CreateID ()
{
- 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;
+ return Guid.NewGuid ().ToString ("N");
}
internal bool Remove (string id)
@@ -532,19 +487,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 +511,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 +540,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 +566,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 +580,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 +611,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 +630,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 +663,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 +707,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 +748,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 +762,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 +807,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 +826,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 +859,42 @@ public void BroadcastAsync (Stream stream, int length, Action completed)
}
///
- /// Sends a ping 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.
- ///
- ///
- ///
- /// 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.
- ///
- ///
- ///
- /// 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, status 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 +905,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 +925,8 @@ public void CloseSession (string id)
/// -or-
///
///
- /// is 1005 (no status) and there is
- /// .
+ /// is 1005 (no status) and
+ /// is specified.
///
///
/// -or-
@@ -1042,8 +935,8 @@ public void CloseSession (string id)
/// could not be UTF-8-encoded.
///
///
- ///
- /// The session could not be found.
+ ///
+ /// is .
///
///
///
@@ -1056,43 +949,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, status 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 +995,20 @@ public void CloseSession (string id, ushort code, string reason)
/// -or-
///
///
- /// is
- /// .
+ /// is an undefined enum value.
///
///
/// -or-
///
///
- /// is
- /// and there is
- /// .
+ /// is .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is and
+ /// is specified.
///
///
/// -or-
@@ -1119,75 +1017,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 +1101,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 +1146,6 @@ public bool PingTo (string message, string id)
/// is .
///
///
- ///
- /// is an empty string.
- ///
///
///
/// The session could not be found.
@@ -1247,49 +1154,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 +1209,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 +1266,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 +1285,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.
///
///
- /// 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 an empty string.
+ ///
///
///
/// is .
@@ -1423,9 +1344,6 @@ public void SendTo (Stream stream, int length, string id)
/// is .
///
///
- ///
- /// is an empty string.
- ///
///
///
/// The session could not be found.
@@ -1434,66 +1352,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.
+ ///
+ ///
+ /// 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 .
+ /// 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 +1427,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.
+ /// An delegate.
///
///
- /// The delegate invokes the method called when the send is complete.
+ /// It specifies the delegate called when the send is complete.
///
///
- /// true is passed to the method if the send has done with
- /// no error; otherwise, false.
+ /// The parameter passed to the delegate is
+ /// true if the send has successfully done; otherwise,
+ /// false.
///
- ///
- ///
///
- /// is .
+ /// if not necessary.
///
- ///
- /// -or-
- ///
- ///
- /// is .
- ///
- ///
+ ///
///
///
/// is an empty string.
@@ -1586,6 +1503,17 @@ public void SendToAsync (string data, string id, Action completed)
/// No data could be read from .
///
///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ ///
+ ///
///
///
/// The session could not be found.
@@ -1594,20 +1522,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 +1549,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 +1573,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..5d4727f42 100644
--- a/websocket-sharp/WebSocket.cs
+++ b/websocket-sharp/WebSocket.cs
@@ -8,7 +8,7 @@
* The MIT License
*
* Copyright (c) 2009 Adam MacBeth
- * Copyright (c) 2010-2016 sta.blockhead
+ * Copyright (c) 2010-2025 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -74,16 +74,15 @@ public class WebSocket : IDisposable
private AuthenticationChallenge _authChallenge;
private string _base64Key;
- private bool _client;
private Action _closeContext;
private CompressionMethod _compression;
private WebSocketContext _context;
private CookieCollection _cookies;
private NetworkCredential _credentials;
private bool _emitOnPing;
+ private static readonly byte[] _emptyBytes;
private bool _enableRedirection;
private string _extensions;
- private bool _extensionsRequested;
private object _forMessageEventQueue;
private object _forPing;
private object _forSend;
@@ -93,30 +92,38 @@ public class WebSocket : IDisposable
private Opcode _fragmentsOpcode;
private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private Func _handshakeRequestChecker;
+ private Action _handshakeRequestResponder;
+ private CookieCollection _handshakeResponseCookies;
+ private NameValueCollection _handshakeResponseHeaders;
+ private bool _hasExtension;
+ private bool _hasProtocol;
private bool _ignoreExtensions;
private bool _inContinuation;
private volatile bool _inMessage;
- private volatile Logger _logger;
+ private bool _isClient;
+ private bool _isSecure;
+ private volatile Logger _log;
private static readonly int _maxRetryCountForConnect;
private Action _message;
private Queue _messageEventQueue;
+ private bool _noDelay;
private uint _nonceCount;
private string _origin;
private ManualResetEvent _pongReceived;
private bool _preAuth;
private string _protocol;
private string[] _protocols;
- private bool _protocolsRequested;
private NetworkCredential _proxyCredentials;
private Uri _proxyUri;
private volatile WebSocketState _readyState;
private ManualResetEvent _receivingExited;
private int _retryCountForConnect;
- private bool _secure;
+ private Socket _socket;
private ClientSslConfiguration _sslConfig;
private Stream _stream;
private TcpClient _tcpClient;
private Uri _uri;
+ private WebHeaderCollection _userHeaders;
private const string _version = "13";
private TimeSpan _waitTime;
@@ -125,20 +132,17 @@ public class WebSocket : IDisposable
#region Internal Fields
///
- /// Represents the empty array of used internally.
- ///
- internal static readonly byte[] EmptyBytes;
-
- ///
- /// Represents the length used to determine whether the data should be fragmented in sending.
+ /// Represents the length used to determine whether the data should
+ /// be fragmented in sending.
///
///
///
- /// The data will be fragmented if that length is greater than the value of this field.
+ /// The data will be fragmented if its length is greater than
+ /// the value of this field.
///
///
- /// If you would like to change the value, you must set it to a value between 125 and
- /// Int32.MaxValue - 14 inclusive.
+ /// If you would like to change the value, you must set it to
+ /// a value between 125 and Int32.MaxValue - 14 inclusive.
///
///
internal static readonly int FragmentLength;
@@ -154,8 +158,9 @@ public class WebSocket : IDisposable
static WebSocket ()
{
+ _emptyBytes = new byte[0];
_maxRetryCountForConnect = 10;
- EmptyBytes = new byte[0];
+
FragmentLength = 1016;
RandomNumber = new RNGCryptoServiceProvider ();
}
@@ -171,9 +176,10 @@ internal WebSocket (HttpListenerWebSocketContext context, string protocol)
_protocol = protocol;
_closeContext = context.Close;
- _logger = context.Log;
+ _isSecure = context.IsSecureConnection;
+ _log = context.Log;
_message = messages;
- _secure = context.IsSecureConnection;
+ _socket = context.Socket;
_stream = context.Stream;
_waitTime = TimeSpan.FromSeconds (1);
@@ -187,9 +193,10 @@ internal WebSocket (TcpListenerWebSocketContext context, string protocol)
_protocol = protocol;
_closeContext = context.Close;
- _logger = context.Log;
+ _isSecure = context.IsSecureConnection;
+ _log = context.Log;
_message = messages;
- _secure = context.IsSecureConnection;
+ _socket = context.Socket;
_stream = context.Stream;
_waitTime = TimeSpan.FromSeconds (1);
@@ -202,7 +209,7 @@ internal WebSocket (TcpListenerWebSocketContext context, string protocol)
///
/// Initializes a new instance of the class with
- /// and optionally .
+ /// the specified URL and optionally subprotocols.
///
///
///
@@ -226,9 +233,6 @@ internal WebSocket (TcpListenerWebSocketContext context, string protocol)
/// RFC 2616.
///
///
- ///
- /// is .
- ///
///
///
/// is an empty string.
@@ -252,6 +256,9 @@ internal WebSocket (TcpListenerWebSocketContext context, string protocol)
/// contains a value twice.
///
///
+ ///
+ /// is .
+ ///
public WebSocket (string url, params string[] protocols)
{
if (url == null)
@@ -261,6 +268,7 @@ public WebSocket (string url, params string[] protocols)
throw new ArgumentException ("An empty string.", "url");
string msg;
+
if (!url.TryCreateWebSocketUri (out _uri, out msg))
throw new ArgumentException (msg, "url");
@@ -269,13 +277,15 @@ public WebSocket (string url, params string[] protocols)
throw new ArgumentException (msg, "protocols");
_protocols = protocols;
+ _hasProtocol = true;
}
_base64Key = CreateBase64Key ();
- _client = true;
- _logger = new Logger ();
+ _isClient = true;
+ _isSecure = _uri.Scheme == "wss";
+ _log = new Logger ();
_message = messagec;
- _secure = _uri.Scheme == "wss";
+ _retryCountForConnect = -1;
_waitTime = TimeSpan.FromSeconds (5);
init ();
@@ -285,8 +295,11 @@ public WebSocket (string url, params string[] protocols)
#region Internal Properties
- internal CookieCollection CookieCollection {
+ internal CookieCollection Cookies {
get {
+ if (_cookies == null)
+ _cookies = new CookieCollection ();
+
return _cookies;
}
}
@@ -302,10 +315,14 @@ internal Func CustomHandshakeRequestChecker {
}
}
- internal bool HasMessage {
+ // As server
+ internal Action CustomHandshakeRequestResponder {
get {
- lock (_forMessageEventQueue)
- return _messageEventQueue.Count > 0;
+ return _handshakeRequestResponder;
+ }
+
+ set {
+ _handshakeRequestResponder = value;
}
}
@@ -320,9 +337,17 @@ internal bool IgnoreExtensions {
}
}
- internal bool IsConnected {
+ internal WebHeaderCollection UserHeaders {
get {
- return _readyState == WebSocketState.Open || _readyState == WebSocketState.Closing;
+ if (_userHeaders == null) {
+ var state = _isClient
+ ? HttpHeaderType.Request
+ : HttpHeaderType.Response;
+
+ _userHeaders = new WebHeaderCollection (state, false);
+ }
+
+ return _userHeaders;
}
}
@@ -333,23 +358,29 @@ internal bool IsConnected {
///
/// Gets or sets the compression method used to compress a message.
///
- ///
- /// The set operation does nothing if the connection has already been
- /// established or it is closing.
- ///
///
///
/// One of the enum values.
///
///
- /// It specifies the compression method used to compress a message.
+ /// It indicates the compression method used to compress a message.
///
///
/// The default value is .
///
///
///
- /// The set operation is not available if this instance is not a client.
+ ///
+ /// The set operation is not available if the interface is not for
+ /// the client.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The set operation is not available when the current state of
+ /// the interface is neither New nor Closed.
+ ///
///
public CompressionMethod Compression {
get {
@@ -357,22 +388,17 @@ public CompressionMethod Compression {
}
set {
- string msg = null;
+ if (!_isClient) {
+ var msg = "The set operation is not available.";
- if (!_client) {
- msg = "This instance is not a client.";
throw new InvalidOperationException (msg);
}
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
- }
-
lock (_forState) {
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
+ if (!canSet ()) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
}
_compression = value;
@@ -380,28 +406,6 @@ public CompressionMethod Compression {
}
}
- ///
- /// Gets the HTTP cookies included in the handshake request/response.
- ///
- ///
- ///
- /// An
- /// instance.
- ///
- ///
- /// It provides an enumerator which supports the iteration over
- /// the collection of the cookies.
- ///
- ///
- public IEnumerable Cookies {
- get {
- lock (_cookies.SyncRoot) {
- foreach (Cookie cookie in _cookies)
- yield return cookie;
- }
- }
- }
-
///
/// Gets the credentials for the HTTP authentication (Basic/Digest).
///
@@ -421,25 +425,37 @@ public NetworkCredential Credentials {
}
///
- /// Gets or sets a value indicating whether a event
- /// is emitted when a ping is received.
+ /// Gets or sets a value indicating whether the interface emits
+ /// the message event when it receives a ping.
///
///
///
- /// true if this instance emits a event
- /// when receives a ping; otherwise, false.
+ /// true if the interface emits the message event when
+ /// it receives a ping; otherwise, false.
///
///
/// The default value is false.
///
///
+ ///
+ /// The set operation is not available when the current state of
+ /// the interface is neither New nor Closed.
+ ///
public bool EmitOnPing {
get {
return _emitOnPing;
}
set {
- _emitOnPing = value;
+ lock (_forState) {
+ if (!canSet ()) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _emitOnPing = value;
+ }
}
}
@@ -447,13 +463,9 @@ public bool EmitOnPing {
/// Gets or sets a value indicating whether the URL redirection for
/// the handshake request is allowed.
///
- ///
- /// The set operation does nothing if the connection has already been
- /// established or it is closing.
- ///
///
///
- /// true if this instance allows the URL redirection for
+ /// true if the interface allows the URL redirection for
/// the handshake request; otherwise, false.
///
///
@@ -461,7 +473,17 @@ public bool EmitOnPing {
///
///
///
- /// The set operation is not available if this instance is not a client.
+ ///
+ /// The set operation is not available if the interface is not for
+ /// the client.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The set operation is not available when the current state of
+ /// the interface is neither New nor Closed.
+ ///
///
public bool EnableRedirection {
get {
@@ -469,22 +491,17 @@ public bool EnableRedirection {
}
set {
- string msg = null;
+ if (!_isClient) {
+ var msg = "The set operation is not available.";
- if (!_client) {
- msg = "This instance is not a client.";
throw new InvalidOperationException (msg);
}
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
- }
-
lock (_forState) {
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
+ if (!canSet ()) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
}
_enableRedirection = value;
@@ -493,12 +510,16 @@ public bool EnableRedirection {
}
///
- /// Gets the extensions selected by server.
+ /// Gets the extensions selected by the server.
///
///
- /// A that will be a list of the extensions
- /// negotiated between client and server, or an empty string if
- /// not specified or selected.
+ ///
+ /// A that represents a list of the extensions
+ /// negotiated between the client and server.
+ ///
+ ///
+ /// An empty string if not specified or selected.
+ ///
///
public string Extensions {
get {
@@ -507,31 +528,122 @@ public string Extensions {
}
///
- /// Gets a value indicating whether the connection is alive.
+ /// Gets the HTTP cookies included in the handshake response.
+ ///
+ ///
+ ///
+ /// A that contains the cookies
+ /// included in the handshake response if any.
+ ///
+ ///
+ /// if the interface could not receive
+ /// the handshake response.
+ ///
+ ///
+ ///
+ ///
+ /// The get operation is not available if the interface is not for
+ /// the client.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The get operation is not available when the current state of
+ /// the interface is New or Connecting.
+ ///
+ ///
+ public CookieCollection HandshakeResponseCookies {
+ get {
+ if (!_isClient) {
+ var msg = "The get operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ lock (_forState) {
+ var canGet = _readyState > WebSocketState.Connecting;
+
+ if (!canGet) {
+ var msg = "The get operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _handshakeResponseCookies;
+ }
+ }
+ }
+
+ ///
+ /// Gets the HTTP headers included in the handshake response.
+ ///
+ ///
+ ///
+ /// A that contains the headers
+ /// included in the handshake response.
+ ///
+ ///
+ /// if the interface could not receive
+ /// the handshake response.
+ ///
+ ///
+ ///
+ ///
+ /// The get operation is not available if the interface is not for
+ /// the client.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The get operation is not available when the current state of
+ /// the interface is New or Connecting.
+ ///
+ ///
+ public NameValueCollection HandshakeResponseHeaders {
+ get {
+ if (!_isClient) {
+ var msg = "The get operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ lock (_forState) {
+ var canGet = _readyState > WebSocketState.Connecting;
+
+ if (!canGet) {
+ var msg = "The get operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _handshakeResponseHeaders;
+ }
+ }
+ }
+
+ ///
+ /// Gets a value indicating whether the communication is possible.
///
- ///
- /// The get operation returns the value by using a ping/pong
- /// if the current state of the connection is Open.
- ///
///
- /// true if the connection is alive; otherwise, false.
+ /// true if the communication is possible; otherwise, false.
///
public bool IsAlive {
get {
- return ping (EmptyBytes);
+ return ping (_emptyBytes);
}
}
///
- /// Gets a value indicating whether a secure connection is used.
+ /// Gets a value indicating whether the connection is secure.
///
///
- /// true if this instance uses a secure connection; otherwise,
- /// false.
+ /// true if the connection is secure; otherwise, false.
///
public bool IsSecure {
get {
- return _secure;
+ return _isSecure;
}
}
@@ -544,13 +656,58 @@ public bool IsSecure {
///
/// A that provides the logging function.
///
+ ///
+ /// The get operation is not available if the interface is not for
+ /// the client.
+ ///
public Logger Log {
get {
- return _logger;
+ if (!_isClient) {
+ var msg = "The get operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ return _log;
}
internal set {
- _logger = value;
+ _log = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the underlying TCP socket
+ /// disables a delay when send or receive buffer is not full.
+ ///
+ ///
+ ///
+ /// true if the delay is disabled; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ ///
+ ///
+ /// The set operation is not available when the current state of
+ /// the interface is neither New nor Closed.
+ ///
+ public bool NoDelay {
+ get {
+ return _noDelay;
+ }
+
+ set {
+ lock (_forState) {
+ if (!canSet ()) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ _noDelay = value;
+ }
}
}
@@ -565,11 +722,7 @@ internal set {
/// Section 7 of RFC 6454.
///
///
- /// This instance sends the Origin header if this property has any.
- ///
- ///
- /// The set operation does nothing if the connection has already been
- /// established or it is closing.
+ /// The interface sends the Origin header if this property has any.
///
///
///
@@ -584,9 +737,6 @@ internal set {
/// The default value is .
///
///
- ///
- /// The set operation is not available if this instance is not a client.
- ///
///
///
/// The value specified for a set operation is not an absolute URI string.
@@ -598,41 +748,52 @@ internal set {
/// The value specified for a set operation includes the path segments.
///
///
+ ///
+ ///
+ /// The set operation is not available if the interface is not for
+ /// the client.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The set operation is not available when the current state of
+ /// the interface is neither New nor Closed.
+ ///
+ ///
public string Origin {
get {
return _origin;
}
set {
- string msg = null;
+ if (!_isClient) {
+ var msg = "The set operation is not available.";
- if (!_client) {
- msg = "This instance is not a client.";
throw new InvalidOperationException (msg);
}
if (!value.IsNullOrEmpty ()) {
Uri uri;
+
if (!Uri.TryCreate (value, UriKind.Absolute, out uri)) {
- msg = "Not an absolute URI string.";
+ var msg = "Not an absolute URI string.";
+
throw new ArgumentException (msg, "value");
}
if (uri.Segments.Length > 1) {
- msg = "It includes the path segments.";
+ var msg = "It includes the path segments.";
+
throw new ArgumentException (msg, "value");
}
}
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
- }
-
lock (_forState) {
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
+ if (!canSet ()) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
}
_origin = !value.IsNullOrEmpty () ? value.TrimEnd ('/') : value;
@@ -663,17 +824,17 @@ internal set {
}
///
- /// Gets the current state of the connection.
+ /// Gets the current state of the interface.
///
///
///
/// One of the enum values.
///
///
- /// It indicates the current state of the connection.
+ /// It indicates the current state of the interface.
///
///
- /// The default value is .
+ /// The default value is .
///
///
public WebSocketState ReadyState {
@@ -686,30 +847,37 @@ public WebSocketState ReadyState {
/// Gets the configuration for secure connection.
///
///
- /// This configuration will be referenced when attempts to connect,
+ /// The configuration is used when the interface attempts to connect,
/// so it must be configured before any connect method is called.
///
///
- /// A that represents
- /// the configuration used to establish a secure connection.
+ /// A that represents the
+ /// configuration used to establish a secure connection.
///
///
///
- /// This instance is not a client.
+ /// The get operation is not available if the interface is not for
+ /// the client.
+ ///
+ ///
+ /// -or-
///
///
- /// This instance does not use a secure connection.
+ /// The get operation is not available if the interface does not use
+ /// a secure connection.
///
///
public ClientSslConfiguration SslConfiguration {
get {
- if (!_client) {
- var msg = "This instance is not a client.";
+ if (!_isClient) {
+ var msg = "The get operation is not available.";
+
throw new InvalidOperationException (msg);
}
- if (!_secure) {
- var msg = "This instance does not use a secure connection.";
+ if (!_isSecure) {
+ var msg = "The get operation is not available.";
+
throw new InvalidOperationException (msg);
}
@@ -721,52 +889,57 @@ public ClientSslConfiguration SslConfiguration {
/// Gets the URL to which to connect.
///
///
- /// A that represents the URL to which to connect.
+ ///
+ /// A that represents the URL to which to connect.
+ ///
+ ///
+ /// Also it represents the URL requested by the client if the interface
+ /// is for the server.
+ ///
///
public Uri Url {
get {
- return _client ? _uri : _context.RequestUri;
+ return _isClient ? _uri : _context.RequestUri;
}
}
///
/// Gets or sets the time to wait for the response to the ping or close.
///
- ///
- /// The set operation does nothing if the connection has already been
- /// established or it is closing.
- ///
///
///
- /// A to wait for the response.
+ /// A that represents the time to wait for
+ /// the response.
///
///
- /// The default value is the same as 5 seconds if this instance is
- /// a client.
+ /// The default value is the same as 5 seconds if the interface is
+ /// for the client.
///
///
///
/// The value specified for a set operation is zero or less.
///
+ ///
+ /// The set operation is not available when the current state of
+ /// the interface is neither New nor Closed.
+ ///
public TimeSpan WaitTime {
get {
return _waitTime;
}
set {
- if (value <= TimeSpan.Zero)
- throw new ArgumentOutOfRangeException ("value", "Zero or less.");
+ if (value <= TimeSpan.Zero) {
+ var msg = "Zero or less.";
- string msg;
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
+ throw new ArgumentOutOfRangeException ("value", msg);
}
lock (_forState) {
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
+ if (!canSet ()) {
+ var msg = "The set operation is not available.";
+
+ throw new InvalidOperationException (msg);
}
_waitTime = value;
@@ -779,22 +952,22 @@ public TimeSpan WaitTime {
#region Public Events
///
- /// Occurs when the WebSocket connection has been closed.
+ /// Occurs when the connection has been closed.
///
public event EventHandler OnClose;
///
- /// Occurs when the gets an error.
+ /// Occurs when the interface gets an error.
///
public event EventHandler OnError;
///
- /// Occurs when the receives a message.
+ /// Occurs when the interface receives a message.
///
public event EventHandler OnMessage;
///
- /// Occurs when the WebSocket connection has been established.
+ /// Occurs when the connection has been established.
///
public event EventHandler OnOpen;
@@ -802,59 +975,67 @@ public TimeSpan WaitTime {
#region Private Methods
- // As server
- private bool accept ()
+ private void abort (string reason, Exception exception)
{
- if (_readyState == WebSocketState.Open) {
- var msg = "The handshake request has already been accepted.";
- _logger.Warn (msg);
+ var code = exception is WebSocketException
+ ? ((WebSocketException) exception).Code
+ : (ushort) 1006;
- return false;
- }
+ abort (code, reason);
+ }
+
+ private void abort (ushort code, string reason)
+ {
+ var data = new PayloadData (code, reason);
+ close (data, false, false);
+ }
+
+ // As server
+ private bool accept ()
+ {
lock (_forState) {
if (_readyState == WebSocketState.Open) {
- var msg = "The handshake request has already been accepted.";
- _logger.Warn (msg);
+ _log.Trace ("The connection has already been established.");
return false;
}
if (_readyState == WebSocketState.Closing) {
- var msg = "The close process has set in.";
- _logger.Error (msg);
+ _log.Error ("The close process is in progress.");
- msg = "An interruption has occurred while attempting to accept.";
- error (msg, null);
+ error ("An error has occurred before accepting.", null);
return false;
}
if (_readyState == WebSocketState.Closed) {
- var msg = "The connection has been closed.";
- _logger.Error (msg);
+ _log.Error ("The connection has been closed.");
- msg = "An interruption has occurred while attempting to accept.";
- error (msg, null);
+ error ("An error has occurred before accepting.", null);
return false;
}
+ _readyState = WebSocketState.Connecting;
+
+ var accepted = false;
+
try {
- if (!acceptHandshake ())
- return false;
+ accepted = acceptHandshake ();
}
catch (Exception ex) {
- _logger.Fatal (ex.Message);
- _logger.Debug (ex.ToString ());
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
- var msg = "An exception has occurred while attempting to accept.";
- fatal (msg, ex);
+ abort (1011, "An exception has occurred while accepting.");
+ }
+ if (!accepted)
return false;
- }
_readyState = WebSocketState.Open;
+
return true;
}
}
@@ -862,31 +1043,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,132 +1066,186 @@ 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 ());
- }
-
- private bool canSet (out string message)
- {
- message = null;
+ customRespondToHandshakeRequest (_context);
- if (_readyState == WebSocketState.Open) {
- message = "The connection has already been established.";
- return false;
- }
+ if (_noDelay)
+ _socket.NoDelay = true;
- if (_readyState == WebSocketState.Closing) {
- message = "The connection is closing.";
- return false;
- }
+ createHandshakeResponse ().WriteTo (_stream);
return true;
}
+ private bool canSet ()
+ {
+ return _readyState == WebSocketState.New
+ || _readyState == WebSocketState.Closed;
+ }
+
// As server
private bool checkHandshakeRequest (
- WebSocketContext context, out string message
+ WebSocketContext context,
+ out string message
)
{
message = null;
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.";
- return false;
- }
+ message = "The Sec-WebSocket-Key header is invalid.";
- var version = headers["Sec-WebSocket-Version"];
- if (version == null) {
- message = "It includes no Sec-WebSocket-Version header.";
return false;
}
- if (version != _version) {
- message = "It includes an invalid Sec-WebSocket-Version header.";
+ var ver = headers["Sec-WebSocket-Version"];
+
+ if (ver == null) {
+ message = "The Sec-WebSocket-Version header is non-existent.";
+
return false;
}
- var protocol = headers["Sec-WebSocket-Protocol"];
- if (protocol != null && protocol.Length == 0) {
- message = "It includes an invalid Sec-WebSocket-Protocol header.";
+ if (ver != _version) {
+ message = "The Sec-WebSocket-Version 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.";
+ var subps = headers["Sec-WebSocket-Protocol"];
+
+ if (subps != null) {
+ if (subps.Length == 0) {
+ message = "The Sec-WebSocket-Protocol header is invalid.";
+
return false;
}
}
- return true;
- }
+ if (!_ignoreExtensions) {
+ var exts = headers["Sec-WebSocket-Extensions"];
+
+ if (exts != null) {
+ if (exts.Length == 0) {
+ message = "The Sec-WebSocket-Extensions header is invalid.";
+
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
// 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 (_hasProtocol) {
+ message = "The Sec-WebSocket-Protocol header is non-existent.";
+
+ return false;
+ }
+ }
+ else {
+ var isValid = _hasProtocol
+ && subp.Length > 0
+ && _protocols.Contains (p => p == subp);
+
+ if (!isValid) {
+ 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 +1255,110 @@ private static bool checkProtocols (string[] protocols, out string message)
{
message = null;
- Func cond = protocol => protocol.IsNullOrEmpty ()
- || !protocol.IsToken ();
+ Func cond = p => p.IsNullOrEmpty () || !p.IsToken ();
if (protocols.Contains (cond)) {
message = "It contains a value that is not a token.";
+
return false;
}
if (protocols.ContainsTwice ()) {
message = "It contains a value twice.";
+
return false;
}
return true;
}
- private bool checkReceivedFrame (WebSocketFrame frame, out string message)
+ // As client
+ private bool checkProxyConnectResponse (
+ HttpResponse response,
+ out string message
+ )
{
message = null;
- var masked = frame.IsMasked;
- if (_client && masked) {
- message = "A frame from the server is masked.";
+ if (response.IsProxyAuthenticationRequired) {
+ message = "The proxy authentication is required.";
+
return false;
}
- if (!_client && !masked) {
- message = "A frame from a client is not masked.";
+ if (!response.IsSuccess) {
+ message = "The proxy has failed a connection to the requested URL.";
+
return false;
}
- if (_inContinuation && frame.IsData) {
- message = "A data frame has been received while receiving continuation frames.";
- return false;
+ return true;
+ }
+
+ private bool checkReceivedFrame (WebSocketFrame frame, out string message)
+ {
+ message = null;
+
+ if (frame.IsMasked) {
+ if (_isClient) {
+ message = "A frame from the server is masked.";
+
+ return false;
+ }
}
+ else {
+ if (!_isClient) {
+ message = "A frame from a client is not masked.";
- if (frame.IsCompressed && _compression == CompressionMethod.None) {
- message = "A compressed frame has been received without any agreement for it.";
- return false;
+ return false;
+ }
+ }
+
+ if (frame.IsCompressed) {
+ if (_compression == CompressionMethod.None) {
+ message = "A frame is compressed without any agreement for it.";
+
+ return false;
+ }
+
+ if (!frame.IsData) {
+ message = "A non data frame is compressed.";
+
+ return false;
+ }
+ }
+
+ if (frame.IsData) {
+ if (_inContinuation) {
+ message = "A data frame was received while receiving continuation frames.";
+
+ return false;
+ }
+ }
+
+ if (frame.IsControl) {
+ if (frame.Fin == Fin.More) {
+ message = "A control frame is fragmented.";
+
+ return false;
+ }
+
+ if (frame.PayloadLength > 125) {
+ message = "The payload length of a control frame is greater than 125.";
+
+ return false;
+ }
}
if (frame.Rsv2 == Rsv.On) {
message = "The RSV2 of a frame is non-zero without any negotiation for it.";
+
return false;
}
if (frame.Rsv3 == Rsv.On) {
message = "The RSV3 of a frame is non-zero without any negotiation for it.";
+
return false;
}
@@ -1086,51 +1368,56 @@ private bool checkReceivedFrame (WebSocketFrame frame, out string message)
private void close (ushort code, string reason)
{
if (_readyState == WebSocketState.Closing) {
- _logger.Info ("The closing is already in progress.");
+ _log.Trace ("The close process is already in progress.");
+
return;
}
if (_readyState == WebSocketState.Closed) {
- _logger.Info ("The connection has already been closed.");
+ _log.Trace ("The connection has already been closed.");
+
return;
}
- if (code == 1005) { // == no status
- close (PayloadData.Empty, true, true, false);
+ if (code == 1005) {
+ close (PayloadData.Empty, true, false);
+
return;
}
- var send = !code.IsReserved ();
- close (new PayloadData (code, reason), send, send, false);
+ var data = new PayloadData (code, reason);
+ var send = !code.IsReservedStatusCode ();
+
+ close (data, send, false);
}
- private void close (
- PayloadData payloadData, bool send, bool receive, bool received
- )
+ private void close (PayloadData payloadData, bool send, bool received)
{
lock (_forState) {
if (_readyState == WebSocketState.Closing) {
- _logger.Info ("The closing is already in progress.");
+ _log.Trace ("The close process is already in progress.");
+
return;
}
if (_readyState == WebSocketState.Closed) {
- _logger.Info ("The connection has already been closed.");
+ _log.Trace ("The connection has already been closed.");
+
return;
}
send = send && _readyState == WebSocketState.Open;
- receive = send && receive;
_readyState = WebSocketState.Closing;
}
- _logger.Trace ("Begin closing the connection.");
+ _log.Trace ("Begin closing the connection.");
+
+ var res = closeHandshake (payloadData, send, received);
- var res = closeHandshake (payloadData, send, receive, received);
releaseResources ();
- _logger.Trace ("End closing the connection.");
+ _log.Trace ("End closing the connection.");
_readyState = WebSocketState.Closed;
@@ -1140,85 +1427,83 @@ private void close (
OnClose.Emit (this, e);
}
catch (Exception ex) {
- _logger.Error (ex.Message);
- _logger.Debug (ex.ToString ());
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
}
}
private void closeAsync (ushort code, string reason)
{
if (_readyState == WebSocketState.Closing) {
- _logger.Info ("The closing is already in progress.");
+ _log.Trace ("The close process is already in progress.");
+
return;
}
if (_readyState == WebSocketState.Closed) {
- _logger.Info ("The connection has already been closed.");
+ _log.Trace ("The connection has already been closed.");
+
return;
}
- if (code == 1005) { // == no status
- closeAsync (PayloadData.Empty, true, true, false);
+ if (code == 1005) {
+ closeAsync (PayloadData.Empty, true, false);
+
return;
}
- var send = !code.IsReserved ();
- closeAsync (new PayloadData (code, reason), send, send, false);
- }
+ var data = new PayloadData (code, reason);
+ var send = !code.IsReservedStatusCode ();
- private void closeAsync (
- PayloadData payloadData, bool send, bool receive, bool received
- )
- {
- Action closer = close;
- closer.BeginInvoke (
- payloadData, send, receive, received, ar => closer.EndInvoke (ar), null
- );
+ closeAsync (data, send, false);
}
- private bool closeHandshake (byte[] frameAsBytes, bool receive, bool received)
+ private void closeAsync (PayloadData payloadData, bool send, bool received)
{
- var sent = frameAsBytes != null && sendBytes (frameAsBytes);
-
- var wait = !received && sent && receive && _receivingExited != null;
- if (wait)
- received = _receivingExited.WaitOne (_waitTime);
+ Action closer = close;
- var ret = sent && received;
-
- _logger.Debug (
- String.Format (
- "Was clean?: {0}\n sent: {1}\n received: {2}", ret, sent, received
- )
+ closer.BeginInvoke (
+ payloadData,
+ send,
+ received,
+ ar => closer.EndInvoke (ar),
+ null
);
-
- return ret;
}
private bool closeHandshake (
- PayloadData payloadData, bool send, bool receive, bool received
+ PayloadData payloadData,
+ bool send,
+ bool received
)
{
var sent = false;
+
if (send) {
- var frame = WebSocketFrame.CreateCloseFrame (payloadData, _client);
- sent = sendBytes (frame.ToArray ());
+ var frame = WebSocketFrame.CreateCloseFrame (payloadData, _isClient);
+ var bytes = frame.ToArray ();
- if (_client)
+ sent = sendBytes (bytes);
+
+ if (_isClient)
frame.Unmask ();
}
- var wait = !received && sent && receive && _receivingExited != null;
+ var wait = !received && sent && _receivingExited != null;
+
if (wait)
received = _receivingExited.WaitOne (_waitTime);
var ret = sent && received;
- _logger.Debug (
- String.Format (
- "Was clean?: {0}\n sent: {1}\n received: {2}", ret, sent, received
- )
- );
+ var msg = String.Format (
+ "The closing was clean? {0} (sent: {1} received: {2})",
+ ret,
+ sent,
+ received
+ );
+
+ _log.Debug (msg);
return ret;
}
@@ -1226,64 +1511,84 @@ private bool closeHandshake (
// As client
private bool connect ()
{
- if (_readyState == WebSocketState.Open) {
- var msg = "The connection has already been established.";
- _logger.Warn (msg);
+ if (_readyState == WebSocketState.Connecting) {
+ _log.Trace ("The connect process is in progress.");
return false;
}
lock (_forState) {
if (_readyState == WebSocketState.Open) {
- var msg = "The connection has already been established.";
- _logger.Warn (msg);
+ _log.Trace ("The connection has already been established.");
return false;
}
if (_readyState == WebSocketState.Closing) {
- var msg = "The close process has set in.";
- _logger.Error (msg);
+ _log.Error ("The close process is in progress.");
- msg = "An interruption has occurred while attempting to connect.";
- error (msg, null);
+ error ("An error has occurred before connecting.", null);
return false;
}
- if (_retryCountForConnect > _maxRetryCountForConnect) {
- var msg = "An opportunity for reconnecting has been lost.";
- _logger.Error (msg);
+ if (_retryCountForConnect >= _maxRetryCountForConnect) {
+ _log.Error ("An opportunity for reconnecting has been lost.");
- msg = "An interruption has occurred while attempting to connect.";
- error (msg, null);
+ error ("An error has occurred before connecting.", null);
return false;
}
+ if (_readyState == WebSocketState.Closed)
+ initr ();
+
+ _retryCountForConnect++;
+
_readyState = WebSocketState.Connecting;
+ var done = false;
+
try {
- doHandshake ();
+ done = doHandshake ();
}
catch (Exception ex) {
- _retryCountForConnect++;
-
- _logger.Fatal (ex.Message);
- _logger.Debug (ex.ToString ());
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
- var msg = "An exception has occurred while attempting to connect.";
- fatal (msg, ex);
+ abort ("An exception has occurred while connecting.", ex);
+ }
+ if (!done)
return false;
- }
- _retryCountForConnect = 1;
+ _retryCountForConnect = -1;
+
_readyState = WebSocketState.Open;
return true;
}
}
+
+ // As client
+ private AuthenticationResponse createAuthenticationResponse ()
+ {
+ if (_credentials == null)
+ return null;
+
+ if (_authChallenge == null)
+ return _preAuth ? new AuthenticationResponse (_credentials) : null;
+
+ var ret = new AuthenticationResponse (
+ _authChallenge,
+ _credentials,
+ _nonceCount
+ );
+
+ _nonceCount = ret.NonceCount;
+
+ return ret;
+ }
// As client
private string createExtensions ()
@@ -1292,24 +1597,28 @@ 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,37 +1627,39 @@ 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 (_hasProtocol)
headers["Sec-WebSocket-Protocol"] = _protocols.ToString (", ");
- _extensionsRequested = _compression != CompressionMethod.None;
- if (_extensionsRequested)
- headers["Sec-WebSocket-Extensions"] = createExtensions ();
+ var exts = createExtensions ();
- headers["Sec-WebSocket-Version"] = _version;
+ _hasExtension = exts != null;
- 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 (_hasExtension)
+ headers["Sec-WebSocket-Extensions"] = exts;
+
+ var ares = createAuthenticationResponse ();
+
+ if (ares != null)
+ headers["Authorization"] = ares.ToString ();
- if (authRes != null)
- headers["Authorization"] = authRes.ToString ();
+ var hasUserHeader = _userHeaders != null && _userHeaders.Count > 0;
- if (_cookies.Count > 0)
+ if (hasUserHeader)
+ headers.Add (_userHeaders);
+
+ var hasCookie = _cookies != null && _cookies.Count > 0;
+
+ if (hasCookie)
ret.SetCookies (_cookies);
return ret;
@@ -1357,9 +1668,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)
@@ -1368,15 +1680,34 @@ private HttpResponse createHandshakeResponse ()
if (_extensions != null)
headers["Sec-WebSocket-Extensions"] = _extensions;
- if (_cookies.Count > 0)
+ var hasUserHeader = _userHeaders != null && _userHeaders.Count > 0;
+
+ if (hasUserHeader)
+ headers.Add (_userHeaders);
+
+ var hasCookie = _cookies != null && _cookies.Count > 0;
+
+ if (hasCookie)
ret.SetCookies (_cookies);
return ret;
}
+ // As client
+ private TcpClient createTcpClient (string hostname, int port)
+ {
+ var ret = new TcpClient (hostname, port);
+
+ if (_noDelay)
+ ret.NoDelay = true;
+
+ return ret;
+ }
+
// As server
private bool customCheckHandshakeRequest (
- WebSocketContext context, out string message
+ WebSocketContext context,
+ out string message
)
{
message = null;
@@ -1385,32 +1716,66 @@ private bool customCheckHandshakeRequest (
return true;
message = _handshakeRequestChecker (context);
+
return message == null;
}
+ // As server
+ private void customRespondToHandshakeRequest (WebSocketContext context)
+ {
+ if (_handshakeRequestResponder == null)
+ return;
+
+ _handshakeRequestResponder (context);
+ }
+
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 ();
+ _log.Debug (res.ToString ());
+
+ _handshakeResponseHeaders = res.Headers;
+ _handshakeResponseCookies = res.Cookies;
+
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);
+
+ abort (1002, "A handshake error has occurred.");
- if (_extensionsRequested)
- processSecWebSocketExtensionsServerHeader (res.Headers["Sec-WebSocket-Extensions"]);
+ return false;
+ }
- processCookies (res.Cookies);
+ if (_hasProtocol)
+ _protocol = _handshakeResponseHeaders["Sec-WebSocket-Protocol"];
+
+ if (_hasExtension) {
+ var exts = _handshakeResponseHeaders["Sec-WebSocket-Extensions"];
+
+ if (exts != null)
+ _extensions = exts;
+ else
+ _compression = CompressionMethod.None;
+ }
+
+ if (_handshakeResponseCookies.Count > 0)
+ Cookies.SetOrRemove (_handshakeResponseCookies);
+
+ return true;
}
private void enqueueToMessageEventQueue (MessageEventArgs e)
@@ -1421,35 +1786,17 @@ private void enqueueToMessageEventQueue (MessageEventArgs e)
private void error (string message, Exception exception)
{
+ var e = new ErrorEventArgs (message, exception);
+
try {
- OnError.Emit (this, new ErrorEventArgs (message, exception));
+ OnError.Emit (this, e);
}
catch (Exception ex) {
- _logger.Error (ex.Message);
- _logger.Debug (ex.ToString ());
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
}
}
- private void fatal (string message, Exception exception)
- {
- var code = exception is WebSocketException
- ? ((WebSocketException) exception).Code
- : CloseStatusCode.Abnormal;
-
- fatal (message, (ushort) code);
- }
-
- private void fatal (string message, ushort code)
- {
- var payload = new PayloadData (code, message);
- close (payload, !code.IsReserved (), false, false);
- }
-
- private void fatal (string message, CloseStatusCode code)
- {
- fatal (message, (ushort) code);
- }
-
private ClientSslConfiguration getSslConfiguration ()
{
if (_sslConfig == null)
@@ -1461,24 +1808,38 @@ private ClientSslConfiguration getSslConfiguration ()
private void init ()
{
_compression = CompressionMethod.None;
- _cookies = new CookieCollection ();
_forPing = new object ();
_forSend = new object ();
_forState = new object ();
_messageEventQueue = new Queue ();
_forMessageEventQueue = ((ICollection) _messageEventQueue).SyncRoot;
- _readyState = WebSocketState.Connecting;
+ _readyState = WebSocketState.New;
+ }
+
+ // As client
+ private void initr ()
+ {
+ _handshakeResponseCookies = null;
+ _handshakeResponseHeaders = null;
}
private void message ()
{
MessageEventArgs e = null;
+
lock (_forMessageEventQueue) {
- if (_inMessage || _messageEventQueue.Count == 0 || _readyState != WebSocketState.Open)
+ if (_inMessage)
+ return;
+
+ if (_messageEventQueue.Count == 0)
+ return;
+
+ if (_readyState != WebSocketState.Open)
return;
- _inMessage = true;
e = _messageEventQueue.Dequeue ();
+
+ _inMessage = true;
}
_message (e);
@@ -1491,13 +1852,22 @@ private void messagec (MessageEventArgs e)
OnMessage.Emit (this, e);
}
catch (Exception ex) {
- _logger.Error (ex.ToString ());
- error ("An error has occurred during an OnMessage event.", ex);
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ error ("An exception has occurred during an OnMessage event.", ex);
}
lock (_forMessageEventQueue) {
- if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) {
+ if (_messageEventQueue.Count == 0) {
+ _inMessage = false;
+
+ break;
+ }
+
+ if (_readyState != WebSocketState.Open) {
_inMessage = false;
+
break;
}
@@ -1513,13 +1883,22 @@ private void messages (MessageEventArgs e)
OnMessage.Emit (this, e);
}
catch (Exception ex) {
- _logger.Error (ex.ToString ());
- error ("An error has occurred during an OnMessage event.", ex);
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ error ("An exception has occurred during an OnMessage event.", ex);
}
lock (_forMessageEventQueue) {
- if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) {
+ if (_messageEventQueue.Count == 0) {
+ _inMessage = false;
+
+ return;
+ }
+
+ if (_readyState != WebSocketState.Open) {
_inMessage = false;
+
return;
}
@@ -1532,19 +1911,31 @@ private void messages (MessageEventArgs e)
private void open ()
{
_inMessage = true;
+
startReceiving ();
+
try {
OnOpen.Emit (this, EventArgs.Empty);
}
catch (Exception ex) {
- _logger.Error (ex.ToString ());
- error ("An error has occurred during the OnOpen event.", ex);
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ error ("An exception has occurred during the OnOpen event.", ex);
}
MessageEventArgs e = null;
+
lock (_forMessageEventQueue) {
- if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) {
+ if (_messageEventQueue.Count == 0) {
_inMessage = false;
+
+ return;
+ }
+
+ if (_readyState != WebSocketState.Open) {
+ _inMessage = false;
+
return;
}
@@ -1559,17 +1950,21 @@ private bool ping (byte[] data)
if (_readyState != WebSocketState.Open)
return false;
- var pongReceived = _pongReceived;
- if (pongReceived == null)
+ var received = _pongReceived;
+
+ if (received == null)
return false;
lock (_forPing) {
try {
- pongReceived.Reset ();
- if (!send (Fin.Final, Opcode.Ping, data, false))
+ received.Reset ();
+
+ var sent = send (Fin.Final, Opcode.Ping, data, false);
+
+ if (!sent)
return false;
- return pongReceived.WaitOne (_waitTime);
+ return received.WaitOne (_waitTime);
}
catch (ObjectDisposedException) {
return false;
@@ -1579,28 +1974,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 +1999,6 @@ private bool processDataFrame (WebSocketFrame frame)
private bool processFragmentFrame (WebSocketFrame frame)
{
if (!_inContinuation) {
- // Must process first fragment.
if (frame.IsContinuation)
return true;
@@ -1619,13 +2009,16 @@ private bool processFragmentFrame (WebSocketFrame frame)
}
_fragmentsBuffer.WriteBytes (frame.PayloadData.ApplicationData, 1024);
+
if (frame.IsFinal) {
using (_fragmentsBuffer) {
var data = _fragmentsCompressed
? _fragmentsBuffer.DecompressToArray (_compression)
: _fragmentsBuffer.ToArray ();
- enqueueToMessageEventQueue (new MessageEventArgs (_fragmentsOpcode, data));
+ var e = new MessageEventArgs (_fragmentsOpcode, data);
+
+ enqueueToMessageEventQueue (e);
}
_fragmentsBuffer = null;
@@ -1637,27 +2030,33 @@ private bool processFragmentFrame (WebSocketFrame frame)
private bool processPingFrame (WebSocketFrame frame)
{
- _logger.Trace ("A ping was received.");
+ _log.Trace ("A ping was received.");
- var pong = WebSocketFrame.CreatePongFrame (frame.PayloadData, _client);
+ var pong = WebSocketFrame.CreatePongFrame (frame.PayloadData, _isClient);
lock (_forState) {
if (_readyState != WebSocketState.Open) {
- _logger.Error ("The connection is closing.");
+ _log.Trace ("A pong to this ping cannot be sent.");
+
return true;
}
- if (!sendBytes (pong.ToArray ()))
+ var bytes = pong.ToArray ();
+ var sent = sendBytes (bytes);
+
+ if (!sent)
return false;
}
- _logger.Trace ("A pong to this ping has been sent.");
+ _log.Trace ("A pong to this ping has been sent.");
if (_emitOnPing) {
- if (_client)
+ if (_isClient)
pong.Unmask ();
- enqueueToMessageEventQueue (new MessageEventArgs (frame));
+ var e = new MessageEventArgs (frame);
+
+ enqueueToMessageEventQueue (e);
}
return true;
@@ -1665,25 +2064,19 @@ private bool processPingFrame (WebSocketFrame frame)
private bool processPongFrame (WebSocketFrame frame)
{
- _logger.Trace ("A pong was received.");
+ _log.Trace ("A pong was received.");
try {
_pongReceived.Set ();
}
- catch (NullReferenceException ex) {
- _logger.Error (ex.Message);
- _logger.Debug (ex.ToString ());
-
+ catch (NullReferenceException) {
return false;
}
- catch (ObjectDisposedException ex) {
- _logger.Error (ex.Message);
- _logger.Debug (ex.ToString ());
-
+ catch (ObjectDisposedException) {
return false;
}
- _logger.Trace ("It has been signaled.");
+ _log.Trace ("It has been signaled.");
return true;
}
@@ -1691,10 +2084,18 @@ private bool processPongFrame (WebSocketFrame frame)
private bool processReceivedFrame (WebSocketFrame frame)
{
string msg;
- if (!checkReceivedFrame (frame, out msg))
- throw new WebSocketException (CloseStatusCode.ProtocolError, msg);
+
+ if (!checkReceivedFrame (frame, out msg)) {
+ _log.Error (msg);
+ _log.Debug (frame.ToString (false));
+
+ abort (1002, "An error has occurred while receiving.");
+
+ return false;
+ }
frame.Unmask ();
+
return frame.IsFragment
? processFragmentFrame (frame)
: frame.IsData
@@ -1715,88 +2116,57 @@ private void processSecWebSocketExtensionsClientHeader (string value)
return;
var buff = new StringBuilder (80);
- var comp = false;
+
+ var compRequested = 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 (!compRequested) {
+ 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;
+ compRequested = true;
}
}
}
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);
+ createHandshakeFailureResponse ().WriteTo (_stream);
- releaseServerResources ();
-
- _readyState = WebSocketState.Closed;
-
- var e = new CloseEventArgs ((ushort) code, reason, false);
-
- try {
- OnClose.Emit (this, e);
- }
- catch (Exception ex) {
- _logger.Error (ex.Message);
- _logger.Debug (ex.ToString ());
- }
+ abort (code, reason);
}
// As client
@@ -1804,11 +2174,13 @@ private void releaseClientResources ()
{
if (_stream != null) {
_stream.Dispose ();
+
_stream = null;
}
if (_tcpClient != null) {
_tcpClient.Close ();
+
_tcpClient = null;
}
}
@@ -1817,24 +2189,27 @@ private void releaseCommonResources ()
{
if (_fragmentsBuffer != null) {
_fragmentsBuffer.Dispose ();
+
_fragmentsBuffer = null;
_inContinuation = false;
}
if (_pongReceived != null) {
_pongReceived.Close ();
+
_pongReceived = null;
}
if (_receivingExited != null) {
_receivingExited.Close ();
+
_receivingExited = null;
}
}
private void releaseResources ()
{
- if (_client)
+ if (_isClient)
releaseClientResources ();
else
releaseServerResources ();
@@ -1845,81 +2220,108 @@ private void releaseResources ()
// As server
private void releaseServerResources ()
{
- if (_closeContext == null)
- return;
+ if (_closeContext != null) {
+ _closeContext ();
+
+ _closeContext = null;
+ }
- _closeContext ();
- _closeContext = null;
_stream = null;
_context = null;
}
- private bool send (Opcode opcode, Stream stream)
+ private bool send (byte[] rawFrame)
+ {
+ lock (_forState) {
+ if (_readyState != WebSocketState.Open) {
+ _log.Error ("The current state of the interface is not Open.");
+
+ return false;
+ }
+
+ return sendBytes (rawFrame);
+ }
+ }
+
+ private bool send (Opcode opcode, Stream sourceStream)
{
lock (_forSend) {
- var src = stream;
+ var dataStream = sourceStream;
var compressed = false;
var sent = false;
+
try {
if (_compression != CompressionMethod.None) {
- stream = stream.Compress (_compression);
+ dataStream = sourceStream.Compress (_compression);
compressed = true;
}
- sent = send (opcode, stream, compressed);
+ sent = send (opcode, dataStream, compressed);
+
if (!sent)
- error ("A send has been interrupted.", null);
+ error ("A send has failed.", null);
}
catch (Exception ex) {
- _logger.Error (ex.ToString ());
- error ("An error has occurred during a send.", ex);
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ error ("An exception has occurred during a send.", ex);
}
finally {
if (compressed)
- stream.Dispose ();
+ dataStream.Dispose ();
- src.Dispose ();
+ sourceStream.Dispose ();
}
return sent;
}
}
- private bool send (Opcode opcode, Stream stream, bool compressed)
+ private bool send (Opcode opcode, Stream dataStream, bool compressed)
{
- var len = stream.Length;
+ var len = dataStream.Length;
+
if (len == 0)
- return send (Fin.Final, opcode, EmptyBytes, false);
+ return send (Fin.Final, opcode, _emptyBytes, false);
var quo = len / FragmentLength;
var rem = (int) (len % FragmentLength);
byte[] buff = null;
+
if (quo == 0) {
buff = new byte[rem];
- return stream.Read (buff, 0, rem) == rem
+
+ return dataStream.Read (buff, 0, rem) == rem
&& send (Fin.Final, opcode, buff, compressed);
}
if (quo == 1 && rem == 0) {
buff = new byte[FragmentLength];
- return stream.Read (buff, 0, FragmentLength) == FragmentLength
+
+ return dataStream.Read (buff, 0, FragmentLength) == FragmentLength
&& send (Fin.Final, opcode, buff, compressed);
}
/* Send fragments */
// Begin
+
buff = new byte[FragmentLength];
- var sent = stream.Read (buff, 0, FragmentLength) == FragmentLength
+
+ var sent = dataStream.Read (buff, 0, FragmentLength) == FragmentLength
&& send (Fin.More, opcode, buff, compressed);
if (!sent)
return false;
+ // Continue
+
var n = rem == 0 ? quo - 2 : quo - 1;
+
for (long i = 0; i < n; i++) {
- sent = stream.Read (buff, 0, FragmentLength) == FragmentLength
+ sent = dataStream.Read (buff, 0, FragmentLength) == FragmentLength
&& send (Fin.More, Opcode.Cont, buff, false);
if (!sent)
@@ -1927,44 +2329,48 @@ private bool send (Opcode opcode, Stream stream, bool compressed)
}
// End
+
if (rem == 0)
rem = FragmentLength;
else
buff = new byte[rem];
- return stream.Read (buff, 0, rem) == rem
+ return dataStream.Read (buff, 0, rem) == rem
&& send (Fin.Final, Opcode.Cont, buff, false);
}
private bool send (Fin fin, Opcode opcode, byte[] data, bool compressed)
{
- lock (_forState) {
- if (_readyState != WebSocketState.Open) {
- _logger.Error ("The connection is closing.");
- return false;
- }
+ var frame = new WebSocketFrame (fin, opcode, data, compressed, _isClient);
+ var rawFrame = frame.ToArray ();
- var frame = new WebSocketFrame (fin, opcode, data, compressed, _client);
- return sendBytes (frame.ToArray ());
- }
+ return send (rawFrame);
}
- private void sendAsync (Opcode opcode, Stream stream, Action completed)
+ private void sendAsync (
+ Opcode opcode,
+ Stream sourceStream,
+ Action completed
+ )
{
Func sender = send;
+
sender.BeginInvoke (
opcode,
- stream,
+ sourceStream,
ar => {
try {
var sent = sender.EndInvoke (ar);
+
if (completed != null)
completed (sent);
}
catch (Exception ex) {
- _logger.Error (ex.ToString ());
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
+
error (
- "An error has occurred during the callback for an async send.",
+ "An exception has occurred during the callback for an async send.",
ex
);
}
@@ -1979,8 +2385,8 @@ private bool sendBytes (byte[] bytes)
_stream.Write (bytes, 0, bytes.Length);
}
catch (Exception ex) {
- _logger.Error (ex.Message);
- _logger.Debug (ex.ToString ());
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
return false;
}
@@ -1992,161 +2398,189 @@ private bool sendBytes (byte[] bytes)
private HttpResponse sendHandshakeRequest ()
{
var req = createHandshakeRequest ();
- var res = sendHttpRequest (req, 90000);
+
+ _log.Debug (req.ToString ());
+
+ 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;
- var authRes = new AuthenticationResponse (_authChallenge, _credentials, _nonceCount);
- _nonceCount = authRes.NonceCount;
- req.Headers["Authorization"] = authRes.ToString ();
- res = sendHttpRequest (req, 15000);
+ if (_credentials == null)
+ return res;
+
+ var ares = new AuthenticationResponse (
+ _authChallenge,
+ _credentials,
+ _nonceCount
+ );
+
+ _nonceCount = ares.NonceCount;
+
+ req.Headers["Authorization"] = ares.ToString ();
+
+ if (res.CloseConnection) {
+ releaseClientResources ();
+ setClientStream ();
}
+
+ _log.Debug (req.ToString ());
+
+ 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;
+ _isSecure = 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);
- var authRes = new AuthenticationResponse (authChal, _proxyCredentials, 0);
- req.Headers["Proxy-Authorization"] = authRes.ToString ();
- res = sendHttpRequest (req, 15000);
+ 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 ();
+
+ _tcpClient = createTcpClient (_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
private void setClientStream ()
{
if (_proxyUri != null) {
- _tcpClient = new TcpClient (_proxyUri.DnsSafeHost, _proxyUri.Port);
+ _tcpClient = createTcpClient (_proxyUri.DnsSafeHost, _proxyUri.Port);
_stream = _tcpClient.GetStream ();
- sendProxyConnectRequest ();
+
+ var res = sendProxyConnectRequest ();
+
+ string msg;
+
+ if (!checkProxyConnectResponse (res, out msg))
+ throw new WebSocketException (msg);
}
else {
- _tcpClient = new TcpClient (_uri.DnsSafeHost, _uri.Port);
+ _tcpClient = createTcpClient (_uri.DnsSafeHost, _uri.Port);
_stream = _tcpClient.GetStream ();
}
- if (_secure) {
+ if (_isSecure) {
var conf = getSslConfiguration ();
var host = conf.TargetHost;
- 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
+ );
}
}
}
@@ -2161,79 +2595,82 @@ private void startReceiving ()
Action receive = null;
receive =
- () =>
- WebSocketFrame.ReadFrameAsync (
- _stream,
- false,
- frame => {
- if (!processReceivedFrame (frame) || _readyState == WebSocketState.Closed) {
- var exited = _receivingExited;
- if (exited != null)
- exited.Set ();
-
- return;
- }
-
- // Receive next asap because the Ping or Close needs a response to it.
- receive ();
-
- if (_inMessage || !HasMessage || _readyState != WebSocketState.Open)
- return;
-
- message ();
- },
- ex => {
- _logger.Fatal (ex.ToString ());
- fatal ("An exception has occurred while receiving.", ex);
- }
- );
+ () => WebSocketFrame.ReadFrameAsync (
+ _stream,
+ false,
+ frame => {
+ var doNext = processReceivedFrame (frame)
+ && _readyState != WebSocketState.Closed;
- receive ();
- }
+ if (!doNext) {
+ var exited = _receivingExited;
- // As client
- private bool validateSecWebSocketAcceptHeader (string value)
- {
- return value != null && value == CreateResponseKey (_base64Key);
+ if (exited != null)
+ exited.Set ();
+
+ return;
+ }
+
+ receive ();
+
+ if (_inMessage)
+ return;
+
+ message ();
+ },
+ ex => {
+ _log.Fatal (ex.Message);
+ _log.Debug (ex.ToString ());
+
+ abort ("An exception has occurred while receiving.", ex);
+ }
+ );
+
+ receive ();
}
// As client
private bool validateSecWebSocketExtensionsServerHeader (string value)
{
- if (value == null)
- return true;
+ if (!_hasExtension)
+ return false;
if (value.Length == 0)
return false;
- if (!_extensionsRequested)
- return false;
+ var compRequested = _compression != CompressionMethod.None;
+
+ foreach (var elm in value.SplitHeaderValue (',')) {
+ var ext = elm.Trim ();
+
+ if (compRequested && ext.IsCompressionExtension (_compression)) {
+ 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".
- var comp = _compression != CompressionMethod.None;
- foreach (var e in value.SplitHeaderValue (',')) {
- var ext = e.Trim ();
- if (comp && ext.IsCompressionExtension (_compression)) {
- if (!ext.Contains ("server_no_context_takeover")) {
- _logger.Error ("The server hasn't sent back 'server_no_context_takeover'.");
return false;
}
- if (!ext.Contains ("client_no_context_takeover"))
- _logger.Warn ("The server hasn't sent back 'client_no_context_takeover'.");
-
- var method = _compression.ToExtensionString ();
- var invalid =
- ext.SplitHeaderValue (';').Contains (
- t => {
- t = t.Trim ();
- return t != method
- && t != "server_no_context_takeover"
- && t != "client_no_context_takeover";
- }
- );
+ var name = _compression.ToExtensionString ();
+
+ var isInvalid = ext.SplitHeaderValue (';').Contains (
+ t => {
+ t = t.Trim ();
- if (invalid)
+ var isValid = t == name
+ || t == param1
+ || t == param2;
+
+ return !isValid;
+ }
+ );
+
+ if (isInvalid)
return false;
+
+ compRequested = false;
}
else {
return false;
@@ -2243,81 +2680,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 +2763,53 @@ internal void Close (PayloadData payloadData, byte[] frameAsBytes)
OnClose.Emit (this, e);
}
catch (Exception ex) {
- _logger.Error (ex.Message);
- _logger.Debug (ex.ToString ());
+ _log.Error (ex.Message);
+ _log.Debug (ex.ToString ());
}
}
// As client
internal static string CreateBase64Key ()
{
- var src = new byte[16];
- RandomNumber.GetBytes (src);
+ var key = new byte[16];
+
+ RandomNumber.GetBytes (key);
- return Convert.ToBase64String (src);
+ 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;
@@ -2405,44 +2819,44 @@ internal bool Ping (byte[] frameAsBytes, TimeSpan timeout)
// As server
internal void Send (
- Opcode opcode, byte[] data, Dictionary cache
+ Opcode opcode,
+ byte[] data,
+ Dictionary cache
)
{
lock (_forSend) {
- lock (_forState) {
- if (_readyState != WebSocketState.Open) {
- _logger.Error ("The connection is closing.");
- return;
- }
+ byte[] found;
- byte[] found;
- if (!cache.TryGetValue (_compression, out found)) {
- found = new WebSocketFrame (
- Fin.Final,
- opcode,
- data.Compress (_compression),
- _compression != CompressionMethod.None,
- false
- )
- .ToArray ();
-
- cache.Add (_compression, found);
- }
+ if (!cache.TryGetValue (_compression, out found)) {
+ found = new WebSocketFrame (
+ Fin.Final,
+ opcode,
+ data.Compress (_compression),
+ _compression != CompressionMethod.None,
+ false
+ )
+ .ToArray ();
- sendBytes (found);
+ cache.Add (_compression, found);
}
+
+ send (found);
}
}
// As server
internal void Send (
- Opcode opcode, Stream stream, Dictionary cache
+ Opcode opcode,
+ Stream sourceStream,
+ Dictionary cache
)
{
lock (_forSend) {
Stream found;
+
if (!cache.TryGetValue (_compression, out found)) {
- found = stream.Compress (_compression);
+ found = sourceStream.Compress (_compression);
+
cache.Add (_compression, found);
}
else {
@@ -2457,112 +2871,11 @@ internal void Send (
#region Public Methods
- ///
- /// Accepts the handshake request.
- ///
- ///
- /// This method does nothing if the handshake request has already been
- /// accepted.
- ///
- ///
- ///
- /// This instance is a client.
- ///
- ///
- /// -or-
- ///
- ///
- /// The close process is in progress.
- ///
- ///
- /// -or-
- ///
- ///
- /// The connection has already been closed.
- ///
- ///
- public void Accept ()
- {
- if (_client) {
- var msg = "This instance is a client.";
- throw new InvalidOperationException (msg);
- }
-
- if (_readyState == WebSocketState.Closing) {
- var msg = "The close process is in progress.";
- throw new InvalidOperationException (msg);
- }
-
- if (_readyState == WebSocketState.Closed) {
- var msg = "The connection has already been closed.";
- throw new InvalidOperationException (msg);
- }
-
- if (accept ())
- open ();
- }
-
- ///
- /// Accepts the handshake request asynchronously.
- ///
- ///
- ///
- /// This method does not wait for the accept process to be complete.
- ///
- ///
- /// This method does nothing if the handshake request has already been
- /// accepted.
- ///
- ///
- ///
- ///
- /// This instance is a client.
- ///
- ///
- /// -or-
- ///
- ///
- /// The close process is in progress.
- ///
- ///
- /// -or-
- ///
- ///
- /// The connection has already been closed.
- ///
- ///
- public void AcceptAsync ()
- {
- if (_client) {
- var msg = "This instance is a client.";
- throw new InvalidOperationException (msg);
- }
-
- if (_readyState == WebSocketState.Closing) {
- var msg = "The close process is in progress.";
- throw new InvalidOperationException (msg);
- }
-
- if (_readyState == WebSocketState.Closed) {
- var msg = "The connection has already been closed.";
- throw new InvalidOperationException (msg);
- }
-
- Func acceptor = accept;
- acceptor.BeginInvoke (
- ar => {
- if (acceptor.EndInvoke (ar))
- open ();
- },
- null
- );
- }
-
///
/// Closes the connection.
///
///
- /// This method does nothing if the current state of the connection is
+ /// This method does nothing if the current state of the interface is
/// Closing or Closed.
///
public void Close ()
@@ -2571,15 +2884,15 @@ public void Close ()
}
///
- /// Closes the connection with the specified code.
+ /// Closes the connection with the specified status code.
///
///
- /// This method does nothing if the current state of the connection is
+ /// This method does nothing if the current state of the interface is
/// Closing or Closed.
///
///
///
- /// A that represents the status code indicating
+ /// A that specifies the status code indicating
/// the reason for the close.
///
///
@@ -2588,47 +2901,32 @@ public void Close ()
/// Section 7.4 of RFC 6455.
///
///
- ///
- /// is less than 1000 or greater than 4999.
- ///
///
///
/// is 1011 (server error).
- /// It cannot be used by clients.
+ /// It cannot be used by a client.
///
///
/// -or-
///
///
/// is 1010 (mandatory extension).
- /// It cannot be used by servers.
+ /// It cannot be used by a server.
///
///
+ ///
+ /// is less than 1000 or greater than 4999.
+ ///
public void Close (ushort code)
{
- if (!code.IsCloseStatusCode ()) {
- var msg = "Less than 1000 or greater than 4999.";
- throw new ArgumentOutOfRangeException ("code", msg);
- }
-
- if (_client && code == 1011) {
- var msg = "1011 cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- if (!_client && code == 1010) {
- var msg = "1010 cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- close (code, String.Empty);
+ Close (code, String.Empty);
}
///
- /// Closes the connection with the specified code.
+ /// Closes the connection with the specified status code.
///
///
- /// This method does nothing if the current state of the connection is
+ /// This method does nothing if the current state of the interface is
/// Closing or Closed.
///
///
@@ -2636,49 +2934,43 @@ public void Close (ushort code)
/// One of the enum values.
///
///
- /// It represents the status code indicating the reason for the close.
+ /// It specifies the status code indicating the reason for the close.
///
///
///
///
- /// is
- /// .
- /// It cannot be used by clients.
+ /// is an undefined enum value.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ /// It cannot be used by a client.
///
///
/// -or-
///
///
- /// is
- /// .
- /// It cannot be used by servers.
+ /// is .
+ /// It cannot be used by a server.
///
///
public void Close (CloseStatusCode code)
{
- if (_client && code == CloseStatusCode.ServerError) {
- var msg = "ServerError cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- if (!_client && code == CloseStatusCode.MandatoryExtension) {
- var msg = "MandatoryExtension cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- close ((ushort) code, String.Empty);
+ Close (code, String.Empty);
}
///
- /// Closes the connection with the specified code and reason.
+ /// Closes the connection with the specified status code and reason.
///
///
- /// This method does nothing if the current state of the connection is
+ /// This method does nothing if the current state of the interface is
/// Closing or Closed.
///
///
///
- /// A that represents the status code indicating
+ /// A that specifies the status code indicating
/// the reason for the close.
///
///
@@ -2689,83 +2981,95 @@ public void Close (CloseStatusCode code)
///
///
///
- /// A that represents the reason for the close.
+ /// A that specifies the reason for the close.
///
///
- /// The size must be 123 bytes or less in UTF-8.
+ /// Its size must be 123 bytes or less in UTF-8.
///
///
- ///
+ ///
///
- /// is less than 1000 or greater than 4999.
+ /// is 1011 (server error).
+ /// It cannot be used by a client.
///
///
/// -or-
///
///
- /// The size of is greater than 123 bytes.
- ///
- ///
- ///
- ///
- /// is 1011 (server error).
- /// It cannot be used by clients.
+ /// is 1010 (mandatory extension).
+ /// It cannot be used by a server.
///
///
/// -or-
///
///
- /// is 1010 (mandatory extension).
- /// It cannot be used by servers.
+ /// is 1005 (no status) and
+ /// is specified.
///
///
/// -or-
///
///
- /// is 1005 (no status) and there is reason.
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ ///
+ ///
+ /// is less than 1000 or greater than 4999.
///
///
/// -or-
///
///
- /// could not be UTF-8-encoded.
+ /// The size of is greater than 123 bytes.
///
///
public void Close (ushort code, string reason)
{
if (!code.IsCloseStatusCode ()) {
var msg = "Less than 1000 or greater than 4999.";
+
throw new ArgumentOutOfRangeException ("code", msg);
}
- if (_client && code == 1011) {
- var msg = "1011 cannot be used.";
- throw new ArgumentException (msg, "code");
+ if (_isClient) {
+ if (code == 1011) {
+ var msg = "1011 cannot be used.";
+
+ throw new ArgumentException (msg, "code");
+ }
}
+ else {
+ if (code == 1010) {
+ var msg = "1010 cannot be used.";
- if (!_client && code == 1010) {
- var msg = "1010 cannot be used.";
- throw new ArgumentException (msg, "code");
+ throw new ArgumentException (msg, "code");
+ }
}
if (reason.IsNullOrEmpty ()) {
close (code, String.Empty);
+
return;
}
if (code == 1005) {
var msg = "1005 cannot be used.";
+
throw new ArgumentException (msg, "code");
}
byte[] bytes;
+
if (!reason.TryGetUTF8EncodedBytes (out bytes)) {
var msg = "It could not be UTF-8-encoded.";
+
throw new ArgumentException (msg, "reason");
}
if (bytes.Length > 123) {
var msg = "Its size is greater than 123 bytes.";
+
throw new ArgumentOutOfRangeException ("reason", msg);
}
@@ -2773,10 +3077,10 @@ public void Close (ushort code, string reason)
}
///
- /// Closes the connection with the specified code and reason.
+ /// Closes the connection with the specified status code and reason.
///
///
- /// This method does nothing if the current state of the connection is
+ /// This method does nothing if the current state of the interface is
/// Closing or Closed.
///
///
@@ -2784,37 +3088,41 @@ public void Close (ushort code, string reason)
/// One of the enum values.
///
///
- /// It represents the status code indicating the reason for the close.
+ /// It specifies the status code indicating the reason for the close.
///
///
///
///
- /// A that represents the reason for the close.
+ /// A that specifies the reason for the close.
///
///
- /// The size must be 123 bytes or less in UTF-8.
+ /// Its size must be 123 bytes or less in UTF-8.
///
///
///
///
- /// is
- /// .
- /// It cannot be used by clients.
+ /// is an undefined enum value.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ /// It cannot be used by a client.
///
///
/// -or-
///
///
- /// is
- /// .
- /// It cannot be used by servers.
+ /// is .
+ /// It cannot be used by a server.
///
///
/// -or-
///
///
- /// is
- /// and there is reason.
+ /// is and
+ /// is specified.
///
///
/// -or-
@@ -2828,34 +3136,50 @@ public void Close (ushort code, string reason)
///
public void Close (CloseStatusCode code, string reason)
{
- if (_client && code == CloseStatusCode.ServerError) {
- var msg = "ServerError cannot be used.";
+ if (!code.IsDefined ()) {
+ var msg = "An undefined enum value.";
+
throw new ArgumentException (msg, "code");
}
- if (!_client && code == CloseStatusCode.MandatoryExtension) {
- var msg = "MandatoryExtension cannot be used.";
- throw new ArgumentException (msg, "code");
+ if (_isClient) {
+ if (code == CloseStatusCode.ServerError) {
+ var msg = "ServerError cannot be used.";
+
+ throw new ArgumentException (msg, "code");
+ }
+ }
+ else {
+ if (code == CloseStatusCode.MandatoryExtension) {
+ var msg = "MandatoryExtension cannot be used.";
+
+ throw new ArgumentException (msg, "code");
+ }
}
if (reason.IsNullOrEmpty ()) {
close ((ushort) code, String.Empty);
+
return;
}
if (code == CloseStatusCode.NoStatus) {
var msg = "NoStatus cannot be used.";
+
throw new ArgumentException (msg, "code");
}
byte[] bytes;
+
if (!reason.TryGetUTF8EncodedBytes (out bytes)) {
var msg = "It could not be UTF-8-encoded.";
+
throw new ArgumentException (msg, "reason");
}
if (bytes.Length > 123) {
var msg = "Its size is greater than 123 bytes.";
+
throw new ArgumentOutOfRangeException ("reason", msg);
}
@@ -2870,7 +3194,7 @@ public void Close (CloseStatusCode code, string reason)
/// This method does not wait for the close to be complete.
///
///
- /// This method does nothing if the current state of the connection is
+ /// This method does nothing if the current state of the interface is
/// Closing or Closed.
///
///
@@ -2880,20 +3204,20 @@ public void CloseAsync ()
}
///
- /// Closes the connection asynchronously with the specified code.
+ /// Closes the connection asynchronously with the specified status code.
///
///
///
/// This method does not wait for the close to be complete.
///
///
- /// This method does nothing if the current state of the connection is
+ /// This method does nothing if the current state of the interface is
/// Closing or Closed.
///
///
///
///
- /// A that represents the status code indicating
+ /// A that specifies the status code indicating
/// the reason for the close.
///
///
@@ -2902,51 +3226,36 @@ public void CloseAsync ()
/// Section 7.4 of RFC 6455.
///
///
- ///
- /// is less than 1000 or greater than 4999.
- ///
///
///
/// is 1011 (server error).
- /// It cannot be used by clients.
+ /// It cannot be used by a client.
///
///
/// -or-
///
///
/// is 1010 (mandatory extension).
- /// It cannot be used by servers.
+ /// It cannot be used by a server.
///
///
+ ///
+ /// is less than 1000 or greater than 4999.
+ ///
public void CloseAsync (ushort code)
{
- if (!code.IsCloseStatusCode ()) {
- var msg = "Less than 1000 or greater than 4999.";
- throw new ArgumentOutOfRangeException ("code", msg);
- }
-
- if (_client && code == 1011) {
- var msg = "1011 cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- if (!_client && code == 1010) {
- var msg = "1010 cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- closeAsync (code, String.Empty);
+ CloseAsync (code, String.Empty);
}
///
- /// Closes the connection asynchronously with the specified code.
+ /// Closes the connection asynchronously with the specified status code.
///
///
///
/// This method does not wait for the close to be complete.
///
///
- /// This method does nothing if the current state of the connection is
+ /// This method does nothing if the current state of the interface is
/// Closing or Closed.
///
///
@@ -2955,54 +3264,49 @@ public void CloseAsync (ushort code)
/// One of the enum values.
///
///
- /// It represents the status code indicating the reason for the close.
+ /// It specifies the status code indicating the reason for the close.
///
///
///
///
- /// is
- /// .
- /// It cannot be used by clients.
+ /// is an undefined enum value.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ /// It cannot be used by 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);
}
///
- /// Closes the connection asynchronously with the specified code and reason.
+ /// Closes the connection asynchronously with the specified status code and
+ /// reason.
///
///
///
/// This method does not wait for the close to be complete.
///
///
- /// This method does nothing if the current state of the connection is
+ /// This method does nothing if the current state of the interface is
/// Closing or Closed.
///
///
///
///
- /// A that represents the status code indicating
+ /// A that specifies the status code indicating
/// the reason for the close.
///
///
@@ -3013,83 +3317,95 @@ public void CloseAsync (CloseStatusCode code)
///
///
///
- /// A that represents the reason for the close.
+ /// A that specifies the reason for the close.
///
///
- /// The size must be 123 bytes or less in UTF-8.
+ /// Its size must be 123 bytes or less in UTF-8.
///
///
- ///
+ ///
///
- /// is less than 1000 or greater than 4999.
+ /// is 1011 (server error).
+ /// It cannot be used by a client.
///
///
/// -or-
///
///
- /// The size of is greater than 123 bytes.
- ///
- ///
- ///
- ///
- /// is 1011 (server error).
- /// It cannot be used by clients.
+ /// is 1010 (mandatory extension).
+ /// It cannot be used by a server.
///
///
/// -or-
///
///
- /// is 1010 (mandatory extension).
- /// It cannot be used by servers.
+ /// is 1005 (no status) and
+ /// is specified.
///
///
/// -or-
///
///
- /// is 1005 (no status) and there is reason.
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ ///
+ ///
+ /// is less than 1000 or greater than 4999.
///
///
/// -or-
///
///
- /// could not be UTF-8-encoded.
+ /// The size of is greater than 123 bytes.
///
///
public void CloseAsync (ushort code, string reason)
{
if (!code.IsCloseStatusCode ()) {
var msg = "Less than 1000 or greater than 4999.";
+
throw new ArgumentOutOfRangeException ("code", msg);
}
- if (_client && code == 1011) {
- var msg = "1011 cannot be used.";
- throw new ArgumentException (msg, "code");
+ if (_isClient) {
+ if (code == 1011) {
+ var msg = "1011 cannot be used.";
+
+ throw new ArgumentException (msg, "code");
+ }
}
+ else {
+ if (code == 1010) {
+ var msg = "1010 cannot be used.";
- if (!_client && code == 1010) {
- var msg = "1010 cannot be used.";
- throw new ArgumentException (msg, "code");
+ throw new ArgumentException (msg, "code");
+ }
}
if (reason.IsNullOrEmpty ()) {
closeAsync (code, String.Empty);
+
return;
}
if (code == 1005) {
var msg = "1005 cannot be used.";
+
throw new ArgumentException (msg, "code");
}
byte[] bytes;
+
if (!reason.TryGetUTF8EncodedBytes (out bytes)) {
var msg = "It could not be UTF-8-encoded.";
+
throw new ArgumentException (msg, "reason");
}
if (bytes.Length > 123) {
var msg = "Its size is greater than 123 bytes.";
+
throw new ArgumentOutOfRangeException ("reason", msg);
}
@@ -3097,14 +3413,15 @@ public void CloseAsync (ushort code, string reason)
}
///
- /// Closes the connection asynchronously with the specified code and reason.
+ /// Closes the connection asynchronously with the specified status code and
+ /// reason.
///
///
///
/// This method does not wait for the close to be complete.
///
///
- /// This method does nothing if the current state of the connection is
+ /// This method does nothing if the current state of the interface is
/// Closing or Closed.
///
///
@@ -3113,37 +3430,41 @@ public void CloseAsync (ushort code, string reason)
/// One of the enum values.
///
///
- /// It represents the status code indicating the reason for the close.
+ /// It specifies the status code indicating the reason for the close.
///
///
///
///
- /// A that represents the reason for the close.
+ /// A that specifies the reason for the close.
///
///
- /// The size must be 123 bytes or less in UTF-8.
+ /// Its size must be 123 bytes or less in UTF-8.
///
///
///
///
- /// is
- /// .
- /// It cannot be used by clients.
+ /// is an undefined enum value.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ /// It cannot be used by a client.
///
///
/// -or-
///
///
- /// is
- /// .
- /// It cannot be used by servers.
+ /// is .
+ /// It cannot be used by a server.
///
///
/// -or-
///
///
- /// is
- /// and there is reason.
+ /// is and
+ /// is specified.
///
///
/// -or-
@@ -3157,34 +3478,50 @@ public void CloseAsync (ushort code, string reason)
///
public void CloseAsync (CloseStatusCode code, string reason)
{
- if (_client && code == CloseStatusCode.ServerError) {
- var msg = "ServerError cannot be used.";
+ if (!code.IsDefined ()) {
+ var msg = "An undefined enum value.";
+
throw new ArgumentException (msg, "code");
}
- if (!_client && code == CloseStatusCode.MandatoryExtension) {
- var msg = "MandatoryExtension cannot be used.";
- throw new ArgumentException (msg, "code");
+ if (_isClient) {
+ if (code == CloseStatusCode.ServerError) {
+ var msg = "ServerError cannot be used.";
+
+ throw new ArgumentException (msg, "code");
+ }
+ }
+ else {
+ if (code == CloseStatusCode.MandatoryExtension) {
+ var msg = "MandatoryExtension cannot be used.";
+
+ throw new ArgumentException (msg, "code");
+ }
}
if (reason.IsNullOrEmpty ()) {
closeAsync ((ushort) code, String.Empty);
+
return;
}
if (code == CloseStatusCode.NoStatus) {
var msg = "NoStatus cannot be used.";
+
throw new ArgumentException (msg, "code");
}
byte[] bytes;
+
if (!reason.TryGetUTF8EncodedBytes (out bytes)) {
var msg = "It could not be UTF-8-encoded.";
+
throw new ArgumentException (msg, "reason");
}
if (bytes.Length > 123) {
var msg = "Its size is greater than 123 bytes.";
+
throw new ArgumentOutOfRangeException ("reason", msg);
}
@@ -3195,44 +3532,42 @@ public void CloseAsync (CloseStatusCode code, string reason)
/// Establishes a connection.
///
///
- /// This method does nothing if the connection has already been established.
+ /// This method does nothing when the current state of the interface is
+ /// Connecting or Open.
///
///
///
- /// This instance is not a client.
- ///
- ///
- /// -or-
- ///
- ///
- /// The close process is in progress.
+ /// The Connect method is not available if the interface is not for
+ /// the client.
///
///
/// -or-
///
///
- /// A series of reconnecting has failed.
+ /// The Connect method is not available if a series of reconnecting
+ /// has failed.
///
///
public void Connect ()
{
- if (!_client) {
- var msg = "This instance is not a client.";
- throw new InvalidOperationException (msg);
- }
+ if (!_isClient) {
+ var msg = "The Connect method is not available.";
- if (_readyState == WebSocketState.Closing) {
- var msg = "The close process is in progress.";
throw new InvalidOperationException (msg);
}
- if (_retryCountForConnect > _maxRetryCountForConnect) {
- var msg = "A series of reconnecting has failed.";
+ if (_retryCountForConnect >= _maxRetryCountForConnect) {
+ var msg = "The Connect method is not available.";
+
throw new InvalidOperationException (msg);
}
- if (connect ())
- open ();
+ var connected = connect ();
+
+ if (!connected)
+ return;
+
+ open ();
}
///
@@ -3243,80 +3578,77 @@ public void Connect ()
/// This method does not wait for the connect process to be complete.
///
///
- /// This method does nothing if the connection has already been
- /// established.
+ /// This method does nothing when the current state of the interface is
+ /// Connecting or Open.
///
///
///
///
- /// This instance is not a client.
- ///
- ///
- /// -or-
- ///
- ///
- /// The close process is in progress.
+ /// The ConnectAsync method is not available if the interface is not
+ /// for the client.
///
///
/// -or-
///
///
- /// A series of reconnecting has failed.
+ /// The ConnectAsync method is not available if a series of reconnecting
+ /// has failed.
///
///
public void ConnectAsync ()
{
- if (!_client) {
- var msg = "This instance is not a client.";
- throw new InvalidOperationException (msg);
- }
+ if (!_isClient) {
+ var msg = "The ConnectAsync method is not available.";
- if (_readyState == WebSocketState.Closing) {
- var msg = "The close process is in progress.";
throw new InvalidOperationException (msg);
}
- if (_retryCountForConnect > _maxRetryCountForConnect) {
- var msg = "A series of reconnecting has failed.";
+ if (_retryCountForConnect >= _maxRetryCountForConnect) {
+ var msg = "The ConnectAsync method is not available.";
+
throw new InvalidOperationException (msg);
}
Func connector = connect;
+
connector.BeginInvoke (
ar => {
- if (connector.EndInvoke (ar))
- open ();
+ var connected = connector.EndInvoke (ar);
+
+ if (!connected)
+ return;
+
+ open ();
},
null
);
}
///
- /// Sends a ping using the WebSocket connection.
+ /// Sends a ping to the remote endpoint.
///
///
- /// true if the send has done with no error and a pong has been
+ /// true if the send has successfully done and a pong has been
/// received within a time; otherwise, false.
///
public bool Ping ()
{
- return ping (EmptyBytes);
+ return ping (_emptyBytes);
}
///
- /// Sends a ping with using the WebSocket
- /// connection.
+ /// Sends a ping with the specified message to the remote endpoint.
///
///
- /// true if the send has done with no error and a pong has been
+ /// true if the send has successfully done and a pong has been
/// received within a time; otherwise, false.
///
///
///
- /// A that represents the message to send.
+ /// A that specifies the message to send.
///
///
- /// The size must be 125 bytes or less in UTF-8.
+ /// Its size must be 125 bytes or less in UTF-8.
///
///
///
@@ -3328,16 +3660,19 @@ public bool Ping ()
public bool Ping (string message)
{
if (message.IsNullOrEmpty ())
- return ping (EmptyBytes);
+ return ping (_emptyBytes);
byte[] bytes;
+
if (!message.TryGetUTF8EncodedBytes (out bytes)) {
var msg = "It could not be UTF-8-encoded.";
+
throw new ArgumentException (msg, "message");
}
if (bytes.Length > 125) {
var msg = "Its size is greater than 125 bytes.";
+
throw new ArgumentOutOfRangeException ("message", msg);
}
@@ -3345,21 +3680,23 @@ public bool Ping (string message)
}
///
- /// Sends the specified data using the WebSocket connection.
+ /// Sends the specified data to the remote endpoint.
///
///
- /// An array of that represents the binary data to send.
+ /// An array of that specifies the binary data to send.
///
- ///
- /// The current state of the connection is not Open.
- ///
///
/// is .
///
+ ///
+ /// The Send method is not available when the current state of
+ /// the interface is not Open.
+ ///
public void Send (byte[] data)
{
if (_readyState != WebSocketState.Open) {
- var msg = "The current state of the connection is not Open.";
+ var msg = "The Send method is not available.";
+
throw new InvalidOperationException (msg);
}
@@ -3370,7 +3707,7 @@ public void Send (byte[] data)
}
///
- /// Sends the specified file using the WebSocket connection.
+ /// Sends the specified file to the remote endpoint.
///
///
///
@@ -3380,12 +3717,6 @@ public void Send (byte[] data)
/// The file is sent as the binary data.
///
///
- ///
- /// The current state of the connection is not Open.
- ///
- ///
- /// is .
- ///
///
///
/// The file does not exist.
@@ -3397,10 +3728,18 @@ public void Send (byte[] data)
/// The file could not be opened.
///
///
+ ///
+ /// is .
+ ///
+ ///
+ /// The Send method is not available when the current state of
+ /// the interface is not Open.
+ ///
public void Send (FileInfo fileInfo)
{
if (_readyState != WebSocketState.Open) {
- var msg = "The current state of the connection is not Open.";
+ var msg = "The Send method is not available.";
+
throw new InvalidOperationException (msg);
}
@@ -3409,12 +3748,15 @@ public void Send (FileInfo fileInfo)
if (!fileInfo.Exists) {
var msg = "The file does not exist.";
+
throw new ArgumentException (msg, "fileInfo");
}
FileStream stream;
+
if (!fileInfo.TryOpenRead (out stream)) {
var msg = "The file could not be opened.";
+
throw new ArgumentException (msg, "fileInfo");
}
@@ -3422,24 +3764,26 @@ public void Send (FileInfo fileInfo)
}
///
- /// Sends the specified data using the WebSocket connection.
+ /// Sends the specified data to the remote endpoint.
///
///
- /// A that represents the text data to send.
+ /// A that specifies the text data to send.
///
- ///
- /// The current state of the connection is not Open.
+ ///
+ /// could not be UTF-8-encoded.
///
///
/// is .
///
- ///
- /// could not be UTF-8-encoded.
+ ///
+ /// The Send method is not available when the current state of
+ /// the interface is not Open.
///
public void Send (string data)
{
if (_readyState != WebSocketState.Open) {
- var msg = "The current state of the connection is not Open.";
+ var msg = "The Send method is not available.";
+
throw new InvalidOperationException (msg);
}
@@ -3447,8 +3791,10 @@ public void Send (string data)
throw new ArgumentNullException ("data");
byte[] bytes;
+
if (!data.TryGetUTF8EncodedBytes (out bytes)) {
var msg = "It could not be UTF-8-encoded.";
+
throw new ArgumentException (msg, "data");
}
@@ -3456,7 +3802,7 @@ public void Send (string data)
}
///
- /// Sends the data from the specified stream using the WebSocket connection.
+ /// Sends the data from the specified stream instance to the remote endpoint.
///
///
///
@@ -3469,12 +3815,6 @@ public void Send (string data)
///
/// An that specifies the number of bytes to send.
///
- ///
- /// The current state of the connection is not Open.
- ///
- ///
- /// is .
- ///
///
///
/// cannot be read.
@@ -3492,10 +3832,18 @@ public void Send (string data)
/// No data could be read from .
///
///
+ ///
+ /// is .
+ ///
+ ///
+ /// The Send method is not available when the current state of
+ /// the interface is not Open.
+ ///
public void Send (Stream stream, int length)
{
if (_readyState != WebSocketState.Open) {
- var msg = "The current state of the connection is not Open.";
+ var msg = "The Send method is not available.";
+
throw new InvalidOperationException (msg);
}
@@ -3504,66 +3852,71 @@ public void Send (Stream stream, int length)
if (!stream.CanRead) {
var msg = "It cannot be read.";
+
throw new ArgumentException (msg, "stream");
}
if (length < 1) {
var msg = "Less than 1.";
+
throw new ArgumentException (msg, "length");
}
var bytes = stream.ReadBytes (length);
-
var len = bytes.Length;
+
if (len == 0) {
var msg = "No data could be read from it.";
+
throw new ArgumentException (msg, "stream");
}
if (len < length) {
- _logger.Warn (
- String.Format (
- "Only {0} byte(s) of data could be read from the stream.",
- len
- )
- );
+ var fmt = "Only {0} byte(s) of data could be read from the stream.";
+ var msg = String.Format (fmt, len);
+
+ _log.Warn (msg);
}
send (Opcode.Binary, new MemoryStream (bytes));
}
///
- /// Sends the specified data asynchronously using the WebSocket connection.
+ /// Sends the specified data to the remote endpoint asynchronously.
///
///
/// This method does not wait for the send to be complete.
///
///
- /// An array of that represents the binary data to send.
+ /// An array of that specifies the binary data to send.
///
///
///
- /// An Action<bool> delegate or
- /// if not needed.
+ /// An delegate.
///
///
- /// The delegate invokes the method called when the send is complete.
+ /// It specifies the delegate called when the send is complete.
///
///
- /// true is passed to the method if the send has done with
- /// no error; otherwise, false.
+ /// The parameter passed to the delegate is true
+ /// if the send has successfully done; otherwise, false.
+ ///
+ ///
+ /// if not necessary.
///
///
- ///
- /// The current state of the connection is not Open.
- ///
///
/// is .
///
+ ///
+ /// The SendAsync method is not available when the current state of
+ /// the interface is not Open.
+ ///
public void SendAsync (byte[] data, Action completed)
{
if (_readyState != WebSocketState.Open) {
- var msg = "The current state of the connection is not Open.";
+ var msg = "The SendAsync method is not available.";
+
throw new InvalidOperationException (msg);
}
@@ -3574,7 +3927,7 @@ public void SendAsync (byte[] data, Action completed)
}
///
- /// Sends the specified file asynchronously using the WebSocket connection.
+ /// Sends the specified file to the remote endpoint asynchronously.
///
///
/// This method does not wait for the send to be complete.
@@ -3589,23 +3942,19 @@ public void SendAsync (byte[] data, Action completed)
///
///
///
- /// An Action<bool> delegate or
- /// if not needed.
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the send is complete.
///
///
- /// The delegate invokes the method called when the send is complete.
+ /// The parameter passed to the delegate is true
+ /// if the send has successfully done; otherwise, false.
///
///
- /// true is passed to the method if the send has done with
- /// no error; otherwise, false.
+ /// if not necessary.
///
///
- ///
- /// The current state of the connection is not Open.
- ///
- ///
- /// is .
- ///
///
///
/// The file does not exist.
@@ -3617,10 +3966,18 @@ public void SendAsync (byte[] data, Action completed)
/// The file could not be opened.
///
///
+ ///
+ /// is .
+ ///
+ ///
+ /// The SendAsync method is not available when the current state of
+ /// the interface is not Open.
+ ///
public void SendAsync (FileInfo fileInfo, Action completed)
{
if (_readyState != WebSocketState.Open) {
- var msg = "The current state of the connection is not Open.";
+ var msg = "The SendAsync method is not available.";
+
throw new InvalidOperationException (msg);
}
@@ -3629,12 +3986,15 @@ public void SendAsync (FileInfo fileInfo, Action completed)
if (!fileInfo.Exists) {
var msg = "The file does not exist.";
+
throw new ArgumentException (msg, "fileInfo");
}
FileStream stream;
+
if (!fileInfo.TryOpenRead (out stream)) {
var msg = "The file could not be opened.";
+
throw new ArgumentException (msg, "fileInfo");
}
@@ -3642,40 +4002,44 @@ public void SendAsync (FileInfo fileInfo, Action completed)
}
///
- /// Sends the specified data asynchronously using the WebSocket connection.
+ /// Sends the specified data to the remote endpoint asynchronously.
///
///
/// This method does not wait for the send to be complete.
///
///
- /// A that represents the text data to send.
+ /// A that specifies the text data to send.
///
///
///
- /// An Action<bool> delegate or
- /// if not needed.
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the send is complete.
///
///
- /// The delegate invokes the method called when the send is complete.
+ /// The parameter passed to the delegate is true
+ /// if the send has successfully done; otherwise, false.
///
///
- /// true is passed to the method if the send has done with
- /// no error; otherwise, false.
+ /// if not necessary.
///
///
- ///
- /// The current state of the connection is not Open.
+ ///
+ /// could not be UTF-8-encoded.
///
///
/// is .
///
- ///
- /// could not be UTF-8-encoded.
+ ///
+ /// The SendAsync method is not available when the current state of
+ /// the interface is not Open.
///
public void SendAsync (string data, Action completed)
{
if (_readyState != WebSocketState.Open) {
- var msg = "The current state of the connection is not Open.";
+ var msg = "The SendAsync method is not available.";
+
throw new InvalidOperationException (msg);
}
@@ -3683,8 +4047,10 @@ public void SendAsync (string data, Action completed)
throw new ArgumentNullException ("data");
byte[] bytes;
+
if (!data.TryGetUTF8EncodedBytes (out bytes)) {
var msg = "It could not be UTF-8-encoded.";
+
throw new ArgumentException (msg, "data");
}
@@ -3692,8 +4058,8 @@ public void SendAsync (string data, Action completed)
}
///
- /// Sends the data from the specified stream asynchronously using
- /// the WebSocket connection.
+ /// Sends the data from the specified stream instance to the remote
+ /// endpoint asynchronously.
///
///
/// This method does not wait for the send to be complete.
@@ -3711,23 +4077,19 @@ public void SendAsync (string data, Action completed)
///
///
///
- /// An Action<bool> delegate or
- /// if not needed.
+ /// An delegate.
+ ///
+ ///
+ /// It specifies the delegate called when the send is complete.
///
///
- /// The delegate invokes the method called when the send is complete.
+ /// The parameter passed to the delegate is true
+ /// if the send has successfully done; otherwise, false.
///
///
- /// true is passed to the method if the send has done with
- /// no error; otherwise, false.
+ /// if not necessary.
///
///
- ///
- /// The current state of the connection is not Open.
- ///
- ///
- /// is .
- ///
///
///
/// cannot be read.
@@ -3745,10 +4107,18 @@ public void SendAsync (string data, Action completed)
/// No data could be read from .
///
///
+ ///
+ /// is .
+ ///
+ ///
+ /// The SendAsync method is not available when the current state of
+ /// the interface is not Open.
+ ///
public void SendAsync (Stream stream, int length, Action completed)
{
if (_readyState != WebSocketState.Open) {
- var msg = "The current state of the connection is not Open.";
+ var msg = "The SendAsync method is not available.";
+
throw new InvalidOperationException (msg);
}
@@ -3757,89 +4127,71 @@ public void SendAsync (Stream stream, int length, Action completed)
if (!stream.CanRead) {
var msg = "It cannot be read.";
+
throw new ArgumentException (msg, "stream");
}
if (length < 1) {
var msg = "Less than 1.";
+
throw new ArgumentException (msg, "length");
}
var bytes = stream.ReadBytes (length);
-
var len = bytes.Length;
+
if (len == 0) {
var msg = "No data could be read from it.";
+
throw new ArgumentException (msg, "stream");
}
if (len < length) {
- _logger.Warn (
- String.Format (
- "Only {0} byte(s) of data could be read from the stream.",
- len
- )
- );
+ var fmt = "Only {0} byte(s) of data could be read from the stream.";
+ var msg = String.Format (fmt, len);
+
+ _log.Warn (msg);
}
sendAsync (Opcode.Binary, new MemoryStream (bytes), completed);
}
///
- /// Sets an HTTP cookie to send with the handshake request.
+ /// Sets an HTTP cookie to send with the handshake request or response.
///
- ///
- /// This method does nothing if the connection has already been
- /// established or it is closing.
- ///
///
- /// A that represents the cookie to send.
+ /// A that specifies the cookie to send.
///
- ///
- /// This instance is not a client.
- ///
///
/// is .
///
+ ///
+ /// The SetCookie method is not available when the current state of
+ /// the interface is neither New nor Closed.
+ ///
public void SetCookie (Cookie cookie)
{
- string msg = null;
-
- if (!_client) {
- msg = "This instance is not a client.";
- throw new InvalidOperationException (msg);
- }
-
if (cookie == null)
throw new ArgumentNullException ("cookie");
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
- }
-
lock (_forState) {
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
+ if (!canSet ()) {
+ var msg = "The SetCookie method is not available.";
+
+ throw new InvalidOperationException (msg);
}
- lock (_cookies.SyncRoot)
- _cookies.SetOrRemove (cookie);
+ Cookies.SetOrRemove (cookie);
}
}
///
/// Sets the credentials for the HTTP authentication (Basic/Digest).
///
- ///
- /// This method does nothing if the connection has already been
- /// established or it is closing.
- ///
///
///
- /// A that represents the username associated with
- /// the credentials.
+ /// A that specifies the username associated
+ /// with the credentials.
///
///
/// or an empty string if initializes
@@ -3848,20 +4200,18 @@ public void SetCookie (Cookie cookie)
///
///
///
- /// A that represents the password for the username
- /// associated with the credentials.
+ /// A that specifies the password for the
+ /// username associated with the credentials.
///
///
/// or an empty string if not necessary.
///
///
///
- /// true if sends the credentials for the Basic authentication in
- /// advance with the first handshake request; otherwise, false.
+ /// A : true if sends the credentials for
+ /// the Basic authentication in advance with the first handshake
+ /// request; otherwise, false.
///
- ///
- /// This instance is not a client.
- ///
///
///
/// contains an invalid character.
@@ -3873,38 +4223,48 @@ public void SetCookie (Cookie cookie)
/// contains an invalid character.
///
///
+ ///
+ ///
+ /// The SetCredentials method is not available if the interface is not for
+ /// the client.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The SetCredentials method is not available when the current state of
+ /// the interface is neither New nor Closed.
+ ///
+ ///
public void SetCredentials (string username, string password, bool preAuth)
{
- string msg = null;
+ if (!_isClient) {
+ var msg = "The SetCredentials method is not available.";
- if (!_client) {
- msg = "This instance is not a client.";
throw new InvalidOperationException (msg);
}
if (!username.IsNullOrEmpty ()) {
if (username.Contains (':') || !username.IsText ()) {
- msg = "It contains an invalid character.";
+ var msg = "It contains an invalid character.";
+
throw new ArgumentException (msg, "username");
}
}
if (!password.IsNullOrEmpty ()) {
if (!password.IsText ()) {
- msg = "It contains an invalid character.";
+ var msg = "It contains an invalid character.";
+
throw new ArgumentException (msg, "password");
}
}
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
- }
-
lock (_forState) {
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
+ if (!canSet ()) {
+ var msg = "The SetCredentials method is not available.";
+
+ throw new InvalidOperationException (msg);
}
if (username.IsNullOrEmpty ()) {
@@ -3915,7 +4275,9 @@ public void SetCredentials (string username, string password, bool preAuth)
}
_credentials = new NetworkCredential (
- username, password, _uri.PathAndQuery
+ username,
+ password,
+ _uri.PathAndQuery
);
_preAuth = preAuth;
@@ -3926,45 +4288,38 @@ public void SetCredentials (string username, string password, bool preAuth)
/// Sets the URL of the HTTP proxy server through which to connect and
/// the credentials for the HTTP proxy authentication (Basic/Digest).
///
- ///
- /// This method does nothing if the connection has already been
- /// established or it is closing.
- ///
///
///
- /// A that represents the URL of the proxy server
- /// through which to connect.
+ /// A that specifies the URL of the proxy
+ /// server through which to connect.
///
///
/// The syntax is http://<host>[:<port>].
///
///
- /// or an empty string if initializes the URL and
- /// the credentials.
+ /// or an empty string if initializes
+ /// the URL and the credentials.
///
///
///
///
- /// A that represents the username associated with
- /// the credentials.
+ /// A that specifies the username associated
+ /// with the credentials.
///
///
- /// or an empty string if the credentials are not
- /// necessary.
+ /// or an empty string if the credentials
+ /// are not necessary.
///
///
///
///
- /// A that represents the password for the username
- /// associated with the credentials.
+ /// A that specifies the password for the
+ /// username associated with the credentials.
///
///
/// or an empty string if not necessary.
///
///
- ///
- /// This instance is not a client.
- ///
///
///
/// is not an absolute URI string.
@@ -3994,12 +4349,24 @@ public void SetCredentials (string username, string password, bool preAuth)
/// contains an invalid character.
///
///
+ ///
+ ///
+ /// The SetProxy method is not available if the interface is not for
+ /// the client.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The SetProxy method is not available when the current state of
+ /// the interface is neither New nor Closed.
+ ///
+ ///
public void SetProxy (string url, string username, string password)
{
- string msg = null;
+ if (!_isClient) {
+ var msg = "The SetProxy method is not available.";
- if (!_client) {
- msg = "This instance is not a client.";
throw new InvalidOperationException (msg);
}
@@ -4007,44 +4374,45 @@ public void SetProxy (string url, string username, string password)
if (!url.IsNullOrEmpty ()) {
if (!Uri.TryCreate (url, UriKind.Absolute, out uri)) {
- msg = "Not an absolute URI string.";
+ var msg = "Not an absolute URI string.";
+
throw new ArgumentException (msg, "url");
}
if (uri.Scheme != "http") {
- msg = "The scheme part is not http.";
+ var msg = "The scheme part is not http.";
+
throw new ArgumentException (msg, "url");
}
if (uri.Segments.Length > 1) {
- msg = "It includes the path segments.";
+ var msg = "It includes the path segments.";
+
throw new ArgumentException (msg, "url");
}
}
if (!username.IsNullOrEmpty ()) {
if (username.Contains (':') || !username.IsText ()) {
- msg = "It contains an invalid character.";
+ var msg = "It contains an invalid character.";
+
throw new ArgumentException (msg, "username");
}
}
if (!password.IsNullOrEmpty ()) {
if (!password.IsText ()) {
- msg = "It contains an invalid character.";
+ var msg = "It contains an invalid character.";
+
throw new ArgumentException (msg, "password");
}
}
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
- }
-
lock (_forState) {
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
+ if (!canSet ()) {
+ var msg = "The SetProxy method is not available.";
+
+ throw new InvalidOperationException (msg);
}
if (url.IsNullOrEmpty ()) {
@@ -4055,15 +4423,87 @@ public void SetProxy (string url, string username, string password)
}
_proxyUri = uri;
- _proxyCredentials = !username.IsNullOrEmpty ()
- ? new NetworkCredential (
- username,
- password,
- String.Format (
- "{0}:{1}", _uri.DnsSafeHost, _uri.Port
- )
- )
- : null;
+
+ if (username.IsNullOrEmpty ()) {
+ _proxyCredentials = null;
+
+ return;
+ }
+
+ var domain = String.Format ("{0}:{1}", _uri.DnsSafeHost, _uri.Port);
+
+ _proxyCredentials = new NetworkCredential (username, password, domain);
+ }
+ }
+
+ ///
+ /// Sets a user header to send with the handshake request or response.
+ ///
+ ///
+ /// A that specifies the name of the header to set.
+ ///
+ ///
+ /// A that specifies the value of the header to set.
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a string of spaces.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is a restricted header name.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// The length of is greater than 65,535
+ /// characters.
+ ///
+ ///
+ ///
+ /// The SetUserHeader method is not available when the current state of
+ /// the interface is neither New nor Closed.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The SetUserHeader method is not available if the interface does not
+ /// allow the header type.
+ ///
+ ///
+ public void SetUserHeader (string name, string value)
+ {
+ lock (_forState) {
+ if (!canSet ()) {
+ var msg = "The SetUserHeader method is not available.";
+
+ throw new InvalidOperationException (msg);
+ }
+
+ UserHeaders.Set (name, value);
}
}
@@ -4079,7 +4519,7 @@ public void SetProxy (string url, string username, string password)
/// This method closes the connection with close status 1001 (going away).
///
///
- /// And this method does nothing if the current state of the connection is
+ /// This method does nothing if the current state of the interface is
/// Closing or Closed.
///
///
diff --git a/websocket-sharp/WebSocketException.cs b/websocket-sharp/WebSocketException.cs
index 81d7c8081..12bfc48f8 100644
--- a/websocket-sharp/WebSocketException.cs
+++ b/websocket-sharp/WebSocketException.cs
@@ -4,7 +4,7 @@
*
* The MIT License
*
- * Copyright (c) 2012-2016 sta.blockhead
+ * Copyright (c) 2012-2024 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -38,7 +38,21 @@ public class WebSocketException : Exception
{
#region Private Fields
- private CloseStatusCode _code;
+ private ushort _code;
+
+ #endregion
+
+ #region Private Constructors
+
+ private WebSocketException (
+ ushort code,
+ string message,
+ Exception innerException
+ )
+ : base (message ?? code.GetErrorMessage (), innerException)
+ {
+ _code = code;
+ }
#endregion
@@ -80,11 +94,12 @@ internal WebSocketException (CloseStatusCode code, string message)
}
internal WebSocketException (
- CloseStatusCode code, string message, Exception innerException
+ CloseStatusCode code,
+ string message,
+ Exception innerException
)
- : base (message ?? code.GetMessage (), innerException)
+ : this ((ushort) code, message, innerException)
{
- _code = code;
}
#endregion
@@ -95,10 +110,15 @@ internal WebSocketException (
/// Gets the status code indicating the cause of the exception.
///
///
- /// One of the enum values that represents
- /// the status code indicating the cause of the exception.
+ ///
+ /// A that represents the status code indicating
+ /// the cause of the exception.
+ ///
+ ///
+ /// It is one of the status codes for the WebSocket connection close.
+ ///
///
- public CloseStatusCode Code {
+ public ushort Code {
get {
return _code;
}
diff --git a/websocket-sharp/WebSocketFrame.cs b/websocket-sharp/WebSocketFrame.cs
index ba0de3cd8..9ce51b945 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-2025 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -45,30 +45,19 @@ 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 static readonly byte[] _emptyBytes;
+ private byte[] _extPayloadLength;
+ private Fin _fin;
+ private Mask _mask;
+ private byte[] _maskingKey;
+ private Opcode _opcode;
+ private PayloadData _payloadData;
+ private int _payloadLength;
+ private Rsv _rsv1;
+ private Rsv _rsv2;
+ private Rsv _rsv3;
#endregion
@@ -76,7 +65,9 @@ internal class WebSocketFrame : IEnumerable
static WebSocketFrame ()
{
- EmptyPingBytes = CreatePingFrame (false).ToArray ();
+ _defaultHeaderLength = 2;
+ _defaultMaskingKeyLength = 4;
+ _emptyBytes = new byte[0];
}
#endregion
@@ -91,13 +82,12 @@ 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
+ Fin fin,
+ Opcode opcode,
+ byte[] data,
+ bool compressed,
+ bool mask
)
: this (fin, opcode, new PayloadData (data), compressed, mask)
{
@@ -114,32 +104,34 @@ bool mask
_fin = fin;
_opcode = opcode;
- _rsv1 = opcode.IsData () && compressed ? Rsv.On : Rsv.Off;
+ _rsv1 = compressed ? Rsv.On : Rsv.Off;
_rsv2 = Rsv.Off;
_rsv3 = Rsv.Off;
var len = payloadData.Length;
+
if (len < 126) {
- _payloadLength = (byte) len;
- _extPayloadLength = WebSocket.EmptyBytes;
+ _payloadLength = (int) len;
+ _extPayloadLength = _emptyBytes;
}
else if (len < 0x010000) {
- _payloadLength = (byte) 126;
- _extPayloadLength = ((ushort) len).InternalToByteArray (ByteOrder.Big);
+ _payloadLength = 126;
+ _extPayloadLength = ((ushort) len).ToByteArray (ByteOrder.Big);
}
else {
- _payloadLength = (byte) 127;
- _extPayloadLength = len.InternalToByteArray (ByteOrder.Big);
+ _payloadLength = 127;
+ _extPayloadLength = len.ToByteArray (ByteOrder.Big);
}
if (mask) {
_mask = Mask.On;
_maskingKey = createMaskingKey ();
+
payloadData.Mask (_maskingKey);
}
else {
_mask = Mask.Off;
- _maskingKey = WebSocket.EmptyBytes;
+ _maskingKey = _emptyBytes;
}
_payloadData = payloadData;
@@ -152,7 +144,7 @@ bool mask
internal ulong ExactPayloadLength {
get {
return _payloadLength < 126
- ? _payloadLength
+ ? (ulong) _payloadLength
: _payloadLength == 126
? _extPayloadLength.ToUInt16 (ByteOrder.Big)
: _extPayloadLength.ToUInt64 (ByteOrder.Big);
@@ -259,8 +251,11 @@ public bool IsText {
public ulong Length {
get {
- return 2
- + (ulong) (_extPayloadLength.Length + _maskingKey.Length)
+ return (ulong) (
+ _defaultHeaderLength
+ + _extPayloadLength.Length
+ + _maskingKey.Length
+ )
+ _payloadData.Length;
}
}
@@ -289,7 +284,7 @@ public PayloadData PayloadData {
}
}
- public byte PayloadLength {
+ public int PayloadLength {
get {
return _payloadLength;
}
@@ -319,159 +314,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);
}
@@ -488,37 +342,22 @@ private static WebSocketFrame processHeader (byte[] header)
var rsv3 = (header[0] & 0x10) == 0x10 ? Rsv.On : Rsv.Off;
// Opcode
- var opcode = (byte) (header[0] & 0x0f);
+ var opcode = header[0] & 0x0f;
// MASK
var mask = (header[1] & 0x80) == 0x80 ? Mask.On : Mask.Off;
// Payload Length
- var payloadLen = (byte) (header[1] & 0x7f);
+ var payloadLen = header[1] & 0x7f;
- if (!opcode.IsSupported ()) {
- var msg = "A frame has an unsupported opcode.";
- throw new WebSocketException (CloseStatusCode.ProtocolError, msg);
- }
+ if (!opcode.IsSupportedOpcode ()) {
+ var msg = "The opcode of a frame is not supported.";
- if (!opcode.IsData () && rsv1 == Rsv.On) {
- var msg = "A non data frame is compressed.";
- throw new WebSocketException (CloseStatusCode.ProtocolError, msg);
- }
-
- if (opcode.IsControl ()) {
- if (fin == Fin.More) {
- var msg = "A control frame is fragmented.";
- throw new WebSocketException (CloseStatusCode.ProtocolError, msg);
- }
-
- if (payloadLen > 125) {
- var msg = "A control frame has too long payload length.";
- throw new WebSocketException (CloseStatusCode.ProtocolError, msg);
- }
+ throw new WebSocketException (CloseStatusCode.UnsupportedData, msg);
}
var frame = new WebSocketFrame ();
+
frame._fin = fin;
frame._rsv1 = rsv1;
frame._rsv2 = rsv2;
@@ -531,22 +370,28 @@ private static WebSocketFrame processHeader (byte[] header)
}
private static WebSocketFrame readExtendedPayloadLength (
- Stream stream, WebSocketFrame frame
+ Stream stream,
+ WebSocketFrame frame
)
{
var len = frame.ExtendedPayloadLengthWidth;
+
if (len == 0) {
- frame._extPayloadLength = WebSocket.EmptyBytes;
+ frame._extPayloadLength = _emptyBytes;
+
return frame;
}
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 +403,10 @@ Action error
)
{
var len = frame.ExtendedPayloadLengthWidth;
+
if (len == 0) {
- frame._extPayloadLength = WebSocket.EmptyBytes;
+ frame._extPayloadLength = _emptyBytes;
+
completed (frame);
return;
@@ -570,10 +417,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,36 +431,49 @@ 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 (
- Stream stream, Action completed, Action error
+ Stream stream,
+ Action completed,
+ Action error
)
{
stream.ReadBytesAsync (
- 2, bytes => completed (processHeader (bytes)), error
+ _defaultHeaderLength,
+ bytes => {
+ var frame = processHeader (bytes);
+
+ completed (frame);
+ },
+ error
);
}
private static WebSocketFrame readMaskingKey (
- Stream stream, WebSocketFrame frame
+ Stream stream,
+ WebSocketFrame frame
)
{
if (!frame.IsMasked) {
- frame._maskingKey = WebSocket.EmptyBytes;
+ frame._maskingKey = _emptyBytes;
+
return frame;
}
- var len = 4;
- var bytes = stream.ReadBytes (len);
+ var bytes = stream.ReadBytes (_defaultMaskingKeyLength);
- if (bytes.Length != len) {
+ if (bytes.Length != _defaultMaskingKeyLength) {
var msg = "The masking key of a frame could not be read.";
+
throw new WebSocketException (msg);
}
frame._maskingKey = bytes;
+
return frame;
}
@@ -623,23 +485,24 @@ Action error
)
{
if (!frame.IsMasked) {
- frame._maskingKey = WebSocket.EmptyBytes;
+ frame._maskingKey = _emptyBytes;
+
completed (frame);
return;
}
- var len = 4;
-
stream.ReadBytesAsync (
- len,
+ _defaultMaskingKeyLength,
bytes => {
- if (bytes.Length != len) {
+ if (bytes.Length != _defaultMaskingKeyLength) {
var msg = "The masking key of a frame could not be read.";
+
throw new WebSocketException (msg);
}
frame._maskingKey = bytes;
+
completed (frame);
},
error
@@ -647,31 +510,37 @@ Action error
}
private static WebSocketFrame readPayloadData (
- Stream stream, WebSocketFrame frame
+ 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 +551,179 @@ 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}";
}
- catch {
- return null;
+ else if (cnt < 0x0100000000) {
+ spFmt = "{0,8}";
+ cntFmt = "{0,8:X}";
+ }
+ else {
+ spFmt = "{0,16}";
+ cntFmt = "{0,16:X}";
}
+
+ var baseFmt = @"{0} 01234567 89ABCDEF 01234567 89ABCDEF
+{0}+--------+--------+--------+--------+
+";
+ var headerFmt = String.Format (baseFmt, spFmt);
+
+ baseFmt = "{0}|{{1,8}} {{2,8}} {{3,8}} {{4,8}}|\n";
+ var lineFmt = String.Format (baseFmt, cntFmt);
+
+ baseFmt = "{0}+--------+--------+--------+--------+";
+ var footerFmt = String.Format (baseFmt, spFmt);
+
+ var buff = new StringBuilder (64);
+
+ Func> lineWriter =
+ () => {
+ long lineCnt = 0;
+
+ return (arg1, arg2, arg3, arg4) => {
+ buff.AppendFormat (
+ lineFmt,
+ ++lineCnt,
+ arg1,
+ arg2,
+ arg3,
+ arg4
+ );
+ };
+ };
+
+ var writeLine = lineWriter ();
+ var bytes = ToArray ();
+
+ buff.AppendFormat (headerFmt, String.Empty);
+
+ for (long i = 0; i <= cnt; i++) {
+ var j = i * 4;
+
+ if (i < cnt) {
+ var arg1 = Convert.ToString (bytes[j], 2).PadLeft (8, '0');
+ var arg2 = Convert.ToString (bytes[j + 1], 2).PadLeft (8, '0');
+ var arg3 = Convert.ToString (bytes[j + 2], 2).PadLeft (8, '0');
+ var arg4 = Convert.ToString (bytes[j + 3], 2).PadLeft (8, '0');
+
+ writeLine (arg1, arg2, arg3, arg4);
+
+ continue;
+ }
+
+ if (rem > 0) {
+ var arg1 = Convert.ToString (bytes[j], 2).PadLeft (8, '0');
+ var arg2 = rem >= 2
+ ? Convert.ToString (bytes[j + 1], 2).PadLeft (8, '0')
+ : String.Empty;
+
+ var arg3 = rem == 3
+ ? Convert.ToString (bytes[j + 2], 2).PadLeft (8, '0')
+ : String.Empty;
+
+ writeLine (arg1, arg2, arg3, String.Empty);
+ }
+ }
+
+ buff.AppendFormat (footerFmt, String.Empty);
+
+ return buff.ToString ();
+ }
+
+ private string toString ()
+ {
+ var extPayloadLen = _payloadLength >= 126
+ ? ExactPayloadLength.ToString ()
+ : String.Empty;
+
+ var maskingKey = _mask == Mask.On
+ ? BitConverter.ToString (_maskingKey)
+ : String.Empty;
+
+ var payloadData = _payloadLength >= 126
+ ? "***"
+ : _payloadLength > 0
+ ? _payloadData.ToString ()
+ : String.Empty;
+
+ var fmt = @" FIN: {0}
+ RSV1: {1}
+ RSV2: {2}
+ RSV3: {3}
+ Opcode: {4}
+ MASK: {5}
+ Payload Length: {6}
+Extended Payload Length: {7}
+ Masking Key: {8}
+ Payload Data: {9}";
+
+ return String.Format (
+ fmt,
+ _fin,
+ _rsv1,
+ _rsv2,
+ _rsv3,
+ _opcode,
+ _mask,
+ _payloadLength,
+ extPayloadLen,
+ maskingKey,
+ payloadData
+ );
}
#endregion
@@ -730,40 +731,59 @@ private static string utf8Decode (byte[] bytes)
#region Internal Methods
internal static WebSocketFrame CreateCloseFrame (
- PayloadData payloadData, bool mask
+ PayloadData payloadData,
+ bool mask
)
{
return new WebSocketFrame (
- Fin.Final, Opcode.Close, payloadData, false, mask
+ Fin.Final,
+ Opcode.Close,
+ payloadData,
+ false,
+ mask
);
}
internal static WebSocketFrame CreatePingFrame (bool mask)
{
return new WebSocketFrame (
- Fin.Final, Opcode.Ping, PayloadData.Empty, false, mask
+ Fin.Final,
+ Opcode.Ping,
+ PayloadData.Empty,
+ false,
+ mask
);
}
internal static WebSocketFrame CreatePingFrame (byte[] data, bool mask)
{
return new WebSocketFrame (
- Fin.Final, Opcode.Ping, new PayloadData (data), false, mask
+ Fin.Final,
+ Opcode.Ping,
+ new PayloadData (data),
+ false,
+ mask
);
}
internal static WebSocketFrame CreatePongFrame (
- PayloadData payloadData, bool mask
+ PayloadData payloadData,
+ bool mask
)
{
return new WebSocketFrame (
- Fin.Final, Opcode.Pong, payloadData, false, mask
+ Fin.Final,
+ Opcode.Pong,
+ payloadData,
+ false,
+ mask
);
}
internal static WebSocketFrame ReadFrame (Stream stream, bool unmask)
{
var frame = readHeader (stream);
+
readExtendedPayloadLength (stream, frame);
readMaskingKey (stream, frame);
readPayloadData (stream, frame);
@@ -811,14 +831,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;
+
+ _maskingKey = _emptyBytes;
+ _mask = Mask.Off;
}
#endregion
@@ -831,54 +857,49 @@ 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 ()) {
var header = (int) _fin;
+
header = (header << 1) + (int) _rsv1;
header = (header << 1) + (int) _rsv2;
header = (header << 1) + (int) _rsv3;
header = (header << 4) + (int) _opcode;
header = (header << 1) + (int) _mask;
- header = (header << 7) + (int) _payloadLength;
+ header = (header << 7) + _payloadLength;
- buff.Write (
- ((ushort) header).InternalToByteArray (ByteOrder.Big), 0, 2
- );
+ var headerAsUInt16 = (ushort) header;
+ var headerAsBytes = headerAsUInt16.ToByteArray (ByteOrder.Big);
- if (_payloadLength > 125)
- buff.Write (_extPayloadLength, 0, _payloadLength == 126 ? 2 : 8);
+ buff.Write (headerAsBytes, 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 @@
-