diff --git a/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationBase.cs b/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationBase.cs
index 2ca1941..35c4d75 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationBase.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationBase.cs
@@ -84,7 +84,7 @@ namespace EonaCat.Network
/// A collection of authentication parameters.
internal static NameValueCollection ParseParameters(string value)
{
- var res = new NameValueCollection();
+ var result = new NameValueCollection();
foreach (var param in value.SplitHeaderValue(','))
{
var i = param.IndexOf('=');
@@ -95,10 +95,10 @@ namespace EonaCat.Network
? param.Substring(i + 1).Trim().Trim('"')
: string.Empty;
- res.Add(name, val);
+ result.Add(name, val);
}
- return res;
+ return result;
}
///
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationChallenge.cs b/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationChallenge.cs
index 7b98248..4c4ba68 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationChallenge.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationChallenge.cs
@@ -15,16 +15,6 @@ namespace EonaCat.Network
private const string DIGEST = "digest";
private const int DIGEST_SIZE = 128;
- ///
- /// Initializes a new instance of the class.
- ///
- /// The authentication scheme.
- /// The collection of authentication parameters.
- private AuthenticationChallenge(AuthenticationSchemes scheme, NameValueCollection parameters)
- : base(scheme, parameters)
- {
- }
-
///
/// Initializes a new instance of the class for Basic or Digest authentication.
///
@@ -42,6 +32,15 @@ namespace EonaCat.Network
}
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The authentication scheme.
+ /// The collection of authentication parameters.
+ private AuthenticationChallenge(AuthenticationSchemes scheme, NameValueCollection parameters)
+ : base(scheme, parameters)
+ {
+ }
///
/// Gets the domain for Digest authentication.
///
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationResponse.cs b/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationResponse.cs
index 6cc4b4f..10d14c6 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationResponse.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Authentication/AuthenticationResponse.cs
@@ -18,16 +18,6 @@ namespace EonaCat.Network
private const string DIGEST = "digest";
private uint _nonceCount;
- ///
- /// Initializes a new instance of the class.
- ///
- /// The authentication scheme.
- /// The collection of authentication parameters.
- private AuthenticationResponse(AuthenticationSchemes scheme, NameValueCollection parameters)
- : base(scheme, parameters)
- {
- }
-
///
/// Initializes a new instance of the class for Basic authentication.
///
@@ -74,12 +64,14 @@ namespace EonaCat.Network
}
///
- /// Gets the nonce count.
+ /// Initializes a new instance of the class.
///
- internal uint NonceCount => _nonceCount < uint.MaxValue
- ? _nonceCount
- : 0;
-
+ /// The authentication scheme.
+ /// The collection of authentication parameters.
+ private AuthenticationResponse(AuthenticationSchemes scheme, NameValueCollection parameters)
+ : base(scheme, parameters)
+ {
+ }
///
/// Gets the cnonce value for Digest authentication.
///
@@ -111,97 +103,23 @@ namespace EonaCat.Network
public string UserName => Parameters["username"];
///
- /// Creates the A1 value for Digest authentication.
+ /// Gets the nonce count.
///
- /// The username.
- /// The password.
- /// The realm.
- /// The A1 value.
- private static string CreateA1(string username, string password, string realm)
- {
- return $"{username}:{realm}:{password}";
- }
-
+ internal uint NonceCount => _nonceCount < uint.MaxValue
+ ? _nonceCount
+ : 0;
///
- /// Creates the A1 value for Digest authentication with cnonce and nonce.
+ /// Converts the authentication response to an identity.
///
- /// The username.
- /// The password.
- /// The realm.
- /// The nonce.
- /// The cnonce.
- /// The A1 value.
- private static string CreateA1(
- string username, string password, string realm, string nonce, string cnonce)
+ /// An instance of .
+ public IIdentity ToIdentity()
{
- return $"{Hash(CreateA1(username, password, realm))}:{nonce}:{cnonce}";
- }
-
- ///
- /// Creates the A2 value for Digest authentication.
- ///
- /// The HTTP method.
- /// The URI.
- /// The A2 value.
- private static string CreateA2(string method, string uri)
- {
- return $"{method}:{uri}";
- }
-
- ///
- /// Creates the A2 value for Digest authentication with an entity.
- ///
- /// The HTTP method.
- /// The URI.
- /// The entity.
- /// The A2 value.
- private static string CreateA2(string method, string uri, string entity)
- {
- return $"{method}:{uri}:{Hash(entity)}";
- }
-
- ///
- /// Computes the MD5 hash of the given value.
- ///
- /// The value to hash.
- /// The MD5 hash.
- private static string Hash(string value)
- {
- var source = Encoding.UTF8.GetBytes(value);
- var md5 = MD5.Create();
- var hashed = md5.ComputeHash(source);
-
- var result = new StringBuilder(64);
- foreach (var currentByte in hashed)
- {
- result.Append(currentByte.ToString("x2"));
- }
-
- return result.ToString();
- }
-
- ///
- /// Initializes the authentication as Digest.
- ///
- private void InitAsDigest()
- {
- 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);
- }
- else
- {
- Parameters["qop"] = null;
- }
- }
-
- Parameters["method"] = "GET";
- Parameters["response"] = CreateRequestDigest(Parameters);
+ var scheme = Scheme;
+ return scheme == AuthenticationSchemes.Basic
+ ? new HttpBasicIdentity(Parameters["username"], Parameters["password"])
+ : scheme == AuthenticationSchemes.Digest
+ ? new HttpDigestIdentity(Parameters)
+ : null;
}
///
@@ -343,17 +261,97 @@ namespace EonaCat.Network
}
///
- /// Converts the authentication response to an identity.
+ /// Creates the A1 value for Digest authentication.
///
- /// An instance of .
- public IIdentity ToIdentity()
+ /// The username.
+ /// The password.
+ /// The realm.
+ /// The A1 value.
+ private static string CreateA1(string username, string password, string realm)
{
- var scheme = Scheme;
- return scheme == AuthenticationSchemes.Basic
- ? new HttpBasicIdentity(Parameters["username"], Parameters["password"])
- : scheme == AuthenticationSchemes.Digest
- ? new HttpDigestIdentity(Parameters)
- : null;
+ return $"{username}:{realm}:{password}";
+ }
+
+ ///
+ /// Creates the A1 value for Digest authentication with cnonce and nonce.
+ ///
+ /// The username.
+ /// The password.
+ /// The realm.
+ /// The nonce.
+ /// The cnonce.
+ /// The A1 value.
+ private static string CreateA1(
+ string username, string password, string realm, string nonce, string cnonce)
+ {
+ return $"{Hash(CreateA1(username, password, realm))}:{nonce}:{cnonce}";
+ }
+
+ ///
+ /// Creates the A2 value for Digest authentication.
+ ///
+ /// The HTTP method.
+ /// The URI.
+ /// The A2 value.
+ private static string CreateA2(string method, string uri)
+ {
+ return $"{method}:{uri}";
+ }
+
+ ///
+ /// Creates the A2 value for Digest authentication with an entity.
+ ///
+ /// The HTTP method.
+ /// The URI.
+ /// The entity.
+ /// The A2 value.
+ private static string CreateA2(string method, string uri, string entity)
+ {
+ return $"{method}:{uri}:{Hash(entity)}";
+ }
+
+ ///
+ /// Computes the MD5 hash of the given value.
+ ///
+ /// The value to hash.
+ /// The MD5 hash.
+ private static string Hash(string value)
+ {
+ var source = Encoding.UTF8.GetBytes(value);
+ var md5 = MD5.Create();
+ var hashed = md5.ComputeHash(source);
+
+ var result = new StringBuilder(64);
+ foreach (var currentByte in hashed)
+ {
+ result.Append(currentByte.ToString("x2"));
+ }
+
+ return result.ToString();
+ }
+
+ ///
+ /// Initializes the authentication as Digest.
+ ///
+ private void InitAsDigest()
+ {
+ 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);
+ }
+ else
+ {
+ Parameters["qop"] = null;
+ }
+ }
+
+ Parameters["method"] = "GET";
+ Parameters["response"] = CreateRequestDigest(Parameters);
}
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Authentication/NetworkCredential.cs b/EonaCat.Network/System/Sockets/Web/Core/Authentication/NetworkCredential.cs
index ded296b..ce6e467 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Authentication/NetworkCredential.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Authentication/NetworkCredential.cs
@@ -10,8 +10,8 @@ namespace EonaCat.Network
///
public class NetworkCredential
{
- private string _domain;
private static readonly string[] _noRoles;
+ private string _domain;
private string _password;
private string[] _roles;
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Chunks/ChunkStream.cs b/EonaCat.Network/System/Sockets/Web/Core/Chunks/ChunkStream.cs
index a7a9e4a..4fa478b 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Chunks/ChunkStream.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Chunks/ChunkStream.cs
@@ -15,11 +15,11 @@ namespace EonaCat.Network
///
internal class ChunkStream
{
+ private readonly List _chunks;
+ private readonly StringBuilder _saved;
private int _chunkRead;
private int _chunkSize;
- private readonly List _chunks;
private bool _foundSPCode;
- private readonly StringBuilder _saved;
private bool _gotChunck;
private InputChunkState _state;
private int _trailerState;
@@ -49,11 +49,6 @@ namespace EonaCat.Network
Write(buffer, offset, count);
}
- ///
- /// Gets the web headers associated with the chunk stream.
- ///
- internal WebHeaderCollection Headers { get; }
-
///
/// Gets the number of bytes left in the current chunk.
///
@@ -64,6 +59,78 @@ namespace EonaCat.Network
///
public bool WantMore => _state != InputChunkState.End;
+ ///
+ /// Gets the web headers associated with the chunk stream.
+ ///
+ internal WebHeaderCollection Headers { get; }
+ ///
+ /// Reads a specified amount of data from the chunk stream.
+ ///
+ /// The destination buffer.
+ /// The zero-based byte offset in the buffer at which to begin storing the data.
+ /// The maximum number of bytes to read.
+ /// The total number of bytes read into the buffer.
+ public int Read(byte[] buffer, int offset, int count)
+ {
+ if (count <= 0)
+ {
+ return 0;
+ }
+
+ return read(buffer, offset, count);
+ }
+
+ ///
+ /// Writes a specified amount of data to the chunk stream.
+ ///
+ /// The byte array containing data to be written to the chunk stream.
+ /// The offset in the buffer at which to begin writing.
+ /// The number of bytes to write to the chunk stream.
+ public void Write(byte[] buffer, int offset, int count)
+ {
+ if (count <= 0)
+ {
+ return;
+ }
+
+ Write(buffer, ref offset, offset + count);
+ }
+
+ ///
+ /// Resets the internal buffer and state of the chunk stream.
+ ///
+ internal void ResetBuffer()
+ {
+ _chunkRead = 0;
+ _chunkSize = -1;
+ _chunks.Clear();
+ }
+
+ ///
+ /// Writes a specified amount of data to the chunk stream and reads it back.
+ ///
+ /// The byte array containing data to be written to the chunk stream.
+ /// The offset in the buffer at which to begin writing.
+ /// The number of bytes to write to the chunk stream.
+ /// The number of bytes to read back from the chunk stream.
+ /// The number of bytes read from the chunk stream.
+ internal int WriteAndReadBack(byte[] buffer, int offset, int writeCount, int readCount)
+ {
+ Write(buffer, offset, writeCount);
+ return Read(buffer, offset, readCount);
+ }
+
+ private static string RemoveChunkExtension(string value)
+ {
+ var index = value.IndexOf(';');
+ return index > -1 ? value.Substring(0, index) : value;
+ }
+
+ private static void ThrowProtocolViolation(string message)
+ {
+ throw new WebException($"EonaCat Network: {message}", null, WebExceptionStatus.ServerProtocolViolation, null);
+ }
+
private int read(byte[] buffer, int offset, int count)
{
var nread = 0;
@@ -92,13 +159,6 @@ namespace EonaCat.Network
return nread;
}
-
- private static string RemoveChunkExtension(string value)
- {
- var index = value.IndexOf(';');
- return index > -1 ? value.Substring(0, index) : value;
- }
-
private InputChunkState SeekCrLf(byte[] buffer, ref int offset, int length)
{
if (!_gotChunck)
@@ -256,12 +316,6 @@ namespace EonaCat.Network
return InputChunkState.End;
}
-
- private static void ThrowProtocolViolation(string message)
- {
- throw new WebException($"EonaCat Network: {message}", null, WebExceptionStatus.ServerProtocolViolation, null);
- }
-
private void Write(byte[] buffer, ref int offset, int length)
{
if (_state == InputChunkState.End)
@@ -337,62 +391,5 @@ namespace EonaCat.Network
return _chunkRead == _chunkSize ? InputChunkState.DataEnded : InputChunkState.Data;
}
-
- ///
- /// Resets the internal buffer and state of the chunk stream.
- ///
- internal void ResetBuffer()
- {
- _chunkRead = 0;
- _chunkSize = -1;
- _chunks.Clear();
- }
-
- ///
- /// Writes a specified amount of data to the chunk stream and reads it back.
- ///
- /// The byte array containing data to be written to the chunk stream.
- /// The offset in the buffer at which to begin writing.
- /// The number of bytes to write to the chunk stream.
- /// The number of bytes to read back from the chunk stream.
- /// The number of bytes read from the chunk stream.
- internal int WriteAndReadBack(byte[] buffer, int offset, int writeCount, int readCount)
- {
- Write(buffer, offset, writeCount);
- return Read(buffer, offset, readCount);
- }
-
- ///
- /// Reads a specified amount of data from the chunk stream.
- ///
- /// The destination buffer.
- /// The zero-based byte offset in the buffer at which to begin storing the data.
- /// The maximum number of bytes to read.
- /// The total number of bytes read into the buffer.
- public int Read(byte[] buffer, int offset, int count)
- {
- if (count <= 0)
- {
- return 0;
- }
-
- return read(buffer, offset, count);
- }
-
- ///
- /// Writes a specified amount of data to the chunk stream.
- ///
- /// The byte array containing data to be written to the chunk stream.
- /// The offset in the buffer at which to begin writing.
- /// The number of bytes to write to the chunk stream.
- public void Write(byte[] buffer, int offset, int count)
- {
- if (count <= 0)
- {
- return;
- }
-
- Write(buffer, ref offset, offset + count);
- }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Chunks/ChunkedRequestStream.cs b/EonaCat.Network/System/Sockets/Web/Core/Chunks/ChunkedRequestStream.cs
index e76eb43..d62b77d 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Chunks/ChunkedRequestStream.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Chunks/ChunkedRequestStream.cs
@@ -36,37 +36,6 @@ namespace EonaCat.Network
///
internal ChunkStream Decoder { get; set; }
- private void OnRead(IAsyncResult asyncResult)
- {
- var readBufferState = (ReadBufferState)asyncResult.AsyncState;
- var result = readBufferState.AsyncResult;
- try
- {
- var nread = base.EndRead(asyncResult);
- Decoder.Write(result.Buffer, result.Offset, nread);
- nread = Decoder.Read(readBufferState.Buffer, readBufferState.Offset, readBufferState.Count);
- readBufferState.Offset += nread;
- readBufferState.Count -= nread;
- if (readBufferState.Count == 0 || !Decoder.WantMore || nread == 0)
- {
- _noMoreData = !Decoder.WantMore && nread == 0;
- result.Count = readBufferState.InitialCount - readBufferState.Count;
- result.Complete();
-
- return;
- }
-
- result.Offset = 0;
- result.Count = Math.Min(_bufferLength, Decoder.ChunkLeft + 6);
- base.BeginRead(result.Buffer, result.Offset, result.Count, OnRead, readBufferState);
- }
- catch (Exception ex)
- {
- _context.Connection.SendError(ex.Message, 400);
- result.Complete(ex);
- }
- }
-
///
/// Begins an asynchronous read operation from the stream.
///
@@ -204,5 +173,36 @@ namespace EonaCat.Network
var result = BeginRead(buffer, offset, count, null, null);
return EndRead(result);
}
+
+ private void OnRead(IAsyncResult asyncResult)
+ {
+ var readBufferState = (ReadBufferState)asyncResult.AsyncState;
+ var result = readBufferState.AsyncResult;
+ try
+ {
+ var nread = base.EndRead(asyncResult);
+ Decoder.Write(result.Buffer, result.Offset, nread);
+ nread = Decoder.Read(readBufferState.Buffer, readBufferState.Offset, readBufferState.Count);
+ readBufferState.Offset += nread;
+ readBufferState.Count -= nread;
+ if (readBufferState.Count == 0 || !Decoder.WantMore || nread == 0)
+ {
+ _noMoreData = !Decoder.WantMore && nread == 0;
+ result.Count = readBufferState.InitialCount - readBufferState.Count;
+ result.Complete();
+
+ return;
+ }
+
+ result.Offset = 0;
+ result.Count = Math.Min(_bufferLength, Decoder.ChunkLeft + 6);
+ base.BeginRead(result.Buffer, result.Offset, result.Count, OnRead, readBufferState);
+ }
+ catch (Exception ex)
+ {
+ _context.Connection.SendError(ex.Message, 400);
+ result.Complete(ex);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Collections/WebHeaderCollection.cs b/EonaCat.Network/System/Sockets/Web/Core/Collections/WebHeaderCollection.cs
index 16416dd..0e0ca9b 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Collections/WebHeaderCollection.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Collections/WebHeaderCollection.cs
@@ -90,6 +90,10 @@ namespace EonaCat.Network
};
}
+ public WebHeaderCollection()
+ {
+ }
+
///
/// Initializes a new instance of the WebHeaderCollection class with the specified parameters.
///
@@ -132,18 +136,10 @@ namespace EonaCat.Network
throw new ArgumentException(ex.Message, nameof(serializationInfo), ex);
}
}
-
-
- public WebHeaderCollection()
- {
- }
-
- internal HttpHeaderType State => _state;
-
public override string[] AllKeys => base.AllKeys;
-
public override int Count => base.Count;
-
+ public override KeysCollection Keys => base.Keys;
+ internal HttpHeaderType State => _state;
public string this[HttpRequestHeader header]
{
get
@@ -169,272 +165,14 @@ namespace EonaCat.Network
Add(header, value);
}
}
-
- public override KeysCollection Keys => base.Keys;
-
- private void add(string name, string value, bool ignoreRestricted)
+ public static bool IsRestricted(string headerName)
{
- var act = ignoreRestricted
- ? (Action)addWithoutCheckingNameAndRestricted
- : addWithoutCheckingName;
-
- DoWithCheckingState(act, CheckName(name), value, true);
+ return isRestricted(CheckName(headerName), false);
}
- private void addWithoutCheckingName(string name, string value)
+ public static bool IsRestricted(string headerName, bool response)
{
- DoWithoutCheckingName(base.Add, name, value);
- }
-
- private void addWithoutCheckingNameAndRestricted(string name, string value)
- {
- base.Add(name, CheckValue(value));
- }
-
- private static int checkColonSeparated(string header)
- {
- var idx = header.IndexOf(':');
- if (idx == -1)
- {
- throw new ArgumentException("No colon could be found.", nameof(header));
- }
-
- return idx;
- }
-
- private static HttpHeaderType CheckHeaderType(string name)
- {
- var info = GetHeaderInfo(name);
- return info == null
- ? HttpHeaderType.Unspecified
- : info.IsRequest && !info.IsResponse
- ? HttpHeaderType.Request
- : !info.IsRequest && info.IsResponse
- ? HttpHeaderType.Response
- : HttpHeaderType.Unspecified;
- }
-
- private static string CheckName(string name)
- {
- if (name == null || name.Length == 0)
- {
- throw new ArgumentNullException(nameof(name));
- }
-
- name = name.Trim();
- if (!IsHeaderName(name))
- {
- throw new ArgumentException("Contains invalid characters.", nameof(name));
- }
-
- return name;
- }
-
- private void CheckRestricted(string name)
- {
- if (!_internallyUsed && isRestricted(name, true))
- {
- throw new ArgumentException("This header must be modified with the appropiate property.");
- }
- }
-
- private void CheckState(bool response)
- {
- if (_state == HttpHeaderType.Unspecified)
- {
- return;
- }
-
- if (response && _state == HttpHeaderType.Request)
- {
- throw new InvalidOperationException(
- "This collection has already been used to store the request headers.");
- }
-
- if (!response && _state == HttpHeaderType.Response)
- {
- throw new InvalidOperationException(
- "This collection has already been used to store the response headers.");
- }
- }
-
- private static string CheckValue(string value)
- {
- if (value == null || value.Length == 0)
- {
- return string.Empty;
- }
-
- value = value.Trim();
- if (value.Length > 65535)
- {
- throw new ArgumentOutOfRangeException(nameof(value), "Greater than 65,535 characters.");
- }
-
- if (!IsHeaderValue(value))
- {
- throw new ArgumentException("Contains invalid characters.", nameof(value));
- }
-
- return value;
- }
-
- private static string Convert(string key)
- {
- return _headers.TryGetValue(key, out HttpHeaderInfo info) ? info.Name : string.Empty;
- }
-
- private void DoWithCheckingState(
- Action action, string name, string value, bool setState)
- {
- var type = CheckHeaderType(name);
- if (type == HttpHeaderType.Request)
- {
- DoWithCheckingState(action, name, value, false, setState);
- }
- else if (type == HttpHeaderType.Response)
- {
- DoWithCheckingState(action, name, value, true, setState);
- }
- else
- {
- action(name, value);
- }
- }
-
- private void DoWithCheckingState(
- Action action, string name, string value, bool response, bool setState)
- {
- CheckState(response);
- action(name, value);
- if (setState && _state == HttpHeaderType.Unspecified)
- {
- _state = response ? HttpHeaderType.Response : HttpHeaderType.Request;
- }
- }
-
- private void DoWithoutCheckingName(Action action, string name, string value)
- {
- CheckRestricted(name);
- action(name, CheckValue(value));
- }
-
- private static HttpHeaderInfo GetHeaderInfo(string name)
- {
- foreach (var info in _headers.Values)
- {
- if (info.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))
- {
- return info;
- }
- }
-
- return null;
- }
-
- private static bool isRestricted(string name, bool response)
- {
- var info = GetHeaderInfo(name);
- return info != null && info.IsRestricted(response);
- }
-
- private void removeWithoutCheckingName(string name, string unuse)
- {
- CheckRestricted(name);
- base.Remove(name);
- }
-
- private void setWithoutCheckingName(string name, string value)
- {
- DoWithoutCheckingName(base.Set, name, value);
- }
-
- ///
- /// Converts the specified HttpRequestHeader to a string.
- ///
- /// The HttpRequestHeader to convert.
- /// A string representing the converted HttpRequestHeader.
- internal static string Convert(HttpRequestHeader header)
- {
- return Convert(header.ToString());
- }
-
- internal static string Convert(HttpResponseHeader header)
- {
- return Convert(header.ToString());
- }
-
- internal void InternalRemove(string name)
- {
- base.Remove(name);
- }
-
- internal void InternalSet(string header, bool response)
- {
- var pos = checkColonSeparated(header);
- InternalSet(header.Substring(0, pos), header.Substring(pos + 1), response);
- }
-
- internal void InternalSet(string name, string value, bool response)
- {
- value = CheckValue(value);
- if (IsMultiValue(name, response))
- {
- base.Add(name, value);
- }
- else
- {
- base.Set(name, value);
- }
- }
-
- internal static bool IsHeaderName(string name)
- {
- return name != null && name.Length > 0 && name.IsToken();
- }
-
- internal static bool IsHeaderValue(string value)
- {
- return value.IsText();
- }
-
- internal static bool IsMultiValue(string headerName, bool response)
- {
- if (headerName == null || headerName.Length == 0)
- {
- return false;
- }
-
- var info = GetHeaderInfo(headerName);
- return info != null && info.IsMultiValue(response);
- }
-
- internal string ToStringMultiValue(bool response)
- {
- var buff = new StringBuilder();
- Count.Times(
- i =>
- {
- var key = GetKey(i);
- if (IsMultiValue(key, response))
- {
- foreach (var val in GetValues(i))
- {
- buff.AppendFormat($"{key}: {val}\r\n");
- }
- }
- else
- {
- buff.AppendFormat($"{key}: {Get(i)}\r\n");
- }
- });
-
- return buff.Append("\r\n").ToString();
- }
-
- protected void AddWithoutValidate(string headerName, string headerValue)
- {
- add(headerName, headerValue, true);
+ return isRestricted(CheckName(headerName), response);
}
public void Add(string header)
@@ -489,18 +227,6 @@ namespace EonaCat.Network
return base.GetKey(index);
}
- public override string[] GetValues(int index)
- {
- var vals = base.GetValues(index);
- return vals != null && vals.Length > 0 ? vals : null;
- }
-
- public override string[] GetValues(string header)
- {
- var vals = base.GetValues(header);
- return vals != null && vals.Length > 0 ? vals : null;
- }
-
[SecurityPermission(
SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
public override void GetObjectData(
@@ -524,14 +250,26 @@ namespace EonaCat.Network
});
}
- public static bool IsRestricted(string headerName)
+ [SecurityPermission(
+ SecurityAction.LinkDemand,
+ Flags = SecurityPermissionFlag.SerializationFormatter,
+ SerializationFormatter = true)]
+ void ISerializable.GetObjectData(
+ SerializationInfo serializationInfo, StreamingContext streamingContext)
{
- return isRestricted(CheckName(headerName), false);
+ GetObjectData(serializationInfo, streamingContext);
}
- public static bool IsRestricted(string headerName, bool response)
+ public override string[] GetValues(int index)
{
- return isRestricted(CheckName(headerName), response);
+ var vals = base.GetValues(index);
+ return vals != null && vals.Length > 0 ? vals : null;
+ }
+
+ public override string[] GetValues(string header)
+ {
+ var vals = base.GetValues(header);
+ return vals != null && vals.Length > 0 ? vals : null;
}
public override void OnDeserialization(object sender)
@@ -589,14 +327,266 @@ namespace EonaCat.Network
return buff.Append("\r\n").ToString();
}
- [SecurityPermission(
- SecurityAction.LinkDemand,
- Flags = SecurityPermissionFlag.SerializationFormatter,
- SerializationFormatter = true)]
- void ISerializable.GetObjectData(
- SerializationInfo serializationInfo, StreamingContext streamingContext)
+ ///
+ /// Converts the specified HttpRequestHeader to a string.
+ ///
+ /// The HttpRequestHeader to convert.
+ /// A string representing the converted HttpRequestHeader.
+ internal static string Convert(HttpRequestHeader header)
{
- GetObjectData(serializationInfo, streamingContext);
+ return Convert(header.ToString());
+ }
+
+ internal static string Convert(HttpResponseHeader header)
+ {
+ return Convert(header.ToString());
+ }
+
+ internal static bool IsHeaderName(string name)
+ {
+ return name != null && name.Length > 0 && name.IsToken();
+ }
+
+ internal static bool IsHeaderValue(string value)
+ {
+ return value.IsText();
+ }
+
+ internal static bool IsMultiValue(string headerName, bool response)
+ {
+ if (headerName == null || headerName.Length == 0)
+ {
+ return false;
+ }
+
+ var info = GetHeaderInfo(headerName);
+ return info != null && info.IsMultiValue(response);
+ }
+
+ internal void InternalRemove(string name)
+ {
+ base.Remove(name);
+ }
+
+ internal void InternalSet(string header, bool response)
+ {
+ var pos = checkColonSeparated(header);
+ InternalSet(header.Substring(0, pos), header.Substring(pos + 1), response);
+ }
+
+ internal void InternalSet(string name, string value, bool response)
+ {
+ value = CheckValue(value);
+ if (IsMultiValue(name, response))
+ {
+ base.Add(name, value);
+ }
+ else
+ {
+ base.Set(name, value);
+ }
+ }
+
+ internal string ToStringMultiValue(bool response)
+ {
+ var buff = new StringBuilder();
+ Count.Times(
+ i =>
+ {
+ var key = GetKey(i);
+ if (IsMultiValue(key, response))
+ {
+ foreach (var val in GetValues(i))
+ {
+ buff.AppendFormat($"{key}: {val}\r\n");
+ }
+ }
+ else
+ {
+ buff.AppendFormat($"{key}: {Get(i)}\r\n");
+ }
+ });
+
+ return buff.Append("\r\n").ToString();
+ }
+
+ protected void AddWithoutValidate(string headerName, string headerValue)
+ {
+ add(headerName, headerValue, true);
+ }
+
+ private static int checkColonSeparated(string header)
+ {
+ var idx = header.IndexOf(':');
+ if (idx == -1)
+ {
+ throw new ArgumentException("No colon could be found.", nameof(header));
+ }
+
+ return idx;
+ }
+
+ private static HttpHeaderType CheckHeaderType(string name)
+ {
+ var info = GetHeaderInfo(name);
+ return info == null
+ ? HttpHeaderType.Unspecified
+ : info.IsRequest && !info.IsResponse
+ ? HttpHeaderType.Request
+ : !info.IsRequest && info.IsResponse
+ ? HttpHeaderType.Response
+ : HttpHeaderType.Unspecified;
+ }
+
+ private static string CheckName(string name)
+ {
+ if (name == null || name.Length == 0)
+ {
+ throw new ArgumentNullException(nameof(name));
+ }
+
+ name = name.Trim();
+ if (!IsHeaderName(name))
+ {
+ throw new ArgumentException("Contains invalid characters.", nameof(name));
+ }
+
+ return name;
+ }
+
+ private static string CheckValue(string value)
+ {
+ if (value == null || value.Length == 0)
+ {
+ return string.Empty;
+ }
+
+ value = value.Trim();
+ if (value.Length > 65535)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), "Greater than 65,535 characters.");
+ }
+
+ if (!IsHeaderValue(value))
+ {
+ throw new ArgumentException("Contains invalid characters.", nameof(value));
+ }
+
+ return value;
+ }
+
+ private static string Convert(string key)
+ {
+ return _headers.TryGetValue(key, out HttpHeaderInfo info) ? info.Name : string.Empty;
+ }
+
+ private static HttpHeaderInfo GetHeaderInfo(string name)
+ {
+ foreach (var info in _headers.Values)
+ {
+ if (info.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))
+ {
+ return info;
+ }
+ }
+
+ return null;
+ }
+
+ private static bool isRestricted(string name, bool response)
+ {
+ var info = GetHeaderInfo(name);
+ return info != null && info.IsRestricted(response);
+ }
+
+ private void add(string name, string value, bool ignoreRestricted)
+ {
+ var act = ignoreRestricted
+ ? (Action)addWithoutCheckingNameAndRestricted
+ : addWithoutCheckingName;
+
+ DoWithCheckingState(act, CheckName(name), value, true);
+ }
+
+ private void addWithoutCheckingName(string name, string value)
+ {
+ DoWithoutCheckingName(base.Add, name, value);
+ }
+
+ private void addWithoutCheckingNameAndRestricted(string name, string value)
+ {
+ base.Add(name, CheckValue(value));
+ }
+ private void CheckRestricted(string name)
+ {
+ if (!_internallyUsed && isRestricted(name, true))
+ {
+ throw new ArgumentException("This header must be modified with the appropiate property.");
+ }
+ }
+
+ private void CheckState(bool response)
+ {
+ if (_state == HttpHeaderType.Unspecified)
+ {
+ return;
+ }
+
+ if (response && _state == HttpHeaderType.Request)
+ {
+ throw new InvalidOperationException(
+ "This collection has already been used to store the request headers.");
+ }
+
+ if (!response && _state == HttpHeaderType.Response)
+ {
+ throw new InvalidOperationException(
+ "This collection has already been used to store the response headers.");
+ }
+ }
+ private void DoWithCheckingState(
+ Action action, string name, string value, bool setState)
+ {
+ var type = CheckHeaderType(name);
+ if (type == HttpHeaderType.Request)
+ {
+ DoWithCheckingState(action, name, value, false, setState);
+ }
+ else if (type == HttpHeaderType.Response)
+ {
+ DoWithCheckingState(action, name, value, true, setState);
+ }
+ else
+ {
+ action(name, value);
+ }
+ }
+
+ private void DoWithCheckingState(
+ Action action, string name, string value, bool response, bool setState)
+ {
+ CheckState(response);
+ action(name, value);
+ if (setState && _state == HttpHeaderType.Unspecified)
+ {
+ _state = response ? HttpHeaderType.Response : HttpHeaderType.Request;
+ }
+ }
+
+ private void DoWithoutCheckingName(Action action, string name, string value)
+ {
+ CheckRestricted(name);
+ action(name, CheckValue(value));
+ }
+ private void removeWithoutCheckingName(string name, string unuse)
+ {
+ CheckRestricted(name);
+ base.Remove(name);
+ }
+
+ private void setWithoutCheckingName(string name, string value)
+ {
+ DoWithoutCheckingName(base.Set, name, value);
}
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Context/HttpListenerWSContext.cs b/EonaCat.Network/System/Sockets/Web/Core/Context/HttpListenerWSContext.cs
index 32a851d..1375c36 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Context/HttpListenerWSContext.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Context/HttpListenerWSContext.cs
@@ -33,11 +33,6 @@ namespace EonaCat.Network
_websocket = new WSClient(this, protocol);
}
- ///
- /// Gets the stream of the underlying TCP connection.
- ///
- internal Stream Stream => _context.Connection.Stream;
-
public override CookieCollection CookieCollection => _context.Request.Cookies;
public override NameValueCollection Headers => _context.Request.Headers;
@@ -85,6 +80,16 @@ namespace EonaCat.Network
public override WSClient WebSocket => _websocket;
+ ///
+ /// Gets the stream of the underlying TCP connection.
+ ///
+ internal Stream Stream => _context.Connection.Stream;
+ ///
+ public override string ToString()
+ {
+ return _context.Request.ToString();
+ }
+
///
/// Closes the WebSocket connection.
///
@@ -101,11 +106,5 @@ namespace EonaCat.Network
{
_context.Response.Close(code);
}
-
- ///
- public override string ToString()
- {
- return _context.Request.ToString();
- }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Context/TcpListenerWSContext.cs b/EonaCat.Network/System/Sockets/Web/Core/Context/TcpListenerWSContext.cs
index 30fee85..e359cc9 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Context/TcpListenerWSContext.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Context/TcpListenerWSContext.cs
@@ -21,15 +21,14 @@ namespace EonaCat.Network
///
internal class TcpListenerWSContext : WSContext
{
- private CookieCollection _cookies;
- private NameValueCollection _queryString;
- private WebRequest _request;
private readonly bool _secure;
private readonly TcpClient _tcpClient;
private readonly Uri _uri;
- private IPrincipal _user;
private readonly WSClient _websocket;
-
+ private CookieCollection _cookies;
+ private NameValueCollection _queryString;
+ private WebRequest _request;
+ private IPrincipal _user;
///
/// Initializes a new instance of the class.
///
@@ -76,11 +75,6 @@ namespace EonaCat.Network
_websocket = new WSClient(this, protocol);
}
- ///
- /// Gets the stream of the underlying TCP connection.
- ///
- internal Stream Stream { get; }
-
public override CookieCollection CookieCollection => _cookies ??= _request.Cookies;
public override NameValueCollection Headers => _request.Headers;
@@ -98,10 +92,10 @@ namespace EonaCat.Network
public override string Origin => _request.Headers["Origin"];
public override NameValueCollection QueryString => _queryString ??=
- HttpUtility.InternalParseQueryString(
- _uri?.Query, Encoding.UTF8
- )
-;
+ HttpUtility.InternalParseQueryString(
+ _uri?.Query, Encoding.UTF8
+ )
+ ;
public override Uri RequestUri => _uri;
@@ -132,6 +126,16 @@ namespace EonaCat.Network
public override WSClient WebSocket => _websocket;
+ ///
+ /// Gets the stream of the underlying TCP connection.
+ ///
+ internal Stream Stream { get; }
+ ///
+ public override string ToString()
+ {
+ return _request.ToString();
+ }
+
///
/// Authenticates the WebSocket connection based on the specified authentication scheme.
///
@@ -220,11 +224,5 @@ namespace EonaCat.Network
Stream.Write(buff, 0, buff.Length);
_request = WebRequest.Read(Stream, 15000);
}
-
- ///
- public override string ToString()
- {
- return _request.ToString();
- }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Cookies/Cookie.cs b/EonaCat.Network/System/Sockets/Web/Core/Cookies/Cookie.cs
index f0557d5..0a513be 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Cookies/Cookie.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Cookies/Cookie.cs
@@ -13,6 +13,9 @@ namespace EonaCat.Network
[Serializable]
public sealed class Cookie
{
+ private static readonly char[] _reservedCharsForName;
+ private static readonly char[] _reservedCharsForValue;
+ private readonly DateTime _timestamp;
private string _comment;
private Uri _commentUri;
private bool _discard;
@@ -23,10 +26,7 @@ namespace EonaCat.Network
private string _path;
private string _port;
private int[] _ports;
- private static readonly char[] _reservedCharsForName;
- private static readonly char[] _reservedCharsForValue;
private bool _secure;
- private readonly DateTime _timestamp;
private string _value;
private int _version;
@@ -90,33 +90,6 @@ namespace EonaCat.Network
Domain = domain;
}
- internal bool ExactDomain
- {
- get; set;
- }
-
- internal int MaxAge
- {
- get
- {
- if (_expires == DateTime.MinValue)
- {
- return 0;
- }
-
- var expires = _expires.Kind != DateTimeKind.Local
- ? _expires.ToLocalTime()
- : _expires;
-
- var span = expires - DateTime.Now;
- return span > TimeSpan.Zero
- ? (int)span.TotalSeconds
- : 0;
- }
- }
-
- internal int[] Ports => _ports;
-
public string Comment
{
get
@@ -333,6 +306,118 @@ namespace EonaCat.Network
}
}
+ internal bool ExactDomain
+ {
+ get; set;
+ }
+
+ internal int MaxAge
+ {
+ get
+ {
+ if (_expires == DateTime.MinValue)
+ {
+ return 0;
+ }
+
+ var expires = _expires.Kind != DateTimeKind.Local
+ ? _expires.ToLocalTime()
+ : _expires;
+
+ var span = expires - DateTime.Now;
+ return span > TimeSpan.Zero
+ ? (int)span.TotalSeconds
+ : 0;
+ }
+ }
+
+ internal int[] Ports => _ports;
+ ///
+ public override bool Equals(object comparand)
+ {
+ return comparand is Cookie cookie &&
+ _name.Equals(cookie.Name, StringComparison.InvariantCultureIgnoreCase) &&
+ _value.Equals(cookie.Value, StringComparison.InvariantCulture) &&
+ _path.Equals(cookie.Path, StringComparison.InvariantCulture) &&
+ _domain.Equals(cookie.Domain, StringComparison.InvariantCultureIgnoreCase) &&
+ _version == cookie.Version;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return hash(
+ StringComparer.InvariantCultureIgnoreCase.GetHashCode(_name),
+ _value.GetHashCode(),
+ _path.GetHashCode(),
+ StringComparer.InvariantCultureIgnoreCase.GetHashCode(_domain),
+ _version);
+ }
+
+ ///
+ public override string ToString()
+ {
+ return ToRequestString(null);
+ }
+
+ // From client to server
+ internal string ToRequestString(Uri uri)
+ {
+ if (_name.Length == 0)
+ {
+ return string.Empty;
+ }
+
+ if (_version == 0)
+ {
+ return string.Format("{0}={1}", _name, _value);
+ }
+
+ var output = new StringBuilder(64);
+ output.AppendFormat("$Version={0}; {1}={2}", _version, _name, _value);
+
+ if (!_path.IsNullOrEmpty())
+ {
+ output.AppendFormat("; $Path={0}", _path);
+ }
+ else if (uri != null)
+ {
+ output.AppendFormat("; $Path={0}", uri.GetAbsolutePath());
+ }
+ else
+ {
+ output.Append("; $Path=/");
+ }
+
+ var appendDomain = uri == null || uri.Host != _domain;
+ if (appendDomain && !_domain.IsNullOrEmpty())
+ {
+ output.AppendFormat("; $Domain={0}", _domain);
+ }
+
+ if (!_port.IsNullOrEmpty())
+ {
+ if (_port == "\"\"")
+ {
+ output.Append("; $Port");
+ }
+ else
+ {
+ output.AppendFormat("; $Port={0}", _port);
+ }
+ }
+
+ return output.ToString();
+ }
+
+ // From server to client
+ internal string ToResponseString()
+ {
+ return _name.Length > 0
+ ? (_version == 0 ? toResponseStringVersion0() : toResponseStringVersion1())
+ : string.Empty;
+ }
+
private static bool canSetName(string name, out string message)
{
if (name.IsNullOrEmpty())
@@ -378,6 +463,36 @@ namespace EonaCat.Network
(m << 20 | m >> 12);
}
+ private static bool tryCreatePorts(string value, out int[] result, out string parseError)
+ {
+ var ports = value.Trim('"').Split(',');
+ var len = ports.Length;
+ var res = new int[len];
+ for (var i = 0; i < len; i++)
+ {
+ res[i] = int.MinValue;
+
+ var port = ports[i].Trim();
+ if (port.Length == 0)
+ {
+ continue;
+ }
+
+ if (!int.TryParse(port, out res[i]))
+ {
+ result = new int[0];
+ parseError = port;
+
+ return false;
+ }
+ }
+
+ result = res;
+ parseError = string.Empty;
+
+ return true;
+ }
+
private string toResponseStringVersion0()
{
var output = new StringBuilder(64);
@@ -470,121 +585,5 @@ namespace EonaCat.Network
return output.ToString();
}
-
- private static bool tryCreatePorts(string value, out int[] result, out string parseError)
- {
- var ports = value.Trim('"').Split(',');
- var len = ports.Length;
- var res = new int[len];
- for (var i = 0; i < len; i++)
- {
- res[i] = int.MinValue;
-
- var port = ports[i].Trim();
- if (port.Length == 0)
- {
- continue;
- }
-
- if (!int.TryParse(port, out res[i]))
- {
- result = new int[0];
- parseError = port;
-
- return false;
- }
- }
-
- result = res;
- parseError = string.Empty;
-
- return true;
- }
-
- // From client to server
- internal string ToRequestString(Uri uri)
- {
- if (_name.Length == 0)
- {
- return string.Empty;
- }
-
- if (_version == 0)
- {
- return string.Format("{0}={1}", _name, _value);
- }
-
- var output = new StringBuilder(64);
- output.AppendFormat("$Version={0}; {1}={2}", _version, _name, _value);
-
- if (!_path.IsNullOrEmpty())
- {
- output.AppendFormat("; $Path={0}", _path);
- }
- else if (uri != null)
- {
- output.AppendFormat("; $Path={0}", uri.GetAbsolutePath());
- }
- else
- {
- output.Append("; $Path=/");
- }
-
- var appendDomain = uri == null || uri.Host != _domain;
- if (appendDomain && !_domain.IsNullOrEmpty())
- {
- output.AppendFormat("; $Domain={0}", _domain);
- }
-
- if (!_port.IsNullOrEmpty())
- {
- if (_port == "\"\"")
- {
- output.Append("; $Port");
- }
- else
- {
- output.AppendFormat("; $Port={0}", _port);
- }
- }
-
- return output.ToString();
- }
-
- // From server to client
- internal string ToResponseString()
- {
- return _name.Length > 0
- ? (_version == 0 ? toResponseStringVersion0() : toResponseStringVersion1())
- : string.Empty;
- }
-
- ///
- public override bool Equals(object comparand)
- {
- return comparand is Cookie cookie &&
- _name.Equals(cookie.Name, StringComparison.InvariantCultureIgnoreCase) &&
- _value.Equals(cookie.Value, StringComparison.InvariantCulture) &&
- _path.Equals(cookie.Path, StringComparison.InvariantCulture) &&
- _domain.Equals(cookie.Domain, StringComparison.InvariantCultureIgnoreCase) &&
- _version == cookie.Version;
- }
-
- ///
- public override int GetHashCode()
- {
- return hash(
- StringComparer.InvariantCultureIgnoreCase.GetHashCode(_name),
- _value.GetHashCode(),
- _path.GetHashCode(),
- StringComparer.InvariantCultureIgnoreCase.GetHashCode(_domain),
- _version);
- }
-
- ///
- public override string ToString()
- {
- return ToRequestString(null);
- }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Cookies/CookieCollection.cs b/EonaCat.Network/System/Sockets/Web/Core/Cookies/CookieCollection.cs
index 2d85554..6b5be1f 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Cookies/CookieCollection.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Cookies/CookieCollection.cs
@@ -27,22 +27,6 @@ namespace EonaCat.Network
}
- internal IList List => _list;
-
- internal IEnumerable Sorted
- {
- get
- {
- var list = new List(_list);
- if (list.Count > 1)
- {
- list.Sort(compareCookieWithinSorted);
- }
-
- return list;
- }
- }
-
///
/// Gets the number of cookies in the collection.
///
@@ -58,6 +42,26 @@ namespace EonaCat.Network
///
public bool IsSynchronized => false;
+ ///
+ /// Gets an object that can be used to synchronize access to the collection.
+ ///
+ public object SyncRoot => _sync ??= ((ICollection)_list).SyncRoot;
+
+ internal IList List => _list;
+
+ internal IEnumerable Sorted
+ {
+ get
+ {
+ var list = new List(_list);
+ if (list.Count > 1)
+ {
+ list.Sort(compareCookieWithinSorted);
+ }
+
+ return list;
+ }
+ }
///
/// Gets or sets the cookie at the specified index.
///
@@ -101,11 +105,164 @@ namespace EonaCat.Network
return null;
}
}
+ ///
+ /// Adds the specified cookie to the collection, updating it if it already exists.
+ ///
+ /// The cookie to add or update.
+ public void Add(Cookie cookie)
+ {
+ if (cookie == null)
+ {
+ throw new ArgumentNullException(nameof(cookie));
+ }
+
+ var pos = searchCookie(cookie);
+ if (pos == -1)
+ {
+ _list.Add(cookie);
+ return;
+ }
+
+ _list[pos] = cookie;
+ }
///
- /// Gets an object that can be used to synchronize access to the collection.
+ /// Adds the cookies from the specified to this collection, updating existing cookies.
///
- public object SyncRoot => _sync ??= ((ICollection)_list).SyncRoot;
+ /// The to add or update from.
+ public void Add(CookieCollection cookies)
+ {
+ if (cookies == null)
+ {
+ throw new ArgumentNullException(nameof(cookies));
+ }
+
+ foreach (Cookie cookie in cookies)
+ {
+ Add(cookie);
+ }
+ }
+
+ ///
+ /// Copies the cookies in the collection to the specified array, starting at the specified index.
+ ///
+ /// The destination array.
+ /// The index in the destination array at which copying begins.
+ public void CopyTo(Array array, int index)
+ {
+ if (array == null)
+ {
+ throw new ArgumentNullException(nameof(array));
+ }
+
+ if (index < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), "Less than zero.");
+ }
+
+ if (array.Rank > 1)
+ {
+ throw new ArgumentException("Multidimensional.", nameof(array));
+ }
+
+ if (array.Length - index < _list.Count)
+ {
+ throw new ArgumentException(
+ "The number of elements in this collection is greater than the available space of the destination array.");
+ }
+
+ if (!array.GetType().GetElementType().IsAssignableFrom(typeof(Cookie)))
+ {
+ throw new InvalidCastException(
+ "The elements in this collection cannot be cast automatically to the type of the destination array.");
+ } ((IList)_list).CopyTo(array, index);
+ }
+
+ ///
+ /// Copies the cookies in the collection to the specified array, starting at the specified index.
+ ///
+ /// The destination array.
+ /// The index in the destination array at which copying begins.
+ public void CopyTo(Cookie[] array, int index)
+ {
+ if (array == null)
+ {
+ throw new ArgumentNullException(nameof(array));
+ }
+
+ if (index < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(index), "Less than zero.");
+ }
+
+ if (array.Length - index < _list.Count)
+ {
+ throw new ArgumentException(
+ "The number of elements in this collection is greater than the available space of the destination array.");
+ }
+
+ _list.CopyTo(array, index);
+ }
+
+ ///
+ /// Returns an enumerator that iterates through the collection.
+ ///
+ /// An enumerator for the collection.
+ public IEnumerator GetEnumerator()
+ {
+ return _list.GetEnumerator();
+ }
+
+ ///
+ /// Parses the specified cookie string, creating a .
+ ///
+ /// The cookie string to parse.
+ /// True if parsing a response header; otherwise, false.
+ /// A instance representing the parsed cookies.
+ internal static CookieCollection Parse(string value, bool response)
+ {
+ return response
+ ? parseResponse(value)
+ : parseRequest(value);
+ }
+
+ internal void SetOrRemove(Cookie cookie)
+ {
+ var pos = searchCookie(cookie);
+ if (pos == -1)
+ {
+ if (!cookie.Expired)
+ {
+ _list.Add(cookie);
+ }
+
+ return;
+ }
+
+ if (!cookie.Expired)
+ {
+ _list[pos] = cookie;
+ return;
+ }
+
+ _list.RemoveAt(pos);
+ }
+
+ internal void SetOrRemove(CookieCollection cookies)
+ {
+ foreach (Cookie cookie in cookies)
+ {
+ SetOrRemove(cookie);
+ }
+ }
+
+ internal void Sort()
+ {
+ if (_list.Count > 1)
+ {
+ _list.Sort(compareCookieWithinSort);
+ }
+ }
private static int compareCookieWithinSort(Cookie x, Cookie y)
{
@@ -357,6 +514,11 @@ namespace EonaCat.Network
return cookies;
}
+ private static string[] splitCookieHeaderValue(string value)
+ {
+ return new List(value.SplitHeaderValue(',', ';')).ToArray();
+ }
+
private int searchCookie(Cookie cookie)
{
var name = cookie.Name;
@@ -378,169 +540,5 @@ namespace EonaCat.Network
return -1;
}
-
- private static string[] splitCookieHeaderValue(string value)
- {
- return new List(value.SplitHeaderValue(',', ';')).ToArray();
- }
-
- ///
- /// Parses the specified cookie string, creating a .
- ///
- /// The cookie string to parse.
- /// True if parsing a response header; otherwise, false.
- /// A instance representing the parsed cookies.
- internal static CookieCollection Parse(string value, bool response)
- {
- return response
- ? parseResponse(value)
- : parseRequest(value);
- }
-
- internal void SetOrRemove(Cookie cookie)
- {
- var pos = searchCookie(cookie);
- if (pos == -1)
- {
- if (!cookie.Expired)
- {
- _list.Add(cookie);
- }
-
- return;
- }
-
- if (!cookie.Expired)
- {
- _list[pos] = cookie;
- return;
- }
-
- _list.RemoveAt(pos);
- }
-
- internal void SetOrRemove(CookieCollection cookies)
- {
- foreach (Cookie cookie in cookies)
- {
- SetOrRemove(cookie);
- }
- }
-
- internal void Sort()
- {
- if (_list.Count > 1)
- {
- _list.Sort(compareCookieWithinSort);
- }
- }
-
- ///
- /// Adds the specified cookie to the collection, updating it if it already exists.
- ///
- /// The cookie to add or update.
- public void Add(Cookie cookie)
- {
- if (cookie == null)
- {
- throw new ArgumentNullException(nameof(cookie));
- }
-
- var pos = searchCookie(cookie);
- if (pos == -1)
- {
- _list.Add(cookie);
- return;
- }
-
- _list[pos] = cookie;
- }
-
- ///
- /// Adds the cookies from the specified to this collection, updating existing cookies.
- ///
- /// The to add or update from.
- public void Add(CookieCollection cookies)
- {
- if (cookies == null)
- {
- throw new ArgumentNullException(nameof(cookies));
- }
-
- foreach (Cookie cookie in cookies)
- {
- Add(cookie);
- }
- }
-
- ///
- /// Copies the cookies in the collection to the specified array, starting at the specified index.
- ///
- /// The destination array.
- /// The index in the destination array at which copying begins.
- public void CopyTo(Array array, int index)
- {
- if (array == null)
- {
- throw new ArgumentNullException(nameof(array));
- }
-
- if (index < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(index), "Less than zero.");
- }
-
- if (array.Rank > 1)
- {
- throw new ArgumentException("Multidimensional.", nameof(array));
- }
-
- if (array.Length - index < _list.Count)
- {
- throw new ArgumentException(
- "The number of elements in this collection is greater than the available space of the destination array.");
- }
-
- if (!array.GetType().GetElementType().IsAssignableFrom(typeof(Cookie)))
- {
- throw new InvalidCastException(
- "The elements in this collection cannot be cast automatically to the type of the destination array.");
- } ((IList)_list).CopyTo(array, index);
- }
-
- ///
- /// Copies the cookies in the collection to the specified array, starting at the specified index.
- ///
- /// The destination array.
- /// The index in the destination array at which copying begins.
- public void CopyTo(Cookie[] array, int index)
- {
- if (array == null)
- {
- throw new ArgumentNullException(nameof(array));
- }
-
- if (index < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(index), "Less than zero.");
- }
-
- if (array.Length - index < _list.Count)
- {
- throw new ArgumentException(
- "The number of elements in this collection is greater than the available space of the destination array.");
- }
-
- _list.CopyTo(array, index);
- }
-
- ///
- /// Returns an enumerator that iterates through the collection.
- ///
- /// An enumerator for the collection.
- public IEnumerator GetEnumerator()
- {
- return _list.GetEnumerator();
- }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Cookies/CookieException.cs b/EonaCat.Network/System/Sockets/Web/Core/Cookies/CookieException.cs
index 1fe2ae0..1ef28f4 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Cookies/CookieException.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Cookies/CookieException.cs
@@ -13,6 +13,14 @@ namespace EonaCat.Network
[Serializable]
public class CookieException : FormatException, ISerializable
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public CookieException()
+ : base()
+ {
+ }
+
///
/// Initializes a new instance of the class with a specified error message.
///
@@ -41,15 +49,6 @@ namespace EonaCat.Network
: base(serializationInfo, streamingContext)
{
}
-
- ///
- /// Initializes a new instance of the class.
- ///
- public CookieException()
- : base()
- {
- }
-
///
/// Populates a with the data needed to serialize the exception.
///
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Endpoints/EndPointListener.cs b/EonaCat.Network/System/Sockets/Web/Core/Endpoints/EndPointListener.cs
index 181c274..84bab1c 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Endpoints/EndPointListener.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Endpoints/EndPointListener.cs
@@ -18,15 +18,14 @@ namespace EonaCat.Network
///
internal sealed class EndPointListener
{
- private List _all; // host == '+'
private static readonly string _defaultCertFolderPath;
private readonly IPEndPoint _endpoint;
- private Dictionary _prefixes;
private readonly Socket _socket;
- private List _unhandled; // host == '*'
private readonly Dictionary _unregistered;
private readonly object _unregisteredSync;
-
+ private List _all; // host == '+'
+ private Dictionary _prefixes;
+ private List _unhandled; // host == '*'
static EndPointListener()
{
_defaultCertFolderPath =
@@ -93,293 +92,6 @@ namespace EonaCat.Network
///
public SSLConfigServer SSL { get; }
- private static void addSpecial(List prefixes, HttpListenerPrefix prefix)
- {
- var path = prefix.Path;
- foreach (var pref in prefixes)
- {
- if (pref.Path == path)
- {
- throw new HttpListenerException(87, "The prefix is already in use.");
- }
- }
-
- prefixes.Add(prefix);
- }
-
- private static RSACryptoServiceProvider createRSAFromFile(string filename)
- {
- byte[] pvk = null;
- using (var fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
- {
- pvk = new byte[fs.Length];
- fs.Read(pvk, 0, pvk.Length);
- }
-
- var rsa = new RSACryptoServiceProvider();
- rsa.ImportCspBlob(pvk);
-
- return rsa;
- }
-
- private static X509Certificate2 getCertificate(
- int port, string folderPath, X509Certificate2 defaultCertificate
- )
- {
- if (folderPath == null || folderPath.Length == 0)
- {
- folderPath = _defaultCertFolderPath;
- }
-
- try
- {
- var cer = Path.Combine(folderPath, string.Format("{0}.cer", port));
- var key = Path.Combine(folderPath, string.Format("{0}.key", port));
- if (File.Exists(cer) && File.Exists(key))
- {
- var cert = new X509Certificate2(cer);
- cert.PrivateKey = createRSAFromFile(key);
-
- return cert;
- }
- }
- catch
- {
- }
-
- return defaultCertificate;
- }
-
- private void leaveIfNoPrefix()
- {
- if (_prefixes.Count > 0)
- {
- return;
- }
-
- var prefs = _unhandled;
- if (prefs != null && prefs.Count > 0)
- {
- return;
- }
-
- prefs = _all;
- if (prefs != null && prefs.Count > 0)
- {
- return;
- }
-
- EndPointManager.RemoveEndPoint(_endpoint);
- }
-
- private static void onAccept(IAsyncResult asyncResult)
- {
- var lsnr = (EndPointListener)asyncResult.AsyncState;
-
- Socket sock = null;
- try
- {
- sock = lsnr._socket.EndAccept(asyncResult);
- }
- catch (SocketException)
- {
- // Do nothing
- }
- catch (ObjectDisposedException)
- {
- return;
- }
-
- try
- {
- lsnr._socket.BeginAccept(onAccept, lsnr);
- }
- catch
- {
- sock?.Close();
-
- return;
- }
-
- if (sock == null)
- {
- return;
- }
-
- processAccepted(sock, lsnr);
- }
-
- private static void processAccepted(Socket socket, EndPointListener listener)
- {
- HttpConnection conn = null;
- try
- {
- conn = new HttpConnection(socket, listener);
- lock (listener._unregisteredSync)
- {
- listener._unregistered[conn] = conn;
- }
-
- conn.BeginReadRequest();
- }
- catch
- {
- if (conn != null)
- {
- conn.Close(true);
- return;
- }
-
- socket.Close();
- }
- }
-
- private static bool removeSpecial(List prefixes, HttpListenerPrefix prefix)
- {
- var path = prefix.Path;
- var cnt = prefixes.Count;
- for (var i = 0; i < cnt; i++)
- {
- if (prefixes[i].Path == path)
- {
- prefixes.RemoveAt(i);
- return true;
- }
- }
-
- return false;
- }
-
- private static HttpListener searchHttpListenerFromSpecial(
- string path, List prefixes
- )
- {
- if (prefixes == null)
- {
- return null;
- }
-
- HttpListener bestMatch = null;
-
- var bestLen = -1;
- foreach (var pref in prefixes)
- {
- var prefPath = pref.Path;
-
- var len = prefPath.Length;
- if (len < bestLen)
- {
- continue;
- }
-
- if (path.StartsWith(prefPath))
- {
- bestLen = len;
- bestMatch = pref.Listener;
- }
- }
-
- return bestMatch;
- }
-
- internal static bool CertificateExists(int port, string folderPath)
- {
- if (folderPath == null || folderPath.Length == 0)
- {
- folderPath = _defaultCertFolderPath;
- }
-
- var cer = Path.Combine(folderPath, string.Format("{0}.cer", port));
- var key = Path.Combine(folderPath, string.Format("{0}.key", port));
-
- return File.Exists(cer) && File.Exists(key);
- }
-
- internal void RemoveConnection(HttpConnection connection)
- {
- lock (_unregisteredSync)
- {
- _unregistered.Remove(connection);
- }
- }
-
- internal bool TrySearchHttpListener(Uri uri, out HttpListener listener)
- {
- listener = null;
-
- if (uri == null)
- {
- return false;
- }
-
- var host = uri.Host;
- var dns = Uri.CheckHostName(host) == UriHostNameType.Dns;
- var port = uri.Port.ToString();
- var path = HttpUtility.UrlDecode(uri.AbsolutePath);
- var pathSlash = path[path.Length - 1] != '/' ? path + "/" : path;
-
- if (host != null && host.Length > 0)
- {
- var bestLen = -1;
- foreach (var pref in _prefixes.Keys)
- {
- if (dns)
- {
- var prefHost = pref.Host;
- if (Uri.CheckHostName(prefHost) == UriHostNameType.Dns && prefHost != host)
- {
- continue;
- }
- }
-
- if (pref.Port != port)
- {
- continue;
- }
-
- var prefPath = pref.Path;
-
- var len = prefPath.Length;
- if (len < bestLen)
- {
- continue;
- }
-
- if (path.StartsWith(prefPath) || pathSlash.StartsWith(prefPath))
- {
- bestLen = len;
- listener = _prefixes[pref];
- }
- }
-
- if (bestLen != -1)
- {
- return true;
- }
- }
-
- var prefs = _unhandled;
- listener = searchHttpListenerFromSpecial(path, prefs);
- if (listener == null && pathSlash != path)
- {
- listener = searchHttpListenerFromSpecial(pathSlash, prefs);
- }
-
- if (listener != null)
- {
- return true;
- }
-
- prefs = _all;
- listener = searchHttpListenerFromSpecial(path, prefs);
- if (listener == null && pathSlash != path)
- {
- listener = searchHttpListenerFromSpecial(pathSlash, prefs);
- }
-
- return listener != null;
- }
-
public void AddPrefix(HttpListenerPrefix prefix, HttpListener listener)
{
List current, future;
@@ -528,5 +240,292 @@ namespace EonaCat.Network
leaveIfNoPrefix();
}
+
+ internal static bool CertificateExists(int port, string folderPath)
+ {
+ if (folderPath == null || folderPath.Length == 0)
+ {
+ folderPath = _defaultCertFolderPath;
+ }
+
+ var cer = Path.Combine(folderPath, string.Format("{0}.cer", port));
+ var key = Path.Combine(folderPath, string.Format("{0}.key", port));
+
+ return File.Exists(cer) && File.Exists(key);
+ }
+
+ internal void RemoveConnection(HttpConnection connection)
+ {
+ lock (_unregisteredSync)
+ {
+ _unregistered.Remove(connection);
+ }
+ }
+
+ internal bool TrySearchHttpListener(Uri uri, out HttpListener listener)
+ {
+ listener = null;
+
+ if (uri == null)
+ {
+ return false;
+ }
+
+ var host = uri.Host;
+ var dns = Uri.CheckHostName(host) == UriHostNameType.Dns;
+ var port = uri.Port.ToString();
+ var path = HttpUtility.UrlDecode(uri.AbsolutePath);
+ var pathSlash = path[path.Length - 1] != '/' ? path + "/" : path;
+
+ if (host != null && host.Length > 0)
+ {
+ var bestLen = -1;
+ foreach (var pref in _prefixes.Keys)
+ {
+ if (dns)
+ {
+ var prefHost = pref.Host;
+ if (Uri.CheckHostName(prefHost) == UriHostNameType.Dns && prefHost != host)
+ {
+ continue;
+ }
+ }
+
+ if (pref.Port != port && !_prefixes[pref].AllowForwardedRequest)
+ {
+ continue;
+ }
+
+ var prefPath = pref.Path;
+
+ var len = prefPath.Length;
+ if (len < bestLen)
+ {
+ continue;
+ }
+
+ if (path.StartsWith(prefPath) || pathSlash.StartsWith(prefPath))
+ {
+ bestLen = len;
+ listener = _prefixes[pref];
+ }
+ }
+
+ if (bestLen != -1)
+ {
+ return true;
+ }
+ }
+
+ var prefs = _unhandled;
+ listener = searchHttpListenerFromSpecial(path, prefs);
+ if (listener == null && pathSlash != path)
+ {
+ listener = searchHttpListenerFromSpecial(pathSlash, prefs);
+ }
+
+ if (listener != null)
+ {
+ return true;
+ }
+
+ prefs = _all;
+ listener = searchHttpListenerFromSpecial(path, prefs);
+ if (listener == null && pathSlash != path)
+ {
+ listener = searchHttpListenerFromSpecial(pathSlash, prefs);
+ }
+
+ return listener != null;
+ }
+
+ private static void addSpecial(List prefixes, HttpListenerPrefix prefix)
+ {
+ var path = prefix.Path;
+ foreach (var pref in prefixes)
+ {
+ if (pref.Path == path)
+ {
+ throw new HttpListenerException(87, "The prefix is already in use.");
+ }
+ }
+
+ prefixes.Add(prefix);
+ }
+
+ private static RSACryptoServiceProvider createRSAFromFile(string filename)
+ {
+ byte[] pvk = null;
+ using (var fs = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
+ {
+ pvk = new byte[fs.Length];
+ fs.Read(pvk, 0, pvk.Length);
+ }
+
+ var rsa = new RSACryptoServiceProvider();
+ rsa.ImportCspBlob(pvk);
+
+ return rsa;
+ }
+
+ private static X509Certificate2 getCertificate(
+ int port, string folderPath, X509Certificate2 defaultCertificate
+ )
+ {
+ if (folderPath == null || folderPath.Length == 0)
+ {
+ folderPath = _defaultCertFolderPath;
+ }
+
+ try
+ {
+ var cer = Path.Combine(folderPath, string.Format("{0}.cer", port));
+ var key = Path.Combine(folderPath, string.Format("{0}.key", port));
+ if (File.Exists(cer) && File.Exists(key))
+ {
+ var cert = new X509Certificate2(cer);
+ cert.PrivateKey = createRSAFromFile(key);
+
+ return cert;
+ }
+ }
+ catch
+ {
+ }
+
+ return defaultCertificate;
+ }
+
+ private static void onAccept(IAsyncResult asyncResult)
+ {
+ var lsnr = (EndPointListener)asyncResult.AsyncState;
+
+ Socket sock = null;
+ try
+ {
+ sock = lsnr._socket.EndAccept(asyncResult);
+ }
+ catch (SocketException)
+ {
+ // Do nothing
+ }
+ catch (ObjectDisposedException)
+ {
+ return;
+ }
+
+ try
+ {
+ lsnr._socket.BeginAccept(onAccept, lsnr);
+ }
+ catch
+ {
+ sock?.Close();
+
+ return;
+ }
+
+ if (sock == null)
+ {
+ return;
+ }
+
+ processAccepted(sock, lsnr);
+ }
+
+ private static void processAccepted(Socket socket, EndPointListener listener)
+ {
+ HttpConnection conn = null;
+ try
+ {
+ conn = new HttpConnection(socket, listener);
+ lock (listener._unregisteredSync)
+ {
+ listener._unregistered[conn] = conn;
+ }
+
+ conn.BeginReadRequest();
+ }
+ catch
+ {
+ if (conn != null)
+ {
+ conn.Close(true);
+ return;
+ }
+
+ socket.Close();
+ }
+ }
+
+ private static bool removeSpecial(List prefixes, HttpListenerPrefix prefix)
+ {
+ var path = prefix.Path;
+ var cnt = prefixes.Count;
+ for (var i = 0; i < cnt; i++)
+ {
+ if (prefixes[i].Path == path)
+ {
+ prefixes.RemoveAt(i);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static HttpListener searchHttpListenerFromSpecial(
+ string path, List prefixes
+ )
+ {
+ if (prefixes == null)
+ {
+ return null;
+ }
+
+ HttpListener bestMatch = null;
+
+ var bestLen = -1;
+ foreach (var pref in prefixes)
+ {
+ var prefPath = pref.Path;
+
+ var len = prefPath.Length;
+ if (len < bestLen)
+ {
+ continue;
+ }
+
+ if (path.StartsWith(prefPath))
+ {
+ bestLen = len;
+ bestMatch = pref.Listener;
+ }
+ }
+
+ return bestMatch;
+ }
+
+ private void leaveIfNoPrefix()
+ {
+ if (_prefixes.Count > 0)
+ {
+ return;
+ }
+
+ var prefs = _unhandled;
+ if (prefs != null && prefs.Count > 0)
+ {
+ return;
+ }
+
+ prefs = _all;
+ if (prefs != null && prefs.Count > 0)
+ {
+ return;
+ }
+
+ EndPointManager.RemoveEndPoint(_endpoint);
+ }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Endpoints/EndPointManager.cs b/EonaCat.Network/System/Sockets/Web/Core/Endpoints/EndPointManager.cs
index e03d515..f2aa2a2 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Endpoints/EndPointManager.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Endpoints/EndPointManager.cs
@@ -30,6 +30,97 @@ namespace EonaCat.Network
{
}
+ ///
+ /// Adds an HTTP listener and its associated prefixes.
+ ///
+ /// The HTTP listener to be added.
+ public static void AddListener(HttpListener listener)
+ {
+ var added = new List();
+ lock (((ICollection)_endpoints).SyncRoot)
+ {
+ try
+ {
+ foreach (var pref in listener.Prefixes)
+ {
+ addPrefix(pref, listener);
+ added.Add(pref);
+ }
+ }
+ catch
+ {
+ foreach (var pref in added)
+ {
+ removePrefix(pref, listener);
+ }
+
+ throw;
+ }
+ }
+ }
+
+ ///
+ /// Adds an HTTP listener prefix.
+ ///
+ /// The URI prefix to be added.
+ /// The HTTP listener associated with the prefix.
+ public static void AddPrefix(string uriPrefix, HttpListener listener)
+ {
+ lock (((ICollection)_endpoints).SyncRoot)
+ {
+ addPrefix(uriPrefix, listener);
+ }
+ }
+
+ ///
+ /// Removes an HTTP listener and its associated prefixes.
+ ///
+ /// The HTTP listener to be removed.
+ public static void RemoveListener(HttpListener listener)
+ {
+ lock (((ICollection)_endpoints).SyncRoot)
+ {
+ foreach (var pref in listener.Prefixes)
+ {
+ removePrefix(pref, listener);
+ }
+ }
+ }
+
+ ///
+ /// Removes an HTTP listener prefix.
+ ///
+ /// The URI prefix to be removed.
+ /// The HTTP listener associated with the prefix.
+ public static void RemovePrefix(string uriPrefix, HttpListener listener)
+ {
+ lock (((ICollection)_endpoints).SyncRoot)
+ {
+ removePrefix(uriPrefix, listener);
+ }
+ }
+
+ ///
+ /// Removes an endpoint and closes its associated listener.
+ ///
+ /// The endpoint to be removed.
+ /// true if the endpoint is successfully removed; otherwise, false.
+ internal static bool RemoveEndPoint(IPEndPoint endpoint)
+ {
+ lock (((ICollection)_endpoints).SyncRoot)
+ {
+ if (!_endpoints.TryGetValue(endpoint, out EndPointListener lsnr))
+ {
+ return false;
+ }
+
+ _endpoints.Remove(endpoint);
+ lsnr.Close();
+
+ return true;
+ }
+ }
+
private static void addPrefix(string uriPrefix, HttpListener listener)
{
var pref = new HttpListenerPrefix(uriPrefix);
@@ -137,96 +228,5 @@ namespace EonaCat.Network
lsnr.RemovePrefix(pref, listener);
}
-
- ///
- /// Removes an endpoint and closes its associated listener.
- ///
- /// The endpoint to be removed.
- /// true if the endpoint is successfully removed; otherwise, false.
- internal static bool RemoveEndPoint(IPEndPoint endpoint)
- {
- lock (((ICollection)_endpoints).SyncRoot)
- {
- if (!_endpoints.TryGetValue(endpoint, out EndPointListener lsnr))
- {
- return false;
- }
-
- _endpoints.Remove(endpoint);
- lsnr.Close();
-
- return true;
- }
- }
-
- ///
- /// Adds an HTTP listener and its associated prefixes.
- ///
- /// The HTTP listener to be added.
- public static void AddListener(HttpListener listener)
- {
- var added = new List();
- lock (((ICollection)_endpoints).SyncRoot)
- {
- try
- {
- foreach (var pref in listener.Prefixes)
- {
- addPrefix(pref, listener);
- added.Add(pref);
- }
- }
- catch
- {
- foreach (var pref in added)
- {
- removePrefix(pref, listener);
- }
-
- throw;
- }
- }
- }
-
- ///
- /// Adds an HTTP listener prefix.
- ///
- /// The URI prefix to be added.
- /// The HTTP listener associated with the prefix.
- public static void AddPrefix(string uriPrefix, HttpListener listener)
- {
- lock (((ICollection)_endpoints).SyncRoot)
- {
- addPrefix(uriPrefix, listener);
- }
- }
-
- ///
- /// Removes an HTTP listener and its associated prefixes.
- ///
- /// The HTTP listener to be removed.
- public static void RemoveListener(HttpListener listener)
- {
- lock (((ICollection)_endpoints).SyncRoot)
- {
- foreach (var pref in listener.Prefixes)
- {
- removePrefix(pref, listener);
- }
- }
- }
-
- ///
- /// Removes an HTTP listener prefix.
- ///
- /// The URI prefix to be removed.
- /// The HTTP listener associated with the prefix.
- public static void RemovePrefix(string uriPrefix, HttpListener listener)
- {
- lock (((ICollection)_endpoints).SyncRoot)
- {
- removePrefix(uriPrefix, listener);
- }
- }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpConnection.cs b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpConnection.cs
index 025e54a..3487ed6 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpConnection.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpConnection.cs
@@ -17,10 +17,13 @@ namespace EonaCat.Network
///
internal sealed class HttpConnection
{
- private byte[] _buffer;
private const int _bufferLength = 8192;
private const int TIMEOUT_CONTINUE = 15000;
private const int TIMEOUT_INITIAL = 90000;
+ private readonly EndPointListener _listener;
+ private readonly object _lock;
+ private readonly Dictionary _timeoutCanceled;
+ private byte[] _buffer;
private HttpListenerContext _context;
private bool _contextRegistered;
private StringBuilder _currentLine;
@@ -28,14 +31,11 @@ namespace EonaCat.Network
private RequestStream _inputStream;
private HttpListener _lastListener;
private LineState _lineState;
- private readonly EndPointListener _listener;
private ResponseStream _outputStream;
private int _position;
private MemoryStream _requestBuffer;
private Socket _socket;
- private readonly object _lock;
private int _timeout;
- private readonly Dictionary _timeoutCanceled;
private Timer _timer;
///
@@ -106,375 +106,6 @@ namespace EonaCat.Network
///
public Stream Stream { get; private set; }
- private void close()
- {
- lock (_lock)
- {
- if (_socket == null)
- {
- return;
- }
-
- DisposeTimer();
- DisposeRequestBuffer();
- DisposeStream();
- CloseSocket();
- }
-
- UnregisterContext();
- RemoveConnection();
- }
-
- private void CloseSocket()
- {
- try
- {
- _socket.Shutdown(SocketShutdown.Both);
- }
- catch
- {
- }
-
- _socket.Close();
- _socket = null;
- }
-
- private void DisposeRequestBuffer()
- {
- if (_requestBuffer == null)
- {
- return;
- }
-
- _requestBuffer.Dispose();
- _requestBuffer = null;
- }
-
- private void DisposeStream()
- {
- if (Stream == null)
- {
- return;
- }
-
- _inputStream = null;
- _outputStream = null;
-
- Stream.Dispose();
- Stream = null;
- }
-
- private void DisposeTimer()
- {
- if (_timer == null)
- {
- return;
- }
-
- try
- {
- _timer.Change(Timeout.Infinite, Timeout.Infinite);
- }
- catch
- {
- }
-
- _timer.Dispose();
- _timer = null;
- }
-
- private void Setup()
- {
- _context = new HttpListenerContext(this);
- _inputState = InputState.RequestLine;
- _inputStream = null;
- _lineState = LineState.None;
- _outputStream = null;
- _position = 0;
- _requestBuffer = new MemoryStream();
- }
-
- private static void OnRead(IAsyncResult asyncResult)
- {
- var httpConnection = (HttpConnection)asyncResult.AsyncState;
- if (httpConnection._socket == null)
- {
- return;
- }
-
- lock (httpConnection._lock)
- {
- if (httpConnection._socket == null)
- {
- return;
- }
-
- var nread = -1;
- var len = 0;
- try
- {
- var current = httpConnection.Reuses;
- if (!httpConnection._timeoutCanceled[current])
- {
- httpConnection._timer.Change(Timeout.Infinite, Timeout.Infinite);
- httpConnection._timeoutCanceled[current] = true;
- }
-
- nread = httpConnection.Stream.EndRead(asyncResult);
- httpConnection._requestBuffer.Write(httpConnection._buffer, 0, nread);
- len = (int)httpConnection._requestBuffer.Length;
- }
- catch (Exception exception)
- {
- if (httpConnection._requestBuffer != null && httpConnection._requestBuffer.Length > 0)
- {
- httpConnection.SendError(exception.Message, 400);
- return;
- }
-
- httpConnection.close();
- return;
- }
-
- if (nread <= 0)
- {
- httpConnection.close();
- return;
- }
-
- if (httpConnection.ProcessInput(httpConnection._requestBuffer.GetBuffer(), len))
- {
- if (!httpConnection._context.HasError)
- {
- httpConnection._context.Request.FinishInitialization();
- }
-
- if (httpConnection._context.HasError)
- {
- httpConnection.SendError();
- return;
- }
-
- if (!httpConnection._listener.TrySearchHttpListener(httpConnection._context.Request.Url, out HttpListener httpListener))
- {
- httpConnection.SendError(null, 404);
- return;
- }
-
- if (httpConnection._lastListener != httpListener)
- {
- httpConnection.RemoveConnection();
- if (!httpListener.AddConnection(httpConnection))
- {
- httpConnection.close();
- return;
- }
-
- httpConnection._lastListener = httpListener;
- }
-
- httpConnection._context.Listener = httpListener;
- if (!httpConnection._context.Authenticate())
- {
- return;
- }
-
- if (httpConnection._context.Register())
- {
- httpConnection._contextRegistered = true;
- }
-
- return;
- }
-
- httpConnection.Stream.BeginRead(httpConnection._buffer, 0, _bufferLength, OnRead, httpConnection);
- }
- }
-
- private static void OnTimeout(object state)
- {
- var httpConnection = (HttpConnection)state;
- var current = httpConnection.Reuses;
- if (httpConnection._socket == null)
- {
- return;
- }
-
- lock (httpConnection._lock)
- {
- if (httpConnection._socket == null)
- {
- return;
- }
-
- if (httpConnection._timeoutCanceled[current])
- {
- return;
- }
-
- httpConnection.SendError(null, 408);
- }
- }
-
- private bool ProcessInput(byte[] data, int length)
- {
- _currentLine ??= new StringBuilder(64);
-
- var nread = 0;
- try
- {
- string line;
- while ((line = ReadLineFrom(data, _position, length, out nread)) != null)
- {
- _position += nread;
- if (line.Length == 0)
- {
- if (_inputState == InputState.RequestLine)
- {
- continue;
- }
-
- if (_position > 32768)
- {
- _context.ErrorMessage = "Headers too long";
- }
-
- _currentLine = null;
- return true;
- }
-
- if (_inputState == InputState.RequestLine)
- {
- _context.Request.SetRequestLine(line);
- _inputState = InputState.Headers;
- }
- else
- {
- _context.Request.AddHeader(line);
- }
-
- if (_context.HasError)
- {
- return true;
- }
- }
- }
- catch (Exception exception)
- {
- _context.ErrorMessage = exception.Message;
- return true;
- }
-
- _position += nread;
- if (_position >= 32768)
- {
- _context.ErrorMessage = "Headers too long";
- return true;
- }
-
- return false;
- }
-
- private string ReadLineFrom(byte[] buffer, int offset, int length, out int read)
- {
- read = 0;
-
- for (var i = offset; i < length && _lineState != LineState.LineFeed; i++)
- {
- read++;
-
- var b = buffer[i];
- if (b == 13)
- {
- _lineState = LineState.CarriageReturn;
- }
- else if (b == 10)
- {
- _lineState = LineState.LineFeed;
- }
- else
- {
- _currentLine.Append((char)b);
- }
- }
-
- if (_lineState != LineState.LineFeed)
- {
- return null;
- }
-
- var line = _currentLine.ToString();
-
- _currentLine.Length = 0;
- _lineState = LineState.None;
-
- return line;
- }
-
- private void RemoveConnection()
- {
- if (_lastListener != null)
- {
- _lastListener.RemoveConnection(this);
- }
- else
- {
- _listener.RemoveConnection(this);
- }
- }
-
- private void UnregisterContext()
- {
- if (!_contextRegistered)
- {
- return;
- }
-
- _context.Unregister();
- _contextRegistered = false;
- }
-
- ///
- /// Closes the connection.
- ///
- /// True to force close, false otherwise.
- internal void Close(bool force)
- {
- if (_socket == null)
- {
- return;
- }
-
- lock (_lock)
- {
- if (_socket == null)
- {
- return;
- }
-
- if (!force)
- {
- GetResponseStream().Close(false);
- if (!_context.Response.CloseConnection && _context.Request.FlushInput())
- {
- // Don't close. Keep working.
- Reuses++;
- DisposeRequestBuffer();
- UnregisterContext();
- Setup();
- BeginReadRequest();
- return;
- }
- }
- else
- {
- _outputStream?.Close(true);
- }
-
- close();
- }
- }
-
///
/// Initiates reading the request.
///
@@ -629,5 +260,373 @@ namespace EonaCat.Network
}
}
}
+
+ ///
+ /// Closes the connection.
+ ///
+ /// True to force close, false otherwise.
+ internal void Close(bool force)
+ {
+ if (_socket == null)
+ {
+ return;
+ }
+
+ lock (_lock)
+ {
+ if (_socket == null)
+ {
+ return;
+ }
+
+ if (!force)
+ {
+ GetResponseStream().Close(false);
+ if (!_context.Response.CloseConnection && _context.Request.FlushInput())
+ {
+ // Don't close. Keep working.
+ Reuses++;
+ DisposeRequestBuffer();
+ UnregisterContext();
+ Setup();
+ BeginReadRequest();
+ return;
+ }
+ }
+ else
+ {
+ _outputStream?.Close(true);
+ }
+
+ close();
+ }
+ }
+
+ private static void OnRead(IAsyncResult asyncResult)
+ {
+ var httpConnection = (HttpConnection)asyncResult.AsyncState;
+ if (httpConnection._socket == null)
+ {
+ return;
+ }
+
+ lock (httpConnection._lock)
+ {
+ if (httpConnection._socket == null)
+ {
+ return;
+ }
+
+ var nread = -1;
+ var len = 0;
+ try
+ {
+ var current = httpConnection.Reuses;
+ if (!httpConnection._timeoutCanceled[current])
+ {
+ httpConnection._timer.Change(Timeout.Infinite, Timeout.Infinite);
+ httpConnection._timeoutCanceled[current] = true;
+ }
+
+ nread = httpConnection.Stream.EndRead(asyncResult);
+ httpConnection._requestBuffer.Write(httpConnection._buffer, 0, nread);
+ len = (int)httpConnection._requestBuffer.Length;
+ }
+ catch (Exception exception)
+ {
+ if (httpConnection._requestBuffer != null && httpConnection._requestBuffer.Length > 0)
+ {
+ httpConnection.SendError(exception.Message, 400);
+ return;
+ }
+
+ httpConnection.close();
+ return;
+ }
+
+ if (nread <= 0)
+ {
+ httpConnection.close();
+ return;
+ }
+
+ if (httpConnection.ProcessInput(httpConnection._requestBuffer.GetBuffer(), len))
+ {
+ if (!httpConnection._context.HasError)
+ {
+ httpConnection._context.Request.FinishInitialization();
+ }
+
+ if (httpConnection._context.HasError)
+ {
+ httpConnection.SendError();
+ return;
+ }
+
+ if (!httpConnection._listener.TrySearchHttpListener(httpConnection._context.Request.Url, out HttpListener httpListener))
+ {
+ httpConnection.SendError(null, 404);
+ return;
+ }
+
+ if (httpConnection._lastListener != httpListener)
+ {
+ httpConnection.RemoveConnection();
+ if (!httpListener.AddConnection(httpConnection))
+ {
+ httpConnection.close();
+ return;
+ }
+
+ httpConnection._lastListener = httpListener;
+ }
+
+ httpConnection._context.Listener = httpListener;
+ if (!httpConnection._context.Authenticate())
+ {
+ return;
+ }
+
+ if (httpConnection._context.Register())
+ {
+ httpConnection._contextRegistered = true;
+ }
+
+ return;
+ }
+
+ httpConnection.Stream.BeginRead(httpConnection._buffer, 0, _bufferLength, OnRead, httpConnection);
+ }
+ }
+
+ private static void OnTimeout(object state)
+ {
+ var httpConnection = (HttpConnection)state;
+ var current = httpConnection.Reuses;
+ if (httpConnection._socket == null)
+ {
+ return;
+ }
+
+ lock (httpConnection._lock)
+ {
+ if (httpConnection._socket == null)
+ {
+ return;
+ }
+
+ if (httpConnection._timeoutCanceled[current])
+ {
+ return;
+ }
+
+ httpConnection.SendError(null, 408);
+ }
+ }
+
+ private void close()
+ {
+ lock (_lock)
+ {
+ if (_socket == null)
+ {
+ return;
+ }
+
+ DisposeTimer();
+ DisposeRequestBuffer();
+ DisposeStream();
+ CloseSocket();
+ }
+
+ UnregisterContext();
+ RemoveConnection();
+ }
+
+ private void CloseSocket()
+ {
+ try
+ {
+ _socket.Shutdown(SocketShutdown.Both);
+ }
+ catch
+ {
+ }
+
+ _socket.Close();
+ _socket = null;
+ }
+
+ private void DisposeRequestBuffer()
+ {
+ if (_requestBuffer == null)
+ {
+ return;
+ }
+
+ _requestBuffer.Dispose();
+ _requestBuffer = null;
+ }
+
+ private void DisposeStream()
+ {
+ if (Stream == null)
+ {
+ return;
+ }
+
+ _inputStream = null;
+ _outputStream = null;
+
+ Stream.Dispose();
+ Stream = null;
+ }
+
+ private void DisposeTimer()
+ {
+ if (_timer == null)
+ {
+ return;
+ }
+
+ try
+ {
+ _timer.Change(Timeout.Infinite, Timeout.Infinite);
+ }
+ catch
+ {
+ }
+
+ _timer.Dispose();
+ _timer = null;
+ }
+
+ private bool ProcessInput(byte[] data, int length)
+ {
+ _currentLine ??= new StringBuilder(64);
+
+ var nread = 0;
+ try
+ {
+ string line;
+ while ((line = ReadLineFrom(data, _position, length, out nread)) != null)
+ {
+ _position += nread;
+ if (line.Length == 0)
+ {
+ if (_inputState == InputState.RequestLine)
+ {
+ continue;
+ }
+
+ if (_position > 32768)
+ {
+ _context.ErrorMessage = "Headers too long";
+ }
+
+ _currentLine = null;
+ return true;
+ }
+
+ if (_inputState == InputState.RequestLine)
+ {
+ _context.Request.SetRequestLine(line);
+ _inputState = InputState.Headers;
+ }
+ else
+ {
+ _context.Request.AddHeader(line);
+ }
+
+ if (_context.HasError)
+ {
+ return true;
+ }
+ }
+ }
+ catch (Exception exception)
+ {
+ _context.ErrorMessage = exception.Message;
+ return true;
+ }
+
+ _position += nread;
+ if (_position >= 32768)
+ {
+ _context.ErrorMessage = "Headers too long";
+ return true;
+ }
+
+ return false;
+ }
+
+ private string ReadLineFrom(byte[] buffer, int offset, int length, out int read)
+ {
+ read = 0;
+
+ for (var i = offset; i < length && _lineState != LineState.LineFeed; i++)
+ {
+ read++;
+
+ var b = buffer[i];
+ if (b == 13)
+ {
+ _lineState = LineState.CarriageReturn;
+ }
+ else if (b == 10)
+ {
+ _lineState = LineState.LineFeed;
+ }
+ else
+ {
+ _currentLine.Append((char)b);
+ }
+ }
+
+ if (_lineState != LineState.LineFeed)
+ {
+ return null;
+ }
+
+ var line = _currentLine.ToString();
+
+ _currentLine.Length = 0;
+ _lineState = LineState.None;
+
+ return line;
+ }
+
+ private void RemoveConnection()
+ {
+ if (_lastListener != null)
+ {
+ _lastListener.RemoveConnection(this);
+ }
+ else
+ {
+ _listener.RemoveConnection(this);
+ }
+ }
+
+ private void Setup()
+ {
+ _context = new HttpListenerContext(this);
+ _inputState = InputState.RequestLine;
+ _inputStream = null;
+ _lineState = LineState.None;
+ _outputStream = null;
+ _position = 0;
+ _requestBuffer = new MemoryStream();
+ }
+ private void UnregisterContext()
+ {
+ if (!_contextRegistered)
+ {
+ return;
+ }
+
+ _context.Unregister();
+ _contextRegistered = false;
+ }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpHeaderInfo.cs b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpHeaderInfo.cs
index 1473562..25c380d 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpHeaderInfo.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpHeaderInfo.cs
@@ -19,16 +19,6 @@ namespace EonaCat.Network
Type = type;
}
- ///
- /// Gets a value indicating whether the header is multi-value in a request.
- ///
- internal bool IsMultiValueInRequest => (Type & HttpHeaderType.MultiValueInRequest) == HttpHeaderType.MultiValueInRequest;
-
- ///
- /// Gets a value indicating whether the header is multi-value in a response.
- ///
- internal bool IsMultiValueInResponse => (Type & HttpHeaderType.MultiValueInResponse) == HttpHeaderType.MultiValueInResponse;
-
///
/// Gets a value indicating whether the header is for a request.
///
@@ -49,6 +39,15 @@ namespace EonaCat.Network
///
public HttpHeaderType Type { get; }
+ ///
+ /// Gets a value indicating whether the header is multi-value in a request.
+ ///
+ internal bool IsMultiValueInRequest => (Type & HttpHeaderType.MultiValueInRequest) == HttpHeaderType.MultiValueInRequest;
+
+ ///
+ /// Gets a value indicating whether the header is multi-value in a response.
+ ///
+ internal bool IsMultiValueInResponse => (Type & HttpHeaderType.MultiValueInResponse) == HttpHeaderType.MultiValueInResponse;
///
/// Gets a value indicating whether the header is multi-value.
///
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListener.cs b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListener.cs
index 54f0427..09d10ab 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListener.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListener.cs
@@ -13,25 +13,25 @@ namespace EonaCat.Network
///
public sealed class HttpListener : IDisposable
{
- private AuthenticationSchemes _authSchemes;
- private Func _authSchemeSelector;
- private string _certFolderPath;
+ private static readonly string _defaultRealm;
private readonly Dictionary _connections;
private readonly object _connectionsSync;
- private readonly List contextQueue;
private readonly object _contextQueueLock;
private readonly Dictionary _contextRegistry;
private readonly object _contextRegistryLock;
- private static readonly string _defaultRealm;
+ private readonly HttpListenerPrefixCollection _prefixes;
+ private readonly List _waitQueue;
+ private readonly object _waitQueueLock;
+ private readonly List contextQueue;
+ private bool _allowForwardedRequest;
+ private AuthenticationSchemes _authSchemes;
+ private Func _authSchemeSelector;
+ private string _certFolderPath;
private bool _ignoreWriteExceptions;
private volatile bool _listening;
- private readonly HttpListenerPrefixCollection _prefixes;
private string _realm;
private SSLConfigServer _sslConfig;
private Func _userCredFinder;
- private readonly List _waitQueue;
- private readonly object _waitQueueLock;
-
static HttpListener()
{
_defaultRealm = "SECRET AREA";
@@ -59,9 +59,29 @@ namespace EonaCat.Network
_waitQueueLock = ((ICollection)_waitQueue).SyncRoot;
}
- internal bool IsDisposed { get; private set; }
-
- internal bool ReuseAddress { get; set; }
+ public static bool IsSupported => true;
+ ///
+ /// 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 { _allowForwardedRequest = value; }
+ }
///
/// Gets or sets the authentication schemes used by this listener.
@@ -127,9 +147,6 @@ namespace EonaCat.Network
}
public bool IsListening => _listening;
-
- public static bool IsSupported => true;
-
public HttpListenerPrefixCollection Prefixes
{
get
@@ -197,6 +214,289 @@ namespace EonaCat.Network
}
}
+ internal bool IsDisposed { get; private set; }
+
+ internal bool ReuseAddress { get; set; }
+ ///
+ /// Aborts the listener and releases all resources associated with it.
+ ///
+ public void Abort()
+ {
+ if (IsDisposed)
+ {
+ return;
+ }
+
+ close(true);
+ }
+
+ ///
+ /// Begins asynchronously getting an HTTP context from the listener.
+ ///
+ /// The method to call when the operation completes.
+ /// A user-defined object that contains information about the asynchronous operation.
+ /// An that represents the asynchronous operation.
+ 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 (!_listening)
+ {
+ throw new InvalidOperationException("The listener hasn't been started.");
+ }
+
+ return BeginGetContext(new HttpListenerAsyncResult(callback, state));
+ }
+
+ ///
+ /// Closes the listener.
+ ///
+ public void Close()
+ {
+ if (IsDisposed)
+ {
+ return;
+ }
+
+ close(false);
+ }
+
+ ///
+ /// Disposes of the resources used by the .
+ ///
+ void IDisposable.Dispose()
+ {
+ if (IsDisposed)
+ {
+ return;
+ }
+
+ close(true);
+ }
+
+ ///
+ /// Ends an asynchronous operation to get an HTTP context from the listener.
+ ///
+ /// The reference to the pending asynchronous request to finish.
+ /// An that represents the context of the asynchronous operation.
+ public HttpListenerContext EndGetContext(IAsyncResult asyncResult)
+ {
+ CheckDisposed();
+ if (asyncResult == null)
+ {
+ throw new ArgumentNullException(nameof(asyncResult));
+ }
+
+ if (asyncResult is not HttpListenerAsyncResult result)
+ {
+ throw new ArgumentException("A wrong IAsyncResult.", nameof(asyncResult));
+ }
+
+ if (result.EndCalled)
+ {
+ throw new InvalidOperationException("This IAsyncResult cannot be reused.");
+ }
+
+ result.EndCalled = true;
+ if (!result.IsCompleted)
+ {
+ result.AsyncWaitHandle.WaitOne();
+ }
+
+ return result.GetContext();
+ }
+
+ ///
+ /// Gets the next available HTTP context from the listener.
+ ///
+ /// An that represents the context of the HTTP request.
+ public HttpListenerContext GetContext()
+ {
+ CheckDisposed();
+ if (_prefixes.Count == 0)
+ {
+ throw new InvalidOperationException("The listener has no URI prefix on which listens.");
+ }
+
+ if (!_listening)
+ {
+ throw new InvalidOperationException("The listener hasn't been started.");
+ }
+
+ var result = BeginGetContext(new HttpListenerAsyncResult(null, null));
+ result.InGet = true;
+
+ return EndGetContext(result);
+ }
+
+ ///
+ /// Starts listening for incoming requests.
+ ///
+ public void Start()
+ {
+ CheckDisposed();
+ if (_listening)
+ {
+ return;
+ }
+
+ EndPointManager.AddListener(this);
+ _listening = true;
+ }
+
+ ///
+ /// Stops listening for incoming requests.
+ ///
+ public void Stop()
+ {
+ CheckDisposed();
+ if (!_listening)
+ {
+ return;
+ }
+
+ _listening = false;
+ EndPointManager.RemoveListener(this);
+
+ lock (_contextRegistryLock)
+ {
+ CleanupContextQueue(true);
+ }
+
+ CleanupContextRegistry();
+ CleanupConnections();
+ CleanupWaitQueue(new HttpListenerException(995, "The listener is closed."));
+ }
+
+ internal bool AddConnection(HttpConnection connection)
+ {
+ if (!_listening)
+ {
+ return false;
+ }
+
+ lock (_connectionsSync)
+ {
+ if (!_listening)
+ {
+ return false;
+ }
+
+ _connections[connection] = connection;
+ return true;
+ }
+ }
+
+ internal HttpListenerAsyncResult BeginGetContext(HttpListenerAsyncResult asyncResult)
+ {
+ lock (_contextRegistryLock)
+ {
+ if (!_listening)
+ {
+ throw new HttpListenerException(995);
+ }
+
+ var ctx = GetContextFromQueue();
+ if (ctx == null)
+ {
+ _waitQueue.Add(asyncResult);
+ }
+ else
+ {
+ asyncResult.Complete(ctx, true);
+ }
+
+ return asyncResult;
+ }
+ }
+
+ internal void CheckDisposed()
+ {
+ if (IsDisposed)
+ {
+ throw new ObjectDisposedException(GetType().ToString());
+ }
+ }
+
+ internal string GetRealm()
+ {
+ var realm = _realm;
+ return realm != null && realm.Length > 0 ? realm : _defaultRealm;
+ }
+
+ internal Func GetUserCredentialsFinder()
+ {
+ return _userCredFinder;
+ }
+
+ internal bool RegisterContext(HttpListenerContext context)
+ {
+ if (!_listening)
+ {
+ return false;
+ }
+
+ lock (_contextRegistryLock)
+ {
+ if (!_listening)
+ {
+ return false;
+ }
+
+ _contextRegistry[context] = context;
+
+ var result = GetAsyncResultFromQueue();
+ if (result == null)
+ {
+ contextQueue.Add(context);
+ }
+ else
+ {
+ result.Complete(context);
+ }
+
+ return true;
+ }
+ }
+
+ internal void RemoveConnection(HttpConnection connection)
+ {
+ lock (_connectionsSync)
+ {
+ _connections.Remove(connection);
+ }
+ }
+
+ internal AuthenticationSchemes SelectAuthenticationScheme(HttpListenerRequest request)
+ {
+ var selector = _authSchemeSelector;
+ if (selector == null)
+ {
+ return _authSchemes;
+ }
+
+ try
+ {
+ return selector(request);
+ }
+ catch
+ {
+ return AuthenticationSchemes.None;
+ }
+ }
+
+ internal void UnregisterContext(HttpListenerContext context)
+ {
+ lock (_contextRegistryLock)
+ {
+ _contextRegistry.Remove(context);
+ }
+ }
+
private void CleanupConnections()
{
HttpConnection[] httpConnections = null;
@@ -335,285 +635,5 @@ namespace EonaCat.Network
return ctx;
}
-
- internal bool AddConnection(HttpConnection connection)
- {
- if (!_listening)
- {
- return false;
- }
-
- lock (_connectionsSync)
- {
- if (!_listening)
- {
- return false;
- }
-
- _connections[connection] = connection;
- return true;
- }
- }
-
- internal HttpListenerAsyncResult BeginGetContext(HttpListenerAsyncResult asyncResult)
- {
- lock (_contextRegistryLock)
- {
- if (!_listening)
- {
- throw new HttpListenerException(995);
- }
-
- var ctx = GetContextFromQueue();
- if (ctx == null)
- {
- _waitQueue.Add(asyncResult);
- }
- else
- {
- asyncResult.Complete(ctx, true);
- }
-
- return asyncResult;
- }
- }
-
- internal void CheckDisposed()
- {
- if (IsDisposed)
- {
- throw new ObjectDisposedException(GetType().ToString());
- }
- }
-
- internal string GetRealm()
- {
- var realm = _realm;
- return realm != null && realm.Length > 0 ? realm : _defaultRealm;
- }
-
- internal Func GetUserCredentialsFinder()
- {
- return _userCredFinder;
- }
-
- internal bool RegisterContext(HttpListenerContext context)
- {
- if (!_listening)
- {
- return false;
- }
-
- lock (_contextRegistryLock)
- {
- if (!_listening)
- {
- return false;
- }
-
- _contextRegistry[context] = context;
-
- var result = GetAsyncResultFromQueue();
- if (result == null)
- {
- contextQueue.Add(context);
- }
- else
- {
- result.Complete(context);
- }
-
- return true;
- }
- }
-
- internal void RemoveConnection(HttpConnection connection)
- {
- lock (_connectionsSync)
- {
- _connections.Remove(connection);
- }
- }
-
- internal AuthenticationSchemes SelectAuthenticationScheme(HttpListenerRequest request)
- {
- var selector = _authSchemeSelector;
- if (selector == null)
- {
- return _authSchemes;
- }
-
- try
- {
- return selector(request);
- }
- catch
- {
- return AuthenticationSchemes.None;
- }
- }
-
- internal void UnregisterContext(HttpListenerContext context)
- {
- lock (_contextRegistryLock)
- {
- _contextRegistry.Remove(context);
- }
- }
-
- ///
- /// Aborts the listener and releases all resources associated with it.
- ///
- public void Abort()
- {
- if (IsDisposed)
- {
- return;
- }
-
- close(true);
- }
-
- ///
- /// Begins asynchronously getting an HTTP context from the listener.
- ///
- /// The method to call when the operation completes.
- /// A user-defined object that contains information about the asynchronous operation.
- /// An that represents the asynchronous operation.
- 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 (!_listening)
- {
- throw new InvalidOperationException("The listener hasn't been started.");
- }
-
- return BeginGetContext(new HttpListenerAsyncResult(callback, state));
- }
-
- ///
- /// Closes the listener.
- ///
- public void Close()
- {
- if (IsDisposed)
- {
- return;
- }
-
- close(false);
- }
-
- ///
- /// Ends an asynchronous operation to get an HTTP context from the listener.
- ///
- /// The reference to the pending asynchronous request to finish.
- /// An that represents the context of the asynchronous operation.
- public HttpListenerContext EndGetContext(IAsyncResult asyncResult)
- {
- CheckDisposed();
- if (asyncResult == null)
- {
- throw new ArgumentNullException(nameof(asyncResult));
- }
-
- if (asyncResult is not HttpListenerAsyncResult result)
- {
- throw new ArgumentException("A wrong IAsyncResult.", nameof(asyncResult));
- }
-
- if (result.EndCalled)
- {
- throw new InvalidOperationException("This IAsyncResult cannot be reused.");
- }
-
- result.EndCalled = true;
- if (!result.IsCompleted)
- {
- result.AsyncWaitHandle.WaitOne();
- }
-
- return result.GetContext();
- }
-
- ///
- /// Gets the next available HTTP context from the listener.
- ///
- /// An that represents the context of the HTTP request.
- public HttpListenerContext GetContext()
- {
- CheckDisposed();
- if (_prefixes.Count == 0)
- {
- throw new InvalidOperationException("The listener has no URI prefix on which listens.");
- }
-
- if (!_listening)
- {
- throw new InvalidOperationException("The listener hasn't been started.");
- }
-
- var result = BeginGetContext(new HttpListenerAsyncResult(null, null));
- result.InGet = true;
-
- return EndGetContext(result);
- }
-
- ///
- /// Starts listening for incoming requests.
- ///
- public void Start()
- {
- CheckDisposed();
- if (_listening)
- {
- return;
- }
-
- EndPointManager.AddListener(this);
- _listening = true;
- }
-
- ///
- /// Stops listening for incoming requests.
- ///
- public void Stop()
- {
- CheckDisposed();
- if (!_listening)
- {
- return;
- }
-
- _listening = false;
- EndPointManager.RemoveListener(this);
-
- lock (_contextRegistryLock)
- {
- CleanupContextQueue(true);
- }
-
- CleanupContextRegistry();
- CleanupConnections();
- CleanupWaitQueue(new HttpListenerException(995, "The listener is closed."));
- }
-
- ///
- /// Disposes of the resources used by the .
- ///
- void IDisposable.Dispose()
- {
- if (IsDisposed)
- {
- return;
- }
-
- close(true);
- }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerAsyncResult.cs b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerAsyncResult.cs
index 899cbfb..384e0b1 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerAsyncResult.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerAsyncResult.cs
@@ -12,10 +12,10 @@ namespace EonaCat.Network
internal class HttpListenerAsyncResult : IAsyncResult
{
private readonly AsyncCallback _callback;
+ private readonly object _sync;
private bool _completed;
private HttpListenerContext _context;
private Exception _exception;
- private readonly object _sync;
private ManualResetEvent _waitHandle;
///
@@ -30,16 +30,6 @@ namespace EonaCat.Network
_sync = new object();
}
- ///
- /// Gets or sets a value indicating whether the method has been called.
- ///
- internal bool EndCalled { get; set; }
-
- ///
- /// Gets or sets a value indicating whether the asynchronous operation is in progress.
- ///
- internal bool InGet { get; set; }
-
///
/// Gets the user-defined object that contains information about the asynchronous operation.
///
@@ -78,38 +68,15 @@ namespace EonaCat.Network
}
}
- // Private method to complete the asynchronous operation
- private static void complete(HttpListenerAsyncResult asyncResult)
- {
- lock (asyncResult._sync)
- {
- asyncResult._completed = true;
-
- var waitHandle = asyncResult._waitHandle;
- waitHandle?.Set();
- }
-
- var callback = asyncResult._callback;
- if (callback == null)
- {
- return;
- }
-
- ThreadPool.QueueUserWorkItem(
- state =>
- {
- try
- {
- callback(asyncResult);
- }
- catch
- {
- }
- },
- null
- );
- }
+ ///
+ /// Gets or sets a value indicating whether the method has been called.
+ ///
+ internal bool EndCalled { get; set; }
+ ///
+ /// Gets or sets a value indicating whether the asynchronous operation is in progress.
+ ///
+ internal bool InGet { get; set; }
///
/// Completes the asynchronous operation with the specified exception.
///
@@ -158,5 +125,37 @@ namespace EonaCat.Network
return _context;
}
+
+ // Private method to complete the asynchronous operation
+ private static void complete(HttpListenerAsyncResult asyncResult)
+ {
+ lock (asyncResult._sync)
+ {
+ asyncResult._completed = true;
+
+ var waitHandle = asyncResult._waitHandle;
+ waitHandle?.Set();
+ }
+
+ var callback = asyncResult._callback;
+ if (callback == null)
+ {
+ return;
+ }
+
+ ThreadPool.QueueUserWorkItem(
+ state =>
+ {
+ try
+ {
+ callback(asyncResult);
+ }
+ catch
+ {
+ }
+ },
+ null
+ );
+ }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerContext.cs b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerContext.cs
index 54b9a13..c8c0463 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerContext.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerContext.cs
@@ -25,6 +25,21 @@ namespace EonaCat.Network
Response = new HttpListenerResponse(this);
}
+ ///
+ /// Gets the associated with the context.
+ ///
+ public HttpListenerRequest Request { get; }
+
+ ///
+ /// Gets the associated with the context.
+ ///
+ public HttpListenerResponse Response { get; }
+
+ ///
+ /// Gets or sets the associated with the user.
+ ///
+ public IPrincipal User { get; private set; }
+
///
/// Gets the underlying for the context.
///
@@ -49,21 +64,34 @@ namespace EonaCat.Network
/// Gets or sets the associated with the context.
///
internal HttpListener Listener { get; set; }
-
///
- /// Gets the associated with the context.
+ /// Accepts a WebSocket connection with the specified protocol.
///
- public HttpListenerRequest Request { get; }
+ /// The WebSocket subprotocol to negotiate.
+ /// The for the WebSocket connection.
+ public HttpListenerWSContext AcceptWebSocket(string protocol)
+ {
+ if (_websocketContext != null)
+ {
+ throw new InvalidOperationException("Accepting already in progress.");
+ }
- ///
- /// Gets the associated with the context.
- ///
- public HttpListenerResponse Response { get; }
+ if (protocol != null)
+ {
+ if (protocol.Length == 0)
+ {
+ throw new ArgumentException("Empty string.", nameof(protocol));
+ }
- ///
- /// Gets or sets the associated with the user.
- ///
- public IPrincipal User { get; private set; }
+ if (!protocol.IsToken())
+ {
+ throw new ArgumentException("Contains invalid characters", nameof(protocol));
+ }
+ }
+
+ _websocketContext = new HttpListenerWSContext(this, protocol);
+ return _websocketContext;
+ }
///
/// Authenticates the user based on the specified authentication scheme.
@@ -119,34 +147,5 @@ namespace EonaCat.Network
{
Listener.UnregisterContext(this);
}
-
- ///
- /// Accepts a WebSocket connection with the specified protocol.
- ///
- /// The WebSocket subprotocol to negotiate.
- /// The for the WebSocket connection.
- public HttpListenerWSContext AcceptWebSocket(string protocol)
- {
- if (_websocketContext != null)
- {
- throw new InvalidOperationException("Accepting already in progress.");
- }
-
- if (protocol != null)
- {
- if (protocol.Length == 0)
- {
- throw new ArgumentException("Empty string.", nameof(protocol));
- }
-
- if (!protocol.IsToken())
- {
- throw new ArgumentException("Contains invalid characters", nameof(protocol));
- }
- }
-
- _websocketContext = new HttpListenerWSContext(this, protocol);
- return _websocketContext;
- }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerException.cs b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerException.cs
index 00d1e85..fe8610e 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerException.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerException.cs
@@ -13,15 +13,6 @@ namespace EonaCat.Network
[Serializable]
public class HttpListenerException : Win32Exception
{
- ///
- /// Initializes a new instance of the class.
- ///
- protected HttpListenerException(
- SerializationInfo serializationInfo, StreamingContext streamingContext)
- : base(serializationInfo, streamingContext)
- {
- }
-
///
/// Initializes a new instance of the class with no error message.
///
@@ -48,6 +39,14 @@ namespace EonaCat.Network
{
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected HttpListenerException(
+ SerializationInfo serializationInfo, StreamingContext streamingContext)
+ : base(serializationInfo, streamingContext)
+ {
+ }
///
/// Gets the Win32 error code associated with this exception.
///
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerPrefix.cs b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerPrefix.cs
index 0db7458..1270378 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerPrefix.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerPrefix.cs
@@ -52,35 +52,6 @@ namespace EonaCat.Network
///
public string Port { get; private set; }
- private void parse(string uriPrefix)
- {
- if (uriPrefix.StartsWith("https"))
- {
- IsSecure = true;
- }
-
- var len = uriPrefix.Length;
- var startHost = uriPrefix.IndexOf(':') + 3;
- var root = uriPrefix.IndexOf('/', startHost + 1, len - startHost - 1);
-
- var colon = uriPrefix.LastIndexOf(':', root - 1, root - startHost - 1);
- if (uriPrefix[root - 1] != ']' && colon > startHost)
- {
- Host = uriPrefix.Substring(startHost, colon - startHost);
- Port = uriPrefix.Substring(colon + 1, root - colon - 1);
- }
- else
- {
- Host = uriPrefix.Substring(startHost, root - startHost);
- Port = IsSecure ? "443" : "80";
- }
-
- Path = uriPrefix.Substring(root);
-
- _prefix =
- string.Format("http{0}://{1}:{2}{3}", IsSecure ? "s" : "", Host, Port, Path);
- }
-
///
/// Checks if the specified URI prefix is valid.
///
@@ -163,5 +134,34 @@ namespace EonaCat.Network
{
return _prefix;
}
+
+ private void parse(string uriPrefix)
+ {
+ if (uriPrefix.StartsWith("https"))
+ {
+ IsSecure = true;
+ }
+
+ var len = uriPrefix.Length;
+ var startHost = uriPrefix.IndexOf(':') + 3;
+ var root = uriPrefix.IndexOf('/', startHost + 1, len - startHost - 1);
+
+ var colon = uriPrefix.LastIndexOf(':', root - 1, root - startHost - 1);
+ if (uriPrefix[root - 1] != ']' && colon > startHost)
+ {
+ Host = uriPrefix.Substring(startHost, colon - startHost);
+ Port = uriPrefix.Substring(colon + 1, root - colon - 1);
+ }
+ else
+ {
+ Host = uriPrefix.Substring(startHost, root - startHost);
+ Port = IsSecure ? "443" : "80";
+ }
+
+ Path = uriPrefix.Substring(root);
+
+ _prefix =
+ string.Format("http{0}://{1}:{2}{3}", IsSecure ? "s" : "", Host, Port, Path);
+ }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerPrefixCollection.cs b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerPrefixCollection.cs
index 0749aa7..5204118 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerPrefixCollection.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerPrefixCollection.cs
@@ -120,6 +120,15 @@ namespace EonaCat.Network
return _prefixes.GetEnumerator();
}
+ ///
+ /// Returns an enumerator that iterates through the collection.
+ ///
+ /// An enumerator that can be used to iterate through the collection.
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return _prefixes.GetEnumerator();
+ }
+
///
/// Removes the specified URI prefix from the collection.
///
@@ -141,14 +150,5 @@ namespace EonaCat.Network
return ret;
}
-
- ///
- /// Returns an enumerator that iterates through the collection.
- ///
- /// An enumerator that can be used to iterate through the collection.
- IEnumerator IEnumerable.GetEnumerator()
- {
- return _prefixes.GetEnumerator();
- }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerRequest.cs b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerRequest.cs
index 50753c3..78f691d 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerRequest.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerRequest.cs
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
-using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace EonaCat.Network
@@ -17,12 +16,12 @@ namespace EonaCat.Network
public sealed class HttpListenerRequest
{
private static readonly byte[] _100continue;
+ private readonly HttpListenerContext _context;
+ private readonly WebHeaderCollection _headers;
private bool _chunked;
private Encoding _contentEncoding;
private bool _contentLengthSet;
- private readonly HttpListenerContext _context;
private CookieCollection _cookies;
- private readonly WebHeaderCollection _headers;
private Stream _inputStream;
private bool _keepAlive;
private bool _keepAliveSet;
@@ -162,7 +161,7 @@ namespace EonaCat.Network
///
/// Gets the query string in the request.
///
- public NameValueCollection QueryString => _queryString ??= HttpUtility.InternalParseQueryString(Url.Query, Encoding.UTF8);
+ public NameValueCollection QueryString => _queryString ??= HttpUtility.InternalParseQueryString(Url.Query, ContentEncoding);
///
/// Gets the raw URL of the request.
@@ -209,18 +208,13 @@ namespace EonaCat.Network
///
public string[] UserLanguages { get; private set; }
- private static bool tryCreateVersion(string version, out Version result)
+ public override string ToString()
{
- try
- {
- result = new Version(version);
- return true;
- }
- catch
- {
- result = null;
- return false;
- }
+ var buff = new StringBuilder(64);
+ buff.AppendFormat("{0} {1} HTTP/{2}\r\n", HttpMethod, _uri, _version);
+ buff.Append(_headers.ToString());
+
+ return buff.ToString();
}
internal void AddHeader(string header)
@@ -404,13 +398,18 @@ namespace EonaCat.Network
}
}
- public override string ToString()
+ private static bool tryCreateVersion(string version, out Version result)
{
- var buff = new StringBuilder(64);
- buff.AppendFormat("{0} {1} HTTP/{2}\r\n", HttpMethod, _uri, _version);
- buff.Append(_headers.ToString());
-
- return buff.ToString();
+ try
+ {
+ result = new Version(version);
+ return true;
+ }
+ catch
+ {
+ result = null;
+ return false;
+ }
}
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerResponse.cs b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerResponse.cs
index ef7372d..d99e046 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerResponse.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpListenerResponse.cs
@@ -1,12 +1,10 @@
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
-using EonaCat.Logger;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
-using System.Reflection;
using System.Text;
namespace EonaCat.Network
@@ -16,10 +14,10 @@ namespace EonaCat.Network
///
public sealed class HttpListenerResponse : IDisposable
{
+ private readonly HttpListenerContext _context;
private Encoding _contentEncoding;
private long _contentLength;
private string _contentType;
- private readonly HttpListenerContext _context;
private CookieCollection _cookies;
private bool _disposed;
private WebHeaderCollection _headers;
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpStreamAsyncResult.cs b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpStreamAsyncResult.cs
index 4139de1..8f25045 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpStreamAsyncResult.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpStreamAsyncResult.cs
@@ -9,8 +9,8 @@ namespace EonaCat.Network
internal class HttpStreamAsyncResult : IAsyncResult
{
private readonly AsyncCallback _callback;
- private bool _isCompleted;
private readonly object _sync;
+ private bool _isCompleted;
private ManualResetEvent _waitHandle;
internal HttpStreamAsyncResult(AsyncCallback callback, object state)
@@ -20,6 +20,30 @@ namespace EonaCat.Network
_sync = new object();
}
+ public object AsyncState { get; }
+ public WaitHandle AsyncWaitHandle
+ {
+ get
+ {
+ lock (_sync)
+ {
+ return _waitHandle ??= new ManualResetEvent(_isCompleted);
+ }
+ }
+ }
+
+ public bool CompletedSynchronously => SyncRead == Count;
+ public bool IsCompleted
+ {
+ get
+ {
+ lock (_sync)
+ {
+ return _isCompleted;
+ }
+ }
+ }
+
internal byte[] Buffer { get; set; }
internal int Count { get; set; }
@@ -31,33 +55,6 @@ namespace EonaCat.Network
internal int Offset { get; set; }
internal int SyncRead { get; set; }
-
- public object AsyncState { get; }
-
- public WaitHandle AsyncWaitHandle
- {
- get
- {
- lock (_sync)
- {
- return _waitHandle ??= new ManualResetEvent(_isCompleted);
- }
- }
- }
-
- public bool CompletedSynchronously => SyncRead == Count;
-
- public bool IsCompleted
- {
- get
- {
- lock (_sync)
- {
- return _isCompleted;
- }
- }
- }
-
internal void Complete()
{
lock (_sync)
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpUtility.cs b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpUtility.cs
index d3a1bf9..a37f4b5 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Http/HttpUtility.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Http/HttpUtility.cs
@@ -14,777 +14,9 @@ namespace EonaCat.Network
{
internal sealed class HttpUtility
{
- private static Dictionary _entities;
private static readonly char[] _hexChars = "0123456789abcdef".ToCharArray();
private static readonly object _sync = new object();
-
- private static int getChar(byte[] bytes, int offset, int length)
- {
- var val = 0;
- var end = length + offset;
- for (var i = offset; i < end; i++)
- {
- var current = getInt(bytes[i]);
- if (current == -1)
- {
- return -1;
- }
-
- val = (val << 4) + current;
- }
-
- return val;
- }
-
- private static int getChar(string s, int offset, int length)
- {
- var val = 0;
- var end = length + offset;
- for (var i = offset; i < end; i++)
- {
- var c = s[i];
- if (c > 127)
- {
- return -1;
- }
-
- var current = getInt((byte)c);
- if (current == -1)
- {
- return -1;
- }
-
- val = (val << 4) + current;
- }
-
- return val;
- }
-
- private static char[] getChars(MemoryStream buffer, Encoding encoding)
- {
- return encoding.GetChars(buffer.GetBuffer(), 0, (int)buffer.Length);
- }
-
- private static Dictionary getEntities()
- {
- lock (_sync)
- {
- if (_entities == null)
- {
- GenerateHtmlEntityRefrencesDictionary();
- }
-
- return _entities;
- }
- }
-
- private static int getInt(byte b)
- {
- var c = (char)b;
- return c >= '0' && c <= '9'
- ? c - '0'
- : c >= 'a' && c <= 'f'
- ? c - 'a' + 10
- : c >= 'A' && c <= 'F'
- ? c - 'A' + 10
- : -1;
- }
-
- private static void GenerateHtmlEntityRefrencesDictionary()
- {
- // Build the dictionary of HTML entity references.
- _entities = new Dictionary
- {
- { "nbsp", '\u00A0' },
- { "iexcl", '\u00A1' },
- { "cent", '\u00A2' },
- { "pound", '\u00A3' },
- { "curren", '\u00A4' },
- { "yen", '\u00A5' },
- { "brvbar", '\u00A6' },
- { "sect", '\u00A7' },
- { "uml", '\u00A8' },
- { "copy", '\u00A9' },
- { "ordf", '\u00AA' },
- { "laquo", '\u00AB' },
- { "not", '\u00AC' },
- { "shy", '\u00AD' },
- { "reg", '\u00AE' },
- { "macr", '\u00AF' },
- { "deg", '\u00B0' },
- { "plusmn", '\u00B1' },
- { "sup2", '\u00B2' },
- { "sup3", '\u00B3' },
- { "acute", '\u00B4' },
- { "micro", '\u00B5' },
- { "para", '\u00B6' },
- { "middot", '\u00B7' },
- { "cedil", '\u00B8' },
- { "sup1", '\u00B9' },
- { "ordm", '\u00BA' },
- { "raquo", '\u00BB' },
- { "frac14", '\u00BC' },
- { "frac12", '\u00BD' },
- { "frac34", '\u00BE' },
- { "iquest", '\u00BF' },
- { "Agrave", '\u00C0' },
- { "Aacute", '\u00C1' },
- { "Acirc", '\u00C2' },
- { "Atilde", '\u00C3' },
- { "Auml", '\u00C4' },
- { "Aring", '\u00C5' },
- { "AElig", '\u00C6' },
- { "Ccedil", '\u00C7' },
- { "Egrave", '\u00C8' },
- { "Eacute", '\u00C9' },
- { "Ecirc", '\u00CA' },
- { "Euml", '\u00CB' },
- { "Igrave", '\u00CC' },
- { "Iacute", '\u00CD' },
- { "Icirc", '\u00CE' },
- { "Iuml", '\u00CF' },
- { "ETH", '\u00D0' },
- { "Ntilde", '\u00D1' },
- { "Ograve", '\u00D2' },
- { "Oacute", '\u00D3' },
- { "Ocirc", '\u00D4' },
- { "Otilde", '\u00D5' },
- { "Ouml", '\u00D6' },
- { "times", '\u00D7' },
- { "Oslash", '\u00D8' },
- { "Ugrave", '\u00D9' },
- { "Uacute", '\u00DA' },
- { "Ucirc", '\u00DB' },
- { "Uuml", '\u00DC' },
- { "Yacute", '\u00DD' },
- { "THORN", '\u00DE' },
- { "szlig", '\u00DF' },
- { "agrave", '\u00E0' },
- { "aacute", '\u00E1' },
- { "acirc", '\u00E2' },
- { "atilde", '\u00E3' },
- { "auml", '\u00E4' },
- { "aring", '\u00E5' },
- { "aelig", '\u00E6' },
- { "ccedil", '\u00E7' },
- { "egrave", '\u00E8' },
- { "eacute", '\u00E9' },
- { "ecirc", '\u00EA' },
- { "euml", '\u00EB' },
- { "igrave", '\u00EC' },
- { "iacute", '\u00ED' },
- { "icirc", '\u00EE' },
- { "iuml", '\u00EF' },
- { "eth", '\u00F0' },
- { "ntilde", '\u00F1' },
- { "ograve", '\u00F2' },
- { "oacute", '\u00F3' },
- { "ocirc", '\u00F4' },
- { "otilde", '\u00F5' },
- { "ouml", '\u00F6' },
- { "divide", '\u00F7' },
- { "oslash", '\u00F8' },
- { "ugrave", '\u00F9' },
- { "uacute", '\u00FA' },
- { "ucirc", '\u00FB' },
- { "uuml", '\u00FC' },
- { "yacute", '\u00FD' },
- { "thorn", '\u00FE' },
- { "yuml", '\u00FF' },
- { "fnof", '\u0192' },
- { "Alpha", '\u0391' },
- { "Beta", '\u0392' },
- { "Gamma", '\u0393' },
- { "Delta", '\u0394' },
- { "Epsilon", '\u0395' },
- { "Zeta", '\u0396' },
- { "Eta", '\u0397' },
- { "Theta", '\u0398' },
- { "Iota", '\u0399' },
- { "Kappa", '\u039A' },
- { "Lambda", '\u039B' },
- { "Mu", '\u039C' },
- { "Nu", '\u039D' },
- { "Xi", '\u039E' },
- { "Omicron", '\u039F' },
- { "Pi", '\u03A0' },
- { "Rho", '\u03A1' },
- { "Sigma", '\u03A3' },
- { "Tau", '\u03A4' },
- { "Upsilon", '\u03A5' },
- { "Phi", '\u03A6' },
- { "Chi", '\u03A7' },
- { "Psi", '\u03A8' },
- { "Omega", '\u03A9' },
- { "alpha", '\u03B1' },
- { "beta", '\u03B2' },
- { "gamma", '\u03B3' },
- { "delta", '\u03B4' },
- { "epsilon", '\u03B5' },
- { "zeta", '\u03B6' },
- { "eta", '\u03B7' },
- { "theta", '\u03B8' },
- { "iota", '\u03B9' },
- { "kappa", '\u03BA' },
- { "lambda", '\u03BB' },
- { "mu", '\u03BC' },
- { "nu", '\u03BD' },
- { "xi", '\u03BE' },
- { "omicron", '\u03BF' },
- { "pi", '\u03C0' },
- { "rho", '\u03C1' },
- { "sigmaf", '\u03C2' },
- { "sigma", '\u03C3' },
- { "tau", '\u03C4' },
- { "upsilon", '\u03C5' },
- { "phi", '\u03C6' },
- { "chi", '\u03C7' },
- { "psi", '\u03C8' },
- { "omega", '\u03C9' },
- { "thetasym", '\u03D1' },
- { "upsih", '\u03D2' },
- { "piv", '\u03D6' },
- { "bull", '\u2022' },
- { "hellip", '\u2026' },
- { "prime", '\u2032' },
- { "Prime", '\u2033' },
- { "oline", '\u203E' },
- { "frasl", '\u2044' },
- { "weierp", '\u2118' },
- { "image", '\u2111' },
- { "real", '\u211C' },
- { "trade", '\u2122' },
- { "alefsym", '\u2135' },
- { "larr", '\u2190' },
- { "uarr", '\u2191' },
- { "rarr", '\u2192' },
- { "darr", '\u2193' },
- { "harr", '\u2194' },
- { "crarr", '\u21B5' },
- { "lArr", '\u21D0' },
- { "uArr", '\u21D1' },
- { "rArr", '\u21D2' },
- { "dArr", '\u21D3' },
- { "hArr", '\u21D4' },
- { "forall", '\u2200' },
- { "part", '\u2202' },
- { "exist", '\u2203' },
- { "empty", '\u2205' },
- { "nabla", '\u2207' },
- { "isin", '\u2208' },
- { "notin", '\u2209' },
- { "ni", '\u220B' },
- { "prod", '\u220F' },
- { "sum", '\u2211' },
- { "minus", '\u2212' },
- { "lowast", '\u2217' },
- { "radic", '\u221A' },
- { "prop", '\u221D' },
- { "infin", '\u221E' },
- { "ang", '\u2220' },
- { "and", '\u2227' },
- { "or", '\u2228' },
- { "cap", '\u2229' },
- { "cup", '\u222A' },
- { "int", '\u222B' },
- { "there4", '\u2234' },
- { "sim", '\u223C' },
- { "cong", '\u2245' },
- { "asymp", '\u2248' },
- { "ne", '\u2260' },
- { "equiv", '\u2261' },
- { "le", '\u2264' },
- { "ge", '\u2265' },
- { "sub", '\u2282' },
- { "sup", '\u2283' },
- { "nsub", '\u2284' },
- { "sube", '\u2286' },
- { "supe", '\u2287' },
- { "oplus", '\u2295' },
- { "otimes", '\u2297' },
- { "perp", '\u22A5' },
- { "sdot", '\u22C5' },
- { "lceil", '\u2308' },
- { "rceil", '\u2309' },
- { "lfloor", '\u230A' },
- { "rfloor", '\u230B' },
- { "lang", '\u2329' },
- { "rang", '\u232A' },
- { "loz", '\u25CA' },
- { "spades", '\u2660' },
- { "clubs", '\u2663' },
- { "hearts", '\u2665' },
- { "diams", '\u2666' },
- { "quot", '\u0022' },
- { "amp", '\u0026' },
- { "lt", '\u003C' },
- { "gt", '\u003E' },
- { "OElig", '\u0152' },
- { "oelig", '\u0153' },
- { "Scaron", '\u0160' },
- { "scaron", '\u0161' },
- { "Yuml", '\u0178' },
- { "circ", '\u02C6' },
- { "tilde", '\u02DC' },
- { "ensp", '\u2002' },
- { "emsp", '\u2003' },
- { "thinsp", '\u2009' },
- { "zwnj", '\u200C' },
- { "zwj", '\u200D' },
- { "lrm", '\u200E' },
- { "rlm", '\u200F' },
- { "ndash", '\u2013' },
- { "mdash", '\u2014' },
- { "lsquo", '\u2018' },
- { "rsquo", '\u2019' },
- { "sbquo", '\u201A' },
- { "ldquo", '\u201C' },
- { "rdquo", '\u201D' },
- { "bdquo", '\u201E' },
- { "dagger", '\u2020' },
- { "Dagger", '\u2021' },
- { "permil", '\u2030' },
- { "lsaquo", '\u2039' },
- { "rsaquo", '\u203A' },
- { "euro", '\u20AC' }
- };
- }
-
- private static bool notEncoded(char c)
- {
- return c == '!' ||
- c == '\'' ||
- c == '(' ||
- c == ')' ||
- c == '*' ||
- c == '-' ||
- c == '.' ||
- c == '_';
- }
-
- private static void urlEncode(char c, Stream result, bool unicode)
- {
- if (c > 255)
- {
- result.WriteByte((byte)'%');
- result.WriteByte((byte)'u');
-
- var i = (int)c;
- var idx = i >> 12;
- result.WriteByte((byte)_hexChars[idx]);
-
- idx = (i >> 8) & 0x0F;
- result.WriteByte((byte)_hexChars[idx]);
-
- idx = (i >> 4) & 0x0F;
- result.WriteByte((byte)_hexChars[idx]);
-
- idx = i & 0x0F;
- result.WriteByte((byte)_hexChars[idx]);
-
- return;
- }
-
- if (c > ' ' && notEncoded(c))
- {
- result.WriteByte((byte)c);
- return;
- }
-
- if (c == ' ')
- {
- result.WriteByte((byte)'+');
- return;
- }
-
- if ((c < '0') ||
- (c < 'A' && c > '9') ||
- (c > 'Z' && c < 'a') ||
- (c > 'z'))
- {
- if (unicode && c > 127)
- {
- result.WriteByte((byte)'%');
- result.WriteByte((byte)'u');
- result.WriteByte((byte)'0');
- result.WriteByte((byte)'0');
- }
- else
- {
- result.WriteByte((byte)'%');
- }
-
- var i = (int)c;
- var idx = i >> 4;
- result.WriteByte((byte)_hexChars[idx]);
-
- idx = i & 0x0F;
- result.WriteByte((byte)_hexChars[idx]);
-
- return;
- }
-
- result.WriteByte((byte)c);
- }
-
- private static void urlPathEncode(char c, Stream result)
- {
- if (c < 33 || c > 126)
- {
- var bytes = Encoding.UTF8.GetBytes(c.ToString());
- foreach (var b in bytes)
- {
- result.WriteByte((byte)'%');
-
- var i = (int)b;
- var idx = i >> 4;
- result.WriteByte((byte)_hexChars[idx]);
-
- idx = i & 0x0F;
- result.WriteByte((byte)_hexChars[idx]);
- }
-
- return;
- }
-
- if (c == ' ')
- {
- result.WriteByte((byte)'%');
- result.WriteByte((byte)'2');
- result.WriteByte((byte)'0');
-
- return;
- }
-
- result.WriteByte((byte)c);
- }
-
- private static void writeCharBytes(char c, IList buffer, Encoding encoding)
- {
- if (c > 255)
- {
- foreach (var b in encoding.GetBytes(new[] { c }))
- {
- buffer.Add(b);
- }
-
- return;
- }
-
- buffer.Add((byte)c);
- }
-
- internal static Uri CreateRequestUrl(
- string requestUri, string host, bool websocketRequest, bool secure)
- {
- if (requestUri == null || requestUri.Length == 0 || host == null || host.Length == 0)
- {
- return null;
- }
-
- string schm = null;
- string path = null;
- if (requestUri.StartsWith("/"))
- {
- path = requestUri;
- }
- else if (requestUri.MaybeUri())
- {
- var valid = Uri.TryCreate(requestUri, UriKind.Absolute, out Uri uri) &&
- (((schm = uri.Scheme).StartsWith("http") && !websocketRequest) ||
- (schm.StartsWith("ws") && websocketRequest));
-
- if (!valid)
- {
- return null;
- }
-
- host = uri.Authority;
- path = uri.PathAndQuery;
- }
- else if (requestUri == "*")
- {
- }
- else
- {
- // As authority form
- host = requestUri;
- }
-
- schm ??= (websocketRequest ? "ws" : "http") + (secure ? "s" : string.Empty);
-
- var colon = host.IndexOf(':');
- if (colon == -1)
- {
- host = string.Format("{0}:{1}", host, schm == "http" || schm == "ws" ? 80 : 443);
- }
-
- var url = string.Format("{0}://{1}{2}", schm, host, path);
-
- if (!Uri.TryCreate(url, UriKind.Absolute, out Uri res))
- {
- return null;
- }
-
- return res;
- }
-
- internal static IPrincipal CreateUser(
- string response,
- AuthenticationSchemes scheme,
- string realm,
- string method,
- Func credentialsFinder
- )
- {
- if (response == null || response.Length == 0)
- {
- return null;
- }
-
- if (credentialsFinder == null)
- {
- return null;
- }
-
- if (!(scheme == AuthenticationSchemes.Basic || scheme == AuthenticationSchemes.Digest))
- {
- return null;
- }
-
- if (scheme == AuthenticationSchemes.Digest)
- {
- if (realm == null || realm.Length == 0)
- {
- return null;
- }
-
- if (method == null || method.Length == 0)
- {
- return null;
- }
- }
-
- if (!response.StartsWith(scheme.ToString(), StringComparison.OrdinalIgnoreCase))
- {
- 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);
- }
- catch
- {
- }
-
- if (cred == null)
- {
- return null;
- }
-
- if (scheme == AuthenticationSchemes.Basic
- && ((HttpBasicIdentity)id).Password != cred.Password
- )
- {
- return null;
- }
-
- if (scheme == AuthenticationSchemes.Digest
- && !((HttpDigestIdentity)id).IsValid(cred.Password, realm, method, null)
- )
- {
- return null;
- }
-
- return new GenericPrincipal(id, cred.Roles);
- }
-
- internal static Encoding GetEncoding(string contentType)
- {
- var parts = contentType.Split(';');
- foreach (var p in parts)
- {
- var part = p.Trim();
- if (part.StartsWith("charset", StringComparison.OrdinalIgnoreCase))
- {
- return Encoding.GetEncoding(part.GetValue('=', true));
- }
- }
-
- return null;
- }
-
- internal static NameValueCollection InternalParseQueryString(string query, Encoding encoding)
- {
- int len;
- if (query == null || (len = query.Length) == 0 || (len == 1 && query[0] == '?'))
- {
- return new NameValueCollection(1);
- }
-
- if (query[0] == '?')
- {
- query = query.Substring(1);
- }
-
- var res = new QueryStringCollection();
- var components = query.Split('&');
- foreach (var component in components)
- {
- var i = component.IndexOf('=');
- if (i > -1)
- {
- var name = UrlDecode(component.Substring(0, i), encoding);
- var val = component.Length > i + 1
- ? UrlDecode(component.Substring(i + 1), encoding)
- : string.Empty;
-
- res.Add(name, val);
- }
- else
- {
- res.Add(null, UrlDecode(component, encoding));
- }
- }
-
- return res;
- }
-
- internal static string InternalUrlDecode(
- byte[] bytes, int offset, int count, Encoding encoding)
- {
- var output = new StringBuilder();
- using (var acc = new MemoryStream())
- {
- var end = count + offset;
- for (var i = offset; i < end; i++)
- {
- if (bytes[i] == '%' && i + 2 < count && bytes[i + 1] != '%')
- {
- int xchar;
- if (bytes[i + 1] == (byte)'u' && i + 5 < end)
- {
- if (acc.Length > 0)
- {
- output.Append(getChars(acc, encoding));
- acc.SetLength(0);
- }
-
- xchar = getChar(bytes, i + 2, 4);
- if (xchar != -1)
- {
- output.Append((char)xchar);
- i += 5;
-
- continue;
- }
- }
- else if ((xchar = getChar(bytes, i + 1, 2)) != -1)
- {
- acc.WriteByte((byte)xchar);
- i += 2;
-
- continue;
- }
- }
-
- if (acc.Length > 0)
- {
- output.Append(getChars(acc, encoding));
- acc.SetLength(0);
- }
-
- if (bytes[i] == '+')
- {
- output.Append(' ');
- continue;
- }
-
- output.Append((char)bytes[i]);
- }
-
- if (acc.Length > 0)
- {
- output.Append(getChars(acc, encoding));
- }
- }
-
- return output.ToString();
- }
-
- internal static byte[] InternalUrlDecodeToBytes(byte[] bytes, int offset, int count)
- {
- using (var res = new MemoryStream())
- {
- var end = offset + count;
- for (var i = offset; i < end; i++)
- {
- var c = (char)bytes[i];
- if (c == '+')
- {
- c = ' ';
- }
- else if (c == '%' && i < end - 2)
- {
- var xchar = getChar(bytes, i + 1, 2);
- if (xchar != -1)
- {
- c = (char)xchar;
- i += 2;
- }
- }
-
- res.WriteByte((byte)c);
- }
-
- res.Close();
- return res.ToArray();
- }
- }
-
- internal static byte[] InternalUrlEncodeToBytes(byte[] bytes, int offset, int count)
- {
- using (var res = new MemoryStream())
- {
- var end = offset + count;
- for (var i = offset; i < end; i++)
- {
- urlEncode((char)bytes[i], res, false);
- }
-
- res.Close();
- return res.ToArray();
- }
- }
-
- internal static byte[] InternalUrlEncodeUnicodeToBytes(string s)
- {
- using (var res = new MemoryStream())
- {
- foreach (var c in s)
- {
- urlEncode(c, res, true);
- }
-
- res.Close();
- return res.ToArray();
- }
- }
-
+ private static Dictionary _entities;
public static string HtmlAttributeEncode(string s)
{
if (s == null || s.Length == 0 || !s.Contains('&', '"', '<', '>'))
@@ -1359,5 +591,771 @@ namespace EonaCat.Network
return Encoding.ASCII.GetString(res.ToArray());
}
}
+
+ internal static Uri CreateRequestUrl(
+ string requestUri, string host, bool websocketRequest, bool secure)
+ {
+ if (requestUri == null || requestUri.Length == 0 || host == null || host.Length == 0)
+ {
+ return null;
+ }
+
+ string schm = null;
+ string path = null;
+ if (requestUri.StartsWith("/"))
+ {
+ path = requestUri;
+ }
+ else if (requestUri.MaybeUri())
+ {
+ var valid = Uri.TryCreate(requestUri, UriKind.Absolute, out Uri uri) &&
+ (((schm = uri.Scheme).StartsWith("http") && !websocketRequest) ||
+ (schm.StartsWith("ws") && websocketRequest));
+
+ if (!valid)
+ {
+ return null;
+ }
+
+ host = uri.Authority;
+ path = uri.PathAndQuery;
+ }
+ else if (requestUri == "*")
+ {
+ }
+ else
+ {
+ // As authority form
+ host = requestUri;
+ }
+
+ schm ??= (websocketRequest ? "ws" : "http") + (secure ? "s" : string.Empty);
+
+ var colon = host.IndexOf(':');
+ if (colon == -1)
+ {
+ host = string.Format("{0}:{1}", host, schm == "http" || schm == "ws" ? 80 : 443);
+ }
+
+ var url = string.Format("{0}://{1}{2}", schm, host, path);
+
+ if (!Uri.TryCreate(url, UriKind.Absolute, out Uri res))
+ {
+ return null;
+ }
+
+ return res;
+ }
+
+ internal static IPrincipal CreateUser(
+ string response,
+ AuthenticationSchemes scheme,
+ string realm,
+ string method,
+ Func credentialsFinder
+ )
+ {
+ if (response == null || response.Length == 0)
+ {
+ return null;
+ }
+
+ if (credentialsFinder == null)
+ {
+ return null;
+ }
+
+ if (!(scheme == AuthenticationSchemes.Basic || scheme == AuthenticationSchemes.Digest))
+ {
+ return null;
+ }
+
+ if (scheme == AuthenticationSchemes.Digest)
+ {
+ if (realm == null || realm.Length == 0)
+ {
+ return null;
+ }
+
+ if (method == null || method.Length == 0)
+ {
+ return null;
+ }
+ }
+
+ if (!response.StartsWith(scheme.ToString(), StringComparison.OrdinalIgnoreCase))
+ {
+ 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);
+ }
+ catch
+ {
+ }
+
+ if (cred == null)
+ {
+ return null;
+ }
+
+ if (scheme == AuthenticationSchemes.Basic
+ && ((HttpBasicIdentity)id).Password != cred.Password
+ )
+ {
+ return null;
+ }
+
+ if (scheme == AuthenticationSchemes.Digest
+ && !((HttpDigestIdentity)id).IsValid(cred.Password, realm, method, null)
+ )
+ {
+ return null;
+ }
+
+ return new GenericPrincipal(id, cred.Roles);
+ }
+
+ internal static Encoding GetEncoding(string contentType)
+ {
+ var parts = contentType.Split(';');
+ foreach (var p in parts)
+ {
+ var part = p.Trim();
+ if (part.StartsWith("charset", StringComparison.OrdinalIgnoreCase))
+ {
+ return Encoding.GetEncoding(part.GetValue('=', true));
+ }
+ }
+
+ return null;
+ }
+
+ internal static NameValueCollection InternalParseQueryString(string query, Encoding encoding)
+ {
+ int len;
+ if (query == null || (len = query.Length) == 0 || (len == 1 && query[0] == '?'))
+ {
+ return new NameValueCollection(1);
+ }
+
+ if (query[0] == '?')
+ {
+ query = query.Substring(1);
+ }
+
+ var res = new QueryStringCollection();
+ var components = query.Split('&');
+ foreach (var component in components)
+ {
+ var i = component.IndexOf('=');
+ if (i > -1)
+ {
+ var name = UrlDecode(component.Substring(0, i), encoding);
+ var val = component.Length > i + 1
+ ? UrlDecode(component.Substring(i + 1), encoding)
+ : string.Empty;
+
+ res.Add(name, val);
+ }
+ else
+ {
+ res.Add(null, UrlDecode(component, encoding));
+ }
+ }
+
+ return res;
+ }
+
+ internal static string InternalUrlDecode(
+ byte[] bytes, int offset, int count, Encoding encoding)
+ {
+ var output = new StringBuilder();
+ using (var acc = new MemoryStream())
+ {
+ var end = count + offset;
+ for (var i = offset; i < end; i++)
+ {
+ if (bytes[i] == '%' && i + 2 < count && bytes[i + 1] != '%')
+ {
+ int xchar;
+ if (bytes[i + 1] == (byte)'u' && i + 5 < end)
+ {
+ if (acc.Length > 0)
+ {
+ output.Append(getChars(acc, encoding));
+ acc.SetLength(0);
+ }
+
+ xchar = getChar(bytes, i + 2, 4);
+ if (xchar != -1)
+ {
+ output.Append((char)xchar);
+ i += 5;
+
+ continue;
+ }
+ }
+ else if ((xchar = getChar(bytes, i + 1, 2)) != -1)
+ {
+ acc.WriteByte((byte)xchar);
+ i += 2;
+
+ continue;
+ }
+ }
+
+ if (acc.Length > 0)
+ {
+ output.Append(getChars(acc, encoding));
+ acc.SetLength(0);
+ }
+
+ if (bytes[i] == '+')
+ {
+ output.Append(' ');
+ continue;
+ }
+
+ output.Append((char)bytes[i]);
+ }
+
+ if (acc.Length > 0)
+ {
+ output.Append(getChars(acc, encoding));
+ }
+ }
+
+ return output.ToString();
+ }
+
+ internal static byte[] InternalUrlDecodeToBytes(byte[] bytes, int offset, int count)
+ {
+ using (var res = new MemoryStream())
+ {
+ var end = offset + count;
+ for (var i = offset; i < end; i++)
+ {
+ var c = (char)bytes[i];
+ if (c == '+')
+ {
+ c = ' ';
+ }
+ else if (c == '%' && i < end - 2)
+ {
+ var xchar = getChar(bytes, i + 1, 2);
+ if (xchar != -1)
+ {
+ c = (char)xchar;
+ i += 2;
+ }
+ }
+
+ res.WriteByte((byte)c);
+ }
+
+ res.Close();
+ return res.ToArray();
+ }
+ }
+
+ internal static byte[] InternalUrlEncodeToBytes(byte[] bytes, int offset, int count)
+ {
+ using (var res = new MemoryStream())
+ {
+ var end = offset + count;
+ for (var i = offset; i < end; i++)
+ {
+ urlEncode((char)bytes[i], res, false);
+ }
+
+ res.Close();
+ return res.ToArray();
+ }
+ }
+
+ internal static byte[] InternalUrlEncodeUnicodeToBytes(string s)
+ {
+ using (var res = new MemoryStream())
+ {
+ foreach (var c in s)
+ {
+ urlEncode(c, res, true);
+ }
+
+ res.Close();
+ return res.ToArray();
+ }
+ }
+
+ private static void GenerateHtmlEntityRefrencesDictionary()
+ {
+ // Build the dictionary of HTML entity references.
+ _entities = new Dictionary
+ {
+ { "nbsp", '\u00A0' },
+ { "iexcl", '\u00A1' },
+ { "cent", '\u00A2' },
+ { "pound", '\u00A3' },
+ { "curren", '\u00A4' },
+ { "yen", '\u00A5' },
+ { "brvbar", '\u00A6' },
+ { "sect", '\u00A7' },
+ { "uml", '\u00A8' },
+ { "copy", '\u00A9' },
+ { "ordf", '\u00AA' },
+ { "laquo", '\u00AB' },
+ { "not", '\u00AC' },
+ { "shy", '\u00AD' },
+ { "reg", '\u00AE' },
+ { "macr", '\u00AF' },
+ { "deg", '\u00B0' },
+ { "plusmn", '\u00B1' },
+ { "sup2", '\u00B2' },
+ { "sup3", '\u00B3' },
+ { "acute", '\u00B4' },
+ { "micro", '\u00B5' },
+ { "para", '\u00B6' },
+ { "middot", '\u00B7' },
+ { "cedil", '\u00B8' },
+ { "sup1", '\u00B9' },
+ { "ordm", '\u00BA' },
+ { "raquo", '\u00BB' },
+ { "frac14", '\u00BC' },
+ { "frac12", '\u00BD' },
+ { "frac34", '\u00BE' },
+ { "iquest", '\u00BF' },
+ { "Agrave", '\u00C0' },
+ { "Aacute", '\u00C1' },
+ { "Acirc", '\u00C2' },
+ { "Atilde", '\u00C3' },
+ { "Auml", '\u00C4' },
+ { "Aring", '\u00C5' },
+ { "AElig", '\u00C6' },
+ { "Ccedil", '\u00C7' },
+ { "Egrave", '\u00C8' },
+ { "Eacute", '\u00C9' },
+ { "Ecirc", '\u00CA' },
+ { "Euml", '\u00CB' },
+ { "Igrave", '\u00CC' },
+ { "Iacute", '\u00CD' },
+ { "Icirc", '\u00CE' },
+ { "Iuml", '\u00CF' },
+ { "ETH", '\u00D0' },
+ { "Ntilde", '\u00D1' },
+ { "Ograve", '\u00D2' },
+ { "Oacute", '\u00D3' },
+ { "Ocirc", '\u00D4' },
+ { "Otilde", '\u00D5' },
+ { "Ouml", '\u00D6' },
+ { "times", '\u00D7' },
+ { "Oslash", '\u00D8' },
+ { "Ugrave", '\u00D9' },
+ { "Uacute", '\u00DA' },
+ { "Ucirc", '\u00DB' },
+ { "Uuml", '\u00DC' },
+ { "Yacute", '\u00DD' },
+ { "THORN", '\u00DE' },
+ { "szlig", '\u00DF' },
+ { "agrave", '\u00E0' },
+ { "aacute", '\u00E1' },
+ { "acirc", '\u00E2' },
+ { "atilde", '\u00E3' },
+ { "auml", '\u00E4' },
+ { "aring", '\u00E5' },
+ { "aelig", '\u00E6' },
+ { "ccedil", '\u00E7' },
+ { "egrave", '\u00E8' },
+ { "eacute", '\u00E9' },
+ { "ecirc", '\u00EA' },
+ { "euml", '\u00EB' },
+ { "igrave", '\u00EC' },
+ { "iacute", '\u00ED' },
+ { "icirc", '\u00EE' },
+ { "iuml", '\u00EF' },
+ { "eth", '\u00F0' },
+ { "ntilde", '\u00F1' },
+ { "ograve", '\u00F2' },
+ { "oacute", '\u00F3' },
+ { "ocirc", '\u00F4' },
+ { "otilde", '\u00F5' },
+ { "ouml", '\u00F6' },
+ { "divide", '\u00F7' },
+ { "oslash", '\u00F8' },
+ { "ugrave", '\u00F9' },
+ { "uacute", '\u00FA' },
+ { "ucirc", '\u00FB' },
+ { "uuml", '\u00FC' },
+ { "yacute", '\u00FD' },
+ { "thorn", '\u00FE' },
+ { "yuml", '\u00FF' },
+ { "fnof", '\u0192' },
+ { "Alpha", '\u0391' },
+ { "Beta", '\u0392' },
+ { "Gamma", '\u0393' },
+ { "Delta", '\u0394' },
+ { "Epsilon", '\u0395' },
+ { "Zeta", '\u0396' },
+ { "Eta", '\u0397' },
+ { "Theta", '\u0398' },
+ { "Iota", '\u0399' },
+ { "Kappa", '\u039A' },
+ { "Lambda", '\u039B' },
+ { "Mu", '\u039C' },
+ { "Nu", '\u039D' },
+ { "Xi", '\u039E' },
+ { "Omicron", '\u039F' },
+ { "Pi", '\u03A0' },
+ { "Rho", '\u03A1' },
+ { "Sigma", '\u03A3' },
+ { "Tau", '\u03A4' },
+ { "Upsilon", '\u03A5' },
+ { "Phi", '\u03A6' },
+ { "Chi", '\u03A7' },
+ { "Psi", '\u03A8' },
+ { "Omega", '\u03A9' },
+ { "alpha", '\u03B1' },
+ { "beta", '\u03B2' },
+ { "gamma", '\u03B3' },
+ { "delta", '\u03B4' },
+ { "epsilon", '\u03B5' },
+ { "zeta", '\u03B6' },
+ { "eta", '\u03B7' },
+ { "theta", '\u03B8' },
+ { "iota", '\u03B9' },
+ { "kappa", '\u03BA' },
+ { "lambda", '\u03BB' },
+ { "mu", '\u03BC' },
+ { "nu", '\u03BD' },
+ { "xi", '\u03BE' },
+ { "omicron", '\u03BF' },
+ { "pi", '\u03C0' },
+ { "rho", '\u03C1' },
+ { "sigmaf", '\u03C2' },
+ { "sigma", '\u03C3' },
+ { "tau", '\u03C4' },
+ { "upsilon", '\u03C5' },
+ { "phi", '\u03C6' },
+ { "chi", '\u03C7' },
+ { "psi", '\u03C8' },
+ { "omega", '\u03C9' },
+ { "thetasym", '\u03D1' },
+ { "upsih", '\u03D2' },
+ { "piv", '\u03D6' },
+ { "bull", '\u2022' },
+ { "hellip", '\u2026' },
+ { "prime", '\u2032' },
+ { "Prime", '\u2033' },
+ { "oline", '\u203E' },
+ { "frasl", '\u2044' },
+ { "weierp", '\u2118' },
+ { "image", '\u2111' },
+ { "real", '\u211C' },
+ { "trade", '\u2122' },
+ { "alefsym", '\u2135' },
+ { "larr", '\u2190' },
+ { "uarr", '\u2191' },
+ { "rarr", '\u2192' },
+ { "darr", '\u2193' },
+ { "harr", '\u2194' },
+ { "crarr", '\u21B5' },
+ { "lArr", '\u21D0' },
+ { "uArr", '\u21D1' },
+ { "rArr", '\u21D2' },
+ { "dArr", '\u21D3' },
+ { "hArr", '\u21D4' },
+ { "forall", '\u2200' },
+ { "part", '\u2202' },
+ { "exist", '\u2203' },
+ { "empty", '\u2205' },
+ { "nabla", '\u2207' },
+ { "isin", '\u2208' },
+ { "notin", '\u2209' },
+ { "ni", '\u220B' },
+ { "prod", '\u220F' },
+ { "sum", '\u2211' },
+ { "minus", '\u2212' },
+ { "lowast", '\u2217' },
+ { "radic", '\u221A' },
+ { "prop", '\u221D' },
+ { "infin", '\u221E' },
+ { "ang", '\u2220' },
+ { "and", '\u2227' },
+ { "or", '\u2228' },
+ { "cap", '\u2229' },
+ { "cup", '\u222A' },
+ { "int", '\u222B' },
+ { "there4", '\u2234' },
+ { "sim", '\u223C' },
+ { "cong", '\u2245' },
+ { "asymp", '\u2248' },
+ { "ne", '\u2260' },
+ { "equiv", '\u2261' },
+ { "le", '\u2264' },
+ { "ge", '\u2265' },
+ { "sub", '\u2282' },
+ { "sup", '\u2283' },
+ { "nsub", '\u2284' },
+ { "sube", '\u2286' },
+ { "supe", '\u2287' },
+ { "oplus", '\u2295' },
+ { "otimes", '\u2297' },
+ { "perp", '\u22A5' },
+ { "sdot", '\u22C5' },
+ { "lceil", '\u2308' },
+ { "rceil", '\u2309' },
+ { "lfloor", '\u230A' },
+ { "rfloor", '\u230B' },
+ { "lang", '\u2329' },
+ { "rang", '\u232A' },
+ { "loz", '\u25CA' },
+ { "spades", '\u2660' },
+ { "clubs", '\u2663' },
+ { "hearts", '\u2665' },
+ { "diams", '\u2666' },
+ { "quot", '\u0022' },
+ { "amp", '\u0026' },
+ { "lt", '\u003C' },
+ { "gt", '\u003E' },
+ { "OElig", '\u0152' },
+ { "oelig", '\u0153' },
+ { "Scaron", '\u0160' },
+ { "scaron", '\u0161' },
+ { "Yuml", '\u0178' },
+ { "circ", '\u02C6' },
+ { "tilde", '\u02DC' },
+ { "ensp", '\u2002' },
+ { "emsp", '\u2003' },
+ { "thinsp", '\u2009' },
+ { "zwnj", '\u200C' },
+ { "zwj", '\u200D' },
+ { "lrm", '\u200E' },
+ { "rlm", '\u200F' },
+ { "ndash", '\u2013' },
+ { "mdash", '\u2014' },
+ { "lsquo", '\u2018' },
+ { "rsquo", '\u2019' },
+ { "sbquo", '\u201A' },
+ { "ldquo", '\u201C' },
+ { "rdquo", '\u201D' },
+ { "bdquo", '\u201E' },
+ { "dagger", '\u2020' },
+ { "Dagger", '\u2021' },
+ { "permil", '\u2030' },
+ { "lsaquo", '\u2039' },
+ { "rsaquo", '\u203A' },
+ { "euro", '\u20AC' }
+ };
+ }
+
+ private static int getChar(byte[] bytes, int offset, int length)
+ {
+ var val = 0;
+ var end = length + offset;
+ for (var i = offset; i < end; i++)
+ {
+ var current = getInt(bytes[i]);
+ if (current == -1)
+ {
+ return -1;
+ }
+
+ val = (val << 4) + current;
+ }
+
+ return val;
+ }
+
+ private static int getChar(string s, int offset, int length)
+ {
+ var val = 0;
+ var end = length + offset;
+ for (var i = offset; i < end; i++)
+ {
+ var c = s[i];
+ if (c > 127)
+ {
+ return -1;
+ }
+
+ var current = getInt((byte)c);
+ if (current == -1)
+ {
+ return -1;
+ }
+
+ val = (val << 4) + current;
+ }
+
+ return val;
+ }
+
+ private static char[] getChars(MemoryStream buffer, Encoding encoding)
+ {
+ return encoding.GetChars(buffer.GetBuffer(), 0, (int)buffer.Length);
+ }
+
+ private static Dictionary getEntities()
+ {
+ lock (_sync)
+ {
+ if (_entities == null)
+ {
+ GenerateHtmlEntityRefrencesDictionary();
+ }
+
+ return _entities;
+ }
+ }
+
+ private static int getInt(byte b)
+ {
+ var c = (char)b;
+ return c >= '0' && c <= '9'
+ ? c - '0'
+ : c >= 'a' && c <= 'f'
+ ? c - 'a' + 10
+ : c >= 'A' && c <= 'F'
+ ? c - 'A' + 10
+ : -1;
+ }
+ private static bool notEncoded(char c)
+ {
+ return c == '!' ||
+ c == '\'' ||
+ c == '(' ||
+ c == ')' ||
+ c == '*' ||
+ c == '-' ||
+ c == '.' ||
+ c == '_';
+ }
+
+ private static void urlEncode(char c, Stream result, bool unicode)
+ {
+ if (c > 255)
+ {
+ result.WriteByte((byte)'%');
+ result.WriteByte((byte)'u');
+
+ var i = (int)c;
+ var idx = i >> 12;
+ result.WriteByte((byte)_hexChars[idx]);
+
+ idx = (i >> 8) & 0x0F;
+ result.WriteByte((byte)_hexChars[idx]);
+
+ idx = (i >> 4) & 0x0F;
+ result.WriteByte((byte)_hexChars[idx]);
+
+ idx = i & 0x0F;
+ result.WriteByte((byte)_hexChars[idx]);
+
+ return;
+ }
+
+ if (c > ' ' && notEncoded(c))
+ {
+ result.WriteByte((byte)c);
+ return;
+ }
+
+ if (c == ' ')
+ {
+ result.WriteByte((byte)'+');
+ return;
+ }
+
+ if ((c < '0') ||
+ (c < 'A' && c > '9') ||
+ (c > 'Z' && c < 'a') ||
+ (c > 'z'))
+ {
+ if (unicode && c > 127)
+ {
+ result.WriteByte((byte)'%');
+ result.WriteByte((byte)'u');
+ result.WriteByte((byte)'0');
+ result.WriteByte((byte)'0');
+ }
+ else
+ {
+ result.WriteByte((byte)'%');
+ }
+
+ var i = (int)c;
+ var idx = i >> 4;
+ result.WriteByte((byte)_hexChars[idx]);
+
+ idx = i & 0x0F;
+ result.WriteByte((byte)_hexChars[idx]);
+
+ return;
+ }
+
+ result.WriteByte((byte)c);
+ }
+
+ private static void urlPathEncode(char c, Stream result)
+ {
+ if (c < 33 || c > 126)
+ {
+ var bytes = Encoding.UTF8.GetBytes(c.ToString());
+ foreach (var b in bytes)
+ {
+ result.WriteByte((byte)'%');
+
+ var i = (int)b;
+ var idx = i >> 4;
+ result.WriteByte((byte)_hexChars[idx]);
+
+ idx = i & 0x0F;
+ result.WriteByte((byte)_hexChars[idx]);
+ }
+
+ return;
+ }
+
+ if (c == ' ')
+ {
+ result.WriteByte((byte)'%');
+ result.WriteByte((byte)'2');
+ result.WriteByte((byte)'0');
+
+ return;
+ }
+
+ result.WriteByte((byte)c);
+ }
+
+ private static void writeCharBytes(char c, IList buffer, Encoding encoding)
+ {
+ if (c > 255)
+ {
+ foreach (var b in encoding.GetBytes(new[] { c }))
+ {
+ buffer.Add(b);
+ }
+
+ return;
+ }
+
+ buffer.Add((byte)c);
+ }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Logger.cs b/EonaCat.Network/System/Sockets/Web/Core/Logger.cs
index b44eb20..d53ce58 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Logger.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Logger.cs
@@ -25,62 +25,23 @@ namespace EonaCat.Network
internal class Logger
{
- internal static string LoggingDirectory { get; private set; }
private static readonly LogManager _logManager;
-
- internal static bool IsLoggingDirectorySet => !string.IsNullOrWhiteSpace(LoggingDirectory);
- private static bool HasBeenSetup { get; set; }
- internal static bool DisableConsole { get; set; }
- internal static bool IsLoggingEnabled { get; set; }
-
static Logger()
{
_logManager = new LogManager(new LoggerSettings { RemoveMessagePrefix = true });
_logManager.OnException += _logManager_OnException;
}
- internal static void Setup(string loggingDirectory = null)
- {
- LoggingDirectory = loggingDirectory;
- _logManager.Settings.FileLoggerOptions.FileNamePrefix = "EonaCat.Network";
-
- if (IsLoggingDirectorySet)
- {
- _logManager.Settings.FileLoggerOptions.LogDirectory = LoggingDirectory;
- }
- _logManager.Settings.UseLocalTime = true;
- _logManager.Settings.FileLoggerOptions.UseLocalTime = true;
- _logManager.Settings.SysLogServers = new List();
- _logManager.Settings.SplunkServers = new List();
- _logManager.Settings.GrayLogServers = new List();
- _logManager.StartNewLogAsync();
- HasBeenSetup = true;
- }
-
- private static void _logManager_OnException(object? sender, ErrorMessage e)
- {
- Console.WriteLine(e.Message);
- if (e.Exception != null)
- {
- Console.WriteLine(e.Exception);
- }
- }
-
+ internal static bool DisableConsole { get; set; }
+ internal static bool IsLoggingDirectorySet => !string.IsNullOrWhiteSpace(LoggingDirectory);
+ internal static bool IsLoggingEnabled { get; set; }
+ internal static string LoggingDirectory { get; private set; }
+ private static bool HasBeenSetup { get; set; }
internal static void AddGrayLogServer(string hostname, int port)
{
_logManager.Settings.GrayLogServers.Add(new GrayLogServer(hostname, port));
}
- internal static bool RemoveGrayLogServer(GrayLogServer grayLogServer)
- {
- return _logManager.Settings.GrayLogServers.Remove(grayLogServer);
- }
-
- internal static void GrayLogState(bool state)
- {
- _logManager.Settings.SendToGrayLogServers = state;
- }
-
internal static void AddSplunkServer(string splunkHecUrl, string splunkHecToken, bool disableSSL = false)
{
var splunkServer = new SplunkServer(splunkHecUrl, splunkHecToken);
@@ -91,56 +52,14 @@ namespace EonaCat.Network
_logManager.Settings.SplunkServers.Add(splunkServer);
}
- internal static bool RemoveSplunkServer(SplunkServer splunkServer)
- {
- return _logManager.Settings.SplunkServers.Remove(splunkServer);
- }
-
- internal static void SplunkState(bool state)
- {
- _logManager.Settings.SendToSplunkServers = state;
- }
-
internal static void AddSyslogServer(string ipAddress, int port)
{
_logManager.Settings.SysLogServers.Add(new SyslogServer(ipAddress, port));
}
- internal static bool RemoveSyslogServer(SyslogServer syslogServer)
+ internal static void Critical(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
{
- return _logManager.Settings.SysLogServers.Remove(syslogServer);
- }
-
- internal static void SysLogState(bool state)
- {
- _logManager.Settings.SendToSyslogServers = state;
- }
-
- internal static void Info(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
- {
- Write(message, ELogType.INFO, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
- }
-
- private static void Write(string message, ELogType logType, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
- {
- if (!HasBeenSetup)
- {
- Setup();
- }
-
- if (DisableConsole)
- {
- writeToConsole = false;
- }
-
- if (grayLogSettings != null)
- {
- _logManager.Write(message, logType, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings.Facility, grayLogSettings.Source, grayLogSettings.Version);
- }
- else
- {
- _logManager.Write(message, logType, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers);
- }
+ Write(message, ELogType.CRITICAL, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
}
internal static void Debug(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
@@ -182,14 +101,62 @@ namespace EonaCat.Network
}
}
- internal static void Warning(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
+ internal static void GrayLogState(bool state)
{
- Write(message, ELogType.WARNING, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
+ _logManager.Settings.SendToGrayLogServers = state;
}
- internal static void Critical(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
+ internal static void Info(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
{
- Write(message, ELogType.CRITICAL, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
+ Write(message, ELogType.INFO, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
+ }
+
+ internal static bool RemoveGrayLogServer(GrayLogServer grayLogServer)
+ {
+ return _logManager.Settings.GrayLogServers.Remove(grayLogServer);
+ }
+
+ internal static bool RemoveSplunkServer(SplunkServer splunkServer)
+ {
+ return _logManager.Settings.SplunkServers.Remove(splunkServer);
+ }
+
+ internal static bool RemoveSyslogServer(SyslogServer syslogServer)
+ {
+ return _logManager.Settings.SysLogServers.Remove(syslogServer);
+ }
+
+ internal static void Setup(string loggingDirectory = null)
+ {
+ LoggingDirectory = loggingDirectory;
+ _logManager.Settings.FileLoggerOptions.FileNamePrefix = "EonaCat.Network";
+
+ if (IsLoggingDirectorySet)
+ {
+ _logManager.Settings.FileLoggerOptions.LogDirectory = LoggingDirectory;
+ }
+ _logManager.Settings.UseLocalTime = true;
+ _logManager.Settings.FileLoggerOptions.UseLocalTime = true;
+ _logManager.Settings.SysLogServers = new List();
+ _logManager.Settings.SplunkServers = new List();
+ _logManager.Settings.GrayLogServers = new List();
+ _logManager.StartNewLogAsync();
+ HasBeenSetup = true;
+ }
+
+ internal static void SplunkState(bool state)
+ {
+ _logManager.Settings.SendToSplunkServers = state;
+ }
+
+ internal static void SysLogState(bool state)
+ {
+ _logManager.Settings.SendToSyslogServers = state;
+ }
+
+ internal static void Trace(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
+ {
+ Write(message, ELogType.TRACE, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
}
internal static void Traffic(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
@@ -197,9 +164,39 @@ namespace EonaCat.Network
Write(message, ELogType.TRAFFIC, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
}
- internal static void Trace(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
+ internal static void Warning(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
{
- Write(message, ELogType.TRACE, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
+ Write(message, ELogType.WARNING, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
+ }
+
+ private static void _logManager_OnException(object? sender, ErrorMessage e)
+ {
+ Console.WriteLine(e.Message);
+ if (e.Exception != null)
+ {
+ Console.WriteLine(e.Exception);
+ }
+ }
+ private static void Write(string message, ELogType logType, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null)
+ {
+ if (!HasBeenSetup)
+ {
+ Setup();
+ }
+
+ if (DisableConsole)
+ {
+ writeToConsole = false;
+ }
+
+ if (grayLogSettings != null)
+ {
+ _logManager.Write(message, logType, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings.Facility, grayLogSettings.Source, grayLogSettings.Version);
+ }
+ else
+ {
+ _logManager.Write(message, logType, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers);
+ }
}
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/SSLConfig/SSLConfigClient.cs b/EonaCat.Network/System/Sockets/Web/Core/SSLConfig/SSLConfigClient.cs
index 93fb22f..240c5e6 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/SSLConfig/SSLConfigClient.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/SSLConfig/SSLConfigClient.cs
@@ -10,19 +10,19 @@ namespace EonaCat.Network
{
public class SSLConfigClient
{
- private LocalCertificateSelectionCallback _clientCertSelectionCallback;
private X509CertificateCollection _clientCertificates;
+ private LocalCertificateSelectionCallback _clientCertSelectionCallback;
private RemoteCertificateValidationCallback _serverCertValidationCallback;
public SSLConfigClient()
{
- SslProtocols = SslProtocols.Tls12;
+ SslProtocols = SslProtocols.None;
}
public SSLConfigClient(string targetHost)
{
TargetHost = targetHost;
- SslProtocols = SslProtocols.Tls12;
+ SslProtocols = SslProtocols.None;
}
public SSLConfigClient(SSLConfigClient sslConfig)
@@ -40,8 +40,6 @@ namespace EonaCat.Network
TargetHost = sslConfig.TargetHost;
}
- public bool CheckForCertificateRevocation { get; set; }
-
public X509CertificateCollection Certificates
{
get
@@ -56,6 +54,7 @@ namespace EonaCat.Network
}
}
+ public bool CheckForCertificateRevocation { get; set; }
public LocalCertificateSelectionCallback ClientCertificateSelectionCallback
{
get
@@ -71,8 +70,6 @@ namespace EonaCat.Network
}
}
- public SslProtocols SslProtocols { get; set; }
-
public RemoteCertificateValidationCallback ServerCertificateValidationCallback
{
get
@@ -88,6 +85,7 @@ namespace EonaCat.Network
}
}
+ public SslProtocols SslProtocols { get; set; }
public string TargetHost { get; set; }
private static X509Certificate SelectClientCertificate(
diff --git a/EonaCat.Network/System/Sockets/Web/Core/SSLConfig/SSLConfigServer.cs b/EonaCat.Network/System/Sockets/Web/Core/SSLConfig/SSLConfigServer.cs
index e57621f..d4aa157 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/SSLConfig/SSLConfigServer.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/SSLConfig/SSLConfigServer.cs
@@ -14,13 +14,13 @@ namespace EonaCat.Network
public SSLConfigServer()
{
- SslProtocols = SslProtocols.Tls12;
+ SslProtocols = SslProtocols.None;
}
public SSLConfigServer(X509Certificate2 certificate)
{
Certificate = certificate;
- SslProtocols = SslProtocols.Tls12;
+ SslProtocols = SslProtocols.None;
}
public SSLConfigServer(SSLConfigServer sslConfig)
@@ -37,10 +37,9 @@ namespace EonaCat.Network
Certificate = sslConfig.Certificate;
}
+ public X509Certificate2 Certificate { get; set; }
public bool CheckForCertificateRevocation { get; set; }
- public bool IsClientCertificateRequired { get; set; }
-
public RemoteCertificateValidationCallback ClientCertificateValidationCallback
{
get
@@ -56,10 +55,8 @@ namespace EonaCat.Network
}
}
+ public bool IsClientCertificateRequired { get; set; }
public SslProtocols SslProtocols { get; set; }
-
- public X509Certificate2 Certificate { get; set; }
-
private static bool ValidateClientCertificate(
object sender,
X509Certificate certificate,
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Stream/RequestStream.cs b/EonaCat.Network/System/Sockets/Web/Core/Stream/RequestStream.cs
index a89f31f..081176d 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Stream/RequestStream.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Stream/RequestStream.cs
@@ -8,13 +8,12 @@ namespace EonaCat.Network
{
internal class RequestStream : Stream
{
- private long _bodyLeft;
private readonly byte[] _buffer;
+ private readonly Stream _stream;
+ private long _bodyLeft;
private int _count;
private bool _disposed;
private int _offset;
- private readonly Stream _stream;
-
internal RequestStream(Stream stream, byte[] buffer, int offset, int count)
: this(stream, buffer, offset, count, -1)
{
@@ -51,66 +50,8 @@ namespace EonaCat.Network
}
}
- // 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)
- {
- if (buffer == null)
- {
- throw new ArgumentNullException(nameof(buffer));
- }
-
- if (offset < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(offset), "A negative value.");
- }
-
- if (count < 0)
- {
- throw new ArgumentOutOfRangeException(nameof(count), "A negative value.");
- }
-
- var bufferLength = buffer.Length;
- if (offset + count > bufferLength)
- {
- throw new ArgumentException(
- "The sum of 'offset' and 'count' is greater than 'buffer' length.");
- }
-
- if (_bodyLeft == 0)
- {
- return -1;
- }
-
- if (_count == 0 || count == 0)
- {
- return 0;
- }
-
- if (count > _count)
- {
- count = _count;
- }
-
- if (_bodyLeft > 0 && count > _bodyLeft)
- {
- count = (int)_bodyLeft;
- }
-
- Buffer.BlockCopy(_buffer, _offset, buffer, offset, count);
- _offset += count;
- _count -= count;
- if (_bodyLeft > 0)
- {
- _bodyLeft -= count;
- }
-
- return count;
- }
-
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)
{
@@ -139,7 +80,7 @@ namespace EonaCat.Network
}
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();
}
@@ -233,5 +174,63 @@ namespace EonaCat.Network
{
throw new NotSupportedException();
}
+
+ // 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)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+
+ if (offset < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset), "A negative value.");
+ }
+
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count), "A negative value.");
+ }
+
+ var bufferLength = buffer.Length;
+ if (offset + count > bufferLength)
+ {
+ throw new ArgumentException(
+ "The sum of 'offset' and 'count' is greater than 'buffer' length.");
+ }
+
+ if (_bodyLeft == 0)
+ {
+ return -1;
+ }
+
+ if (_count == 0 || count == 0)
+ {
+ return 0;
+ }
+
+ if (count > _count)
+ {
+ count = _count;
+ }
+
+ if (_bodyLeft > 0 && count > _bodyLeft)
+ {
+ count = (int)_bodyLeft;
+ }
+
+ Buffer.BlockCopy(_buffer, _offset, buffer, offset, count);
+ _offset += count;
+ _count -= count;
+ if (_bodyLeft > 0)
+ {
+ _bodyLeft -= count;
+ }
+
+ return count;
+ }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Core/Stream/ResponseStream.cs b/EonaCat.Network/System/Sockets/Web/Core/Stream/ResponseStream.cs
index c04f56a..83018e9 100644
--- a/EonaCat.Network/System/Sockets/Web/Core/Stream/ResponseStream.cs
+++ b/EonaCat.Network/System/Sockets/Web/Core/Stream/ResponseStream.cs
@@ -9,16 +9,15 @@ namespace EonaCat.Network
{
internal class ResponseStream : Stream
{
- private MemoryStream _body;
private static readonly byte[] _crlf = new byte[] { 13, 10 };
+ private readonly Action _write;
+ private readonly Action _writeChunked;
+ private MemoryStream _body;
private bool _disposed;
private HttpListenerResponse _response;
private bool _sendChunked;
private Stream _stream;
- private readonly Action _write;
private Action _writeBody;
- private readonly Action _writeChunked;
-
internal ResponseStream(
Stream stream, HttpListenerResponse response, bool ignoreWriteExceptions)
{
@@ -60,6 +59,121 @@ namespace EonaCat.Network
}
}
+ public override IAsyncResult BeginRead(
+ byte[] buffer, int offset, int count, AsyncCallback callback, object state)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override IAsyncResult BeginWrite(
+ byte[] buffer, int offset, int count, AsyncCallback callback, object state)
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(GetType().ToString());
+ }
+
+ return _body.BeginWrite(buffer, offset, count, callback, state);
+ }
+
+ public override void Close()
+ {
+ Close(false);
+ }
+
+ public override int EndRead(IAsyncResult asyncResult)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void EndWrite(IAsyncResult asyncResult)
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(GetType().ToString());
+ }
+
+ _body.EndWrite(asyncResult);
+ }
+
+ public override void Flush()
+ {
+ if (!_disposed && (_sendChunked || _response.SendInChunks))
+ {
+ flush(false);
+ }
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(GetType().ToString());
+ }
+
+ _body.Write(buffer, offset, count);
+ }
+
+ internal void Close(bool force)
+ {
+ if (_disposed)
+ {
+ return;
+ }
+
+ _disposed = true;
+ if (!force && flush(true))
+ {
+ _response.Close();
+ }
+ else
+ {
+ if (_sendChunked)
+ {
+ var last = getChunkSizeBytes(0, true);
+ _write(last, 0, last.Length);
+ }
+
+ _body.Dispose();
+ _body = null;
+
+ _response.Abort();
+ }
+
+ _response = null;
+ _stream = null;
+ }
+
+ internal void InternalWrite(byte[] buffer, int offset, int count)
+ {
+ _write(buffer, offset, count);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ Close(!disposing);
+ }
+
+ private static byte[] getChunkSizeBytes(int size, bool final)
+ {
+ return Encoding.ASCII.GetBytes(string.Format("{0:x}\r\n{1}", size, final ? "\r\n" : ""));
+ }
+
private bool flush(bool closing)
{
if (!_response.HeadersSent)
@@ -137,12 +251,6 @@ namespace EonaCat.Network
return true;
}
-
- private static byte[] getChunkSizeBytes(int size, bool final)
- {
- return Encoding.ASCII.GetBytes(string.Format("{0:x}\r\n{1}", size, final ? "\r\n" : ""));
- }
-
private void writeChunked(byte[] buffer, int offset, int count)
{
var size = getChunkSizeBytes(count, false);
@@ -172,115 +280,5 @@ namespace EonaCat.Network
{
}
}
-
- internal void Close(bool force)
- {
- if (_disposed)
- {
- return;
- }
-
- _disposed = true;
- if (!force && flush(true))
- {
- _response.Close();
- }
- else
- {
- if (_sendChunked)
- {
- var last = getChunkSizeBytes(0, true);
- _write(last, 0, last.Length);
- }
-
- _body.Dispose();
- _body = null;
-
- _response.Abort();
- }
-
- _response = null;
- _stream = null;
- }
-
- internal void InternalWrite(byte[] buffer, int offset, int count)
- {
- _write(buffer, offset, count);
- }
-
- public override IAsyncResult BeginRead(
- byte[] buffer, int offset, int count, AsyncCallback callback, object state)
- {
- throw new NotSupportedException();
- }
-
- public override IAsyncResult BeginWrite(
- byte[] buffer, int offset, int count, AsyncCallback callback, object state)
- {
- if (_disposed)
- {
- throw new ObjectDisposedException(GetType().ToString());
- }
-
- return _body.BeginWrite(buffer, offset, count, callback, state);
- }
-
- public override void Close()
- {
- Close(false);
- }
-
- protected override void Dispose(bool disposing)
- {
- Close(!disposing);
- }
-
- public override int EndRead(IAsyncResult asyncResult)
- {
- throw new NotSupportedException();
- }
-
- public override void EndWrite(IAsyncResult asyncResult)
- {
- if (_disposed)
- {
- throw new ObjectDisposedException(GetType().ToString());
- }
-
- _body.EndWrite(asyncResult);
- }
-
- public override void Flush()
- {
- if (!_disposed && (_sendChunked || _response.SendInChunks))
- {
- flush(false);
- }
- }
-
- public override int Read(byte[] buffer, int offset, int count)
- {
- throw new NotSupportedException();
- }
-
- public override long Seek(long offset, SeekOrigin origin)
- {
- throw new NotSupportedException();
- }
-
- public override void SetLength(long value)
- {
- throw new NotSupportedException();
- }
-
- public override void Write(byte[] buffer, int offset, int count)
- {
- if (_disposed)
- {
- throw new ObjectDisposedException(GetType().ToString());
- }
-
- _body.Write(buffer, offset, count);
- }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/EventArgs/CloseEventArgs.cs b/EonaCat.Network/System/Sockets/Web/EventArgs/CloseEventArgs.cs
index 907fc06..d18797b 100644
--- a/EonaCat.Network/System/Sockets/Web/EventArgs/CloseEventArgs.cs
+++ b/EonaCat.Network/System/Sockets/Web/EventArgs/CloseEventArgs.cs
@@ -37,12 +37,9 @@ namespace EonaCat.Network
{
}
- internal PayloadData PayloadData { get; }
-
public ushort Code => PayloadData.Code;
-
public string Reason => PayloadData.Reason ?? string.Empty;
-
public bool WasClean { get; internal set; }
+ internal PayloadData PayloadData { get; }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/EventArgs/MessageEventArgs.cs b/EonaCat.Network/System/Sockets/Web/EventArgs/MessageEventArgs.cs
index 56b31b3..34114a8 100644
--- a/EonaCat.Network/System/Sockets/Web/EventArgs/MessageEventArgs.cs
+++ b/EonaCat.Network/System/Sockets/Web/EventArgs/MessageEventArgs.cs
@@ -7,10 +7,9 @@ namespace EonaCat.Network
{
public class MessageEventArgs : EventArgs
{
+ private readonly byte[] _rawData;
private string _data;
private bool _dataSet;
- private readonly byte[] _rawData;
-
internal MessageEventArgs(WSFrame frame)
{
Opcode = frame.Opcode;
@@ -28,8 +27,6 @@ namespace EonaCat.Network
_rawData = rawData;
}
- internal OperationCode Opcode { get; }
-
public string Data
{
get
@@ -40,11 +37,8 @@ namespace EonaCat.Network
}
public bool IsBinary => Opcode == OperationCode.Binary;
-
public bool IsPing => Opcode == OperationCode.Ping;
-
public bool IsText => Opcode == OperationCode.Text;
-
public byte[] RawData
{
get
@@ -54,6 +48,7 @@ namespace EonaCat.Network
}
}
+ internal OperationCode Opcode { get; }
private void setData()
{
if (_dataSet)
diff --git a/EonaCat.Network/System/Sockets/Web/Extensions.cs b/EonaCat.Network/System/Sockets/Web/Extensions.cs
index 2961e3c..06faeab 100644
--- a/EonaCat.Network/System/Sockets/Web/Extensions.cs
+++ b/EonaCat.Network/System/Sockets/Web/Extensions.cs
@@ -13,101 +13,601 @@ namespace EonaCat.Network
{
public static class Ext
{
- private static readonly byte[] _last = new byte[] { 0x00 };
- private static readonly int _retry = 5;
private const string _tspecials = "()<>@,;:\\\"/[]?={} \t";
private const int BUFFER_SIZE = 1024;
-
- private static byte[] compress(this byte[] data)
+ private static readonly byte[] _last = new byte[] { 0x00 };
+ private static readonly int _retry = 5;
+ public static bool Contains(this string value, params char[] chars)
{
- if (data.LongLength == 0)
+ return chars == null || chars.Length == 0
+|| value != null && value.Length != 0
+&& value.IndexOfAny(chars) > -1;
+ }
+
+ public static bool Contains(this NameValueCollection collection, string name)
+ {
+ return collection != null && collection.Count > 0 && collection[name] != null;
+ }
+
+ public static bool Contains(this NameValueCollection collection, string name, string value)
+ {
+ if (collection == null || collection.Count == 0)
{
- return data;
+ return false;
}
- using (var input = new MemoryStream(data))
+ var vals = collection[name];
+ if (vals == null)
{
- return input.compressToArray();
+ return false;
+ }
+
+ foreach (var val in vals.Split(','))
+ {
+ if (val.Trim().Equals(value, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static void Emit(this EventHandler eventHandler, object sender, EventArgs e)
+ {
+ if (eventHandler != null)
+ {
+ eventHandler(sender, e);
}
}
- private static MemoryStream compress(this Stream stream)
+ public static void Emit(
+ this EventHandler eventHandler, object sender, TEventArgs e)
+ where TEventArgs : EventArgs
{
- var output = new MemoryStream();
- if (stream.Length == 0)
+ if (eventHandler != null)
{
- return output;
- }
-
- stream.Position = 0;
- using (var ds = new DeflateStream(output, CompressionMode.Compress, true))
- {
- stream.CopyTo(ds, BUFFER_SIZE);
- ds.Close(); // BFINAL set to 1.
- output.Write(_last, 0, 1);
- output.Position = 0;
-
- return output;
+ eventHandler(sender, e);
}
}
- private static byte[] compressToArray(this Stream stream)
+ public static CookieCollection GetCookies(this NameValueCollection headers, bool response)
{
- using (var output = stream.compress())
+ var name = response ? "Set-Cookie" : "Cookie";
+ return headers != null && headers.Contains(name)
+ ? CookieCollection.Parse(headers[name], response)
+ : new CookieCollection();
+ }
+
+ public static string GetDescription(this HttpStatusCode code)
+ {
+ return ((int)code).GetStatusDescription();
+ }
+
+ public static string GetStatusDescription(this int code)
+ {
+ switch (code)
{
- output.Close();
- return output.ToArray();
+ case 100: return "Continue";
+ case 101: return "Switching Protocols";
+ case 102: return "Processing";
+ case 200: return "OK";
+ case 201: return "Created";
+ case 202: return "Accepted";
+ case 203: return "Non-Authoritative Information";
+ case 204: return "No Content";
+ case 205: return "Reset Content";
+ case 206: return "Partial Content";
+ case 207: return "Multi-Status";
+ case 300: return "Multiple Choices";
+ case 301: return "Moved Permanently";
+ case 302: return "Found";
+ case 303: return "See Other";
+ case 304: return "Not Modified";
+ case 305: return "Use Proxy";
+ case 307: return "Temporary Redirect";
+ case 400: return "Bad Request";
+ case 401: return "Unauthorized";
+ case 402: return "Payment Required";
+ case 403: return "Forbidden";
+ case 404: return "Not Found";
+ case 405: return "Method Not Allowed";
+ case 406: return "Not Acceptable";
+ case 407: return "Proxy Authentication Required";
+ case 408: return "Request Timeout";
+ case 409: return "Conflict";
+ case 410: return "Gone";
+ case 411: return "Length Required";
+ case 412: return "Precondition Failed";
+ case 413: return "Request Entity Too Large";
+ case 414: return "Request-Uri Too Long";
+ case 415: return "Unsupported Media Type";
+ case 416: return "Requested Range Not Satisfiable";
+ case 417: return "Expectation Failed";
+ case 422: return "Unprocessable Entity";
+ case 423: return "Locked";
+ case 424: return "Failed Dependency";
+ case 500: return "Internal Server Error";
+ case 501: return "Not Implemented";
+ case 502: return "Bad Gateway";
+ case 503: return "Service Unavailable";
+ case 504: return "Gateway Timeout";
+ case 505: return "Http Version Not Supported";
+ case 507: return "Insufficient Storage";
+ }
+
+ return string.Empty;
+ }
+
+ public static bool IsCloseStatusCode(this ushort value)
+ {
+ return value > 999 && value < 5000;
+ }
+
+ public static bool IsEnclosedIn(this string value, char c)
+ {
+ return value != null
+ && value.Length > 1
+ && value[0] == c
+ && value[value.Length - 1] == c;
+ }
+
+ public static bool IsHostOrder(this ByteOrder order)
+ {
+ // true: !(true ^ true) or !(false ^ false)
+ // false: !(true ^ false) or !(false ^ true)
+ return !(BitConverter.IsLittleEndian ^ (order == ByteOrder.Little));
+ }
+
+ ///
+ /// An extension method to determine if an IP address is internal
+ /// Class A Private IP Range: 10.0.0.0 ? 10.255.255.255
+ /// Class B Private IP Range: 172.16.0.0 ? 172.31.255.255
+ /// Class C Private IP Range: 192.168.0.0 ? 192.168.255.25
+ ///
+ /// The IP address that will be tested
+ /// Returns true if the IP is internal, false if it is external
+ public static bool IsInternal(this System.Net.IPAddress address)
+ {
+ byte[] bytes = address.GetAddressBytes();
+ switch (bytes[0])
+ {
+ case 10:
+ return true;
+ case 172:
+ return bytes[1] < 32 && bytes[1] >= 16;
+ case 192:
+ return bytes[1] == 168;
+ default:
+ return false;
}
}
- private static byte[] decompress(this byte[] data)
+ public static bool IsLocal(this System.Net.IPAddress address)
{
- if (data.LongLength == 0)
+ if (address == null)
{
- return data;
+ return false;
}
- using (var input = new MemoryStream(data))
+ if (address.Equals(System.Net.IPAddress.Any))
{
- return input.decompressToArray();
+ return true;
+ }
+
+ if (address.Equals(System.Net.IPAddress.Loopback))
+ {
+ return true;
+ }
+
+ if (Socket.OSSupportsIPv6)
+ {
+ if (address.Equals(System.Net.IPAddress.IPv6Any))
+ {
+ return true;
+ }
+
+ if (address.Equals(System.Net.IPAddress.IPv6Loopback))
+ {
+ return true;
+ }
+ }
+
+ if (address.IsInternal())
+ return true;
+
+ var host = System.Net.Dns.GetHostName();
+ var addrs = System.Net.Dns.GetHostAddresses(host);
+ foreach (var addr in addrs)
+ {
+ if (address.Equals(addr))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static bool IsNullOrEmpty(this string value)
+ {
+ return value == null || value.Length == 0;
+ }
+
+ 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;
+ }
+
+ public static bool IsUpgradeTo(this HttpListenerRequest request, string protocol)
+ {
+ if (request == null)
+ {
+ throw new ArgumentNullException(nameof(request));
+ }
+
+ if (protocol == null)
+ {
+ throw new ArgumentNullException(nameof(protocol));
+ }
+
+ if (protocol.Length == 0)
+ {
+ throw new ArgumentException("An empty string.", nameof(protocol));
+ }
+
+ return request.Headers.Contains("Upgrade", protocol) &&
+ request.Headers.Contains("Connection", "Upgrade");
+ }
+
+ public static bool MaybeUri(this string value)
+ {
+ if (value == null || 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();
+ }
+
+ public static T[] SubArray(this T[] array, int startIndex, int length)
+ {
+ int len;
+ if (array == null || (len = array.Length) == 0)
+ {
+ return new T[0];
+ }
+
+ if (startIndex < 0 || length <= 0 || startIndex + length > len)
+ {
+ return new T[0];
+ }
+
+ if (startIndex == 0 && length == len)
+ {
+ return array;
+ }
+
+ var subArray = new T[length];
+ Array.Copy(array, startIndex, subArray, 0, length);
+
+ return subArray;
+ }
+
+ public static T[] SubArray(this T[] array, long startIndex, long length)
+ {
+ long len;
+ if (array == null || (len = array.LongLength) == 0)
+ {
+ return new T[0];
+ }
+
+ if (startIndex < 0 || length <= 0 || startIndex + length > len)
+ {
+ return new T[0];
+ }
+
+ if (startIndex == 0 && length == len)
+ {
+ return array;
+ }
+
+ var subArray = new T[length];
+ Array.Copy(array, startIndex, subArray, 0, length);
+
+ return subArray;
+ }
+
+ public static void Times(this int n, Action action)
+ {
+ if (n > 0 && action != null)
+ {
+ ((ulong)n).times(action);
}
}
- private static MemoryStream decompress(this Stream stream)
+ public static void Times(this long n, Action action)
{
- var output = new MemoryStream();
- if (stream.Length == 0)
+ if (n > 0 && action != null)
{
- return output;
- }
-
- stream.Position = 0;
- using (var ds = new DeflateStream(stream, CompressionMode.Decompress, true))
- {
- ds.CopyTo(output, BUFFER_SIZE);
- output.Position = 0;
-
- return output;
+ ((ulong)n).times(action);
}
}
- private static byte[] decompressToArray(this Stream stream)
+ public static void Times(this uint n, Action action)
{
- using (var output = stream.decompress())
+ if (n > 0 && action != null)
{
- output.Close();
- return output.ToArray();
+ ((ulong)n).times(action);
}
}
- private static void times(this ulong n, Action action)
+ public static void Times(this ulong n, Action action)
{
- for (ulong i = 0; i < n; i++)
+ if (n > 0 && action != null)
{
- action();
+ n.times(action);
}
}
+ public static void Times(this int n, Action action)
+ {
+ if (n > 0 && action != null)
+ {
+ for (int i = 0; i < n; i++)
+ {
+ action(i);
+ }
+ }
+ }
+
+ public static void Times(this long n, Action action)
+ {
+ if (n > 0 && action != null)
+ {
+ for (long i = 0; i < n; i++)
+ {
+ action(i);
+ }
+ }
+ }
+
+ public static void Times(this uint n, Action action)
+ {
+ if (n > 0 && action != null)
+ {
+ for (uint i = 0; i < n; i++)
+ {
+ action(i);
+ }
+ }
+ }
+
+ public static void Times(this ulong n, Action action)
+ {
+ if (n > 0 && action != null)
+ {
+ for (ulong i = 0; i < n; i++)
+ {
+ action(i);
+ }
+ }
+ }
+
+ public static T To(this byte[] source, ByteOrder sourceOrder)
+ where T : struct
+ {
+ if (source == null)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ if (source.Length == 0)
+ {
+ return default(T);
+ }
+
+ var type = typeof(T);
+ var buff = source.ToHostOrder(sourceOrder);
+
+ return type == typeof(bool)
+ ? (T)(object)BitConverter.ToBoolean(buff, 0)
+ : type == typeof(char)
+ ? (T)(object)BitConverter.ToChar(buff, 0)
+ : type == typeof(double)
+ ? (T)(object)BitConverter.ToDouble(buff, 0)
+ : type == typeof(short)
+ ? (T)(object)BitConverter.ToInt16(buff, 0)
+ : type == typeof(int)
+ ? (T)(object)BitConverter.ToInt32(buff, 0)
+ : type == typeof(long)
+ ? (T)(object)BitConverter.ToInt64(buff, 0)
+ : type == typeof(float)
+ ? (T)(object)BitConverter.ToSingle(buff, 0)
+ : type == typeof(ushort)
+ ? (T)(object)BitConverter.ToUInt16(buff, 0)
+ : type == typeof(uint)
+ ? (T)(object)BitConverter.ToUInt32(buff, 0)
+ : type == typeof(ulong)
+ ? (T)(object)BitConverter.ToUInt64(buff, 0)
+ : default(T);
+ }
+
+ public static byte[] ToByteArray(this T value, ByteOrder order)
+ where T : struct
+ {
+ var type = typeof(T);
+ var bytes = type == typeof(bool)
+ ? BitConverter.GetBytes((bool)(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(short)
+ ? BitConverter.GetBytes((short)(object)value)
+ : type == typeof(int)
+ ? BitConverter.GetBytes((int)(object)value)
+ : type == typeof(long)
+ ? BitConverter.GetBytes((long)(object)value)
+ : type == typeof(float)
+ ? BitConverter.GetBytes((float)(object)value)
+ : type == typeof(ushort)
+ ? BitConverter.GetBytes((ushort)(object)value)
+ : type == typeof(uint)
+ ? BitConverter.GetBytes((uint)(object)value)
+ : type == typeof(ulong)
+ ? BitConverter.GetBytes((ulong)(object)value)
+ : WSClient.EmptyBytes;
+
+ if (bytes.Length > 1 && !order.IsHostOrder())
+ {
+ Array.Reverse(bytes);
+ }
+
+ return bytes;
+ }
+
+ public static byte[] ToHostOrder(this byte[] source, ByteOrder sourceOrder)
+ {
+ if (source == null)
+ {
+ throw new ArgumentNullException(nameof(source));
+ }
+
+ return source.Length > 1 && !sourceOrder.IsHostOrder() ? source.Reverse() : source;
+ }
+
+ public static string ToString(this T[] array, string separator)
+ {
+ if (array == null)
+ {
+ throw new ArgumentNullException(nameof(array));
+ }
+
+ var len = array.Length;
+ if (len == 0)
+ {
+ return string.Empty;
+ }
+
+ separator ??= string.Empty;
+
+ var buff = new StringBuilder(64);
+ (len - 1).Times(i => buff.AppendFormat("{0}{1}", array[i].ToString(), separator));
+
+ buff.Append(array[len - 1].ToString());
+ return buff.ToString();
+ }
+
+ public static Uri ToUri(this string value)
+ {
+ Uri.TryCreate(
+ value, value.MaybeUri() ? UriKind.Absolute : UriKind.Relative, out Uri ret
+ );
+
+ return ret;
+ }
+
+ public static string UrlDecode(this string value)
+ {
+ return value != null && value.Length > 0
+ ? HttpUtility.UrlDecode(value)
+ : value;
+ }
+
+ public static string UrlEncode(this string value)
+ {
+ return value != null && value.Length > 0
+ ? HttpUtility.UrlEncode(value)
+ : value;
+ }
+
+ public static void WriteContent(this HttpListenerResponse response, byte[] content)
+ {
+ if (response == null)
+ {
+ throw new ArgumentNullException(nameof(response));
+ }
+
+ if (content == null)
+ {
+ throw new ArgumentNullException(nameof(content));
+ }
+
+ var len = content.LongLength;
+ if (len == 0)
+ {
+ response.Close();
+ return;
+ }
+
+ response.ContentLength64 = len;
+ var output = response.OutputStream;
+ if (len <= int.MaxValue)
+ {
+ output.Write(content, 0, (int)len);
+ }
+ else
+ {
+ output.WriteBytes(content, BUFFER_SIZE);
+ }
+
+ output.Close();
+ }
+
internal static byte[] Append(this ushort code, string reason)
{
var ret = code.InternalToByteArray(ByteOrder.Big);
@@ -599,7 +1099,7 @@ namespace EonaCat.Network
return;
}
- if (nread == 0 || nread == length)
+ if (nread == length)
{
if (completed != null)
{
@@ -1075,568 +1575,94 @@ namespace EonaCat.Network
});
}
- public static bool Contains(this string value, params char[] chars)
+ private static byte[] compress(this byte[] data)
{
- return chars == null || chars.Length == 0
-|| value != null && value.Length != 0
-&& value.IndexOfAny(chars) > -1;
- }
-
- public static bool Contains(this NameValueCollection collection, string name)
- {
- return collection != null && collection.Count > 0 && collection[name] != null;
- }
-
- public static bool Contains(this NameValueCollection collection, string name, string value)
- {
- if (collection == null || collection.Count == 0)
+ if (data.LongLength == 0)
{
- return false;
+ return data;
}
- var vals = collection[name];
- if (vals == null)
+ using (var input = new MemoryStream(data))
{
- return false;
- }
-
- foreach (var val in vals.Split(','))
- {
- if (val.Trim().Equals(value, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
- }
-
- return false;
- }
-
- public static void Emit(this EventHandler eventHandler, object sender, EventArgs e)
- {
- if (eventHandler != null)
- {
- eventHandler(sender, e);
+ return input.compressToArray();
}
}
- public static void Emit(
- this EventHandler eventHandler, object sender, TEventArgs e)
- where TEventArgs : EventArgs
+ private static MemoryStream compress(this Stream stream)
{
- if (eventHandler != null)
+ var output = new MemoryStream();
+ if (stream.Length == 0)
{
- eventHandler(sender, e);
+ return output;
+ }
+
+ stream.Position = 0;
+ using (var ds = new DeflateStream(output, CompressionMode.Compress, true))
+ {
+ stream.CopyTo(ds, BUFFER_SIZE);
+ ds.Close(); // BFINAL set to 1.
+ output.Write(_last, 0, 1);
+ output.Position = 0;
+
+ return output;
}
}
- public static CookieCollection GetCookies(this NameValueCollection headers, bool response)
+ private static byte[] compressToArray(this Stream stream)
{
- var name = response ? "Set-Cookie" : "Cookie";
- return headers != null && headers.Contains(name)
- ? CookieCollection.Parse(headers[name], response)
- : new CookieCollection();
- }
-
- public static string GetDescription(this HttpStatusCode code)
- {
- return ((int)code).GetStatusDescription();
- }
-
- public static string GetStatusDescription(this int code)
- {
- switch (code)
+ using (var output = stream.compress())
{
- case 100: return "Continue";
- case 101: return "Switching Protocols";
- case 102: return "Processing";
- case 200: return "OK";
- case 201: return "Created";
- case 202: return "Accepted";
- case 203: return "Non-Authoritative Information";
- case 204: return "No Content";
- case 205: return "Reset Content";
- case 206: return "Partial Content";
- case 207: return "Multi-Status";
- case 300: return "Multiple Choices";
- case 301: return "Moved Permanently";
- case 302: return "Found";
- case 303: return "See Other";
- case 304: return "Not Modified";
- case 305: return "Use Proxy";
- case 307: return "Temporary Redirect";
- case 400: return "Bad Request";
- case 401: return "Unauthorized";
- case 402: return "Payment Required";
- case 403: return "Forbidden";
- case 404: return "Not Found";
- case 405: return "Method Not Allowed";
- case 406: return "Not Acceptable";
- case 407: return "Proxy Authentication Required";
- case 408: return "Request Timeout";
- case 409: return "Conflict";
- case 410: return "Gone";
- case 411: return "Length Required";
- case 412: return "Precondition Failed";
- case 413: return "Request Entity Too Large";
- case 414: return "Request-Uri Too Long";
- case 415: return "Unsupported Media Type";
- case 416: return "Requested Range Not Satisfiable";
- case 417: return "Expectation Failed";
- case 422: return "Unprocessable Entity";
- case 423: return "Locked";
- case 424: return "Failed Dependency";
- case 500: return "Internal Server Error";
- case 501: return "Not Implemented";
- case 502: return "Bad Gateway";
- case 503: return "Service Unavailable";
- case 504: return "Gateway Timeout";
- case 505: return "Http Version Not Supported";
- case 507: return "Insufficient Storage";
- }
-
- return string.Empty;
- }
-
- public static bool IsCloseStatusCode(this ushort value)
- {
- return value > 999 && value < 5000;
- }
-
- public static bool IsEnclosedIn(this string value, char c)
- {
- return value != null
- && value.Length > 1
- && value[0] == c
- && value[value.Length - 1] == c;
- }
-
- public static bool IsHostOrder(this ByteOrder order)
- {
- // true: !(true ^ true) or !(false ^ false)
- // false: !(true ^ false) or !(false ^ true)
- return !(BitConverter.IsLittleEndian ^ (order == ByteOrder.Little));
- }
-
- public static bool IsLocal(this System.Net.IPAddress address)
- {
- if (address == null)
- {
- return false;
- }
-
- if (address.Equals(System.Net.IPAddress.Any))
- {
- return true;
- }
-
- if (address.Equals(System.Net.IPAddress.Loopback))
- {
- return true;
- }
-
- if (Socket.OSSupportsIPv6)
- {
- if (address.Equals(System.Net.IPAddress.IPv6Any))
- {
- return true;
- }
-
- if (address.Equals(System.Net.IPAddress.IPv6Loopback))
- {
- return true;
- }
- }
-
- var host = System.Net.Dns.GetHostName();
- var addrs = System.Net.Dns.GetHostAddresses(host);
- foreach (var addr in addrs)
- {
- if (address.Equals(addr))
- {
- return true;
- }
- }
-
- return false;
- }
-
- public static bool IsNullOrEmpty(this string value)
- {
- return value == null || value.Length == 0;
- }
-
- 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;
- }
-
- public static bool IsUpgradeTo(this HttpListenerRequest request, string protocol)
- {
- if (request == null)
- {
- throw new ArgumentNullException(nameof(request));
- }
-
- if (protocol == null)
- {
- throw new ArgumentNullException(nameof(protocol));
- }
-
- if (protocol.Length == 0)
- {
- throw new ArgumentException("An empty string.", nameof(protocol));
- }
-
- return request.Headers.Contains("Upgrade", protocol) &&
- request.Headers.Contains("Connection", "Upgrade");
- }
-
- public static bool MaybeUri(this string value)
- {
- if (value == null || 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();
- }
-
- public static T[] SubArray(this T[] array, int startIndex, int length)
- {
- int len;
- if (array == null || (len = array.Length) == 0)
- {
- return new T[0];
- }
-
- if (startIndex < 0 || length <= 0 || startIndex + length > len)
- {
- return new T[0];
- }
-
- if (startIndex == 0 && length == len)
- {
- return array;
- }
-
- var subArray = new T[length];
- Array.Copy(array, startIndex, subArray, 0, length);
-
- return subArray;
- }
-
- public static T[] SubArray(this T[] array, long startIndex, long length)
- {
- long len;
- if (array == null || (len = array.LongLength) == 0)
- {
- return new T[0];
- }
-
- if (startIndex < 0 || length <= 0 || startIndex + length > len)
- {
- return new T[0];
- }
-
- if (startIndex == 0 && length == len)
- {
- return array;
- }
-
- var subArray = new T[length];
- Array.Copy(array, startIndex, subArray, 0, length);
-
- return subArray;
- }
-
- public static void Times(this int n, Action action)
- {
- if (n > 0 && action != null)
- {
- ((ulong)n).times(action);
+ output.Close();
+ return output.ToArray();
}
}
- public static void Times(this long n, Action action)
+ private static byte[] decompress(this byte[] data)
{
- if (n > 0 && action != null)
+ if (data.LongLength == 0)
{
- ((ulong)n).times(action);
+ return data;
+ }
+
+ using (var input = new MemoryStream(data))
+ {
+ return input.decompressToArray();
}
}
- public static void Times(this uint n, Action action)
+ private static MemoryStream decompress(this Stream stream)
{
- if (n > 0 && action != null)
+ var output = new MemoryStream();
+ if (stream.Length == 0)
{
- ((ulong)n).times(action);
+ return output;
+ }
+
+ stream.Position = 0;
+ using (var ds = new DeflateStream(stream, CompressionMode.Decompress, true))
+ {
+ ds.CopyTo(output, BUFFER_SIZE);
+ output.Position = 0;
+
+ return output;
}
}
- public static void Times(this ulong n, Action action)
+ private static byte[] decompressToArray(this Stream stream)
{
- if (n > 0 && action != null)
+ using (var output = stream.decompress())
{
- n.times(action);
+ output.Close();
+ return output.ToArray();
}
}
- public static void Times(this int n, Action action)
+ private static void times(this ulong n, Action action)
{
- if (n > 0 && action != null)
+ for (ulong i = 0; i < n; i++)
{
- for (int i = 0; i < n; i++)
- {
- action(i);
- }
+ action();
}
}
-
- public static void Times(this long n, Action action)
- {
- if (n > 0 && action != null)
- {
- for (long i = 0; i < n; i++)
- {
- action(i);
- }
- }
- }
-
- public static void Times(this uint n, Action action)
- {
- if (n > 0 && action != null)
- {
- for (uint i = 0; i < n; i++)
- {
- action(i);
- }
- }
- }
-
- public static void Times(this ulong n, Action action)
- {
- if (n > 0 && action != null)
- {
- for (ulong i = 0; i < n; i++)
- {
- action(i);
- }
- }
- }
-
- public static T To(this byte[] source, ByteOrder sourceOrder)
- where T : struct
- {
- if (source == null)
- {
- throw new ArgumentNullException(nameof(source));
- }
-
- if (source.Length == 0)
- {
- return default(T);
- }
-
- var type = typeof(T);
- var buff = source.ToHostOrder(sourceOrder);
-
- return type == typeof(bool)
- ? (T)(object)BitConverter.ToBoolean(buff, 0)
- : type == typeof(char)
- ? (T)(object)BitConverter.ToChar(buff, 0)
- : type == typeof(double)
- ? (T)(object)BitConverter.ToDouble(buff, 0)
- : type == typeof(short)
- ? (T)(object)BitConverter.ToInt16(buff, 0)
- : type == typeof(int)
- ? (T)(object)BitConverter.ToInt32(buff, 0)
- : type == typeof(long)
- ? (T)(object)BitConverter.ToInt64(buff, 0)
- : type == typeof(float)
- ? (T)(object)BitConverter.ToSingle(buff, 0)
- : type == typeof(ushort)
- ? (T)(object)BitConverter.ToUInt16(buff, 0)
- : type == typeof(uint)
- ? (T)(object)BitConverter.ToUInt32(buff, 0)
- : type == typeof(ulong)
- ? (T)(object)BitConverter.ToUInt64(buff, 0)
- : default(T);
- }
-
- public static byte[] ToByteArray(this T value, ByteOrder order)
- where T : struct
- {
- var type = typeof(T);
- var bytes = type == typeof(bool)
- ? BitConverter.GetBytes((bool)(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(short)
- ? BitConverter.GetBytes((short)(object)value)
- : type == typeof(int)
- ? BitConverter.GetBytes((int)(object)value)
- : type == typeof(long)
- ? BitConverter.GetBytes((long)(object)value)
- : type == typeof(float)
- ? BitConverter.GetBytes((float)(object)value)
- : type == typeof(ushort)
- ? BitConverter.GetBytes((ushort)(object)value)
- : type == typeof(uint)
- ? BitConverter.GetBytes((uint)(object)value)
- : type == typeof(ulong)
- ? BitConverter.GetBytes((ulong)(object)value)
- : WSClient.EmptyBytes;
-
- if (bytes.Length > 1 && !order.IsHostOrder())
- {
- Array.Reverse(bytes);
- }
-
- return bytes;
- }
-
- public static byte[] ToHostOrder(this byte[] source, ByteOrder sourceOrder)
- {
- if (source == null)
- {
- throw new ArgumentNullException(nameof(source));
- }
-
- return source.Length > 1 && !sourceOrder.IsHostOrder() ? source.Reverse() : source;
- }
-
- public static string ToString(this T[] array, string separator)
- {
- if (array == null)
- {
- throw new ArgumentNullException(nameof(array));
- }
-
- var len = array.Length;
- if (len == 0)
- {
- return string.Empty;
- }
-
- separator ??= string.Empty;
-
- var buff = new StringBuilder(64);
- (len - 1).Times(i => buff.AppendFormat("{0}{1}", array[i].ToString(), separator));
-
- buff.Append(array[len - 1].ToString());
- return buff.ToString();
- }
-
- public static Uri ToUri(this string value)
- {
- Uri.TryCreate(
- value, value.MaybeUri() ? UriKind.Absolute : UriKind.Relative, out Uri ret
- );
-
- return ret;
- }
-
- public static string UrlDecode(this string value)
- {
- return value != null && value.Length > 0
- ? HttpUtility.UrlDecode(value)
- : value;
- }
-
- public static string UrlEncode(this string value)
- {
- return value != null && value.Length > 0
- ? HttpUtility.UrlEncode(value)
- : value;
- }
-
- public static void WriteContent(this HttpListenerResponse response, byte[] content)
- {
- if (response == null)
- {
- throw new ArgumentNullException(nameof(response));
- }
-
- if (content == null)
- {
- throw new ArgumentNullException(nameof(content));
- }
-
- var len = content.LongLength;
- if (len == 0)
- {
- response.Close();
- return;
- }
-
- response.ContentLength64 = len;
- var output = response.OutputStream;
- if (len <= int.MaxValue)
- {
- output.Write(content, 0, (int)len);
- }
- else
- {
- output.WriteBytes(content, BUFFER_SIZE);
- }
-
- output.Close();
- }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/OperationCode.cs b/EonaCat.Network/System/Sockets/Web/OperationCode.cs
index d99cc1e..cdcb124 100644
--- a/EonaCat.Network/System/Sockets/Web/OperationCode.cs
+++ b/EonaCat.Network/System/Sockets/Web/OperationCode.cs
@@ -5,7 +5,7 @@ namespace EonaCat.Network
{
internal enum OperationCode : byte
{
- Cont = 0x0,
+ Continue = 0x0,
Text = 0x1,
diff --git a/EonaCat.Network/System/Sockets/Web/PayloadData.cs b/EonaCat.Network/System/Sockets/Web/PayloadData.cs
index a0acd60..dc0386e 100644
--- a/EonaCat.Network/System/Sockets/Web/PayloadData.cs
+++ b/EonaCat.Network/System/Sockets/Web/PayloadData.cs
@@ -37,6 +37,21 @@ namespace EonaCat.Network
_reasonSet = true;
}
+ internal PayloadData(PayloadData original)
+ {
+ _code = original._code;
+ _codeSet = original._codeSet;
+ ExtensionDataLength = original.ExtensionDataLength;
+ _length = original._length;
+ _reason = original._reason;
+ _reasonSet = original._reasonSet;
+
+ _data = new byte[_length];
+ original._data.CopyTo(_data, 0);
+
+
+ }
+
internal PayloadData(byte[] data)
: this(data, data.LongLength)
{
diff --git a/EonaCat.Network/System/Sockets/Web/Server/HttpRequestEventArgs.cs b/EonaCat.Network/System/Sockets/Web/Server/HttpRequestEventArgs.cs
index 1ca206d..8679885 100644
--- a/EonaCat.Network/System/Sockets/Web/Server/HttpRequestEventArgs.cs
+++ b/EonaCat.Network/System/Sockets/Web/Server/HttpRequestEventArgs.cs
@@ -27,36 +27,6 @@ namespace EonaCat.Network
public IPrincipal User => _context.User;
- private string createFilePath(string childPath)
- {
- childPath = childPath.TrimStart('/', '\\');
- return new StringBuilder(_docRootPath, 32)
- .AppendFormat("/{0}", childPath)
- .ToString()
- .Replace('\\', '/');
- }
-
- private static bool tryReadFile(string path, out byte[] contents)
- {
- contents = null;
-
- if (!File.Exists(path))
- {
- return false;
- }
-
- try
- {
- contents = File.ReadAllBytes(path);
- }
- catch
- {
- return false;
- }
-
- return true;
- }
-
public byte[] ReadFile(string path)
{
if (path == null)
@@ -98,5 +68,35 @@ namespace EonaCat.Network
return tryReadFile(createFilePath(path), out contents);
}
+
+ private static bool tryReadFile(string path, out byte[] contents)
+ {
+ contents = null;
+
+ if (!File.Exists(path))
+ {
+ return false;
+ }
+
+ try
+ {
+ contents = File.ReadAllBytes(path);
+ }
+ catch
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ private string createFilePath(string childPath)
+ {
+ childPath = childPath.TrimStart('/', '\\');
+ return new StringBuilder(_docRootPath, 32)
+ .AppendFormat("/{0}", childPath)
+ .ToString()
+ .Replace('\\', '/');
+ }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Server/HttpServer.cs b/EonaCat.Network/System/Sockets/Web/Server/HttpServer.cs
index 42eb020..902b4a7 100644
--- a/EonaCat.Network/System/Sockets/Web/Server/HttpServer.cs
+++ b/EonaCat.Network/System/Sockets/Web/Server/HttpServer.cs
@@ -11,13 +11,13 @@ namespace EonaCat.Network
{
public class HttpServer
{
+ private bool _allowForwardedRequest;
private string _docRootPath;
private string _hostname;
private HttpListener _listener;
private Thread _receiveThread;
private volatile ServerState _state;
private object _sync;
-
public HttpServer()
{
init("*", System.Net.IPAddress.Any, 80, false);
@@ -100,8 +100,49 @@ namespace EonaCat.Network
init(address.ToString(true), address, port, secure);
}
+ public event EventHandler OnConnect;
+
+ public event EventHandler OnDelete;
+
+ public event EventHandler OnGet;
+
+ public event EventHandler OnHead;
+
+ public event EventHandler OnOptions;
+
+ public event EventHandler OnPatch;
+
+ public event EventHandler OnPost;
+
+ public event EventHandler OnPut;
+
+ public event EventHandler OnTrace;
+
public System.Net.IPAddress Address { get; private set; }
+ ///
+ /// 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 { _allowForwardedRequest = value; }
+ }
+
public AuthenticationSchemes AuthenticationSchemes
{
get
@@ -208,6 +249,7 @@ namespace EonaCat.Network
public bool IsListening => _state == ServerState.Start;
+ public bool IsLoggingEnabled { get; private set; }
public bool IsSecure { get; private set; }
public bool KeepClean
@@ -222,11 +264,7 @@ namespace EonaCat.Network
WebSocketServices.AutoCleanSessions = value;
}
}
-
- public bool IsLoggingEnabled { get; private set; }
-
public int Port { get; private set; }
-
public string Realm
{
get
@@ -339,24 +377,174 @@ namespace EonaCat.Network
}
public WSEndpointManager WebSocketServices { get; private set; }
+ public void AddWebSocketService(string path)
+ where TEndpoint : WSEndpoint, new()
+ {
+ WebSocketServices.AddService(path, null);
+ }
- public event EventHandler OnConnect;
+ public void AddWebSocketService(
+ string path, Action initializer
+ )
+ where TEndpoint : WSEndpoint, new()
+ {
+ WebSocketServices.AddService(path, initializer);
+ }
- public event EventHandler OnDelete;
+ public bool RemoveWebSocketService(string path)
+ {
+ return WebSocketServices.RemoveService(path);
+ }
- public event EventHandler OnGet;
+ public void Start()
+ {
+ if (IsSecure)
+ {
+ if (!checkCertificate(out string message))
+ {
+ throw new InvalidOperationException(message);
+ }
+ }
- public event EventHandler OnHead;
+ start();
+ }
- public event EventHandler OnOptions;
+ public void Stop()
+ {
+ stop((ushort)CloseStatusCode.NoStatus, string.Empty);
+ }
- public event EventHandler OnPatch;
+ public void Stop(ushort code, string reason)
+ {
+ if (!code.IsCloseStatusCode())
+ {
+ var message = "Less than 1000 or greater than 4999.";
+ throw new ArgumentOutOfRangeException(nameof(code), message);
+ }
- public event EventHandler OnPost;
+ if (code == (ushort)CloseStatusCode.MandatoryExtension)
+ {
+ var message = $"{(ushort)CloseStatusCode.MandatoryExtension} cannot be used.";
+ throw new ArgumentException(message, nameof(code));
+ }
- public event EventHandler OnPut;
+ if (!reason.IsNullOrEmpty())
+ {
+ if (code == (ushort)CloseStatusCode.NoStatus)
+ {
+ var message = $"{(ushort)CloseStatusCode.NoStatus} cannot be used.";
+ throw new ArgumentException(message, nameof(code));
+ }
- public event EventHandler OnTrace;
+ if (!reason.TryGetUTF8EncodedBytes(out byte[] bytes))
+ {
+ var message = "It could not be UTF-8-encoded.";
+ throw new ArgumentException(message, nameof(reason));
+ }
+
+ if (bytes.Length > 123)
+ {
+ var message = "Its size is greater than 123 bytes.";
+ throw new ArgumentOutOfRangeException(nameof(reason), message);
+ }
+ }
+
+ stop(code, reason);
+ }
+
+ public void Stop(CloseStatusCode code, string reason)
+ {
+ if (code == CloseStatusCode.MandatoryExtension)
+ {
+ var message = "MandatoryExtension cannot be used.";
+ throw new ArgumentException(message, nameof(code));
+ }
+
+ if (!reason.IsNullOrEmpty())
+ {
+ if (code == CloseStatusCode.NoStatus)
+ {
+ var message = "NoStatus cannot be used.";
+ throw new ArgumentException(message, nameof(code));
+ }
+
+ if (!reason.TryGetUTF8EncodedBytes(out byte[] bytes))
+ {
+ var message = "It could not be UTF-8-encoded.";
+ throw new ArgumentException(message, nameof(reason));
+ }
+
+ if (bytes.Length > 123)
+ {
+ var message = "Its size is greater than 123 bytes.";
+ throw new ArgumentOutOfRangeException(nameof(reason), message);
+ }
+ }
+
+ stop((ushort)code, reason);
+ }
+
+ private static HttpListener createListener(
+ string hostname, int port, bool secure
+ )
+ {
+ var lsnr = new HttpListener();
+
+ var schm = secure ? "https" : "http";
+ var pref = string.Format("{0}://{1}:{2}/", schm, hostname, port);
+ lsnr.Prefixes.Add(pref);
+
+ return lsnr;
+ }
+
+ private static bool tryCreateUri(
+ 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"))
+ {
+ 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;
+ }
private void abort()
{
@@ -432,20 +620,6 @@ namespace EonaCat.Network
return true;
}
-
- private static HttpListener createListener(
- string hostname, int port, bool secure
- )
- {
- var lsnr = new HttpListener();
-
- var schm = secure ? "https" : "http";
- var pref = string.Format("{0}://{1}:{2}/", schm, hostname, port);
- lsnr.Prefixes.Add(pref);
-
- return lsnr;
- }
-
private void init(
string hostname, System.Net.IPAddress address, int port, bool secure
)
@@ -703,161 +877,5 @@ namespace EonaCat.Network
_listener.Stop();
_receiveThread.Join(millisecondsTimeout);
}
-
- private static bool tryCreateUri(
- 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"))
- {
- 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;
- }
-
- public void AddWebSocketService(string path)
- where TEndpoint : WSEndpoint, new()
- {
- WebSocketServices.AddService(path, null);
- }
-
- public void AddWebSocketService(
- string path, Action initializer
- )
- where TEndpoint : WSEndpoint, new()
- {
- WebSocketServices.AddService(path, initializer);
- }
-
- public bool RemoveWebSocketService(string path)
- {
- return WebSocketServices.RemoveService(path);
- }
-
- public void Start()
- {
- if (IsSecure)
- {
- if (!checkCertificate(out string message))
- {
- throw new InvalidOperationException(message);
- }
- }
-
- start();
- }
-
- public void Stop()
- {
- stop((ushort)CloseStatusCode.NoStatus, string.Empty);
- }
-
- public void Stop(ushort code, string reason)
- {
- if (!code.IsCloseStatusCode())
- {
- var message = "Less than 1000 or greater than 4999.";
- throw new ArgumentOutOfRangeException(nameof(code), message);
- }
-
- if (code == (ushort)CloseStatusCode.MandatoryExtension)
- {
- var message = $"{(ushort)CloseStatusCode.MandatoryExtension} cannot be used.";
- throw new ArgumentException(message, nameof(code));
- }
-
- if (!reason.IsNullOrEmpty())
- {
- if (code == (ushort)CloseStatusCode.NoStatus)
- {
- var message = $"{(ushort)CloseStatusCode.NoStatus} cannot be used.";
- throw new ArgumentException(message, nameof(code));
- }
-
- if (!reason.TryGetUTF8EncodedBytes(out byte[] bytes))
- {
- var message = "It could not be UTF-8-encoded.";
- throw new ArgumentException(message, nameof(reason));
- }
-
- if (bytes.Length > 123)
- {
- var message = "Its size is greater than 123 bytes.";
- throw new ArgumentOutOfRangeException(nameof(reason), message);
- }
- }
-
- stop(code, reason);
- }
-
- public void Stop(CloseStatusCode code, string reason)
- {
- if (code == CloseStatusCode.MandatoryExtension)
- {
- var message = "MandatoryExtension cannot be used.";
- throw new ArgumentException(message, nameof(code));
- }
-
- if (!reason.IsNullOrEmpty())
- {
- if (code == CloseStatusCode.NoStatus)
- {
- var message = "NoStatus cannot be used.";
- throw new ArgumentException(message, nameof(code));
- }
-
- if (!reason.TryGetUTF8EncodedBytes(out byte[] bytes))
- {
- var message = "It could not be UTF-8-encoded.";
- throw new ArgumentException(message, nameof(reason));
- }
-
- if (bytes.Length > 123)
- {
- var message = "Its size is greater than 123 bytes.";
- throw new ArgumentOutOfRangeException(nameof(reason), message);
- }
- }
-
- stop((ushort)code, reason);
- }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Server/WSEndpoint.cs b/EonaCat.Network/System/Sockets/Web/Server/WSEndpoint.cs
index da7b67c..6f60b6e 100644
--- a/EonaCat.Network/System/Sockets/Web/Server/WSEndpoint.cs
+++ b/EonaCat.Network/System/Sockets/Web/Server/WSEndpoint.cs
@@ -17,12 +17,8 @@ namespace EonaCat.Network
StartTime = DateTime.MaxValue;
}
- protected WSSessionManager Sessions { get; private set; }
-
public WSContext Context { get; private set; }
-
public Func CookiesValidator { get; set; }
-
public bool EmitOnPing
{
get
@@ -43,11 +39,8 @@ namespace EonaCat.Network
}
public string ID { get; private set; }
-
public bool IgnoreExtensions { get; set; }
-
public Func OriginValidator { get; set; }
-
public string Protocol
{
get
@@ -72,53 +65,8 @@ namespace EonaCat.Network
}
public DateTime StartTime { get; private set; }
-
public WSState State => _websocket != null ? _websocket.ReadyState : WSState.Connecting;
-
- private string checkHandshakeRequest(WSContext context)
- {
- return OriginValidator != null && !OriginValidator(context.Origin)
- ? "Includes no Origin header, or it has an invalid value."
- : CookiesValidator != null
- && !CookiesValidator(context.CookieCollection, context.WebSocket.CookieCollection)
- ? "Includes no cookie, or an invalid cookie exists."
- : null;
- }
-
- private void onClose(object sender, CloseEventArgs e)
- {
- if (ID == null)
- {
- return;
- }
-
- Sessions.Remove(ID);
- OnClose(e);
- }
-
- private void onError(object sender, ErrorEventArgs e)
- {
- OnError(e);
- }
-
- private void onMessage(object sender, MessageEventArgs e)
- {
- OnMessage(e);
- }
-
- private void onOpen(object sender, EventArgs e)
- {
- ID = Sessions.Add(this);
- if (ID == null)
- {
- _websocket.Close(CloseStatusCode.Away);
- return;
- }
-
- StartTime = DateTime.Now;
- OnOpen();
- }
-
+ protected WSSessionManager Sessions { get; private set; }
internal void Start(WSContext context, WSSessionManager sessions)
{
if (_websocket != null)
@@ -210,5 +158,49 @@ namespace EonaCat.Network
{
_websocket?.SendAsync(stream, length, completed);
}
+
+ private string checkHandshakeRequest(WSContext context)
+ {
+ return OriginValidator != null && !OriginValidator(context.Origin)
+ ? "Includes no Origin header, or it has an invalid value."
+ : CookiesValidator != null
+ && !CookiesValidator(context.CookieCollection, context.WebSocket.CookieCollection)
+ ? "Includes no cookie, or an invalid cookie exists."
+ : null;
+ }
+
+ private void onClose(object sender, CloseEventArgs e)
+ {
+ if (ID == null)
+ {
+ return;
+ }
+
+ Sessions.Remove(ID);
+ OnClose(e);
+ }
+
+ private void onError(object sender, ErrorEventArgs e)
+ {
+ OnError(e);
+ }
+
+ private void onMessage(object sender, MessageEventArgs e)
+ {
+ OnMessage(e);
+ }
+
+ private void onOpen(object sender, EventArgs e)
+ {
+ ID = Sessions.Add(this);
+ if (ID == null)
+ {
+ _websocket.Close(CloseStatusCode.Away);
+ return;
+ }
+
+ StartTime = DateTime.Now;
+ OnOpen();
+ }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Server/WSEndpointHost.cs b/EonaCat.Network/System/Sockets/Web/Server/WSEndpointHost.cs
index fd1590b..d711343 100644
--- a/EonaCat.Network/System/Sockets/Web/Server/WSEndpointHost.cs
+++ b/EonaCat.Network/System/Sockets/Web/Server/WSEndpointHost.cs
@@ -13,8 +13,7 @@ namespace EonaCat.Network
Sessions = new WSSessionManager();
}
- internal ServerState State => Sessions.State;
-
+ public abstract Type EndpointType { get; }
public bool KeepClean
{
get
@@ -29,11 +28,7 @@ namespace EonaCat.Network
}
public string Path { get; }
-
public WSSessionManager Sessions { get; }
-
- public abstract Type EndpointType { get; }
-
public TimeSpan WaitTime
{
get
@@ -47,6 +42,7 @@ namespace EonaCat.Network
}
}
+ internal ServerState State => Sessions.State;
internal void Start()
{
Sessions.Start();
diff --git a/EonaCat.Network/System/Sockets/Web/Server/WSEndpointManager.cs b/EonaCat.Network/System/Sockets/Web/Server/WSEndpointManager.cs
index 173d44b..663166e 100644
--- a/EonaCat.Network/System/Sockets/Web/Server/WSEndpointManager.cs
+++ b/EonaCat.Network/System/Sockets/Web/Server/WSEndpointManager.cs
@@ -11,10 +11,10 @@ namespace EonaCat.Network
{
public class WSEndpointManager
{
- private volatile bool _clean;
private readonly Dictionary _hosts;
- private volatile ServerState _state;
private readonly object _sync;
+ private volatile bool _clean;
+ private volatile ServerState _state;
private TimeSpan _waitTime;
internal WSEndpointManager()
@@ -26,59 +26,6 @@ namespace EonaCat.Network
_waitTime = TimeSpan.FromSeconds(1);
}
- public int Count
- {
- get
- {
- lock (_sync)
- {
- return _hosts.Count;
- }
- }
- }
-
- public IEnumerable Hosts
- {
- get
- {
- lock (_sync)
- {
- return _hosts.Values.ToList();
- }
- }
- }
-
- public WSEndpointHost this[string path]
- {
- get
- {
- if (path == null)
- {
- throw new ArgumentNullException(nameof(path));
- }
-
- if (path.Length == 0)
- {
- throw new ArgumentException("An empty string.", nameof(path));
- }
-
- if (path[0] != '/')
- {
- throw new ArgumentException("Not an absolute path.", nameof(path));
- }
-
- if (path.IndexOfAny(new[] { '?', '#' }) > -1)
- {
- var message = "It includes either or both query and fragment components.";
- throw new ArgumentException(message, nameof(path));
- }
-
- InternalTryGetServiceHost(path, out WSEndpointHost host);
-
- return host;
- }
- }
-
public bool AutoCleanSessions
{
get
@@ -112,6 +59,28 @@ namespace EonaCat.Network
}
}
+ public int Count
+ {
+ get
+ {
+ lock (_sync)
+ {
+ return _hosts.Count;
+ }
+ }
+ }
+
+ public IEnumerable Hosts
+ {
+ get
+ {
+ lock (_sync)
+ {
+ return _hosts.Values.ToList();
+ }
+ }
+ }
+
public IEnumerable Paths
{
get
@@ -161,169 +130,36 @@ namespace EonaCat.Network
}
}
- private void broadcast(OperationCode opcode, byte[] data, Action completed)
+ public WSEndpointHost this[string path]
{
- var cache = new Dictionary();
-
- try
+ get
{
- foreach (var host in Hosts)
+ if (path == null)
{
- if (_state != ServerState.Start)
- {
- Logger.Error("The server is shutting down.");
- break;
- }
-
- host.Sessions.Broadcast(opcode, data, cache);
+ throw new ArgumentNullException(nameof(path));
}
- if (completed != null)
+ if (path.Length == 0)
{
- completed();
+ throw new ArgumentException("An empty string.", nameof(path));
}
- }
- catch (Exception ex)
- {
- Logger.Error(ex, "Could not broadcast");
- }
- finally
- {
- cache.Clear();
+
+ if (path[0] != '/')
+ {
+ throw new ArgumentException("Not an absolute path.", nameof(path));
+ }
+
+ if (path.IndexOfAny(new[] { '?', '#' }) > -1)
+ {
+ var message = "It includes either or both query and fragment components.";
+ throw new ArgumentException(message, nameof(path));
+ }
+
+ InternalTryGetServiceHost(path, out WSEndpointHost host);
+
+ return host;
}
}
-
- private void broadcast(OperationCode opcode, Stream stream, Action completed)
- {
- var cache = new Dictionary();
-
- try
- {
- foreach (var host in Hosts)
- {
- if (_state != ServerState.Start)
- {
- Logger.Error("The server is shutting down.");
- break;
- }
-
- host.Sessions.Broadcast(opcode, stream, cache);
- }
-
- if (completed != null)
- {
- completed();
- }
- }
- catch (Exception ex)
- {
- Logger.Error(ex, "Could not broadcast");
- }
- finally
- {
- foreach (var cached in cache.Values)
- {
- cached.Dispose();
- }
-
- cache.Clear();
- }
- }
-
- 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;
- }
-
- internal void Add(string path, Func creator)
- where TEndpoint : WSEndpoint
- {
- path = HttpUtility.UrlDecode(path).TrimSlashFromEnd();
-
- lock (_sync)
- {
- if (_hosts.TryGetValue(path, out WSEndpointHost host))
- {
- throw new ArgumentException("Already in use.", nameof(path));
- }
-
- host = new WebSocketEndpointHost(
- path, creator, null
- );
-
- 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 WSEndpointHost host
- )
- {
- path = HttpUtility.UrlDecode(path).TrimSlashFromEnd();
-
- lock (_sync)
- {
- return _hosts.TryGetValue(path, out host);
- }
- }
-
- internal void Start()
- {
- lock (_sync)
- {
- foreach (var host in _hosts.Values)
- {
- host.Start();
- }
-
- _state = ServerState.Start;
- }
- }
-
- internal void Stop(ushort code, string reason)
- {
- lock (_sync)
- {
- _state = ServerState.ShuttingDown;
-
- foreach (var host in _hosts.Values)
- {
- host.Stop(code, reason);
- }
-
- _state = ServerState.Stop;
- }
- }
-
public void AddService(
string path, Action initializer
)
@@ -470,5 +306,168 @@ namespace EonaCat.Network
return InternalTryGetServiceHost(path, out host);
}
+
+ internal void Add(string path, Func creator)
+ where TEndpoint : WSEndpoint
+ {
+ path = HttpUtility.UrlDecode(path).TrimSlashFromEnd();
+
+ lock (_sync)
+ {
+ if (_hosts.TryGetValue(path, out WSEndpointHost host))
+ {
+ throw new ArgumentException("Already in use.", nameof(path));
+ }
+
+ host = new WebSocketEndpointHost(
+ path, creator, null
+ );
+
+ 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 WSEndpointHost host
+ )
+ {
+ path = HttpUtility.UrlDecode(path).TrimSlashFromEnd();
+
+ lock (_sync)
+ {
+ return _hosts.TryGetValue(path, out host);
+ }
+ }
+
+ internal void Start()
+ {
+ lock (_sync)
+ {
+ foreach (var host in _hosts.Values)
+ {
+ host.Start();
+ }
+
+ _state = ServerState.Start;
+ }
+ }
+
+ internal void Stop(ushort code, string reason)
+ {
+ lock (_sync)
+ {
+ _state = ServerState.ShuttingDown;
+
+ foreach (var host in _hosts.Values)
+ {
+ host.Stop(code, reason);
+ }
+
+ _state = ServerState.Stop;
+ }
+ }
+
+ private void broadcast(OperationCode opcode, byte[] data, Action completed)
+ {
+ var cache = new Dictionary();
+
+ try
+ {
+ foreach (var host in Hosts)
+ {
+ if (_state != ServerState.Start)
+ {
+ Logger.Error("The server is shutting down.");
+ break;
+ }
+
+ host.Sessions.Broadcast(opcode, data, cache);
+ }
+
+ if (completed != null)
+ {
+ completed();
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex, "Could not broadcast");
+ }
+ finally
+ {
+ cache.Clear();
+ }
+ }
+
+ private void broadcast(OperationCode opcode, Stream stream, Action completed)
+ {
+ var cache = new Dictionary();
+
+ try
+ {
+ foreach (var host in Hosts)
+ {
+ if (_state != ServerState.Start)
+ {
+ Logger.Error("The server is shutting down.");
+ break;
+ }
+
+ host.Sessions.Broadcast(opcode, stream, cache);
+ }
+
+ if (completed != null)
+ {
+ completed();
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex, "Could not broadcast");
+ }
+ finally
+ {
+ foreach (var cached in cache.Values)
+ {
+ cached.Dispose();
+ }
+
+ cache.Clear();
+ }
+ }
+
+ 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;
+ }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Server/WSServer.cs b/EonaCat.Network/System/Sockets/Web/Server/WSServer.cs
index 9088118..ef74e8e 100644
--- a/EonaCat.Network/System/Sockets/Web/Server/WSServer.cs
+++ b/EonaCat.Network/System/Sockets/Web/Server/WSServer.cs
@@ -10,11 +10,9 @@ namespace EonaCat.Network
{
public class WSServer
{
- public bool IsConsoleLoggingEnabled { get; set; }
- public bool IsLoggingEnabled { get; set; }
+ private static readonly string _defaultRealm;
private bool _allowForwardedRequest;
private AuthenticationSchemes _authSchemes;
- private static readonly string _defaultRealm;
private bool _dnsStyle;
private string _hostname;
private TcpListener _listener;
@@ -27,7 +25,6 @@ namespace EonaCat.Network
private volatile ServerState _state;
private object _sync;
private Func _userCredentialsFinder;
-
static WSServer()
{
_defaultRealm = "SECRET AREA";
@@ -35,8 +32,8 @@ namespace EonaCat.Network
public WSServer()
{
- var addr = System.Net.IPAddress.Any;
- init(addr.ToString(), addr, 80, false);
+ var address = System.Net.IPAddress.Any;
+ init(address.ToString(), address, 80, false);
}
public WSServer(int port)
@@ -118,7 +115,6 @@ namespace EonaCat.Network
}
public System.Net.IPAddress Address { get; private set; }
-
public bool AllowForwardedRequest
{
get
@@ -175,10 +171,6 @@ namespace EonaCat.Network
}
}
- public bool IsListening => _state == ServerState.Start;
-
- public bool IsSecure { get; private set; }
-
///
/// Determines if sessions need to be removed automatically
///
@@ -195,6 +187,39 @@ namespace EonaCat.Network
}
}
+ public WSEndpointManager Endpoints { get; private set; }
+ public Func FindCredentials
+ {
+ get
+ {
+ return _userCredentialsFinder;
+ }
+
+ set
+ {
+ if (!CanSet(out string message))
+ {
+ Logger.Warning(message);
+ return;
+ }
+
+ lock (_sync)
+ {
+ if (!CanSet(out message))
+ {
+ Logger.Warning(message);
+ return;
+ }
+
+ _userCredentialsFinder = value;
+ }
+ }
+ }
+
+ public bool IsConsoleLoggingEnabled { get; set; }
+ public bool IsListening => _state == ServerState.Start;
+ public bool IsLoggingEnabled { get; set; }
+ public bool IsSecure { get; private set; }
public int Port { get; private set; }
public string Realm
@@ -266,35 +291,6 @@ namespace EonaCat.Network
return GetSSLConfig();
}
}
-
- public Func FindCredentials
- {
- get
- {
- return _userCredentialsFinder;
- }
-
- set
- {
- if (!CanSet(out string message))
- {
- Logger.Warning(message);
- return;
- }
-
- lock (_sync)
- {
- if (!CanSet(out message))
- {
- Logger.Warning(message);
- return;
- }
-
- _userCredentialsFinder = value;
- }
- }
- }
-
public TimeSpan WaitTime
{
get
@@ -307,8 +303,146 @@ namespace EonaCat.Network
Endpoints.WaitTime = value;
}
}
+ public void AddEndpoint(string path) where TEndpoint : WSEndpoint, new()
+ {
+ Endpoints.AddService(path, null);
+ }
- public WSEndpointManager Endpoints { get; private set; }
+ public void AddEndpoint(string path, Action initializer) where TEndpoint : WSEndpoint, new()
+ {
+ Endpoints.AddService(path, initializer);
+ }
+
+ public bool RemoveEndpoint(string path)
+ {
+ return Endpoints.RemoveService(path);
+ }
+
+ public void Start()
+ {
+ SSLConfigServer sslConfig = null;
+
+ if (IsSecure)
+ {
+ sslConfig = new SSLConfigServer(GetSSLConfig());
+
+ if (!CheckSslConfig(sslConfig, out string message))
+ {
+ throw new InvalidOperationException(message);
+ }
+ }
+
+ start(sslConfig);
+ }
+
+ public void Stop()
+ {
+ stop((ushort)CloseStatusCode.NoStatus, string.Empty);
+ }
+
+ public void Stop(ushort code, string reason)
+ {
+ if (!code.IsCloseStatusCode())
+ {
+ var message = "Less than 1000 or greater than 4999.";
+ throw new ArgumentOutOfRangeException(nameof(code), message);
+ }
+
+ if (code == (ushort)CloseStatusCode.MandatoryExtension)
+ {
+ var message = $"{(ushort)CloseStatusCode.MandatoryExtension} cannot be used.";
+ throw new ArgumentException(message, nameof(code));
+ }
+
+ if (!reason.IsNullOrEmpty())
+ {
+ if (code == (ushort)CloseStatusCode.NoStatus)
+ {
+ var message = $"{(ushort)CloseStatusCode.NoStatus} cannot be used.";
+ throw new ArgumentException(message, nameof(code));
+ }
+
+ if (!reason.TryGetUTF8EncodedBytes(out byte[] bytes))
+ {
+ var message = "It could not be UTF-8-encoded.";
+ throw new ArgumentException(message, nameof(reason));
+ }
+
+ if (bytes.Length > 123)
+ {
+ var message = "Its size is greater than 123 bytes.";
+ throw new ArgumentOutOfRangeException(nameof(reason), message);
+ }
+ }
+
+ stop(code, reason);
+ }
+
+ public void Stop(CloseStatusCode code, string reason)
+ {
+ if (code == CloseStatusCode.MandatoryExtension)
+ {
+ var message = "MandatoryExtension cannot be used.";
+ throw new ArgumentException(message, nameof(code));
+ }
+
+ if (!reason.IsNullOrEmpty())
+ {
+ if (code == CloseStatusCode.NoStatus)
+ {
+ var message = "NoStatus cannot be used.";
+ throw new ArgumentException(message, nameof(code));
+ }
+
+ if (!reason.TryGetUTF8EncodedBytes(out byte[] bytes))
+ {
+ var message = "It could not be UTF-8-encoded.";
+ throw new ArgumentException(message, nameof(reason));
+ }
+
+ if (bytes.Length > 123)
+ {
+ var message = "Its size is greater than 123 bytes.";
+ throw new ArgumentOutOfRangeException(nameof(reason), message);
+ }
+ }
+
+ stop((ushort)code, reason);
+ }
+
+ private static bool CheckSslConfig(SSLConfigServer sslConfig, out string message
+ )
+ {
+ message = null;
+
+ if (sslConfig.Certificate == null)
+ {
+ message = "There is no server certificate for secure connections.";
+ return false;
+ }
+
+ return true;
+ }
+
+ private static bool tryCreateUri(
+ string uriString, out Uri result, out string message
+ )
+ {
+ if (!uriString.TryCreateWebSocketUri(out result, out message))
+ {
+ return false;
+ }
+
+ if (result.PathAndQuery != "/")
+ {
+ result = null;
+ message = "It includes either or both path and query components.";
+
+ return false;
+ }
+
+ return true;
+ }
private void abort()
{
@@ -365,21 +499,6 @@ namespace EonaCat.Network
|| Uri.CheckHostName(name) != UriHostNameType.Dns
|| name == _hostname;
}
-
- private static bool CheckSslConfig(SSLConfigServer sslConfig, out string message
- )
- {
- message = null;
-
- if (sslConfig.Certificate == null)
- {
- message = "There is no server certificate for secure connections.";
- return false;
- }
-
- return true;
- }
-
private string GetRealm()
{
var realm = _realm;
@@ -662,132 +781,5 @@ namespace EonaCat.Network
_receiveThread.Join(millisecondsTimeout);
}
-
- private static bool tryCreateUri(
- string uriString, out Uri result, out string message
- )
- {
- if (!uriString.TryCreateWebSocketUri(out result, out message))
- {
- return false;
- }
-
- if (result.PathAndQuery != "/")
- {
- result = null;
- message = "It includes either or both path and query components.";
-
- return false;
- }
-
- return true;
- }
-
- public void AddEndpoint(string path) where TEndpoint : WSEndpoint, new()
- {
- Endpoints.AddService(path, null);
- }
-
- public void AddEndpoint(string path, Action initializer) where TEndpoint : WSEndpoint, new()
- {
- Endpoints.AddService(path, initializer);
- }
-
- public bool RemoveEndpoint(string path)
- {
- return Endpoints.RemoveService(path);
- }
-
- public void Start()
- {
- SSLConfigServer sslConfig = null;
-
- if (IsSecure)
- {
- sslConfig = new SSLConfigServer(GetSSLConfig());
-
- if (!CheckSslConfig(sslConfig, out string message))
- {
- throw new InvalidOperationException(message);
- }
- }
-
- start(sslConfig);
- }
-
- public void Stop()
- {
- stop((ushort)CloseStatusCode.NoStatus, string.Empty);
- }
-
- public void Stop(ushort code, string reason)
- {
- if (!code.IsCloseStatusCode())
- {
- var message = "Less than 1000 or greater than 4999.";
- throw new ArgumentOutOfRangeException(nameof(code), message);
- }
-
- if (code == (ushort)CloseStatusCode.MandatoryExtension)
- {
- var message = $"{(ushort)CloseStatusCode.MandatoryExtension} cannot be used.";
- throw new ArgumentException(message, nameof(code));
- }
-
- if (!reason.IsNullOrEmpty())
- {
- if (code == (ushort)CloseStatusCode.NoStatus)
- {
- var message = $"{(ushort)CloseStatusCode.NoStatus} cannot be used.";
- throw new ArgumentException(message, nameof(code));
- }
-
- if (!reason.TryGetUTF8EncodedBytes(out byte[] bytes))
- {
- var message = "It could not be UTF-8-encoded.";
- throw new ArgumentException(message, nameof(reason));
- }
-
- if (bytes.Length > 123)
- {
- var message = "Its size is greater than 123 bytes.";
- throw new ArgumentOutOfRangeException(nameof(reason), message);
- }
- }
-
- stop(code, reason);
- }
-
- public void Stop(CloseStatusCode code, string reason)
- {
- if (code == CloseStatusCode.MandatoryExtension)
- {
- var message = "MandatoryExtension cannot be used.";
- throw new ArgumentException(message, nameof(code));
- }
-
- if (!reason.IsNullOrEmpty())
- {
- if (code == CloseStatusCode.NoStatus)
- {
- var message = "NoStatus cannot be used.";
- throw new ArgumentException(message, nameof(code));
- }
-
- if (!reason.TryGetUTF8EncodedBytes(out byte[] bytes))
- {
- var message = "It could not be UTF-8-encoded.";
- throw new ArgumentException(message, nameof(reason));
- }
-
- if (bytes.Length > 123)
- {
- var message = "Its size is greater than 123 bytes.";
- throw new ArgumentOutOfRangeException(nameof(reason), message);
- }
- }
-
- stop((ushort)code, reason);
- }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Server/WSSessionManager.cs b/EonaCat.Network/System/Sockets/Web/Server/WSSessionManager.cs
index eb76222..edb6a2b 100644
--- a/EonaCat.Network/System/Sockets/Web/Server/WSSessionManager.cs
+++ b/EonaCat.Network/System/Sockets/Web/Server/WSSessionManager.cs
@@ -12,13 +12,13 @@ namespace EonaCat.Network
{
public class WSSessionManager
{
- private volatile bool _clean;
private readonly object _forSweep;
private readonly Dictionary _sessions;
+ private readonly object _sync;
+ private volatile bool _clean;
private volatile ServerState _state;
private volatile bool _sweeping;
private System.Timers.Timer _sweepTimer;
- private readonly object _sync;
private TimeSpan _waitTime;
internal WSSessionManager()
@@ -33,8 +33,6 @@ namespace EonaCat.Network
setSweepTimer(60000);
}
- internal ServerState State => _state;
-
public IEnumerable ActiveIDs
{
get
@@ -95,26 +93,6 @@ namespace EonaCat.Network
}
}
- public IWSSession this[string id]
- {
- get
- {
- if (id == null)
- {
- throw new ArgumentNullException(nameof(id));
- }
-
- if (id.Length == 0)
- {
- throw new ArgumentException("An empty string.", nameof(id));
- }
-
- tryGetSession(id, out IWSSession session);
-
- return session;
- }
- }
-
public bool KeepClean
{
get
@@ -197,277 +175,26 @@ namespace EonaCat.Network
}
}
- private void broadcast(OperationCode opcode, byte[] data, Action completed)
+ internal ServerState State => _state;
+ public IWSSession this[string id]
{
- var cache = new Dictionary();
-
- try
+ get
{
- foreach (var session in Sessions)
+ if (id == null)
{
- if (_state != ServerState.Start)
- {
- Logger.Error("The service is shutting down.");
- break;
- }
-
- session.Context.WebSocket.Send(opcode, data, cache);
+ throw new ArgumentNullException(nameof(id));
}
- if (completed != null)
+ if (id.Length == 0)
{
- completed();
- }
- }
- catch (Exception ex)
- {
- Logger.Error(ex.Message);
- Logger.Debug(ex.ToString());
- }
- finally
- {
- cache.Clear();
- }
- }
-
- private void broadcast(OperationCode opcode, Stream stream, Action completed)
- {
- var cache = new Dictionary();
-
- try
- {
- foreach (var session in Sessions)
- {
- if (_state != ServerState.Start)
- {
- Logger.Error("The service is shutting down.");
- break;
- }
-
- session.Context.WebSocket.Send(opcode, stream, cache);
+ throw new ArgumentException("An empty string.", nameof(id));
}
- if (completed != null)
- {
- completed();
- }
- }
- catch (Exception ex)
- {
- Logger.Error(ex.Message);
- Logger.Debug(ex.ToString());
- }
- finally
- {
- foreach (var cached in cache.Values)
- {
- cached.Dispose();
- }
+ tryGetSession(id, out IWSSession session);
- cache.Clear();
+ return session;
}
}
-
- private void broadcastAsync(OperationCode opcode, byte[] data, Action completed)
- {
- ThreadPool.QueueUserWorkItem(
- state => broadcast(opcode, data, completed)
- );
- }
-
- private void broadcastAsync(OperationCode opcode, Stream stream, Action completed)
- {
- ThreadPool.QueueUserWorkItem(
- state => broadcast(opcode, stream, completed)
- );
- }
-
- private Dictionary broadping(byte[] frameAsBytes)
- {
- var ret = new Dictionary();
-
- foreach (var session in Sessions)
- {
- if (_state != ServerState.Start)
- {
- Logger.Error("The service is shutting down.");
- break;
- }
-
- var res = session.Context.WebSocket.Ping(frameAsBytes, _waitTime);
- ret.Add(session.ID, res);
- }
-
- return ret;
- }
-
- private bool canSet(out string message)
- {
- 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");
- }
-
- private void setSweepTimer(double interval)
- {
- _sweepTimer = new System.Timers.Timer(interval);
- _sweepTimer.Elapsed += (sender, e) => Sweep();
- }
-
- private void stop(PayloadData payloadData, bool send)
- {
- var bytes = send
- ? WSFrame.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);
- }
-
- _state = ServerState.Stop;
- }
- }
-
- private bool tryGetSession(string id, out IWSSession session)
- {
- session = null;
-
- if (_state != ServerState.Start)
- {
- return false;
- }
-
- lock (_sync)
- {
- if (_state != ServerState.Start)
- {
- return false;
- }
-
- return _sessions.TryGetValue(id, out session);
- }
- }
-
- internal string Add(IWSSession session)
- {
- lock (_sync)
- {
- if (_state != ServerState.Start)
- {
- return null;
- }
-
- var id = createID();
- _sessions.Add(id, session);
-
- return id;
- }
- }
-
- internal void Broadcast(
- OperationCode opcode, byte[] data, Dictionary cache
- )
- {
- foreach (var session in Sessions)
- {
- if (_state != ServerState.Start)
- {
- Logger.Error("The service is shutting down.");
- break;
- }
-
- session.Context.WebSocket.Send(opcode, data, cache);
- }
- }
-
- internal void Broadcast(
- OperationCode opcode, Stream stream, Dictionary cache
- )
- {
- foreach (var session in Sessions)
- {
- if (_state != ServerState.Start)
- {
- Logger.Error("The service is shutting down.");
- break;
- }
-
- session.Context.WebSocket.Send(opcode, stream, cache);
- }
- }
-
- internal Dictionary Broadping(
- byte[] frameAsBytes, TimeSpan timeout
- )
- {
- var ret = new Dictionary();
-
- foreach (var session in Sessions)
- {
- if (_state != ServerState.Start)
- {
- Logger.Error("The service is shutting down.");
- break;
- }
-
- var res = session.Context.WebSocket.Ping(frameAsBytes, timeout);
- ret.Add(session.ID, res);
- }
-
- return ret;
- }
-
- internal bool Remove(string id)
- {
- lock (_sync)
- {
- return _sessions.Remove(id);
- }
- }
-
- internal void Start()
- {
- lock (_sync)
- {
- _sweepTimer.Enabled = _clean;
- _state = ServerState.Start;
- }
- }
-
- internal void Stop(ushort code, string reason)
- {
- if (code == (ushort)CloseStatusCode.NoStatus)
- { // == no status
- stop(PayloadData.Empty, true);
- return;
- }
-
- stop(new PayloadData(code, reason), !code.IsReserved());
- }
-
public void Broadcast(byte[] data)
{
if (_state != ServerState.Start)
@@ -872,5 +599,275 @@ namespace EonaCat.Network
return tryGetSession(id, out session);
}
+
+ internal string Add(IWSSession session)
+ {
+ lock (_sync)
+ {
+ if (_state != ServerState.Start)
+ {
+ return null;
+ }
+
+ var id = createID();
+ _sessions.Add(id, session);
+
+ return id;
+ }
+ }
+
+ internal void Broadcast(
+ OperationCode opcode, byte[] data, Dictionary cache
+ )
+ {
+ foreach (var session in Sessions)
+ {
+ if (_state != ServerState.Start)
+ {
+ Logger.Error("The service is shutting down.");
+ break;
+ }
+
+ session.Context.WebSocket.Send(opcode, data, cache);
+ }
+ }
+
+ internal void Broadcast(
+ OperationCode opcode, Stream stream, Dictionary cache
+ )
+ {
+ foreach (var session in Sessions)
+ {
+ if (_state != ServerState.Start)
+ {
+ Logger.Error("The service is shutting down.");
+ break;
+ }
+
+ session.Context.WebSocket.Send(opcode, stream, cache);
+ }
+ }
+
+ internal Dictionary Broadping(
+ byte[] frameAsBytes, TimeSpan timeout
+ )
+ {
+ var ret = new Dictionary();
+
+ foreach (var session in Sessions)
+ {
+ if (_state != ServerState.Start)
+ {
+ Logger.Error("The service is shutting down.");
+ break;
+ }
+
+ var res = session.Context.WebSocket.Ping(frameAsBytes, timeout);
+ ret.Add(session.ID, res);
+ }
+
+ return ret;
+ }
+
+ internal bool Remove(string id)
+ {
+ lock (_sync)
+ {
+ return _sessions.Remove(id);
+ }
+ }
+
+ internal void Start()
+ {
+ lock (_sync)
+ {
+ _sweepTimer.Enabled = _clean;
+ _state = ServerState.Start;
+ }
+ }
+
+ internal void Stop(ushort code, string reason)
+ {
+ if (code == (ushort)CloseStatusCode.NoStatus)
+ { // == no status
+ stop(PayloadData.Empty, true);
+ return;
+ }
+
+ stop(new PayloadData(code, reason), !code.IsReserved());
+ }
+
+ private static string createID()
+ {
+ return Guid.NewGuid().ToString("N");
+ }
+
+ private void broadcast(OperationCode opcode, byte[] data, Action completed)
+ {
+ var cache = new Dictionary();
+
+ try
+ {
+ foreach (var session in Sessions)
+ {
+ if (_state != ServerState.Start)
+ {
+ Logger.Error("The service is shutting down.");
+ break;
+ }
+
+ session.Context.WebSocket.Send(opcode, data, cache);
+ }
+
+ if (completed != null)
+ {
+ completed();
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex.Message);
+ Logger.Debug(ex.ToString());
+ }
+ finally
+ {
+ cache.Clear();
+ }
+ }
+
+ private void broadcast(OperationCode opcode, Stream stream, Action completed)
+ {
+ var cache = new Dictionary();
+
+ try
+ {
+ foreach (var session in Sessions)
+ {
+ if (_state != ServerState.Start)
+ {
+ Logger.Error("The service is shutting down.");
+ break;
+ }
+
+ session.Context.WebSocket.Send(opcode, stream, cache);
+ }
+
+ if (completed != null)
+ {
+ completed();
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex.Message);
+ Logger.Debug(ex.ToString());
+ }
+ finally
+ {
+ foreach (var cached in cache.Values)
+ {
+ cached.Dispose();
+ }
+
+ cache.Clear();
+ }
+ }
+
+ private void broadcastAsync(OperationCode opcode, byte[] data, Action completed)
+ {
+ ThreadPool.QueueUserWorkItem(
+ state => broadcast(opcode, data, completed)
+ );
+ }
+
+ private void broadcastAsync(OperationCode opcode, Stream stream, Action completed)
+ {
+ ThreadPool.QueueUserWorkItem(
+ state => broadcast(opcode, stream, completed)
+ );
+ }
+
+ private Dictionary broadping(byte[] frameAsBytes)
+ {
+ var ret = new Dictionary();
+
+ foreach (var session in Sessions)
+ {
+ if (_state != ServerState.Start)
+ {
+ Logger.Error("The service is shutting down.");
+ break;
+ }
+
+ var res = session.Context.WebSocket.Ping(frameAsBytes, _waitTime);
+ ret.Add(session.ID, res);
+ }
+
+ return ret;
+ }
+
+ private bool canSet(out string message)
+ {
+ 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 void setSweepTimer(double interval)
+ {
+ _sweepTimer = new System.Timers.Timer(interval);
+ _sweepTimer.Elapsed += (sender, e) => Sweep();
+ }
+
+ private void stop(PayloadData payloadData, bool send)
+ {
+ var bytes = send
+ ? WSFrame.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);
+ }
+
+ _state = ServerState.Stop;
+ }
+ }
+
+ private bool tryGetSession(string id, out IWSSession session)
+ {
+ session = null;
+
+ if (_state != ServerState.Start)
+ {
+ return false;
+ }
+
+ lock (_sync)
+ {
+ if (_state != ServerState.Start)
+ {
+ return false;
+ }
+
+ return _sessions.TryGetValue(id, out session);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/Server/WebSocketEndpointHost.cs b/EonaCat.Network/System/Sockets/Web/Server/WebSocketEndpointHost.cs
index ec50ed6..fe211f8 100644
--- a/EonaCat.Network/System/Sockets/Web/Server/WebSocketEndpointHost.cs
+++ b/EonaCat.Network/System/Sockets/Web/Server/WebSocketEndpointHost.cs
@@ -29,8 +29,13 @@ namespace EonaCat.Network
public override Type EndpointType => typeof(TEndpoint);
+ protected override WSEndpoint CreateSession()
+ {
+ return _creator();
+ }
+
private Func createCreator(
- Func creator, Action initializer
+ Func creator, Action initializer
)
{
if (initializer == null)
@@ -46,10 +51,5 @@ namespace EonaCat.Network
return ret;
};
}
-
- protected override WSEndpoint CreateSession()
- {
- return _creator();
- }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/WSClient.cs b/EonaCat.Network/System/Sockets/Web/WSClient.cs
index 62657ca..eda7872 100644
--- a/EonaCat.Network/System/Sockets/Web/WSClient.cs
+++ b/EonaCat.Network/System/Sockets/Web/WSClient.cs
@@ -16,13 +16,20 @@ namespace EonaCat.Network
{
public class WSClient : IDisposable
{
+ internal static readonly byte[] EmptyBytes;
+ internal static readonly RandomNumberGenerator RandomNumber;
+ private const string _version = "13";
+ private const string _webSocketGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ private const int FRAGMENT_LENGTH = 1016;
+ private static readonly int _maxRetryCountForConnect;
+ private readonly bool _client;
+ private readonly Action _message;
+ private readonly string[] _protocols;
private AuthenticationChallenge _authChallenge;
private string _base64Key;
- private readonly bool _client;
private Action _closeContext;
private CompressionMethod _compression;
private WSContext _context;
- private bool _isRedirectionEnabled;
private string _extensions;
private bool _extensionsRequested;
private object _forMessageEventQueue;
@@ -32,18 +39,15 @@ namespace EonaCat.Network
private MemoryStream _fragmentsBuffer;
private bool _fragmentsCompressed;
private OperationCode _fragmentsOpcode;
- private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private bool _inContinuation;
private volatile bool _inMessage;
- private static readonly int _maxRetryCountForConnect;
- private readonly Action _message;
+ private bool _isRedirectionEnabled;
private Queue _messageEventQueue;
private uint _nonceCount;
private string _origin;
private ManualResetEvent _pongReceived;
private bool _preAuth;
private string _protocol;
- private readonly string[] _protocols;
private bool _protocolsRequested;
private NetworkCredential _proxyCredentials;
private Uri _proxyUri;
@@ -54,16 +58,7 @@ namespace EonaCat.Network
private Stream _stream;
private TcpClient _tcpClient;
private Uri _uri;
- private const string _version = "13";
- private const int FRAGMENT_LENGTH = 1016;
private TimeSpan _waitTime;
-
- internal static readonly byte[] EmptyBytes;
-
- public static int FragmentLength;
-
- internal static readonly RandomNumberGenerator RandomNumber;
-
static WSClient()
{
_maxRetryCountForConnect = 10;
@@ -72,36 +67,6 @@ namespace EonaCat.Network
RandomNumber = new RNGCryptoServiceProvider();
}
- // As server
- internal WSClient(HttpListenerWSContext context, string protocol)
- {
- _context = context;
- _protocol = protocol;
-
- _closeContext = context.Close;
- _message = messages;
- IsSecure = context.IsSecureConnection;
- _stream = context.Stream;
- _waitTime = TimeSpan.FromSeconds(1);
-
- Setup();
- }
-
- // As server
- internal WSClient(TcpListenerWSContext context, string protocol)
- {
- _context = context;
- _protocol = protocol;
-
- _closeContext = context.Close;
- _message = messages;
- IsSecure = context.IsSecureConnection;
- _stream = context.Stream;
- _waitTime = TimeSpan.FromSeconds(1);
-
- Setup();
- }
-
public WSClient(string url, params string[] protocols)
{
if (url == null)
@@ -134,28 +99,47 @@ namespace EonaCat.Network
Setup();
}
-
- internal CookieCollection CookieCollection { get; private set; }
-
- // As server
- internal Func CustomHandshakeRequestChecker { get; set; }
-
- internal bool HasMessage
+ internal WSClient(HttpListenerWSContext context, string protocol)
{
- get
- {
- lock (_forMessageEventQueue)
- {
- return _messageEventQueue.Count > 0;
- }
- }
+ _context = context;
+ _protocol = protocol;
+
+ _closeContext = context.Close;
+ _message = messages;
+ IsSecure = context.IsSecureConnection;
+ _stream = context.Stream;
+ _waitTime = TimeSpan.FromSeconds(1);
+
+ Setup();
}
- // As server
- internal bool IgnoreExtensions { get; set; }
+ internal WSClient(TcpListenerWSContext context, string protocol)
+ {
+ _context = context;
+ _protocol = protocol;
- internal bool IsConnected => _readyState == WSState.Open || _readyState == WSState.Closing;
+ _closeContext = context.Close;
+ _message = messages;
+ IsSecure = context.IsSecureConnection;
+ _stream = context.Stream;
+ _waitTime = TimeSpan.FromSeconds(1);
+ Setup();
+ }
+
+ public event EventHandler OnConnect;
+
+ public event EventHandler OnDisconnect;
+
+ public event EventHandler OnError;
+
+ public event EventHandler OnMessageReceived;
+
+ ///
+ /// Fragment length
+ ///
+ public static int FragmentLength { get; set; }
+ public bool CallMessageOnPing { get; set; }
public CompressionMethod Compression
{
get
@@ -207,9 +191,13 @@ namespace EonaCat.Network
}
public NetworkCredential Credentials { get; private set; }
+ ///
+ /// Gets or sets the custom headers
+ ///
+ public IEnumerable> CustomHeaders { get; set; }
- public bool CallMessageOnPing { get; set; }
-
+ public string Extensions => _extensions ?? string.Empty;
+ public bool IsAlive => ping(EmptyBytes);
public bool IsRedirectionEnabled
{
get
@@ -246,12 +234,7 @@ namespace EonaCat.Network
}
}
- public string Extensions => _extensions ?? string.Empty;
-
- public bool IsAlive => ping(EmptyBytes);
-
public bool IsSecure { get; private set; }
-
public string Origin
{
get
@@ -317,7 +300,6 @@ namespace EonaCat.Network
}
public WSState ReadyState => _readyState;
-
public SSLConfigClient SSL
{
get
@@ -340,8 +322,18 @@ namespace EonaCat.Network
}
}
- public Uri Url => _client ? _uri : _context.RequestUri;
+ ///
+ /// Gets the TcpClient used for the WebSocket.
+ ///
+ public TcpClient TcpClient
+ {
+ get
+ {
+ return _tcpClient;
+ }
+ }
+ public Uri Url => _client ? _uri : _context.RequestUri;
public TimeSpan WaitTime
{
get
@@ -375,1896 +367,23 @@ namespace EonaCat.Network
}
}
- public event EventHandler OnDisconnect;
+ internal CookieCollection CookieCollection { get; private set; }
+ internal Func CustomHandshakeRequestChecker { get; set; }
- public event EventHandler OnError;
-
- public event EventHandler OnMessageReceived;
-
- public event EventHandler OnConnect;
-
- // As server
- private bool accept()
+ internal bool HasMessage
{
- lock (_forState)
+ get
{
- if (!checkIfAvailable(true, false, false, false, out string message))
- {
- Logger.Error(message);
- Error("An error has occurred in accepting.", null);
-
- return false;
- }
-
- try
- {
- if (!acceptHandshake())
- {
- return false;
- }
-
- _readyState = WSState.Open;
- }
- catch (Exception ex)
- {
- Logger.Error(ex.ToString());
- fatal("An exception has occurred while accepting.", ex);
-
- return false;
- }
-
- return true;
- }
- }
-
- // As server
- private bool acceptHandshake()
- {
- Logger.Debug($"A request from {_context.UserEndPoint}:\n{_context}");
-
- if (!checkHandshakeRequest(_context, out string message))
- {
- sendHttpResponse(createHandshakeFailureResponse(HttpStatusCode.BadRequest));
-
- Logger.Error(message);
- fatal("An error has occurred while accepting.", CloseStatusCode.ProtocolError);
-
- return false;
- }
-
- if (!customCheckHandshakeRequest(_context, out message))
- {
- sendHttpResponse(createHandshakeFailureResponse(HttpStatusCode.BadRequest));
-
- Logger.Error(message);
- fatal("An error has occurred while accepting.", CloseStatusCode.PolicyViolation);
-
- return false;
- }
-
- _base64Key = _context.Headers["Sec-WebSocket-Key"];
-
- if (_protocol != null)
- {
- processSecWebSocketProtocolHeader(_context.SecWebSocketProtocols);
- }
-
- if (!IgnoreExtensions)
- {
- processSecWebSocketExtensionsClientHeader(_context.Headers["Sec-WebSocket-Extensions"]);
- }
-
- return sendHttpResponse(createHandshakeResponse());
- }
-
- private bool canSet(out string message)
- {
- message = null;
-
- if (_readyState == WSState.Open)
- {
- message = "The connection has already been established.";
- return false;
- }
-
- if (_readyState == WSState.Closing)
- {
- message = "The connection is closing.";
- return false;
- }
-
- return true;
- }
-
- // As server
- private bool checkHandshakeRequest(WSContext context, out string message)
- {
- message = null;
-
- if (context.RequestUri == null)
- {
- message = "Specifies an invalid Request-URI.";
- return false;
- }
-
- if (!context.IsWebSocketRequest)
- {
- message = "Not a WebSocket handshake request.";
- return false;
- }
-
- var headers = context.Headers;
- if (!validateSecWebSocketKeyHeader(headers["Sec-WebSocket-Key"]))
- {
- message = "Includes no Sec-WebSocket-Key header, or it has an invalid value.";
- return false;
- }
-
- if (!validateSecWebSocketVersionClientHeader(headers["Sec-WebSocket-Version"]))
- {
- message = "Includes no Sec-WebSocket-Version header, or it has an invalid value.";
- return false;
- }
-
- if (!validateSecWebSocketProtocolClientHeader(headers["Sec-WebSocket-Protocol"]))
- {
- message = "Includes an invalid Sec-WebSocket-Protocol header.";
- return false;
- }
-
- if (!IgnoreExtensions
- && !validateSecWebSocketExtensionsClientHeader(headers["Sec-WebSocket-Extensions"])
- )
- {
- message = "Includes an invalid Sec-WebSocket-Extensions header.";
- return false;
- }
-
- return true;
- }
-
- // As client
- private bool checkHandshakeResponse(WebResponse response, out string message)
- {
- message = null;
-
- if (response.IsRedirect)
- {
- message = "Indicates the redirection.";
- return false;
- }
-
- if (response.IsUnauthorized)
- {
- message = "Requires the authentication.";
- 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.";
- return false;
- }
-
- if (!validateSecWebSocketProtocolServerHeader(headers["Sec-WebSocket-Protocol"]))
- {
- message = "Includes no Sec-WebSocket-Protocol header, or it has an invalid value.";
- return false;
- }
-
- if (!validateSecWebSocketExtensionsServerHeader(headers["Sec-WebSocket-Extensions"]))
- {
- message = "Includes an invalid Sec-WebSocket-Extensions header.";
- return false;
- }
-
- if (!validateSecWebSocketVersionServerHeader(headers["Sec-WebSocket-Version"]))
- {
- message = "Includes an invalid Sec-WebSocket-Version header.";
- return false;
- }
-
- return true;
- }
-
- private bool checkIfAvailable(
- bool connecting, bool open, bool closing, bool closed, out string message
- )
- {
- message = null;
-
- if (!connecting && _readyState == WSState.Connecting)
- {
- message = "This operation is not available in: connecting";
- return false;
- }
-
- if (!open && _readyState == WSState.Open)
- {
- message = "This operation is not available in: open";
- return false;
- }
-
- if (!closing && _readyState == WSState.Closing)
- {
- message = "This operation is not available in: closing";
- return false;
- }
-
- if (!closed && _readyState == WSState.Closed)
- {
- message = "This operation is not available in: closed";
- return false;
- }
-
- return true;
- }
-
- private bool checkIfAvailable(
- bool client,
- bool server,
- bool connecting,
- bool open,
- bool closing,
- bool closed,
- out string message
- )
- {
- message = null;
-
- if (!client && _client)
- {
- message = "This operation is not available in: client";
- return false;
- }
-
- if (!server && !_client)
- {
- message = "This operation is not available in: server";
- return false;
- }
-
- return checkIfAvailable(connecting, open, closing, closed, out message);
- }
-
- private static bool checkParametersForSetCredentials(
- string username, string password, out string message
- )
- {
- message = null;
-
- if (username.IsNullOrEmpty())
- {
- return true;
- }
-
- if (username.Contains(':') || !username.IsText())
- {
- message = "'username' contains an invalid character.";
- return false;
- }
-
- if (password.IsNullOrEmpty())
- {
- return true;
- }
-
- if (!password.IsText())
- {
- message = "'password' contains an invalid character.";
- return false;
- }
-
- return true;
- }
-
- private static bool checkParametersForSetProxy(
- string url, string username, string password, out string message
- )
- {
- message = null;
-
- if (url.IsNullOrEmpty())
- {
- return true;
- }
-
- if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri)
- || uri.Scheme != "http"
- || uri.Segments.Length > 1
- )
- {
- message = "'url' is an invalid URL.";
- return false;
- }
-
- if (username.IsNullOrEmpty())
- {
- return true;
- }
-
- if (username.Contains(':') || !username.IsText())
- {
- message = "'username' contains an invalid character.";
- return false;
- }
-
- if (password.IsNullOrEmpty())
- {
- return true;
- }
-
- if (!password.IsText())
- {
- message = "'password' contains an invalid character.";
- return false;
- }
-
- return true;
- }
-
- private static bool checkProtocols(string[] protocols, out string message)
- {
- message = null;
-
- Func cond = protocol => protocol.IsNullOrEmpty()
- || !protocol.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(WSFrame frame, out string message)
- {
- message = null;
-
- var masked = frame.IsMasked;
- if (_client && masked)
- {
- message = "A frame from the server is masked.";
- return false;
- }
-
- if (!_client && !masked)
- {
- message = "A frame from a client is not masked.";
- return false;
- }
-
- if (_inContinuation && frame.IsData)
- {
- message = "A data frame has been received while receiving continuation frames.";
- return false;
- }
-
- if (frame.IsCompressed && _compression == CompressionMethod.None)
- {
- message = "A compressed frame has been received without any agreement for it.";
- 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;
- }
-
- return true;
- }
-
- private void close(ushort code, string reason)
- {
- if (_readyState == WSState.Closing)
- {
- Logger.Info("The closing is already in progress.");
- return;
- }
-
- if (_readyState == WSState.Closed)
- {
- Logger.Info("The connection has already been closed.");
- return;
- }
-
- if (code == (ushort)CloseStatusCode.NoStatus)
- { // == no status
- close(PayloadData.Empty, true, true, false);
- return;
- }
-
- var send = !code.IsReserved();
- close(new PayloadData(code, reason), send, send, false);
- }
-
- private void close(
- PayloadData payloadData, bool send, bool receive, bool received
- )
- {
- lock (_forState)
- {
- if (_readyState == WSState.Closing)
- {
- Logger.Info("The closing is already in progress.");
- return;
- }
-
- if (_readyState == WSState.Closed)
- {
- Logger.Info("The connection has already been closed.");
- return;
- }
-
- send = send && _readyState == WSState.Open;
- receive = send && receive;
-
- _readyState = WSState.Closing;
- }
-
- Logger.Trace("Begin closing the connection.");
-
- var res = closeHandshake(payloadData, send, receive, received);
- releaseResources();
-
- Logger.Trace("End closing the connection.");
-
- _readyState = WSState.Closed;
-
- var e = new CloseEventArgs(payloadData);
- e.WasClean = res;
-
- try
- {
- OnDisconnect.Emit(this, e);
- }
- catch (Exception ex)
- {
- Logger.Error(ex.ToString());
- Error("An error has occurred during the OnClose event.", ex);
- }
- }
-
- private void closeAsync(ushort code, string reason)
- {
- if (_readyState == WSState.Closing)
- {
- Logger.Info("The closing is already in progress.");
- return;
- }
-
- if (_readyState == WSState.Closed)
- {
- Logger.Info("The connection has already been closed.");
- return;
- }
-
- if (code == (ushort)CloseStatusCode.NoStatus)
- { // == no status
- closeAsync(PayloadData.Empty, true, true, false);
- return;
- }
-
- var send = !code.IsReserved();
- closeAsync(new PayloadData(code, reason), send, send, false);
- }
-
- private async Task closeAsync(PayloadData payloadData, bool send, bool receive, bool received)
- {
- await Task.Run(() => close(payloadData, send, receive, received));
- }
-
- private bool closeHandshake(
- PayloadData payloadData, bool send, bool receive, bool received
- )
- {
- var sent = false;
- if (send)
- {
- var frame = WSFrame.CreateCloseFrame(payloadData, _client);
- sent = sendBytes(frame.ToArray());
-
- if (_client)
- {
- frame.Unmask();
- }
- }
-
- var wait = !received && sent && receive && _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
- )
- );
-
- return ret;
- }
-
- // As client
- private bool connect()
- {
- lock (_forState)
- {
- if (!checkIfAvailable(true, false, false, true, out string message))
- {
- Logger.Error(message);
- Error("An error has occurred in connecting.", null);
-
- return false;
- }
-
- if (_retryCountForConnect > _maxRetryCountForConnect)
- {
- _retryCountForConnect = 0;
- Logger.Error("A series of reconnecting has failed.");
-
- return false;
- }
-
- _readyState = WSState.Connecting;
-
- try
- {
- Handshake();
- }
- catch (Exception ex)
- {
- _retryCountForConnect++;
- Logger.Error(ex.ToString());
- fatal("An exception has occurred while connecting.", ex);
-
- return false;
- }
-
- _retryCountForConnect = 1;
- _readyState = WSState.Open;
-
- return true;
- }
- }
-
- // As client
- private string createExtensions()
- {
- var buff = new StringBuilder(80);
-
- if (_compression != CompressionMethod.None)
- {
- var str = _compression.ToExtensionString(
- "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;
- }
-
- // As server
- private WebResponse createHandshakeFailureResponse(HttpStatusCode code)
- {
- var ret = WebResponse.CreateCloseResponse(code);
- ret.Headers["Sec-WebSocket-Version"] = _version;
-
- return ret;
- }
-
- // As client
- private WebRequest createHandshakeRequest()
- {
- var ret = WebRequest.CreateWebSocketRequest(_uri);
-
- var headers = ret.Headers;
- if (!_origin.IsNullOrEmpty())
- {
- headers["Origin"] = _origin;
- }
-
- headers["Sec-WebSocket-Key"] = _base64Key;
-
- _protocolsRequested = _protocols != null && _protocols.Length > 0;
- if (_protocolsRequested)
- {
- headers["Sec-WebSocket-Protocol"] = _protocols.ToString(", ");
- }
-
- _extensionsRequested = _compression != CompressionMethod.None;
- if (_extensionsRequested)
- {
- headers["Sec-WebSocket-Extensions"] = createExtensions();
- }
-
- headers["Sec-WebSocket-Version"] = _version;
-
- 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 (authRes != null)
- {
- headers["Authorization"] = authRes.ToString();
- }
-
- if (CookieCollection.Count > 0)
- {
- ret.SetCookies(CookieCollection);
- }
-
- return ret;
- }
-
- // As server
- private WebResponse createHandshakeResponse()
- {
- var ret = WebResponse.CreateWebSocketResponse();
-
- var headers = ret.Headers;
- headers["Sec-WebSocket-Accept"] = CreateResponseKey(_base64Key);
-
- if (_protocol != null)
- {
- headers["Sec-WebSocket-Protocol"] = _protocol;
- }
-
- if (_extensions != null)
- {
- headers["Sec-WebSocket-Extensions"] = _extensions;
- }
-
- if (CookieCollection.Count > 0)
- {
- ret.SetCookies(CookieCollection);
- }
-
- return ret;
- }
-
- // As server
- private bool customCheckHandshakeRequest(WSContext context, out string message)
- {
- message = null;
- return CustomHandshakeRequestChecker == null
- || (message = CustomHandshakeRequestChecker(context)) == null;
- }
-
- // As client
- private void Handshake()
- {
- SetClientStream();
-
- var response = sendHandshakeRequest();
- if (!checkHandshakeResponse(response, out string message))
- {
- throw new WSException(CloseStatusCode.ProtocolError, message);
- }
-
- if (_protocolsRequested)
- {
- _protocol = response.Headers["Sec-WebSocket-Protocol"];
- }
-
- if (_extensionsRequested)
- {
- processSecWebSocketExtensionsServerHeader(response.Headers["Sec-WebSocket-Extensions"]);
- }
-
- processCookies(response.Cookies);
- }
-
- private void EnqueueToMessageEventQueue(MessageEventArgs e)
- {
- lock (_forMessageEventQueue)
- {
- _messageEventQueue.Enqueue(e);
- }
- }
-
- private void Error(string message, Exception exception)
- {
- try
- {
- OnError.Emit(this, new ErrorEventArgs(message, exception));
- }
- catch (Exception ex)
- {
- Logger.Error(ex.ToString());
- }
- }
-
- private void fatal(string message, Exception exception)
- {
- var code = exception is WSException
- ? ((WSException)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 void Setup()
- {
- _compression = CompressionMethod.None;
- CookieCollection = new CookieCollection();
- _forPing = new object();
- _forSend = new object();
- _forState = new object();
- _messageEventQueue = new Queue();
- _forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot;
- _readyState = WSState.Connecting;
- }
-
- private void message()
- {
- MessageEventArgs e = null;
- lock (_forMessageEventQueue)
- {
- if (_inMessage || _messageEventQueue.Count == 0 || _readyState != WSState.Open)
- {
- return;
- }
-
- _inMessage = true;
- e = _messageEventQueue.Dequeue();
- }
-
- _message(e);
- }
-
- private void messagec(MessageEventArgs e)
- {
- do
- {
- try
- {
- OnMessageReceived.Emit(this, e);
- }
- catch (Exception ex)
- {
- Logger.Error(ex.ToString());
- Error("An error has occurred during an OnMessage event.", ex);
- }
-
lock (_forMessageEventQueue)
{
- if (_messageEventQueue.Count == 0 || _readyState != WSState.Open)
- {
- _inMessage = false;
- break;
- }
-
- e = _messageEventQueue.Dequeue();
- }
- }
- while (true);
- }
-
- private void messages(MessageEventArgs e)
- {
- try
- {
- OnMessageReceived.Emit(this, e);
- }
- catch (Exception ex)
- {
- Logger.Error(ex.ToString());
- Error("An error has occurred during an OnMessage event.", ex);
- }
-
- lock (_forMessageEventQueue)
- {
- if (_messageEventQueue.Count == 0 || _readyState != WSState.Open)
- {
- _inMessage = false;
- return;
- }
-
- e = _messageEventQueue.Dequeue();
- }
-
- ThreadPool.QueueUserWorkItem(state => messages(e));
- }
-
- private void open()
- {
- _inMessage = true;
- startReceiving();
- try
- {
- OnConnect.Emit(this, EventArgs.Empty);
- }
- catch (Exception ex)
- {
- Logger.Error(ex.ToString());
- Error("An error has occurred during the OnOpen event.", ex);
- }
-
- MessageEventArgs e = null;
- lock (_forMessageEventQueue)
- {
- if (_messageEventQueue.Count == 0 || _readyState != WSState.Open)
- {
- _inMessage = false;
- return;
- }
-
- e = _messageEventQueue.Dequeue();
- }
-
- Task.Run(() => _message(e));
- }
-
- private bool ping(byte[] data)
- {
- if (_readyState != WSState.Open)
- {
- return false;
- }
-
- var pongReceived = _pongReceived;
- if (pongReceived == null)
- {
- return false;
- }
-
- lock (_forPing)
- {
- try
- {
- pongReceived.Reset();
- if (!send(FinalFrame.Final, OperationCode.Ping, data, false))
- {
- return false;
- }
-
- return pongReceived.WaitOne(_waitTime);
- }
- catch (ObjectDisposedException)
- {
- return false;
+ return _messageEventQueue.Count > 0;
}
}
}
- private bool processCloseFrame(WSFrame frame)
- {
- var payload = frame.PayloadData;
- close(payload, !payload.HasReservedCode, false, true);
-
- return false;
- }
-
- // As client
- private void processCookies(CookieCollection cookies)
- {
- if (cookies.Count == 0)
- {
- return;
- }
-
- CookieCollection.SetOrRemove(cookies);
- }
-
- private bool processDataFrame(WSFrame frame)
- {
- EnqueueToMessageEventQueue(
- frame.IsCompressed
- ? new MessageEventArgs(
- frame.Opcode, frame.PayloadData.ApplicationData.Decompress(_compression))
- : new MessageEventArgs(frame));
-
- return true;
- }
-
- private bool processFragmentFrame(WSFrame frame)
- {
- if (!_inContinuation)
- {
- // Must process first fragment.
- if (frame.IsContinuation)
- {
- return true;
- }
-
- _fragmentsOpcode = frame.Opcode;
- _fragmentsCompressed = frame.IsCompressed;
- _fragmentsBuffer = new MemoryStream();
- _inContinuation = true;
- }
-
- _fragmentsBuffer.WriteBytes(frame.PayloadData.ApplicationData, 1024);
- if (frame.IsFinal)
- {
- using (_fragmentsBuffer)
- {
- var data = _fragmentsCompressed
- ? _fragmentsBuffer.DecompressToArray(_compression)
- : _fragmentsBuffer.ToArray();
-
- EnqueueToMessageEventQueue(new MessageEventArgs(_fragmentsOpcode, data));
- }
-
- _fragmentsBuffer = null;
- _inContinuation = false;
- }
-
- return true;
- }
-
- private bool processPingFrame(WSFrame frame)
- {
- Logger.Trace("A ping was received.");
-
- var pong = WSFrame.CreatePongFrame(frame.PayloadData, _client);
-
- lock (_forState)
- {
- if (_readyState != WSState.Open)
- {
- Logger.Error("The connection is closing.");
- return true;
- }
-
- if (!sendBytes(pong.ToArray()))
- {
- return false;
- }
- }
-
- Logger.Trace("A pong to this ping has been sent.");
-
- if (CallMessageOnPing)
- {
- if (_client)
- {
- pong.Unmask();
- }
-
- EnqueueToMessageEventQueue(new MessageEventArgs(frame));
- }
-
- return true;
- }
-
- private bool processPongFrame(WSFrame frame)
- {
- Logger.Trace("A pong was received.");
-
- try
- {
- _pongReceived.Set();
- }
- catch (NullReferenceException ex)
- {
- Logger.Error(ex.Message);
- Logger.Debug(ex.ToString());
-
- return false;
- }
- catch (ObjectDisposedException ex)
- {
- Logger.Error(ex.Message);
- Logger.Debug(ex.ToString());
-
- return false;
- }
-
- Logger.Trace("It has been signaled.");
-
- return true;
- }
-
- private bool processReceivedFrame(WSFrame frame)
- {
- if (!checkReceivedFrame(frame, out string message))
- {
- throw new WSException(CloseStatusCode.ProtocolError, message);
- }
-
- frame.Unmask();
- return frame.IsFragment
- ? processFragmentFrame(frame)
- : frame.IsData
- ? processDataFrame(frame)
- : frame.IsPing
- ? processPingFrame(frame)
- : frame.IsPong
- ? processPongFrame(frame)
- : frame.IsClose
- ? processCloseFrame(frame)
- : processUnsupportedFrame(frame);
- }
-
- // As server
- private void processSecWebSocketExtensionsClientHeader(string value)
- {
- if (value == null)
- {
- return;
- }
-
- var buff = new StringBuilder(80);
-
- var comp = false;
- foreach (var e in value.SplitHeaderValue(','))
- {
- var ext = e.Trim();
- if (!comp && ext.IsCompressionExtension(CompressionMethod.Deflate))
- {
- _compression = CompressionMethod.Deflate;
- buff.AppendFormat(
- "{0}, ",
- _compression.ToExtensionString(
- "client_no_context_takeover", "server_no_context_takeover"
- )
- );
-
- comp = true;
- }
- }
-
- var len = buff.Length;
- if (len > 2)
- {
- 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 processSecWebSocketProtocolHeader(IEnumerable values)
- {
- if (values.Contains(p => p == _protocol))
- {
- return;
- }
-
- _protocol = null;
- }
-
- private bool processUnsupportedFrame(WSFrame frame)
- {
- Logger.Error("An unsupported frame:" + frame.PrintToString(false));
- fatal("There is no way to handle it.", CloseStatusCode.PolicyViolation);
-
- return false;
- }
-
- // As client
- private void releaseClientResources()
- {
- if (_stream != null)
- {
- _stream.Dispose();
- _stream = null;
- }
-
- if (_tcpClient != null)
- {
- _tcpClient.Close();
- _tcpClient = null;
- }
- }
-
- 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)
- {
- releaseClientResources();
- }
- else
- {
- releaseServerResources();
- }
-
- releaseCommonResources();
- }
-
- // As server
- private void releaseServerResources()
- {
- if (_closeContext == null)
- {
- return;
- }
-
- _closeContext();
- _closeContext = null;
- _stream = null;
- _context = null;
- }
-
- private bool send(OperationCode opcode, Stream stream)
- {
- lock (_forSend)
- {
- var src = stream;
- var compressed = false;
- var sent = false;
- try
- {
- if (_compression != CompressionMethod.None)
- {
- stream = stream.Compress(_compression);
- compressed = true;
- }
-
- sent = send(opcode, stream, compressed);
- if (!sent)
- {
- Error("A send has been interrupted.", null);
- }
- }
- catch (Exception ex)
- {
- Logger.Error(ex.ToString());
- Error("An error has occurred during a send.", ex);
- }
- finally
- {
- if (compressed)
- {
- stream.Dispose();
- }
-
- src.Dispose();
- }
-
- return sent;
- }
- }
-
- private bool send(OperationCode opcode, Stream stream, bool compressed)
- {
- var len = stream.Length;
- if (len == 0)
- {
- return send(FinalFrame.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
- && send(FinalFrame.Final, opcode, buff, compressed);
- }
-
- if (quo == 1 && rem == 0)
- {
- buff = new byte[FragmentLength];
- return stream.Read(buff, 0, FragmentLength) == FragmentLength
- && send(FinalFrame.Final, opcode, buff, compressed);
- }
-
- /* Send fragments */
-
- // Begin
- buff = new byte[FragmentLength];
- var sent = stream.Read(buff, 0, FragmentLength) == FragmentLength
- && send(FinalFrame.More, opcode, buff, compressed);
-
- if (!sent)
- {
- return false;
- }
-
- var n = rem == 0 ? quo - 2 : quo - 1;
- for (long i = 0; i < n; i++)
- {
- sent = stream.Read(buff, 0, FragmentLength) == FragmentLength
- && send(FinalFrame.More, OperationCode.Cont, buff, false);
-
- if (!sent)
- {
- return false;
- }
- }
-
- // End
- if (rem == 0)
- {
- rem = FragmentLength;
- }
- else
- {
- buff = new byte[rem];
- }
-
- return stream.Read(buff, 0, rem) == rem
- && send(FinalFrame.Final, OperationCode.Cont, buff, false);
- }
-
- private bool send(FinalFrame fin, OperationCode opcode, byte[] data, bool compressed)
- {
- lock (_forState)
- {
- if (_readyState != WSState.Open)
- {
- Logger.Error("The connection is closing.");
- return false;
- }
-
- var frame = new WSFrame(fin, opcode, data, compressed, _client);
- return sendBytes(frame.ToArray());
- }
- }
-
- private void sendAsync(OperationCode opcode, Stream stream, Action completed)
- {
- Func sender = send;
- Task.Run(() =>
- {
- try
- {
- var sent = sender(opcode, stream);
- completed?.Invoke(sent);
- }
- catch (Exception ex)
- {
- Logger.Error(ex.ToString());
- Error("An error has occurred during the callback for an async send.", ex);
- }
- });
- }
-
- private bool sendBytes(byte[] bytes)
- {
- try
- {
- _stream.Write(bytes, 0, bytes.Length);
- }
- catch (Exception ex)
- {
- Logger.Error(ex.Message);
- Logger.Debug(ex.ToString());
-
- return false;
- }
-
- return true;
- }
-
- // As client
- private WebResponse sendHandshakeRequest()
- {
- var req = createHandshakeRequest();
- var res = sendHttpRequest(req, 90000);
- if (res.IsUnauthorized)
- {
- var chal = res.Headers["WWW-Authenticate"];
- Logger.Warning(string.Format("Received an authentication requirement for '{0}'.", chal));
- if (chal.IsNullOrEmpty())
- {
- Logger.Error("No authentication challenge is specified.");
- return res;
- }
-
- _authChallenge = AuthenticationChallenge.Parse(chal);
- if (_authChallenge == null)
- {
- Logger.Error("An invalid authentication challenge is specified.");
- return res;
- }
-
- if (Credentials != null &&
- (!_preAuth || _authChallenge.Scheme == AuthenticationSchemes.Digest))
- {
- if (res.HasConnectionClose)
- {
- releaseClientResources();
- SetClientStream();
- }
-
- var authRes = new AuthenticationResponse(_authChallenge, Credentials, _nonceCount);
- _nonceCount = authRes.NonceCount;
- req.Headers["Authorization"] = authRes.ToString();
- res = sendHttpRequest(req, 15000);
- }
- }
-
- if (res.IsRedirect)
- {
- var url = res.Headers["Location"];
- Logger.Warning(string.Format("Received a redirection to '{0}'.", url));
- if (_isRedirectionEnabled)
- {
- if (url.IsNullOrEmpty())
- {
- Logger.Error("No url to redirect is located.");
- return res;
- }
-
- if (!url.TryCreateWebSocketUri(out Uri uri, out string message))
- {
- Logger.Error("An invalid url to redirect is located: " + message);
- return res;
- }
-
- releaseClientResources();
-
- _uri = uri;
- IsSecure = uri.Scheme == "wss";
-
- SetClientStream();
- return sendHandshakeRequest();
- }
- }
-
- return res;
- }
-
- // As client
- private WebResponse sendHttpRequest(WebRequest request, int millisecondsTimeout)
- {
- Logger.Debug("Request to server:\n" + request.ToString());
- var res = request.GetResponse(_stream, millisecondsTimeout);
- Logger.Debug("Response to request:\n" + res.ToString());
-
- return res;
- }
-
- // As server
- private bool sendHttpResponse(WebResponse response)
- {
- Logger.Debug("Response to request:\n" + response.ToString());
- return sendBytes(response.ToByteArray());
- }
-
- // As client
- private void sendProxyConnectRequest()
- {
- var req = WebRequest.CreateConnectRequest(_uri);
- var res = sendHttpRequest(req, 90000);
- if (res.IsProxyAuthenticationRequired)
- {
- var chal = res.Headers["Proxy-Authenticate"];
- Logger.Warning(
- string.Format("Received a proxy authentication requirement for '{0}'.", chal));
-
- if (chal.IsNullOrEmpty())
- {
- throw new WSException("No proxy authentication challenge is specified.");
- }
-
- var authChal = AuthenticationChallenge.Parse(chal);
- if (authChal == null)
- {
- throw new WSException("An invalid proxy authentication challenge is specified.");
- }
-
- if (_proxyCredentials != null)
- {
- if (res.HasConnectionClose)
- {
- releaseClientResources();
- _tcpClient = new TcpClient(_proxyUri.DnsSafeHost, _proxyUri.Port);
- _stream = _tcpClient.GetStream();
- }
-
- var authRes = new AuthenticationResponse(authChal, _proxyCredentials, 0);
- req.Headers["Proxy-Authorization"] = authRes.ToString();
- res = sendHttpRequest(req, 15000);
- }
-
- if (res.IsProxyAuthenticationRequired)
- {
- throw new WSException("A proxy authentication is required.");
- }
- }
-
- if (res.StatusCode[0] != '2')
- {
- throw new WSException(
- "The proxy has failed a connection to the requested host and port.");
- }
- }
-
- // As client
- private void SetClientStream()
- {
- if (_proxyUri != null)
- {
- _tcpClient = new TcpClient(_proxyUri.DnsSafeHost, _proxyUri.Port);
- _stream = _tcpClient.GetStream();
- sendProxyConnectRequest();
- }
- else
- {
- _tcpClient = new TcpClient(_uri.DnsSafeHost, _uri.Port);
- _stream = _tcpClient.GetStream();
- }
-
- if (IsSecure)
- {
- var conf = SSL;
- var host = conf.TargetHost;
- if (host != _uri.DnsSafeHost)
- {
- throw new WSException(
- CloseStatusCode.TlsHandshakeFailure, "An invalid host name is specified.");
- }
-
- try
- {
- var sslStream = new SslStream(
- _stream,
- false,
- conf.ServerCertificateValidationCallback,
- conf.ClientCertificateSelectionCallback);
-
- sslStream.AuthenticateAsClient(
- host,
- conf.Certificates,
- conf.SslProtocols,
- conf.CheckForCertificateRevocation);
-
- _stream = sslStream;
- }
- catch (Exception ex)
- {
- throw new WSException(CloseStatusCode.TlsHandshakeFailure, ex);
- }
- }
- }
-
- private void startReceiving()
- {
- if (_messageEventQueue.Count > 0)
- {
- _messageEventQueue.Clear();
- }
-
- _pongReceived = new ManualResetEvent(false);
- _receivingExited = new ManualResetEvent(false);
-
- Action receive = null;
- receive =
- () =>
- WSFrame.ReadFrameAsync(
- _stream,
- false,
- frame =>
- {
- if (!processReceivedFrame(frame) || _readyState == WSState.Closed)
- {
- var exited = _receivingExited;
- exited?.Set();
-
- return;
- }
-
- // Receive next asap because the Ping or Close needs a response to it.
- receive();
-
- if (_inMessage || !HasMessage || _readyState != WSState.Open)
- {
- return;
- }
-
- message();
- },
- ex =>
- {
- Logger.Error(ex.ToString());
- fatal("An exception has occurred while receiving.", ex);
- }
- );
-
- receive();
- }
-
- // As client
- private bool validateSecWebSocketAcceptHeader(string value)
- {
- return value != null && value == CreateResponseKey(_base64Key);
- }
-
- // As server
- private bool validateSecWebSocketExtensionsClientHeader(string value)
- {
- return value == null || value.Length > 0;
- }
-
- // As client
- private bool validateSecWebSocketExtensionsServerHeader(string value)
- {
- if (value == null)
- {
- return true;
- }
-
- if (value.Length == 0)
- {
- return false;
- }
-
- if (!_extensionsRequested)
- {
- return false;
- }
-
- 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.Warning("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";
- }
- );
-
- if (invalid)
- {
- return false;
- }
- }
- else
- {
- return false;
- }
- }
-
- return true;
- }
-
- // As server
- private bool validateSecWebSocketKeyHeader(string value)
- {
- return value != null && value.Length > 0;
- }
-
- // As server
- private bool validateSecWebSocketProtocolClientHeader(string value)
- {
- return value == null || value.Length > 0;
- }
-
- // 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 server
- private bool validateSecWebSocketVersionClientHeader(string value)
- {
- return value != null && value == _version;
- }
-
- // As client
- private bool validateSecWebSocketVersionServerHeader(string value)
- {
- return value == null || value == _version;
- }
-
- // As server
- internal void Close(WebResponse response)
- {
- _readyState = WSState.Closing;
-
- sendHttpResponse(response);
- releaseServerResources();
-
- _readyState = WSState.Closed;
- }
-
- // As server
- internal void Close(HttpStatusCode code)
- {
- Close(createHandshakeFailureResponse(code));
- }
-
- // As server
- internal void Close(PayloadData payloadData, byte[] frameAsBytes)
- {
- lock (_forState)
- {
- if (_readyState == WSState.Closing)
- {
- Logger.Info("The closing is already in progress.");
- return;
- }
-
- if (_readyState == WSState.Closed)
- {
- Logger.Info("The connection has already been closed.");
- return;
- }
-
- _readyState = WSState.Closing;
- }
-
- Logger.Trace("Begin closing the connection.");
-
- var sent = frameAsBytes != null && sendBytes(frameAsBytes);
- var received = sent && _receivingExited != null
-&& _receivingExited.WaitOne(_waitTime);
-
- var res = sent && received;
-
- Logger.Debug(
- string.Format(
- "Was clean?: {0}\n sent: {1}\n received: {2}", res, sent, received
- )
- );
-
- releaseServerResources();
- releaseCommonResources();
-
- Logger.Trace("End closing the connection.");
-
- _readyState = WSState.Closed;
-
- var e = new CloseEventArgs(payloadData);
- e.WasClean = res;
-
- try
- {
- OnDisconnect.Emit(this, e);
- }
- catch (Exception ex)
- {
- Logger.Error(ex.ToString());
- }
- }
-
- // As client
- internal static string CreateBase64Key()
- {
- var src = new byte[16];
- RandomNumber.GetBytes(src);
-
- return Convert.ToBase64String(src);
- }
-
- 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().UTF8Encode());
-
- return Convert.ToBase64String(src);
- }
-
- // As server
- internal void InternalAccept()
- {
- try
- {
- if (!acceptHandshake())
- {
- return;
- }
-
- _readyState = WSState.Open;
- }
- catch (Exception ex)
- {
- Logger.Error(ex.ToString());
- fatal("An exception has occurred while accepting.", ex);
-
- return;
- }
-
- open();
- }
-
- // As server
- internal bool Ping(byte[] frameAsBytes, TimeSpan timeout)
- {
- if (_readyState != WSState.Open)
- {
- return false;
- }
-
- var pongReceived = _pongReceived;
- if (pongReceived == null)
- {
- return false;
- }
-
- lock (_forPing)
- {
- try
- {
- pongReceived.Reset();
-
- lock (_forState)
- {
- if (_readyState != WSState.Open)
- {
- return false;
- }
-
- if (!sendBytes(frameAsBytes))
- {
- return false;
- }
- }
-
- return pongReceived.WaitOne(timeout);
- }
- catch (ObjectDisposedException)
- {
- return false;
- }
- }
- }
-
- // As server
- internal void Send(
- OperationCode opcode, byte[] data, Dictionary cache
- )
- {
- lock (_forSend)
- {
- lock (_forState)
- {
- if (_readyState != WSState.Open)
- {
- Logger.Error("The connection is closing.");
- return;
- }
-
- if (!cache.TryGetValue(_compression, out byte[] found))
- {
- found = new WSFrame(
- FinalFrame.Final,
- opcode,
- data.Compress(_compression),
- _compression != CompressionMethod.None,
- false
- )
- .ToArray();
-
- cache.Add(_compression, found);
- }
-
- sendBytes(found);
- }
- }
- }
-
- // As server
- internal void Send(
- OperationCode opcode, Stream stream, Dictionary cache
- )
- {
- lock (_forSend)
- {
- if (!cache.TryGetValue(_compression, out Stream found))
- {
- found = stream.Compress(_compression);
- cache.Add(_compression, found);
- }
- else
- {
- found.Position = 0;
- }
-
- send(opcode, found, _compression != CompressionMethod.None);
- }
- }
+ internal bool IgnoreExtensions { get; set; }
+ internal bool IsConnected => _readyState == WSState.Open || _readyState == WSState.Closing;
public void Accept()
{
if (!checkIfAvailable(false, true, true, false, false, false, out string message))
@@ -2601,6 +720,11 @@ namespace EonaCat.Network
});
}
+ void IDisposable.Dispose()
+ {
+ close((ushort)CloseStatusCode.Away, string.Empty);
+ }
+
public bool Ping()
{
return ping(EmptyBytes);
@@ -2628,6 +752,18 @@ namespace EonaCat.Network
return ping(bytes);
}
+ ///
+ /// Sends a ping using the WebSocket connection.
+ ///
+ ///
+ /// true if the send has done with no error and a pong has been
+ /// received within a time; otherwise, false.
+ ///
+ public void PingAsync(Action completed)
+ {
+ pingAsync(EmptyBytes, completed);
+ }
+
public void Send(byte[] data)
{
if (_readyState != WSState.Open)
@@ -2985,9 +1121,1870 @@ namespace EonaCat.Network
}
}
- void IDisposable.Dispose()
+ internal static string CreateBase64Key()
{
- close((ushort)CloseStatusCode.Away, string.Empty);
+ var src = new byte[16];
+ RandomNumber.GetBytes(src);
+
+ return Convert.ToBase64String(src);
+ }
+
+ internal static string CreateResponseKey(string base64Key)
+ {
+ var buff = new StringBuilder(base64Key, 64);
+ buff.Append(_webSocketGuid);
+ SHA1 sha1 = new SHA1CryptoServiceProvider();
+ var src = sha1.ComputeHash(buff.ToString().UTF8Encode());
+
+ return Convert.ToBase64String(src);
+ }
+
+ internal void Close(WebResponse response)
+ {
+ _readyState = WSState.Closing;
+
+ sendHttpResponse(response);
+ releaseServerResources();
+
+ _readyState = WSState.Closed;
+ }
+
+ internal void Close(HttpStatusCode code)
+ {
+ Close(createHandshakeFailureResponse(code));
+ }
+
+ internal void Close(PayloadData payloadData, byte[] frameAsBytes)
+ {
+ lock (_forState)
+ {
+ if (_readyState == WSState.Closing)
+ {
+ Logger.Info("The closing is already in progress.");
+ return;
+ }
+
+ if (_readyState == WSState.Closed)
+ {
+ Logger.Info("The connection has already been closed.");
+ return;
+ }
+
+ _readyState = WSState.Closing;
+ }
+
+ Logger.Trace("Begin closing the connection.");
+
+ var sent = frameAsBytes != null && sendBytes(frameAsBytes);
+ var received = sent && _receivingExited != null
+&& _receivingExited.WaitOne(_waitTime);
+
+ var res = sent && received;
+
+ Logger.Debug(
+ string.Format(
+ "Was clean?: {0}\n sent: {1}\n received: {2}", res, sent, received
+ )
+ );
+
+ releaseServerResources();
+ releaseCommonResources();
+
+ Logger.Trace("End closing the connection.");
+
+ _readyState = WSState.Closed;
+
+ var e = new CloseEventArgs(payloadData);
+ e.WasClean = res;
+
+ try
+ {
+ OnDisconnect.Emit(this, e);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex.ToString());
+ }
+ }
+
+ internal void InternalAccept()
+ {
+ try
+ {
+ if (!acceptHandshake())
+ {
+ return;
+ }
+
+ _readyState = WSState.Open;
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex.ToString());
+ fatal("An exception has occurred while accepting.", ex);
+
+ return;
+ }
+
+ open();
+ }
+
+ internal bool Ping(byte[] frameAsBytes, TimeSpan timeout)
+ {
+ if (_readyState != WSState.Open)
+ {
+ return false;
+ }
+
+ var pongReceived = _pongReceived;
+ if (pongReceived == null)
+ {
+ return false;
+ }
+
+ lock (_forPing)
+ {
+ try
+ {
+ pongReceived.Reset();
+
+ lock (_forState)
+ {
+ if (_readyState != WSState.Open)
+ {
+ return false;
+ }
+
+ if (!sendBytes(frameAsBytes))
+ {
+ return false;
+ }
+ }
+
+ return pongReceived.WaitOne(timeout);
+ }
+ catch (ObjectDisposedException)
+ {
+ return false;
+ }
+ }
+ }
+
+ internal void Send(
+ OperationCode opcode, byte[] data, Dictionary cache
+ )
+ {
+ lock (_forSend)
+ {
+ lock (_forState)
+ {
+ if (_readyState != WSState.Open)
+ {
+ Logger.Error("The connection is closing.");
+ return;
+ }
+
+ if (!cache.TryGetValue(_compression, out byte[] found))
+ {
+ found = new WSFrame(
+ FinalFrame.Final,
+ opcode,
+ data.Compress(_compression),
+ _compression != CompressionMethod.None,
+ false
+ )
+ .ToArray();
+
+ cache.Add(_compression, found);
+ }
+
+ sendBytes(found);
+ }
+ }
+ }
+
+ internal void Send(
+ OperationCode opcode, Stream stream, Dictionary cache
+ )
+ {
+ lock (_forSend)
+ {
+ if (!cache.TryGetValue(_compression, out Stream found))
+ {
+ found = stream.Compress(_compression);
+ cache.Add(_compression, found);
+ }
+ else
+ {
+ found.Position = 0;
+ }
+
+ send(opcode, found, _compression != CompressionMethod.None);
+ }
+ }
+
+ private static bool checkParametersForSetCredentials(
+ string username, string password, out string message
+ )
+ {
+ message = null;
+
+ if (username.IsNullOrEmpty())
+ {
+ return true;
+ }
+
+ if (username.Contains(':') || !username.IsText())
+ {
+ message = "'username' contains an invalid character.";
+ return false;
+ }
+
+ if (password.IsNullOrEmpty())
+ {
+ return true;
+ }
+
+ if (!password.IsText())
+ {
+ message = "'password' contains an invalid character.";
+ return false;
+ }
+
+ return true;
+ }
+
+ private static bool checkParametersForSetProxy(
+ string url, string username, string password, out string message
+ )
+ {
+ message = null;
+
+ if (url.IsNullOrEmpty())
+ {
+ return true;
+ }
+
+ if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri)
+ || uri.Scheme != "http"
+ || uri.Segments.Length > 1
+ )
+ {
+ message = "'url' is an invalid URL.";
+ return false;
+ }
+
+ if (username.IsNullOrEmpty())
+ {
+ return true;
+ }
+
+ if (username.Contains(':') || !username.IsText())
+ {
+ message = "'username' contains an invalid character.";
+ return false;
+ }
+
+ if (password.IsNullOrEmpty())
+ {
+ return true;
+ }
+
+ if (!password.IsText())
+ {
+ message = "'password' contains an invalid character.";
+ return false;
+ }
+
+ return true;
+ }
+
+ private static bool checkProtocols(string[] protocols, out string message)
+ {
+ message = null;
+
+ Func cond = protocol => protocol.IsNullOrEmpty()
+ || !protocol.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 accept()
+ {
+ lock (_forState)
+ {
+ if (!checkIfAvailable(true, false, false, false, out string message))
+ {
+ Logger.Error(message);
+ Error("An error has occurred in accepting.", null);
+
+ return false;
+ }
+
+ try
+ {
+ if (!acceptHandshake())
+ {
+ return false;
+ }
+
+ _readyState = WSState.Open;
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex.ToString());
+ fatal("An exception has occurred while accepting.", ex);
+
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ private bool acceptHandshake()
+ {
+ Logger.Debug($"A request from {_context.UserEndPoint}:\n{_context}");
+
+ if (!checkHandshakeRequest(_context, out string message))
+ {
+ sendHttpResponse(createHandshakeFailureResponse(HttpStatusCode.BadRequest));
+
+ Logger.Error(message);
+ fatal("An error has occurred while accepting.", CloseStatusCode.ProtocolError);
+
+ return false;
+ }
+
+ if (!customCheckHandshakeRequest(_context, out message))
+ {
+ sendHttpResponse(createHandshakeFailureResponse(HttpStatusCode.BadRequest));
+
+ Logger.Error(message);
+ fatal("An error has occurred while accepting.", CloseStatusCode.PolicyViolation);
+
+ return false;
+ }
+
+ _base64Key = _context.Headers["Sec-WebSocket-Key"];
+
+ if (_protocol != null)
+ {
+ processSecWebSocketProtocolHeader(_context.SecWebSocketProtocols);
+ }
+
+ if (!IgnoreExtensions)
+ {
+ processSecWebSocketExtensionsClientHeader(_context.Headers["Sec-WebSocket-Extensions"]);
+ }
+
+ return sendHttpResponse(createHandshakeResponse());
+ }
+
+ private bool canSet(out string message)
+ {
+ message = null;
+
+ if (_readyState == WSState.Open)
+ {
+ message = "The connection has already been established.";
+ return false;
+ }
+
+ if (_readyState == WSState.Closing)
+ {
+ message = "The connection is closing.";
+ return false;
+ }
+
+ return true;
+ }
+
+ private bool checkHandshakeRequest(WSContext context, out string message)
+ {
+ message = null;
+
+ if (context.RequestUri == null)
+ {
+ message = "Specifies an invalid Request-URI.";
+ return false;
+ }
+
+ if (!context.IsWebSocketRequest)
+ {
+ message = "Not a WebSocket handshake request.";
+ return false;
+ }
+
+ var headers = context.Headers;
+ if (!validateSecWebSocketKeyHeader(headers["Sec-WebSocket-Key"]))
+ {
+ message = "Includes no Sec-WebSocket-Key header, or it has an invalid value.";
+ return false;
+ }
+
+ if (!validateSecWebSocketVersionClientHeader(headers["Sec-WebSocket-Version"]))
+ {
+ message = "Includes no Sec-WebSocket-Version header, or it has an invalid value.";
+ return false;
+ }
+
+ if (!validateSecWebSocketProtocolClientHeader(headers["Sec-WebSocket-Protocol"]))
+ {
+ message = "Includes an invalid Sec-WebSocket-Protocol header.";
+ return false;
+ }
+
+ if (!IgnoreExtensions
+ && !validateSecWebSocketExtensionsClientHeader(headers["Sec-WebSocket-Extensions"])
+ )
+ {
+ message = "Includes an invalid Sec-WebSocket-Extensions header.";
+ return false;
+ }
+
+ return true;
+ }
+
+ private bool checkHandshakeResponse(WebResponse response, out string message)
+ {
+ message = null;
+
+ if (response.IsRedirect)
+ {
+ message = "Indicates the redirection.";
+ return false;
+ }
+
+ if (response.IsUnauthorized)
+ {
+ message = "Requires the authentication.";
+ 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.";
+ return false;
+ }
+
+ if (!validateSecWebSocketProtocolServerHeader(headers["Sec-WebSocket-Protocol"]))
+ {
+ message = "Includes no Sec-WebSocket-Protocol header, or it has an invalid value.";
+ return false;
+ }
+
+ if (!validateSecWebSocketExtensionsServerHeader(headers["Sec-WebSocket-Extensions"]))
+ {
+ message = "Includes an invalid Sec-WebSocket-Extensions header.";
+ return false;
+ }
+
+ if (!validateSecWebSocketVersionServerHeader(headers["Sec-WebSocket-Version"]))
+ {
+ message = "Includes an invalid Sec-WebSocket-Version header.";
+ return false;
+ }
+
+ return true;
+ }
+
+ private bool checkIfAvailable(
+ bool connecting, bool open, bool closing, bool closed, out string message
+ )
+ {
+ message = null;
+
+ if (!connecting && _readyState == WSState.Connecting)
+ {
+ message = "This operation is not available in: connecting";
+ return false;
+ }
+
+ if (!open && _readyState == WSState.Open)
+ {
+ message = "This operation is not available in: open";
+ return false;
+ }
+
+ if (!closing && _readyState == WSState.Closing)
+ {
+ message = "This operation is not available in: closing";
+ return false;
+ }
+
+ if (!closed && _readyState == WSState.Closed)
+ {
+ message = "This operation is not available in: closed";
+ return false;
+ }
+
+ return true;
+ }
+
+ private bool checkIfAvailable(
+ bool client,
+ bool server,
+ bool connecting,
+ bool open,
+ bool closing,
+ bool closed,
+ out string message
+ )
+ {
+ message = null;
+
+ if (!client && _client)
+ {
+ message = "This operation is not available in: client";
+ return false;
+ }
+
+ if (!server && !_client)
+ {
+ message = "This operation is not available in: server";
+ return false;
+ }
+
+ return checkIfAvailable(connecting, open, closing, closed, out message);
+ }
+ private bool checkReceivedFrame(WSFrame frame, out string message)
+ {
+ message = null;
+
+ var masked = frame.IsMasked;
+ if (_client && masked)
+ {
+ message = "A frame from the server is masked.";
+ return false;
+ }
+
+ if (!_client && !masked)
+ {
+ message = "A frame from a client is not masked.";
+ return false;
+ }
+
+ if (_inContinuation && frame.IsData)
+ {
+ message = "A data frame has been received while receiving continuation frames.";
+ return false;
+ }
+
+ if (frame.IsCompressed && _compression == CompressionMethod.None)
+ {
+ message = "A compressed frame has been received without any agreement for it.";
+ return false;
+ }
+
+ if (frame.Opcode == OperationCode.Continue && !_inContinuation)
+ {
+ message = "A continuation frame has been received but there is nothing to continue.";
+ 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;
+ }
+
+ return true;
+ }
+
+ private void close(ushort code, string reason)
+ {
+ if (_readyState == WSState.Closing)
+ {
+ Logger.Info("The closing is already in progress.");
+ return;
+ }
+
+ if (_readyState == WSState.Closed)
+ {
+ Logger.Info("The connection has already been closed.");
+ return;
+ }
+
+ if (code == (ushort)CloseStatusCode.NoStatus)
+ { // == no status
+ close(PayloadData.Empty, true, true, false);
+ return;
+ }
+
+ var send = !code.IsReserved();
+ close(new PayloadData(code, reason), send, send, false);
+ }
+
+ private void close(
+ PayloadData payloadData, bool send, bool receive, bool received
+ )
+ {
+ lock (_forState)
+ {
+ if (_readyState == WSState.Closing)
+ {
+ Logger.Info("The closing is already in progress.");
+ return;
+ }
+
+ if (_readyState == WSState.Closed)
+ {
+ Logger.Info("The connection has already been closed.");
+ return;
+ }
+
+ send = send && _readyState == WSState.Open;
+ receive = send && receive;
+
+ _readyState = WSState.Closing;
+ }
+
+ Logger.Trace("Begin closing the connection.");
+
+ var res = closeHandshake(payloadData, send, receive, received);
+ releaseResources();
+
+ Logger.Trace("End closing the connection.");
+
+ _readyState = WSState.Closed;
+
+ var e = new CloseEventArgs(payloadData);
+ e.WasClean = res;
+
+ try
+ {
+ OnDisconnect.Emit(this, e);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex.ToString());
+ Error("An error has occurred during the OnClose event.", ex);
+ }
+ }
+
+ private void closeAsync(ushort code, string reason)
+ {
+ if (_readyState == WSState.Closing)
+ {
+ Logger.Info("The closing is already in progress.");
+ return;
+ }
+
+ if (_readyState == WSState.Closed)
+ {
+ Logger.Info("The connection has already been closed.");
+ return;
+ }
+
+ if (code == (ushort)CloseStatusCode.NoStatus)
+ { // == no status
+ closeAsync(PayloadData.Empty, true, true, false);
+ return;
+ }
+
+ var send = !code.IsReserved();
+ closeAsync(new PayloadData(code, reason), send, send, false);
+ }
+
+ private async Task closeAsync(PayloadData payloadData, bool send, bool receive, bool received)
+ {
+ await Task.Run(() => close(payloadData, send, receive, received));
+ }
+
+ private bool closeHandshake(
+ PayloadData payloadData, bool send, bool receive, bool received
+ )
+ {
+ var sent = false;
+ if (send)
+ {
+ var frame = WSFrame.CreateCloseFrame(payloadData, _client);
+ sent = sendBytes(frame.ToArray());
+
+ if (_client)
+ {
+ frame.Unmask();
+ }
+ }
+
+ var wait = !received && sent && receive && _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
+ )
+ );
+
+ return ret;
+ }
+
+ private bool connect()
+ {
+ lock (_forState)
+ {
+ if (!checkIfAvailable(true, false, false, true, out string message))
+ {
+ Logger.Error(message);
+ Error("An error has occurred in connecting.", null);
+
+ return false;
+ }
+
+ if (_retryCountForConnect > _maxRetryCountForConnect)
+ {
+ _retryCountForConnect = 0;
+ Logger.Error("A series of reconnecting has failed.");
+
+ return false;
+ }
+
+ _readyState = WSState.Connecting;
+
+ try
+ {
+ Handshake();
+ }
+ catch (Exception ex)
+ {
+ _retryCountForConnect++;
+ Logger.Error(ex.ToString());
+ fatal("An exception has occurred while connecting.", ex);
+
+ return false;
+ }
+
+ _retryCountForConnect = 1;
+ _readyState = WSState.Open;
+
+ return true;
+ }
+ }
+
+ private string createExtensions()
+ {
+ var buff = new StringBuilder(80);
+
+ if (_compression != CompressionMethod.None)
+ {
+ var str = _compression.ToExtensionString(
+ "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;
+ }
+
+ private WebResponse createHandshakeFailureResponse(HttpStatusCode code)
+ {
+ var ret = WebResponse.CreateCloseResponse(code);
+ ret.Headers["Sec-WebSocket-Version"] = _version;
+
+ return ret;
+ }
+
+ private WebRequest createHandshakeRequest()
+ {
+ var ret = WebRequest.CreateWebSocketRequest(_uri);
+
+ var headers = ret.Headers;
+ if (!_origin.IsNullOrEmpty())
+ {
+ headers["Origin"] = _origin;
+ }
+
+ headers["Sec-WebSocket-Key"] = _base64Key;
+
+ _protocolsRequested = _protocols != null && _protocols.Length > 0;
+ if (_protocolsRequested)
+ {
+ headers["Sec-WebSocket-Protocol"] = _protocols.ToString(", ");
+ }
+
+ _extensionsRequested = _compression != CompressionMethod.None;
+ if (_extensionsRequested)
+ {
+ headers["Sec-WebSocket-Extensions"] = createExtensions();
+ }
+
+ headers["Sec-WebSocket-Version"] = _version;
+
+ 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 (authRes != null)
+ {
+ headers["Authorization"] = authRes.ToString();
+ }
+
+ if (CookieCollection.Count > 0)
+ {
+ ret.SetCookies(CookieCollection);
+ }
+
+ if (CustomHeaders != null)
+ foreach (var header in CustomHeaders)
+ if (!headers.Contains(header.Key))
+ ret.Headers[header.Key] = header.Value;
+
+ return ret;
+ }
+
+ private WebResponse createHandshakeResponse()
+ {
+ var ret = WebResponse.CreateWebSocketResponse();
+
+ var headers = ret.Headers;
+ headers["Sec-WebSocket-Accept"] = CreateResponseKey(_base64Key);
+
+ if (_protocol != null)
+ {
+ headers["Sec-WebSocket-Protocol"] = _protocol;
+ }
+
+ if (_extensions != null)
+ {
+ headers["Sec-WebSocket-Extensions"] = _extensions;
+ }
+
+ if (CookieCollection.Count > 0)
+ {
+ ret.SetCookies(CookieCollection);
+ }
+
+ return ret;
+ }
+
+ private bool customCheckHandshakeRequest(WSContext context, out string message)
+ {
+ message = null;
+ return CustomHandshakeRequestChecker == null
+ || (message = CustomHandshakeRequestChecker(context)) == null;
+ }
+
+ private void EnqueueToMessageEventQueue(MessageEventArgs e)
+ {
+ lock (_forMessageEventQueue)
+ {
+ _messageEventQueue.Enqueue(e);
+ }
+ }
+
+ private void Error(string message, Exception exception)
+ {
+ try
+ {
+ OnError.Emit(this, new ErrorEventArgs(message, exception));
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex.ToString());
+ }
+ }
+
+ private void fatal(string message, Exception exception)
+ {
+ var code = exception is WSException
+ ? ((WSException)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 void Handshake()
+ {
+ SetClientStream();
+
+ var response = sendHandshakeRequest();
+ if (!checkHandshakeResponse(response, out string message))
+ {
+ throw new WSException(CloseStatusCode.ProtocolError, message);
+ }
+
+ if (_protocolsRequested)
+ {
+ _protocol = response.Headers["Sec-WebSocket-Protocol"];
+ }
+
+ if (_extensionsRequested)
+ {
+ processSecWebSocketExtensionsServerHeader(response.Headers["Sec-WebSocket-Extensions"]);
+ }
+
+ processCookies(response.Cookies);
+ }
+ private void message()
+ {
+ MessageEventArgs e = null;
+ lock (_forMessageEventQueue)
+ {
+ if (_inMessage || _messageEventQueue.Count == 0 || _readyState != WSState.Open)
+ {
+ return;
+ }
+
+ _inMessage = true;
+ e = _messageEventQueue.Dequeue();
+ }
+
+ _message(e);
+ }
+
+ private void messagec(MessageEventArgs e)
+ {
+ do
+ {
+ try
+ {
+ OnMessageReceived.Emit(this, e);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex.ToString());
+ Error("An error has occurred during an OnMessage event.", ex);
+ }
+
+ lock (_forMessageEventQueue)
+ {
+ if (_messageEventQueue.Count == 0 || _readyState != WSState.Open)
+ {
+ _inMessage = false;
+ break;
+ }
+
+ e = _messageEventQueue.Dequeue();
+ }
+ }
+ while (true);
+ }
+
+ private void messages(MessageEventArgs e)
+ {
+ try
+ {
+ OnMessageReceived.Emit(this, e);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex.ToString());
+ Error("An error has occurred during an OnMessage event.", ex);
+ }
+
+ lock (_forMessageEventQueue)
+ {
+ if (_messageEventQueue.Count == 0 || _readyState != WSState.Open)
+ {
+ _inMessage = false;
+ return;
+ }
+
+ e = _messageEventQueue.Dequeue();
+ }
+
+ ThreadPool.QueueUserWorkItem(state => messages(e));
+ }
+
+ private void open()
+ {
+ _inMessage = true;
+ startReceiving();
+ try
+ {
+ OnConnect.Emit(this, EventArgs.Empty);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex.ToString());
+ Error("An error has occurred during the OnOpen event.", ex);
+ }
+
+ MessageEventArgs e = null;
+ lock (_forMessageEventQueue)
+ {
+ if (_messageEventQueue.Count == 0 || _readyState != WSState.Open)
+ {
+ _inMessage = false;
+ return;
+ }
+
+ e = _messageEventQueue.Dequeue();
+ }
+
+ Task.Run(() => _message(e));
+ }
+
+ private bool ping(byte[] data)
+ {
+ if (_readyState != WSState.Open)
+ {
+ return false;
+ }
+
+ var pongReceived = _pongReceived;
+ if (pongReceived == null)
+ {
+ return false;
+ }
+
+ lock (_forPing)
+ {
+ try
+ {
+ pongReceived.Reset();
+ if (!send(FinalFrame.Final, OperationCode.Ping, data, false))
+ {
+ return false;
+ }
+
+ return pongReceived.WaitOne(_waitTime);
+ }
+ catch (ObjectDisposedException)
+ {
+ return false;
+ }
+ }
+ }
+
+ private void pingAsync(byte[] data, Action completed)
+ {
+ if (_readyState != WSState.Open)
+ {
+ var msg = "The current state of the connection is not Open.";
+ throw new InvalidOperationException(msg);
+ }
+
+ sendAsync(OperationCode.Ping, new MemoryStream(data), completed);
+ }
+
+ private bool processCloseFrame(WSFrame frame)
+ {
+ var payload = frame.PayloadData;
+ close(payload, !payload.HasReservedCode, false, true);
+
+ return ping(EmptyBytes);
+ }
+
+ private void processCookies(CookieCollection cookies)
+ {
+ if (cookies.Count == 0)
+ {
+ return;
+ }
+
+ CookieCollection.SetOrRemove(cookies);
+ }
+
+ private bool processDataFrame(WSFrame frame)
+ {
+ EnqueueToMessageEventQueue(
+ frame.IsCompressed
+ ? new MessageEventArgs(
+ frame.Opcode, frame.PayloadData.ApplicationData.Decompress(_compression))
+ : new MessageEventArgs(frame));
+
+ return true;
+ }
+
+ private bool processFragmentFrame(WSFrame frame)
+ {
+ if (!_inContinuation)
+ {
+ // Must process first fragment.
+ if (frame.IsContinuation)
+ {
+ return true;
+ }
+
+ _fragmentsOpcode = frame.Opcode;
+ _fragmentsCompressed = frame.IsCompressed;
+ _fragmentsBuffer = new MemoryStream();
+ _inContinuation = true;
+ }
+
+ _fragmentsBuffer.WriteBytes(frame.PayloadData.ApplicationData, 1024);
+ if (frame.IsFinal)
+ {
+ using (_fragmentsBuffer)
+ {
+ var data = _fragmentsCompressed
+ ? _fragmentsBuffer.DecompressToArray(_compression)
+ : _fragmentsBuffer.ToArray();
+
+ EnqueueToMessageEventQueue(new MessageEventArgs(_fragmentsOpcode, data));
+ }
+
+ _fragmentsBuffer = null;
+ _inContinuation = false;
+ }
+
+ return true;
+ }
+
+ private bool processPingFrame(WSFrame frame)
+ {
+ Logger.Trace("A ping was received.");
+
+ var pong = WSFrame.CreatePongFrame(frame.PayloadData, _client);
+
+ lock (_forState)
+ {
+ if (_readyState != WSState.Open)
+ {
+ Logger.Error("The connection is closing.");
+ return true;
+ }
+
+ if (!sendBytes(pong.ToArray()))
+ {
+ return false;
+ }
+ }
+
+ Logger.Trace("A pong to this ping has been sent.");
+
+ if (CallMessageOnPing)
+ {
+ if (_client)
+ {
+ pong.Unmask();
+ }
+
+ EnqueueToMessageEventQueue(new MessageEventArgs(frame));
+ }
+
+ return true;
+ }
+
+ private bool processPongFrame(WSFrame frame)
+ {
+ Logger.Trace("A pong was received.");
+
+ try
+ {
+ _pongReceived.Set();
+ }
+ catch (NullReferenceException ex)
+ {
+ Logger.Error(ex.Message);
+ Logger.Debug(ex.ToString());
+
+ return false;
+ }
+ catch (ObjectDisposedException ex)
+ {
+ Logger.Error(ex.Message);
+ Logger.Debug(ex.ToString());
+
+ return false;
+ }
+
+ Logger.Trace("It has been signaled.");
+
+ return true;
+ }
+
+ private bool processReceivedFrame(WSFrame frame)
+ {
+ if (!checkReceivedFrame(frame, out string message))
+ {
+ throw new WSException(CloseStatusCode.ProtocolError, message);
+ }
+
+ frame.Unmask();
+ return frame.IsFragment
+ ? processFragmentFrame(frame)
+ : frame.IsData
+ ? processDataFrame(frame)
+ : frame.IsPing
+ ? processPingFrame(frame)
+ : frame.IsPong
+ ? processPongFrame(frame)
+ : frame.IsClose
+ ? processCloseFrame(frame)
+ : processUnsupportedFrame(frame);
+ }
+
+ private void processSecWebSocketExtensionsClientHeader(string value)
+ {
+ if (value == null)
+ {
+ return;
+ }
+
+ var buff = new StringBuilder(80);
+
+ var comp = false;
+ foreach (var e in value.SplitHeaderValue(','))
+ {
+ var ext = e.Trim();
+ if (!comp && ext.IsCompressionExtension(CompressionMethod.Deflate))
+ {
+ _compression = CompressionMethod.Deflate;
+ buff.AppendFormat(
+ "{0}, ",
+ _compression.ToExtensionString(
+ "client_no_context_takeover", "server_no_context_takeover"
+ )
+ );
+
+ comp = true;
+ }
+ }
+
+ var len = buff.Length;
+ if (len > 2)
+ {
+ buff.Length = len - 2;
+ _extensions = buff.ToString();
+ }
+ }
+
+ private void processSecWebSocketExtensionsServerHeader(string value)
+ {
+ if (value == null)
+ {
+ _compression = CompressionMethod.None;
+ return;
+ }
+
+ _extensions = value;
+ }
+
+ private void processSecWebSocketProtocolHeader(IEnumerable values)
+ {
+ if (values.Contains(p => p == _protocol))
+ {
+ return;
+ }
+
+ _protocol = null;
+ }
+
+ private bool processUnsupportedFrame(WSFrame frame)
+ {
+ Logger.Error("An unsupported frame:" + frame.PrintToString(false));
+ fatal("There is no way to handle it.", CloseStatusCode.PolicyViolation);
+
+ return false;
+ }
+
+ private void releaseClientResources()
+ {
+ if (_stream != null)
+ {
+ _stream.Dispose();
+ _stream = null;
+ }
+
+ if (_tcpClient != null)
+ {
+ _tcpClient.Close();
+ _tcpClient = null;
+ }
+ }
+
+ 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)
+ {
+ releaseClientResources();
+ }
+ else
+ {
+ releaseServerResources();
+ }
+
+ releaseCommonResources();
+ }
+
+ private void releaseServerResources()
+ {
+ if (_closeContext == null)
+ {
+ return;
+ }
+
+ _closeContext();
+ _closeContext = null;
+ _stream = null;
+ _context = null;
+ }
+
+ private bool send(OperationCode opcode, Stream stream)
+ {
+ lock (_forSend)
+ {
+ var src = stream;
+ var compressed = false;
+ var sent = false;
+ try
+ {
+ if (_compression != CompressionMethod.None)
+ {
+ stream = stream.Compress(_compression);
+ compressed = true;
+ }
+
+ sent = send(opcode, stream, compressed);
+ if (!sent)
+ {
+ Error("A send has been interrupted.", null);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex.ToString());
+ Error("An error has occurred during a send.", ex);
+ }
+ finally
+ {
+ if (compressed)
+ {
+ stream.Dispose();
+ }
+
+ src.Dispose();
+ }
+
+ return sent;
+ }
+ }
+
+ private bool send(OperationCode opcode, Stream stream, bool compressed)
+ {
+ var len = stream.Length;
+ if (len == 0)
+ {
+ return send(FinalFrame.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
+ && send(FinalFrame.Final, opcode, buff, compressed);
+ }
+
+ if (quo == 1 && rem == 0)
+ {
+ buff = new byte[FragmentLength];
+ return stream.Read(buff, 0, FragmentLength) == FragmentLength
+ && send(FinalFrame.Final, opcode, buff, compressed);
+ }
+
+ /* Send fragments */
+
+ // Begin
+ buff = new byte[FragmentLength];
+ var sent = stream.Read(buff, 0, FragmentLength) == FragmentLength
+ && send(FinalFrame.More, opcode, buff, compressed);
+
+ if (!sent)
+ {
+ return false;
+ }
+
+ var n = rem == 0 ? quo - 2 : quo - 1;
+ for (long i = 0; i < n; i++)
+ {
+ sent = stream.Read(buff, 0, FragmentLength) == FragmentLength
+ && send(FinalFrame.More, OperationCode.Continue, buff, false);
+
+ if (!sent)
+ {
+ return false;
+ }
+ }
+
+ // End
+ if (rem == 0)
+ {
+ rem = FragmentLength;
+ }
+ else
+ {
+ buff = new byte[rem];
+ }
+
+ return stream.Read(buff, 0, rem) == rem
+ && send(FinalFrame.Final, OperationCode.Continue, buff, false);
+ }
+
+ private bool send(FinalFrame fin, OperationCode opcode, byte[] data, bool compressed)
+ {
+ lock (_forState)
+ {
+ if (_readyState != WSState.Open)
+ {
+ Logger.Error("The connection is closing.");
+ return false;
+ }
+
+ var frame = new WSFrame(fin, opcode, data, compressed, _client);
+ return sendBytes(frame.ToArray());
+ }
+ }
+
+ private void sendAsync(OperationCode opcode, Stream stream, Action completed)
+ {
+ Func sender = send;
+ Task.Run(() =>
+ {
+ try
+ {
+ var sent = sender(opcode, stream);
+ completed?.Invoke(sent);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex.ToString());
+ Error("An error has occurred during the callback for an async send.", ex);
+ }
+ });
+ }
+
+ private bool sendBytes(byte[] bytes)
+ {
+ try
+ {
+ _stream.Write(bytes, 0, bytes.Length);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex.Message);
+ Logger.Debug(ex.ToString());
+
+ return false;
+ }
+
+ return true;
+ }
+
+ private WebResponse sendHandshakeRequest()
+ {
+ var req = createHandshakeRequest();
+ var res = sendHttpRequest(req, 90000);
+ if (res.IsUnauthorized)
+ {
+ var chal = res.Headers["WWW-Authenticate"];
+ Logger.Warning(string.Format("Received an authentication requirement for '{0}'.", chal));
+ if (chal.IsNullOrEmpty())
+ {
+ Logger.Error("No authentication challenge is specified.");
+ return res;
+ }
+
+ _authChallenge = AuthenticationChallenge.Parse(chal);
+ if (_authChallenge == null)
+ {
+ Logger.Error("An invalid authentication challenge is specified.");
+ return res;
+ }
+
+ if (Credentials != null &&
+ (!_preAuth || _authChallenge.Scheme == AuthenticationSchemes.Digest))
+ {
+ if (res.HasConnectionClose)
+ {
+ releaseClientResources();
+ SetClientStream();
+ }
+
+ var authRes = new AuthenticationResponse(_authChallenge, Credentials, _nonceCount);
+ _nonceCount = authRes.NonceCount;
+ req.Headers["Authorization"] = authRes.ToString();
+ res = sendHttpRequest(req, 15000);
+ }
+ }
+
+ if (res.IsRedirect)
+ {
+ var url = res.Headers["Location"];
+ Logger.Warning(string.Format("Received a redirection to '{0}'.", url));
+ if (_isRedirectionEnabled)
+ {
+ if (url.IsNullOrEmpty())
+ {
+ Logger.Error("No url to redirect is located.");
+ return res;
+ }
+
+ if (!url.TryCreateWebSocketUri(out Uri uri, out string message))
+ {
+ Logger.Error("An invalid url to redirect is located: " + message);
+ return res;
+ }
+
+ releaseClientResources();
+
+ _uri = uri;
+ IsSecure = uri.Scheme == "wss";
+
+ SetClientStream();
+ return sendHandshakeRequest();
+ }
+ }
+
+ return res;
+ }
+
+ private WebResponse sendHttpRequest(WebRequest request, int millisecondsTimeout)
+ {
+ Logger.Debug("Request to server:\n" + request.ToString());
+ var res = request.GetResponse(_stream, millisecondsTimeout);
+ Logger.Debug("Response to request:\n" + res.ToString());
+
+ return res;
+ }
+
+ private bool sendHttpResponse(WebResponse response)
+ {
+ Logger.Debug("Response to request:\n" + response.ToString());
+ return sendBytes(response.ToByteArray());
+ }
+
+ private void sendProxyConnectRequest()
+ {
+ var req = WebRequest.CreateConnectRequest(_uri);
+ var res = sendHttpRequest(req, 90000);
+ if (res.IsProxyAuthenticationRequired)
+ {
+ var chal = res.Headers["Proxy-Authenticate"];
+ Logger.Warning(
+ string.Format("Received a proxy authentication requirement for '{0}'.", chal));
+
+ if (chal.IsNullOrEmpty())
+ {
+ throw new WSException("No proxy authentication challenge is specified.");
+ }
+
+ var authChal = AuthenticationChallenge.Parse(chal);
+ if (authChal == null)
+ {
+ throw new WSException("An invalid proxy authentication challenge is specified.");
+ }
+
+ if (_proxyCredentials != null)
+ {
+ if (res.HasConnectionClose)
+ {
+ releaseClientResources();
+ _tcpClient = new TcpClient(_proxyUri.DnsSafeHost, _proxyUri.Port);
+ _stream = _tcpClient.GetStream();
+ }
+
+ var authRes = new AuthenticationResponse(authChal, _proxyCredentials, 0);
+ req.Headers["Proxy-Authorization"] = authRes.ToString();
+ res = sendHttpRequest(req, 15000);
+ }
+
+ if (res.IsProxyAuthenticationRequired)
+ {
+ throw new WSException("A proxy authentication is required.");
+ }
+ }
+
+ if (res.StatusCode[0] != '2')
+ {
+ throw new WSException(
+ "The proxy has failed a connection to the requested host and port.");
+ }
+ }
+
+ private void SetClientStream()
+ {
+ if (_proxyUri != null)
+ {
+ _tcpClient = new TcpClient(_proxyUri.DnsSafeHost, _proxyUri.Port);
+ _stream = _tcpClient.GetStream();
+ sendProxyConnectRequest();
+ }
+ else
+ {
+ _tcpClient = new TcpClient(_uri.DnsSafeHost, _uri.Port);
+ _stream = _tcpClient.GetStream();
+ }
+
+ if (IsSecure)
+ {
+ var conf = SSL;
+ var host = conf.TargetHost;
+ if (host != _uri.DnsSafeHost)
+ {
+ throw new WSException(
+ CloseStatusCode.TlsHandshakeFailure, "An invalid host name is specified.");
+ }
+
+ try
+ {
+ var sslStream = new SslStream(
+ _stream,
+ false,
+ conf.ServerCertificateValidationCallback,
+ conf.ClientCertificateSelectionCallback);
+
+ sslStream.ReadTimeout = (int)WaitTime.TotalMilliseconds;
+ sslStream.WriteTimeout = (int)WaitTime.TotalMilliseconds;
+
+ sslStream.AuthenticateAsClient(
+ host,
+ conf.Certificates,
+ conf.SslProtocols,
+ conf.CheckForCertificateRevocation);
+
+ _stream = sslStream;
+ }
+ catch (Exception ex)
+ {
+ throw new WSException(CloseStatusCode.TlsHandshakeFailure, ex);
+ }
+ }
+ }
+
+ private void Setup()
+ {
+ _compression = CompressionMethod.None;
+ CookieCollection = new CookieCollection();
+ _forPing = new object();
+ _forSend = new object();
+ _forState = new object();
+ _messageEventQueue = new Queue();
+ _forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot;
+ _readyState = WSState.Connecting;
+ }
+ private void startReceiving()
+ {
+ if (_messageEventQueue.Count > 0)
+ {
+ _messageEventQueue.Clear();
+ }
+
+ _pongReceived = new ManualResetEvent(false);
+ _receivingExited = new ManualResetEvent(false);
+
+ Action receive = null;
+ receive =
+ () =>
+ WSFrame.ReadFrameAsync(
+ _stream,
+ false,
+ frame =>
+ {
+ if (!processReceivedFrame(frame) || _readyState == WSState.Closed)
+ {
+ var exited = _receivingExited;
+ exited?.Set();
+
+ return;
+ }
+
+ // Receive next asap because the Ping or Close needs a response to it.
+ receive();
+
+ if (_inMessage || !HasMessage || _readyState != WSState.Open)
+ {
+ return;
+ }
+
+ message();
+ },
+ ex =>
+ {
+ Logger.Error(ex.ToString());
+ fatal("An exception has occurred while receiving.", ex);
+ }
+ );
+
+ receive();
+ }
+
+ private bool validateSecWebSocketAcceptHeader(string value)
+ {
+ return value != null && value == CreateResponseKey(_base64Key);
+ }
+
+ private bool validateSecWebSocketExtensionsClientHeader(string value)
+ {
+ return value == null || value.Length > 0;
+ }
+
+ private bool validateSecWebSocketExtensionsServerHeader(string value)
+ {
+ if (value == null)
+ {
+ return true;
+ }
+
+ if (value.Length == 0)
+ {
+ return false;
+ }
+
+ if (!_extensionsRequested)
+ {
+ return false;
+ }
+
+ 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.Warning("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";
+ }
+ );
+
+ if (invalid)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private bool validateSecWebSocketKeyHeader(string value)
+ {
+ return value != null && value.Length > 0;
+ }
+
+ private bool validateSecWebSocketProtocolClientHeader(string value)
+ {
+ return value == null || value.Length > 0;
+ }
+
+ private bool validateSecWebSocketProtocolServerHeader(string value)
+ {
+ if (value == null)
+ {
+ return !_protocolsRequested;
+ }
+
+ if (value.Length == 0)
+ {
+ return false;
+ }
+
+ return _protocolsRequested && _protocols.Contains(p => p == value);
+ }
+
+ private bool validateSecWebSocketVersionClientHeader(string value)
+ {
+ return value != null && value == _version;
+ }
+
+ private bool validateSecWebSocketVersionServerHeader(string value)
+ {
+ return value == null || value == _version;
}
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/WSFrame.cs b/EonaCat.Network/System/Sockets/Web/WSFrame.cs
index 886e386..7cb6250 100644
--- a/EonaCat.Network/System/Sockets/Web/WSFrame.cs
+++ b/EonaCat.Network/System/Sockets/Web/WSFrame.cs
@@ -11,19 +11,13 @@ namespace EonaCat.Network
{
internal class WSFrame : IEnumerable
{
- private const int BUFFER_SIZE = 1024;
-
internal static readonly byte[] EmptyPingBytes;
-
+ private const int BUFFER_SIZE = 1024;
static WSFrame()
{
EmptyPingBytes = CreatePingFrame(false).ToArray();
}
- private WSFrame()
- {
- }
-
internal WSFrame(OperationCode opcode, PayloadData payloadData, bool mask)
: this(FinalFrame.Final, opcode, payloadData, false, mask)
{
@@ -42,8 +36,9 @@ namespace EonaCat.Network
Rsv2 = Rsv.Off;
Rsv3 = Rsv.Off;
Opcode = opcode;
+ PayloadData = new PayloadData(payloadData);
- var len = payloadData.Length;
+ var len = PayloadData.Length;
if (len < 126)
{
PayloadLength = (byte)len;
@@ -64,17 +59,41 @@ namespace EonaCat.Network
{
Mask = Mask.On;
MaskingKey = createMaskingKey();
- payloadData.Mask(MaskingKey);
+ PayloadData.Mask(MaskingKey);
}
else
{
Mask = Mask.Off;
MaskingKey = WSClient.EmptyBytes;
}
-
- PayloadData = payloadData;
}
+ private WSFrame()
+ {
+ }
+ public byte[] ExtendedPayloadLength { get; private set; }
+ public FinalFrame Fin { get; private set; }
+ public bool IsBinary => Opcode == OperationCode.Binary;
+ public bool IsClose => Opcode == OperationCode.Close;
+ public bool IsCompressed => Rsv1 == Rsv.On;
+ public bool IsContinuation => Opcode == OperationCode.Continue;
+ public bool IsControl => Opcode >= OperationCode.Close;
+ public bool IsData => Opcode == OperationCode.Text || Opcode == OperationCode.Binary;
+ public bool IsFinal => Fin == FinalFrame.Final;
+ public bool IsFragment => Fin == FinalFrame.More || Opcode == OperationCode.Continue;
+ public bool IsMasked => Mask == Mask.On;
+ public bool IsPing => Opcode == OperationCode.Ping;
+ public bool IsPong => Opcode == OperationCode.Pong;
+ public bool IsText => Opcode == OperationCode.Text;
+ public ulong Length => 2 + (ulong)(ExtendedPayloadLength.Length + MaskingKey.Length) + PayloadData.Length;
+ public Mask Mask { get; private set; }
+ public byte[] MaskingKey { get; private set; }
+ public OperationCode Opcode { get; private set; }
+ public PayloadData PayloadData { get; private set; }
+ public byte PayloadLength { get; private set; }
+ public Rsv Rsv1 { get; private set; }
+ public Rsv Rsv2 { get; private set; }
+ public Rsv Rsv3 { get; private set; }
internal int ExtendedPayloadLengthCount => PayloadLength < 126 ? 0 : (PayloadLength == 126 ? 2 : 8);
internal ulong FullPayloadLength => PayloadLength < 126
@@ -82,52 +101,173 @@ namespace EonaCat.Network
: PayloadLength == 126
? ExtendedPayloadLength.ToUInt16(ByteOrder.Big)
: ExtendedPayloadLength.ToUInt64(ByteOrder.Big);
+ public IEnumerator GetEnumerator()
+ {
+ foreach (var b in ToArray())
+ {
+ yield return b;
+ }
+ }
- public byte[] ExtendedPayloadLength { get; private set; }
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
- public FinalFrame Fin { get; private set; }
+ public void Print(bool dumped)
+ {
+ Console.WriteLine(dumped ? dump(this) : print(this));
+ }
- public bool IsBinary => Opcode == OperationCode.Binary;
+ public string PrintToString(bool dumped)
+ {
+ return dumped ? dump(this) : print(this);
+ }
- public bool IsClose => Opcode == OperationCode.Close;
+ 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) + PayloadLength;
+ buff.Write(((ushort)header).InternalToByteArray(ByteOrder.Big), 0, 2);
- public bool IsCompressed => Rsv1 == Rsv.On;
+ if (PayloadLength > 125)
+ {
+ buff.Write(ExtendedPayloadLength, 0, PayloadLength == 126 ? 2 : 8);
+ }
- public bool IsContinuation => Opcode == OperationCode.Cont;
+ if (Mask == Mask.On)
+ {
+ buff.Write(MaskingKey, 0, 4);
+ }
- public bool IsControl => Opcode >= OperationCode.Close;
+ if (PayloadLength > 0)
+ {
+ var bytes = PayloadData.ToArray();
+ if (PayloadLength < 127)
+ {
+ buff.Write(bytes, 0, bytes.Length);
+ }
+ else
+ {
+ buff.WriteBytes(bytes, BUFFER_SIZE);
+ }
+ }
- public bool IsData => Opcode == OperationCode.Text || Opcode == OperationCode.Binary;
+ buff.Close();
+ return buff.ToArray();
+ }
+ }
- public bool IsFinal => Fin == FinalFrame.Final;
+ public override string ToString()
+ {
+ return BitConverter.ToString(ToArray());
+ }
- public bool IsFragment => Fin == FinalFrame.More || Opcode == OperationCode.Cont;
+ internal static WSFrame CreateCloseFrame(
+ PayloadData payloadData, bool mask
+ )
+ {
+ return new WSFrame(
+ FinalFrame.Final, OperationCode.Close, payloadData, false, mask
+ );
+ }
- public bool IsMasked => Mask == Mask.On;
+ internal static WSFrame CreatePingFrame(bool mask)
+ {
+ return new WSFrame(
+ FinalFrame.Final, OperationCode.Ping, PayloadData.Empty, false, mask
+ );
+ }
- public bool IsPing => Opcode == OperationCode.Ping;
+ internal static WSFrame CreatePingFrame(byte[] data, bool mask)
+ {
+ return new WSFrame(
+ FinalFrame.Final, OperationCode.Ping, new PayloadData(data), false, mask
+ );
+ }
- public bool IsPong => Opcode == OperationCode.Pong;
+ internal static WSFrame CreatePongFrame(
+ PayloadData payloadData, bool mask
+ )
+ {
+ return new WSFrame(
+ FinalFrame.Final, OperationCode.Pong, payloadData, false, mask
+ );
+ }
- public bool IsText => Opcode == OperationCode.Text;
+ internal static WSFrame ReadFrame(Stream stream, bool unmask)
+ {
+ var frame = readHeader(stream);
+ readExtendedPayloadLength(stream, frame);
+ readMaskingKey(stream, frame);
+ readPayloadData(stream, frame);
- public ulong Length => 2 + (ulong)(ExtendedPayloadLength.Length + MaskingKey.Length) + PayloadData.Length;
+ if (unmask)
+ {
+ frame.Unmask();
+ }
- public Mask Mask { get; private set; }
+ return frame;
+ }
- public byte[] MaskingKey { get; private set; }
+ internal static void ReadFrameAsync(
+ Stream stream,
+ bool unmask,
+ Action completed,
+ Action error
+ )
+ {
+ readHeaderAsync(
+ stream,
+ frame =>
+ readExtendedPayloadLengthAsync(
+ stream,
+ frame,
+ frame1 =>
+ readMaskingKeyAsync(
+ stream,
+ frame1,
+ frame2 =>
+ readPayloadDataAsync(
+ stream,
+ frame2,
+ frame3 =>
+ {
+ if (unmask)
+ {
+ frame3.Unmask();
+ }
- public OperationCode Opcode { get; private set; }
+ completed(frame3);
+ },
+ error
+ ),
+ error
+ ),
+ error
+ ),
+ error
+ );
+ }
- public PayloadData PayloadData { get; private set; }
+ internal void Unmask()
+ {
+ if (Mask == Mask.Off)
+ {
+ return;
+ }
- public byte PayloadLength { get; private set; }
-
- public Rsv Rsv1 { get; private set; }
-
- public Rsv Rsv2 { get; private set; }
-
- public Rsv Rsv3 { get; private set; }
+ Mask = Mask.Off;
+ PayloadData.Mask(MaskingKey);
+ MaskingKey = WSClient.EmptyBytes;
+ }
private static byte[] createMaskingKey()
{
@@ -496,173 +636,5 @@ Extended Payload Length: {7}
stream.ReadBytesAsync(llen, BUFFER_SIZE, compl, error);
}
-
- internal static WSFrame CreateCloseFrame(
- PayloadData payloadData, bool mask
- )
- {
- return new WSFrame(
- FinalFrame.Final, OperationCode.Close, payloadData, false, mask
- );
- }
-
- internal static WSFrame CreatePingFrame(bool mask)
- {
- return new WSFrame(
- FinalFrame.Final, OperationCode.Ping, PayloadData.Empty, false, mask
- );
- }
-
- internal static WSFrame CreatePingFrame(byte[] data, bool mask)
- {
- return new WSFrame(
- FinalFrame.Final, OperationCode.Ping, new PayloadData(data), false, mask
- );
- }
-
- internal static WSFrame CreatePongFrame(
- PayloadData payloadData, bool mask
- )
- {
- return new WSFrame(
- FinalFrame.Final, OperationCode.Pong, payloadData, false, mask
- );
- }
-
- internal static WSFrame ReadFrame(Stream stream, bool unmask)
- {
- var frame = readHeader(stream);
- readExtendedPayloadLength(stream, frame);
- readMaskingKey(stream, frame);
- readPayloadData(stream, frame);
-
- if (unmask)
- {
- frame.Unmask();
- }
-
- return frame;
- }
-
- internal static void ReadFrameAsync(
- Stream stream,
- bool unmask,
- Action completed,
- Action error
- )
- {
- readHeaderAsync(
- stream,
- frame =>
- readExtendedPayloadLengthAsync(
- stream,
- frame,
- frame1 =>
- readMaskingKeyAsync(
- stream,
- frame1,
- frame2 =>
- readPayloadDataAsync(
- stream,
- frame2,
- frame3 =>
- {
- if (unmask)
- {
- frame3.Unmask();
- }
-
- completed(frame3);
- },
- error
- ),
- error
- ),
- error
- ),
- error
- );
- }
-
- internal void Unmask()
- {
- if (Mask == Mask.Off)
- {
- return;
- }
-
- Mask = Mask.Off;
- PayloadData.Mask(MaskingKey);
- MaskingKey = WSClient.EmptyBytes;
- }
-
- public IEnumerator GetEnumerator()
- {
- foreach (var b in ToArray())
- {
- 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) + PayloadLength;
- buff.Write(((ushort)header).InternalToByteArray(ByteOrder.Big), 0, 2);
-
- if (PayloadLength > 125)
- {
- buff.Write(ExtendedPayloadLength, 0, PayloadLength == 126 ? 2 : 8);
- }
-
- if (Mask == Mask.On)
- {
- buff.Write(MaskingKey, 0, 4);
- }
-
- if (PayloadLength > 0)
- {
- var bytes = PayloadData.ToArray();
- if (PayloadLength < 127)
- {
- buff.Write(bytes, 0, bytes.Length);
- }
- else
- {
- buff.WriteBytes(bytes, BUFFER_SIZE);
- }
- }
-
- buff.Close();
- return buff.ToArray();
- }
- }
-
- public override string ToString()
- {
- return BitConverter.ToString(ToArray());
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/WebBase.cs b/EonaCat.Network/System/Sockets/Web/WebBase.cs
index 940df0b..8f372e3 100644
--- a/EonaCat.Network/System/Sockets/Web/WebBase.cs
+++ b/EonaCat.Network/System/Sockets/Web/WebBase.cs
@@ -12,11 +12,9 @@ namespace EonaCat.Network
{
internal abstract class WebBase
{
- private const int _headersMaxLength = 8192;
internal byte[] EntityBodyData;
-
protected const string CrLf = "\r\n";
-
+ private const int _headersMaxLength = 8192;
protected WebBase(Version version, NameValueCollection headers)
{
ProtocolVersion = version;
@@ -48,6 +46,60 @@ namespace EonaCat.Network
public Version ProtocolVersion { get; }
+ public byte[] ToByteArray()
+ {
+ return Encoding.UTF8.GetBytes(ToString());
+ }
+
+ protected static T Read(Stream stream, Func parser, int millisecondsTimeout)
+ where T : WebBase
+ {
+ var timeout = false;
+ var timer = new Timer(
+ state =>
+ {
+ timeout = true;
+ stream.Close();
+ },
+ null,
+ millisecondsTimeout,
+ -1);
+
+ T http = null;
+ Exception exception = null;
+ try
+ {
+ http = parser(readHeaders(stream, _headersMaxLength));
+ var contentLen = http.Headers["Content-Length"];
+ if (contentLen != null && contentLen.Length > 0)
+ {
+ http.EntityBodyData = readEntityBody(stream, contentLen);
+ }
+ }
+ catch (Exception ex)
+ {
+ exception = ex;
+ }
+ finally
+ {
+ timer.Change(-1, -1);
+ timer.Dispose();
+ }
+
+ var message = 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 (message != null)
+ {
+ throw new WSException(message, exception);
+ }
+
+ return http;
+ }
+
private static byte[] readEntityBody(Stream stream, string length)
{
if (!long.TryParse(length, out long len))
@@ -105,59 +157,5 @@ namespace EonaCat.Network
.Replace(CrLf + "\t", " ")
.Split(new[] { CrLf }, StringSplitOptions.RemoveEmptyEntries);
}
-
- protected static T Read(Stream stream, Func parser, int millisecondsTimeout)
- where T : WebBase
- {
- var timeout = false;
- var timer = new Timer(
- state =>
- {
- timeout = true;
- stream.Close();
- },
- null,
- millisecondsTimeout,
- -1);
-
- T http = null;
- Exception exception = null;
- try
- {
- http = parser(readHeaders(stream, _headersMaxLength));
- var contentLen = http.Headers["Content-Length"];
- if (contentLen != null && contentLen.Length > 0)
- {
- http.EntityBodyData = readEntityBody(stream, contentLen);
- }
- }
- catch (Exception ex)
- {
- exception = ex;
- }
- finally
- {
- timer.Change(-1, -1);
- timer.Dispose();
- }
-
- var message = 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 (message != null)
- {
- throw new WSException(message, exception);
- }
-
- return http;
- }
-
- public byte[] ToByteArray()
- {
- return Encoding.UTF8.GetBytes(ToString());
- }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/WebRequest.cs b/EonaCat.Network/System/Sockets/Web/WebRequest.cs
index ced217f..2b14982 100644
--- a/EonaCat.Network/System/Sockets/Web/WebRequest.cs
+++ b/EonaCat.Network/System/Sockets/Web/WebRequest.cs
@@ -14,19 +14,18 @@ namespace EonaCat.Network
private bool _websocketRequest;
private bool _websocketRequestSet;
- private WebRequest(string method, string uri, Version version, NameValueCollection headers)
- : base(version, headers)
- {
- HttpMethod = method;
- RequestUri = uri;
- }
-
internal WebRequest(string method, string uri)
: this(method, uri, HttpVersion.Version11, new NameValueCollection())
{
Headers["User-Agent"] = $"EonaCat.Network/{Constants.Version}";
}
+ private WebRequest(string method, string uri, Version version, NameValueCollection headers)
+ : base(version, headers)
+ {
+ HttpMethod = method;
+ RequestUri = uri;
+ }
public AuthenticationResponse AuthenticationResponse
{
get
@@ -63,67 +62,6 @@ namespace EonaCat.Network
public string RequestUri { get; }
- internal static WebRequest CreateConnectRequest(Uri uri)
- {
- var host = uri.DnsSafeHost;
- var port = uri.Port;
- var authority = string.Format("{0}:{1}", host, port);
- var req = new WebRequest("CONNECT", authority);
- req.Headers["Host"] = port == 80 ? host : authority;
-
- return req;
- }
-
- internal static WebRequest CreateWebSocketRequest(Uri uri)
- {
- var req = new WebRequest("GET", uri.PathAndQuery);
- var headers = req.Headers;
-
- // 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["Upgrade"] = "websocket";
- headers["Connection"] = "Upgrade";
-
- return req;
- }
-
- internal WebResponse GetResponse(Stream stream, int millisecondsTimeout)
- {
- var buff = ToByteArray();
- stream.Write(buff, 0, buff.Length);
-
- return Read(stream, WebResponse.Parse, millisecondsTimeout);
- }
-
- internal static WebRequest Parse(string[] headerParts)
- {
- var requestLine = headerParts[0].Split(new[] { ' ' }, 3);
- if (requestLine.Length != 3)
- {
- throw new ArgumentException("Invalid request line: " + headerParts[0]);
- }
-
- var headers = new WebHeaderCollection();
- for (int i = 1; i < headerParts.Length; i++)
- {
- headers.InternalSet(headerParts[i], false);
- }
-
- return new WebRequest(
- requestLine[0], requestLine[1], new Version(requestLine[2].Substring(5)), headers);
- }
-
- internal static WebRequest Read(Stream stream, int millisecondsTimeout)
- {
- return Read(stream, Parse, millisecondsTimeout);
- }
-
public void SetCookies(CookieCollection cookies)
{
if (cookies == null || cookies.Count == 0)
@@ -169,5 +107,66 @@ namespace EonaCat.Network
return output.ToString();
}
+
+ internal static WebRequest CreateConnectRequest(Uri uri)
+ {
+ var host = uri.DnsSafeHost;
+ var port = uri.Port;
+ var authority = string.Format("{0}:{1}", host, port);
+ var req = new WebRequest("CONNECT", authority);
+ req.Headers["Host"] = port == 80 ? host : authority;
+
+ return req;
+ }
+
+ internal static WebRequest CreateWebSocketRequest(Uri uri)
+ {
+ var req = new WebRequest("GET", uri.PathAndQuery);
+ var headers = req.Headers;
+
+ // 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["Upgrade"] = "websocket";
+ headers["Connection"] = "Upgrade";
+
+ return req;
+ }
+
+ internal static WebRequest Parse(string[] headerParts)
+ {
+ var requestLine = headerParts[0].Split(new[] { ' ' }, 3);
+ if (requestLine.Length != 3)
+ {
+ throw new ArgumentException("Invalid request line: " + headerParts[0]);
+ }
+
+ var headers = new WebHeaderCollection();
+ for (int i = 1; i < headerParts.Length; i++)
+ {
+ headers.InternalSet(headerParts[i], false);
+ }
+
+ return new WebRequest(
+ requestLine[0], requestLine[1], new Version(requestLine[2].Substring(5)), headers);
+ }
+
+ internal static WebRequest Read(Stream stream, int millisecondsTimeout)
+ {
+ return Read(stream, Parse, millisecondsTimeout);
+ }
+
+ internal WebResponse GetResponse(Stream stream, int millisecondsTimeout)
+ {
+ var buff = ToByteArray();
+ stream.Write(buff, 0, buff.Length);
+
+ return Read(stream, WebResponse.Parse, millisecondsTimeout);
+ }
}
}
\ No newline at end of file
diff --git a/EonaCat.Network/System/Sockets/Web/WebResponse.cs b/EonaCat.Network/System/Sockets/Web/WebResponse.cs
index 3353bdf..e5b0e1a 100644
--- a/EonaCat.Network/System/Sockets/Web/WebResponse.cs
+++ b/EonaCat.Network/System/Sockets/Web/WebResponse.cs
@@ -11,13 +11,6 @@ namespace EonaCat.Network
{
internal class WebResponse : WebBase
{
- private WebResponse(string code, string reason, Version version, NameValueCollection headers)
- : base(version, headers)
- {
- StatusCode = code;
- Reason = reason;
- }
-
internal WebResponse(HttpStatusCode code)
: this(code, code.GetDescription())
{
@@ -29,6 +22,12 @@ namespace EonaCat.Network
Headers["Server"] = $"EonaCat.Network/{Constants.Version}";
}
+ private WebResponse(string code, string reason, Version version, NameValueCollection headers)
+ : base(version, headers)
+ {
+ StatusCode = code;
+ Reason = reason;
+ }
public CookieCollection Cookies => Headers.GetCookies(true);
public bool HasConnectionClose => Headers.Contains("Connection", "close");
@@ -55,6 +54,42 @@ namespace EonaCat.Network
public string StatusCode { get; }
+ public void SetCookies(CookieCollection cookies)
+ {
+ if (cookies == null || cookies.Count == 0)
+ {
+ 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, StatusCode, Reason, CrLf);
+
+ 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();
+ }
+
internal static WebResponse CreateCloseResponse(HttpStatusCode code)
{
var res = new WebResponse(code);
@@ -104,41 +139,5 @@ namespace EonaCat.Network
{
return Read(stream, Parse, millisecondsTimeout);
}
-
- public void SetCookies(CookieCollection cookies)
- {
- if (cookies == null || cookies.Count == 0)
- {
- 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, StatusCode, Reason, CrLf);
-
- 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();
- }
}
}
\ No newline at end of file