This commit is contained in:
Jeroen 2023-11-17 20:26:11 +01:00
parent e6517ad662
commit 08a2be3221
49 changed files with 7033 additions and 7069 deletions

View File

@ -84,7 +84,7 @@ namespace EonaCat.Network
/// <returns>A collection of authentication parameters.</returns> /// <returns>A collection of authentication parameters.</returns>
internal static NameValueCollection ParseParameters(string value) internal static NameValueCollection ParseParameters(string value)
{ {
var res = new NameValueCollection(); var result = new NameValueCollection();
foreach (var param in value.SplitHeaderValue(',')) foreach (var param in value.SplitHeaderValue(','))
{ {
var i = param.IndexOf('='); var i = param.IndexOf('=');
@ -95,10 +95,10 @@ namespace EonaCat.Network
? param.Substring(i + 1).Trim().Trim('"') ? param.Substring(i + 1).Trim().Trim('"')
: string.Empty; : string.Empty;
res.Add(name, val); result.Add(name, val);
} }
return res; return result;
} }
/// <summary> /// <summary>

View File

@ -15,16 +15,6 @@ namespace EonaCat.Network
private const string DIGEST = "digest"; private const string DIGEST = "digest";
private const int DIGEST_SIZE = 128; private const int DIGEST_SIZE = 128;
/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationChallenge"/> class.
/// </summary>
/// <param name="scheme">The authentication scheme.</param>
/// <param name="parameters">The collection of authentication parameters.</param>
private AuthenticationChallenge(AuthenticationSchemes scheme, NameValueCollection parameters)
: base(scheme, parameters)
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AuthenticationChallenge"/> class for Basic or Digest authentication. /// Initializes a new instance of the <see cref="AuthenticationChallenge"/> class for Basic or Digest authentication.
/// </summary> /// </summary>
@ -42,6 +32,15 @@ namespace EonaCat.Network
} }
} }
/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationChallenge"/> class.
/// </summary>
/// <param name="scheme">The authentication scheme.</param>
/// <param name="parameters">The collection of authentication parameters.</param>
private AuthenticationChallenge(AuthenticationSchemes scheme, NameValueCollection parameters)
: base(scheme, parameters)
{
}
/// <summary> /// <summary>
/// Gets the domain for Digest authentication. /// Gets the domain for Digest authentication.
/// </summary> /// </summary>

View File

@ -18,16 +18,6 @@ namespace EonaCat.Network
private const string DIGEST = "digest"; private const string DIGEST = "digest";
private uint _nonceCount; private uint _nonceCount;
/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationResponse"/> class.
/// </summary>
/// <param name="scheme">The authentication scheme.</param>
/// <param name="parameters">The collection of authentication parameters.</param>
private AuthenticationResponse(AuthenticationSchemes scheme, NameValueCollection parameters)
: base(scheme, parameters)
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AuthenticationResponse"/> class for Basic authentication. /// Initializes a new instance of the <see cref="AuthenticationResponse"/> class for Basic authentication.
/// </summary> /// </summary>
@ -74,12 +64,14 @@ namespace EonaCat.Network
} }
/// <summary> /// <summary>
/// Gets the nonce count. /// Initializes a new instance of the <see cref="AuthenticationResponse"/> class.
/// </summary> /// </summary>
internal uint NonceCount => _nonceCount < uint.MaxValue /// <param name="scheme">The authentication scheme.</param>
? _nonceCount /// <param name="parameters">The collection of authentication parameters.</param>
: 0; private AuthenticationResponse(AuthenticationSchemes scheme, NameValueCollection parameters)
: base(scheme, parameters)
{
}
/// <summary> /// <summary>
/// Gets the cnonce value for Digest authentication. /// Gets the cnonce value for Digest authentication.
/// </summary> /// </summary>
@ -111,97 +103,23 @@ namespace EonaCat.Network
public string UserName => Parameters["username"]; public string UserName => Parameters["username"];
/// <summary> /// <summary>
/// Creates the A1 value for Digest authentication. /// Gets the nonce count.
/// </summary> /// </summary>
/// <param name="username">The username.</param> internal uint NonceCount => _nonceCount < uint.MaxValue
/// <param name="password">The password.</param> ? _nonceCount
/// <param name="realm">The realm.</param> : 0;
/// <returns>The A1 value.</returns>
private static string CreateA1(string username, string password, string realm)
{
return $"{username}:{realm}:{password}";
}
/// <summary> /// <summary>
/// Creates the A1 value for Digest authentication with cnonce and nonce. /// Converts the authentication response to an identity.
/// </summary> /// </summary>
/// <param name="username">The username.</param> /// <returns>An instance of <see cref="IIdentity"/>.</returns>
/// <param name="password">The password.</param> public IIdentity ToIdentity()
/// <param name="realm">The realm.</param>
/// <param name="nonce">The nonce.</param>
/// <param name="cnonce">The cnonce.</param>
/// <returns>The A1 value.</returns>
private static string CreateA1(
string username, string password, string realm, string nonce, string cnonce)
{ {
return $"{Hash(CreateA1(username, password, realm))}:{nonce}:{cnonce}"; var scheme = Scheme;
} return scheme == AuthenticationSchemes.Basic
? new HttpBasicIdentity(Parameters["username"], Parameters["password"])
/// <summary> : scheme == AuthenticationSchemes.Digest
/// Creates the A2 value for Digest authentication. ? new HttpDigestIdentity(Parameters)
/// </summary> : null;
/// <param name="method">The HTTP method.</param>
/// <param name="uri">The URI.</param>
/// <returns>The A2 value.</returns>
private static string CreateA2(string method, string uri)
{
return $"{method}:{uri}";
}
/// <summary>
/// Creates the A2 value for Digest authentication with an entity.
/// </summary>
/// <param name="method">The HTTP method.</param>
/// <param name="uri">The URI.</param>
/// <param name="entity">The entity.</param>
/// <returns>The A2 value.</returns>
private static string CreateA2(string method, string uri, string entity)
{
return $"{method}:{uri}:{Hash(entity)}";
}
/// <summary>
/// Computes the MD5 hash of the given value.
/// </summary>
/// <param name="value">The value to hash.</param>
/// <returns>The MD5 hash.</returns>
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();
}
/// <summary>
/// Initializes the authentication as Digest.
/// </summary>
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);
} }
/// <summary> /// <summary>
@ -343,17 +261,97 @@ namespace EonaCat.Network
} }
/// <summary> /// <summary>
/// Converts the authentication response to an identity. /// Creates the A1 value for Digest authentication.
/// </summary> /// </summary>
/// <returns>An instance of <see cref="IIdentity"/>.</returns> /// <param name="username">The username.</param>
public IIdentity ToIdentity() /// <param name="password">The password.</param>
/// <param name="realm">The realm.</param>
/// <returns>The A1 value.</returns>
private static string CreateA1(string username, string password, string realm)
{ {
var scheme = Scheme; return $"{username}:{realm}:{password}";
return scheme == AuthenticationSchemes.Basic }
? new HttpBasicIdentity(Parameters["username"], Parameters["password"])
: scheme == AuthenticationSchemes.Digest /// <summary>
? new HttpDigestIdentity(Parameters) /// Creates the A1 value for Digest authentication with cnonce and nonce.
: null; /// </summary>
/// <param name="username">The username.</param>
/// <param name="password">The password.</param>
/// <param name="realm">The realm.</param>
/// <param name="nonce">The nonce.</param>
/// <param name="cnonce">The cnonce.</param>
/// <returns>The A1 value.</returns>
private static string CreateA1(
string username, string password, string realm, string nonce, string cnonce)
{
return $"{Hash(CreateA1(username, password, realm))}:{nonce}:{cnonce}";
}
/// <summary>
/// Creates the A2 value for Digest authentication.
/// </summary>
/// <param name="method">The HTTP method.</param>
/// <param name="uri">The URI.</param>
/// <returns>The A2 value.</returns>
private static string CreateA2(string method, string uri)
{
return $"{method}:{uri}";
}
/// <summary>
/// Creates the A2 value for Digest authentication with an entity.
/// </summary>
/// <param name="method">The HTTP method.</param>
/// <param name="uri">The URI.</param>
/// <param name="entity">The entity.</param>
/// <returns>The A2 value.</returns>
private static string CreateA2(string method, string uri, string entity)
{
return $"{method}:{uri}:{Hash(entity)}";
}
/// <summary>
/// Computes the MD5 hash of the given value.
/// </summary>
/// <param name="value">The value to hash.</param>
/// <returns>The MD5 hash.</returns>
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();
}
/// <summary>
/// Initializes the authentication as Digest.
/// </summary>
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);
} }
} }
} }

View File

@ -10,8 +10,8 @@ namespace EonaCat.Network
/// </summary> /// </summary>
public class NetworkCredential public class NetworkCredential
{ {
private string _domain;
private static readonly string[] _noRoles; private static readonly string[] _noRoles;
private string _domain;
private string _password; private string _password;
private string[] _roles; private string[] _roles;

View File

@ -15,11 +15,11 @@ namespace EonaCat.Network
/// </summary> /// </summary>
internal class ChunkStream internal class ChunkStream
{ {
private readonly List<WebChunk> _chunks;
private readonly StringBuilder _saved;
private int _chunkRead; private int _chunkRead;
private int _chunkSize; private int _chunkSize;
private readonly List<WebChunk> _chunks;
private bool _foundSPCode; private bool _foundSPCode;
private readonly StringBuilder _saved;
private bool _gotChunck; private bool _gotChunck;
private InputChunkState _state; private InputChunkState _state;
private int _trailerState; private int _trailerState;
@ -49,11 +49,6 @@ namespace EonaCat.Network
Write(buffer, offset, count); Write(buffer, offset, count);
} }
/// <summary>
/// Gets the web headers associated with the chunk stream.
/// </summary>
internal WebHeaderCollection Headers { get; }
/// <summary> /// <summary>
/// Gets the number of bytes left in the current chunk. /// Gets the number of bytes left in the current chunk.
/// </summary> /// </summary>
@ -64,6 +59,78 @@ namespace EonaCat.Network
/// </summary> /// </summary>
public bool WantMore => _state != InputChunkState.End; public bool WantMore => _state != InputChunkState.End;
/// <summary>
/// Gets the web headers associated with the chunk stream.
/// </summary>
internal WebHeaderCollection Headers { get; }
/// <summary>
/// Reads a specified amount of data from the chunk stream.
/// </summary>
/// <param name="buffer">The destination buffer.</param>
/// <param name="offset">The zero-based byte offset in the buffer at which to begin storing the data.</param>
/// <param name="count">The maximum number of bytes to read.</param>
/// <returns>The total number of bytes read into the buffer.</returns>
public int Read(byte[] buffer, int offset, int count)
{
if (count <= 0)
{
return 0;
}
return read(buffer, offset, count);
}
/// <summary>
/// Writes a specified amount of data to the chunk stream.
/// </summary>
/// <param name="buffer">The byte array containing data to be written to the chunk stream.</param>
/// <param name="offset">The offset in the buffer at which to begin writing.</param>
/// <param name="count">The number of bytes to write to the chunk stream.</param>
public void Write(byte[] buffer, int offset, int count)
{
if (count <= 0)
{
return;
}
Write(buffer, ref offset, offset + count);
}
/// <summary>
/// Resets the internal buffer and state of the chunk stream.
/// </summary>
internal void ResetBuffer()
{
_chunkRead = 0;
_chunkSize = -1;
_chunks.Clear();
}
/// <summary>
/// Writes a specified amount of data to the chunk stream and reads it back.
/// </summary>
/// <param name="buffer">The byte array containing data to be written to the chunk stream.</param>
/// <param name="offset">The offset in the buffer at which to begin writing.</param>
/// <param name="writeCount">The number of bytes to write to the chunk stream.</param>
/// <param name="readCount">The number of bytes to read back from the chunk stream.</param>
/// <returns>The number of bytes read from the chunk stream.</returns>
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) private int read(byte[] buffer, int offset, int count)
{ {
var nread = 0; var nread = 0;
@ -92,13 +159,6 @@ namespace EonaCat.Network
return nread; 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) private InputChunkState SeekCrLf(byte[] buffer, ref int offset, int length)
{ {
if (!_gotChunck) if (!_gotChunck)
@ -256,12 +316,6 @@ namespace EonaCat.Network
return InputChunkState.End; 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) private void Write(byte[] buffer, ref int offset, int length)
{ {
if (_state == InputChunkState.End) if (_state == InputChunkState.End)
@ -337,62 +391,5 @@ namespace EonaCat.Network
return _chunkRead == _chunkSize ? InputChunkState.DataEnded : InputChunkState.Data; return _chunkRead == _chunkSize ? InputChunkState.DataEnded : InputChunkState.Data;
} }
/// <summary>
/// Resets the internal buffer and state of the chunk stream.
/// </summary>
internal void ResetBuffer()
{
_chunkRead = 0;
_chunkSize = -1;
_chunks.Clear();
}
/// <summary>
/// Writes a specified amount of data to the chunk stream and reads it back.
/// </summary>
/// <param name="buffer">The byte array containing data to be written to the chunk stream.</param>
/// <param name="offset">The offset in the buffer at which to begin writing.</param>
/// <param name="writeCount">The number of bytes to write to the chunk stream.</param>
/// <param name="readCount">The number of bytes to read back from the chunk stream.</param>
/// <returns>The number of bytes read from the chunk stream.</returns>
internal int WriteAndReadBack(byte[] buffer, int offset, int writeCount, int readCount)
{
Write(buffer, offset, writeCount);
return Read(buffer, offset, readCount);
}
/// <summary>
/// Reads a specified amount of data from the chunk stream.
/// </summary>
/// <param name="buffer">The destination buffer.</param>
/// <param name="offset">The zero-based byte offset in the buffer at which to begin storing the data.</param>
/// <param name="count">The maximum number of bytes to read.</param>
/// <returns>The total number of bytes read into the buffer.</returns>
public int Read(byte[] buffer, int offset, int count)
{
if (count <= 0)
{
return 0;
}
return read(buffer, offset, count);
}
/// <summary>
/// Writes a specified amount of data to the chunk stream.
/// </summary>
/// <param name="buffer">The byte array containing data to be written to the chunk stream.</param>
/// <param name="offset">The offset in the buffer at which to begin writing.</param>
/// <param name="count">The number of bytes to write to the chunk stream.</param>
public void Write(byte[] buffer, int offset, int count)
{
if (count <= 0)
{
return;
}
Write(buffer, ref offset, offset + count);
}
} }
} }

View File

@ -36,37 +36,6 @@ namespace EonaCat.Network
/// </summary> /// </summary>
internal ChunkStream Decoder { get; set; } 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);
}
}
/// <summary> /// <summary>
/// Begins an asynchronous read operation from the stream. /// Begins an asynchronous read operation from the stream.
/// </summary> /// </summary>
@ -204,5 +173,36 @@ namespace EonaCat.Network
var result = BeginRead(buffer, offset, count, null, null); var result = BeginRead(buffer, offset, count, null, null);
return EndRead(result); 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);
}
}
} }
} }

View File

@ -90,6 +90,10 @@ namespace EonaCat.Network
}; };
} }
public WebHeaderCollection()
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the WebHeaderCollection class with the specified parameters. /// Initializes a new instance of the WebHeaderCollection class with the specified parameters.
/// </summary> /// </summary>
@ -132,18 +136,10 @@ namespace EonaCat.Network
throw new ArgumentException(ex.Message, nameof(serializationInfo), ex); throw new ArgumentException(ex.Message, nameof(serializationInfo), ex);
} }
} }
public WebHeaderCollection()
{
}
internal HttpHeaderType State => _state;
public override string[] AllKeys => base.AllKeys; public override string[] AllKeys => base.AllKeys;
public override int Count => base.Count; public override int Count => base.Count;
public override KeysCollection Keys => base.Keys;
internal HttpHeaderType State => _state;
public string this[HttpRequestHeader header] public string this[HttpRequestHeader header]
{ {
get get
@ -169,272 +165,14 @@ namespace EonaCat.Network
Add(header, value); Add(header, value);
} }
} }
public static bool IsRestricted(string headerName)
public override KeysCollection Keys => base.Keys;
private void add(string name, string value, bool ignoreRestricted)
{ {
var act = ignoreRestricted return isRestricted(CheckName(headerName), false);
? (Action<string, string>)addWithoutCheckingNameAndRestricted
: addWithoutCheckingName;
DoWithCheckingState(act, CheckName(name), value, true);
} }
private void addWithoutCheckingName(string name, string value) public static bool IsRestricted(string headerName, bool response)
{ {
DoWithoutCheckingName(base.Add, name, value); return isRestricted(CheckName(headerName), response);
}
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<string, string> 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<string, string> 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<string, string> 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);
}
/// <summary>
/// Converts the specified HttpRequestHeader to a string.
/// </summary>
/// <param name="header">The HttpRequestHeader to convert.</param>
/// <returns>A string representing the converted HttpRequestHeader.</returns>
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);
} }
public void Add(string header) public void Add(string header)
@ -489,18 +227,6 @@ namespace EonaCat.Network
return base.GetKey(index); 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( [SecurityPermission(
SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)] SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.SerializationFormatter)]
public override void GetObjectData( 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) public override void OnDeserialization(object sender)
@ -589,14 +327,266 @@ namespace EonaCat.Network
return buff.Append("\r\n").ToString(); return buff.Append("\r\n").ToString();
} }
[SecurityPermission( /// <summary>
SecurityAction.LinkDemand, /// Converts the specified HttpRequestHeader to a string.
Flags = SecurityPermissionFlag.SerializationFormatter, /// </summary>
SerializationFormatter = true)] /// <param name="header">The HttpRequestHeader to convert.</param>
void ISerializable.GetObjectData( /// <returns>A string representing the converted HttpRequestHeader.</returns>
SerializationInfo serializationInfo, StreamingContext streamingContext) 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<string, string>)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<string, string> 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<string, string> 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<string, string> 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);
} }
} }
} }

View File

@ -33,11 +33,6 @@ namespace EonaCat.Network
_websocket = new WSClient(this, protocol); _websocket = new WSClient(this, protocol);
} }
/// <summary>
/// Gets the stream of the underlying TCP connection.
/// </summary>
internal Stream Stream => _context.Connection.Stream;
public override CookieCollection CookieCollection => _context.Request.Cookies; public override CookieCollection CookieCollection => _context.Request.Cookies;
public override NameValueCollection Headers => _context.Request.Headers; public override NameValueCollection Headers => _context.Request.Headers;
@ -85,6 +80,16 @@ namespace EonaCat.Network
public override WSClient WebSocket => _websocket; public override WSClient WebSocket => _websocket;
/// <summary>
/// Gets the stream of the underlying TCP connection.
/// </summary>
internal Stream Stream => _context.Connection.Stream;
/// <inheritdoc />
public override string ToString()
{
return _context.Request.ToString();
}
/// <summary> /// <summary>
/// Closes the WebSocket connection. /// Closes the WebSocket connection.
/// </summary> /// </summary>
@ -101,11 +106,5 @@ namespace EonaCat.Network
{ {
_context.Response.Close(code); _context.Response.Close(code);
} }
/// <inheritdoc />
public override string ToString()
{
return _context.Request.ToString();
}
} }
} }

View File

@ -21,15 +21,14 @@ namespace EonaCat.Network
/// </remarks> /// </remarks>
internal class TcpListenerWSContext : WSContext internal class TcpListenerWSContext : WSContext
{ {
private CookieCollection _cookies;
private NameValueCollection _queryString;
private WebRequest _request;
private readonly bool _secure; private readonly bool _secure;
private readonly TcpClient _tcpClient; private readonly TcpClient _tcpClient;
private readonly Uri _uri; private readonly Uri _uri;
private IPrincipal _user;
private readonly WSClient _websocket; private readonly WSClient _websocket;
private CookieCollection _cookies;
private NameValueCollection _queryString;
private WebRequest _request;
private IPrincipal _user;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TcpListenerWSContext"/> class. /// Initializes a new instance of the <see cref="TcpListenerWSContext"/> class.
/// </summary> /// </summary>
@ -76,11 +75,6 @@ namespace EonaCat.Network
_websocket = new WSClient(this, protocol); _websocket = new WSClient(this, protocol);
} }
/// <summary>
/// Gets the stream of the underlying TCP connection.
/// </summary>
internal Stream Stream { get; }
public override CookieCollection CookieCollection => _cookies ??= _request.Cookies; public override CookieCollection CookieCollection => _cookies ??= _request.Cookies;
public override NameValueCollection Headers => _request.Headers; public override NameValueCollection Headers => _request.Headers;
@ -132,6 +126,16 @@ namespace EonaCat.Network
public override WSClient WebSocket => _websocket; public override WSClient WebSocket => _websocket;
/// <summary>
/// Gets the stream of the underlying TCP connection.
/// </summary>
internal Stream Stream { get; }
/// <inheritdoc />
public override string ToString()
{
return _request.ToString();
}
/// <summary> /// <summary>
/// Authenticates the WebSocket connection based on the specified authentication scheme. /// Authenticates the WebSocket connection based on the specified authentication scheme.
/// </summary> /// </summary>
@ -220,11 +224,5 @@ namespace EonaCat.Network
Stream.Write(buff, 0, buff.Length); Stream.Write(buff, 0, buff.Length);
_request = WebRequest.Read(Stream, 15000); _request = WebRequest.Read(Stream, 15000);
} }
/// <inheritdoc />
public override string ToString()
{
return _request.ToString();
}
} }
} }

View File

@ -13,6 +13,9 @@ namespace EonaCat.Network
[Serializable] [Serializable]
public sealed class Cookie public sealed class Cookie
{ {
private static readonly char[] _reservedCharsForName;
private static readonly char[] _reservedCharsForValue;
private readonly DateTime _timestamp;
private string _comment; private string _comment;
private Uri _commentUri; private Uri _commentUri;
private bool _discard; private bool _discard;
@ -23,10 +26,7 @@ namespace EonaCat.Network
private string _path; private string _path;
private string _port; private string _port;
private int[] _ports; private int[] _ports;
private static readonly char[] _reservedCharsForName;
private static readonly char[] _reservedCharsForValue;
private bool _secure; private bool _secure;
private readonly DateTime _timestamp;
private string _value; private string _value;
private int _version; private int _version;
@ -90,33 +90,6 @@ namespace EonaCat.Network
Domain = domain; 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 public string Comment
{ {
get 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;
/// <inheritdoc />
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;
}
/// <inheritdoc />
public override int GetHashCode()
{
return hash(
StringComparer.InvariantCultureIgnoreCase.GetHashCode(_name),
_value.GetHashCode(),
_path.GetHashCode(),
StringComparer.InvariantCultureIgnoreCase.GetHashCode(_domain),
_version);
}
/// <inheritdoc />
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) private static bool canSetName(string name, out string message)
{ {
if (name.IsNullOrEmpty()) if (name.IsNullOrEmpty())
@ -378,6 +463,36 @@ namespace EonaCat.Network
(m << 20 | m >> 12); (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() private string toResponseStringVersion0()
{ {
var output = new StringBuilder(64); var output = new StringBuilder(64);
@ -470,121 +585,5 @@ namespace EonaCat.Network
return output.ToString(); 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;
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
public override int GetHashCode()
{
return hash(
StringComparer.InvariantCultureIgnoreCase.GetHashCode(_name),
_value.GetHashCode(),
_path.GetHashCode(),
StringComparer.InvariantCultureIgnoreCase.GetHashCode(_domain),
_version);
}
/// <inheritdoc />
public override string ToString()
{
return ToRequestString(null);
}
} }
} }

View File

@ -27,22 +27,6 @@ namespace EonaCat.Network
} }
internal IList<Cookie> List => _list;
internal IEnumerable<Cookie> Sorted
{
get
{
var list = new List<Cookie>(_list);
if (list.Count > 1)
{
list.Sort(compareCookieWithinSorted);
}
return list;
}
}
/// <summary> /// <summary>
/// Gets the number of cookies in the collection. /// Gets the number of cookies in the collection.
/// </summary> /// </summary>
@ -58,6 +42,26 @@ namespace EonaCat.Network
/// </summary> /// </summary>
public bool IsSynchronized => false; public bool IsSynchronized => false;
/// <summary>
/// Gets an object that can be used to synchronize access to the collection.
/// </summary>
public object SyncRoot => _sync ??= ((ICollection)_list).SyncRoot;
internal IList<Cookie> List => _list;
internal IEnumerable<Cookie> Sorted
{
get
{
var list = new List<Cookie>(_list);
if (list.Count > 1)
{
list.Sort(compareCookieWithinSorted);
}
return list;
}
}
/// <summary> /// <summary>
/// Gets or sets the cookie at the specified index. /// Gets or sets the cookie at the specified index.
/// </summary> /// </summary>
@ -101,11 +105,164 @@ namespace EonaCat.Network
return null; return null;
} }
} }
/// <summary>
/// Adds the specified cookie to the collection, updating it if it already exists.
/// </summary>
/// <param name="cookie">The cookie to add or update.</param>
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;
}
/// <summary> /// <summary>
/// Gets an object that can be used to synchronize access to the collection. /// Adds the cookies from the specified <see cref="CookieCollection"/> to this collection, updating existing cookies.
/// </summary> /// </summary>
public object SyncRoot => _sync ??= ((ICollection)_list).SyncRoot; /// <param name="cookies">The <see cref="CookieCollection"/> to add or update from.</param>
public void Add(CookieCollection cookies)
{
if (cookies == null)
{
throw new ArgumentNullException(nameof(cookies));
}
foreach (Cookie cookie in cookies)
{
Add(cookie);
}
}
/// <summary>
/// Copies the cookies in the collection to the specified array, starting at the specified index.
/// </summary>
/// <param name="array">The destination array.</param>
/// <param name="index">The index in the destination array at which copying begins.</param>
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);
}
/// <summary>
/// Copies the cookies in the collection to the specified <see cref="Cookie"/> array, starting at the specified index.
/// </summary>
/// <param name="array">The destination array.</param>
/// <param name="index">The index in the destination array at which copying begins.</param>
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);
}
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>An enumerator for the collection.</returns>
public IEnumerator GetEnumerator()
{
return _list.GetEnumerator();
}
/// <summary>
/// Parses the specified cookie string, creating a <see cref="CookieCollection"/>.
/// </summary>
/// <param name="value">The cookie string to parse.</param>
/// <param name="response">True if parsing a response header; otherwise, false.</param>
/// <returns>A <see cref="CookieCollection"/> instance representing the parsed cookies.</returns>
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) private static int compareCookieWithinSort(Cookie x, Cookie y)
{ {
@ -357,6 +514,11 @@ namespace EonaCat.Network
return cookies; return cookies;
} }
private static string[] splitCookieHeaderValue(string value)
{
return new List<string>(value.SplitHeaderValue(',', ';')).ToArray();
}
private int searchCookie(Cookie cookie) private int searchCookie(Cookie cookie)
{ {
var name = cookie.Name; var name = cookie.Name;
@ -378,169 +540,5 @@ namespace EonaCat.Network
return -1; return -1;
} }
private static string[] splitCookieHeaderValue(string value)
{
return new List<string>(value.SplitHeaderValue(',', ';')).ToArray();
}
/// <summary>
/// Parses the specified cookie string, creating a <see cref="CookieCollection"/>.
/// </summary>
/// <param name="value">The cookie string to parse.</param>
/// <param name="response">True if parsing a response header; otherwise, false.</param>
/// <returns>A <see cref="CookieCollection"/> instance representing the parsed cookies.</returns>
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);
}
}
/// <summary>
/// Adds the specified cookie to the collection, updating it if it already exists.
/// </summary>
/// <param name="cookie">The cookie to add or update.</param>
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;
}
/// <summary>
/// Adds the cookies from the specified <see cref="CookieCollection"/> to this collection, updating existing cookies.
/// </summary>
/// <param name="cookies">The <see cref="CookieCollection"/> to add or update from.</param>
public void Add(CookieCollection cookies)
{
if (cookies == null)
{
throw new ArgumentNullException(nameof(cookies));
}
foreach (Cookie cookie in cookies)
{
Add(cookie);
}
}
/// <summary>
/// Copies the cookies in the collection to the specified array, starting at the specified index.
/// </summary>
/// <param name="array">The destination array.</param>
/// <param name="index">The index in the destination array at which copying begins.</param>
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);
}
/// <summary>
/// Copies the cookies in the collection to the specified <see cref="Cookie"/> array, starting at the specified index.
/// </summary>
/// <param name="array">The destination array.</param>
/// <param name="index">The index in the destination array at which copying begins.</param>
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);
}
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>An enumerator for the collection.</returns>
public IEnumerator GetEnumerator()
{
return _list.GetEnumerator();
}
} }
} }

View File

@ -13,6 +13,14 @@ namespace EonaCat.Network
[Serializable] [Serializable]
public class CookieException : FormatException, ISerializable public class CookieException : FormatException, ISerializable
{ {
/// <summary>
/// Initializes a new instance of the <see cref="CookieException"/> class.
/// </summary>
public CookieException()
: base()
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CookieException"/> class with a specified error message. /// Initializes a new instance of the <see cref="CookieException"/> class with a specified error message.
/// </summary> /// </summary>
@ -41,15 +49,6 @@ namespace EonaCat.Network
: base(serializationInfo, streamingContext) : base(serializationInfo, streamingContext)
{ {
} }
/// <summary>
/// Initializes a new instance of the <see cref="CookieException"/> class.
/// </summary>
public CookieException()
: base()
{
}
/// <summary> /// <summary>
/// Populates a <see cref="SerializationInfo"/> with the data needed to serialize the exception. /// Populates a <see cref="SerializationInfo"/> with the data needed to serialize the exception.
/// </summary> /// </summary>

View File

@ -18,15 +18,14 @@ namespace EonaCat.Network
/// </summary> /// </summary>
internal sealed class EndPointListener internal sealed class EndPointListener
{ {
private List<HttpListenerPrefix> _all; // host == '+'
private static readonly string _defaultCertFolderPath; private static readonly string _defaultCertFolderPath;
private readonly IPEndPoint _endpoint; private readonly IPEndPoint _endpoint;
private Dictionary<HttpListenerPrefix, HttpListener> _prefixes;
private readonly Socket _socket; private readonly Socket _socket;
private List<HttpListenerPrefix> _unhandled; // host == '*'
private readonly Dictionary<HttpConnection, HttpConnection> _unregistered; private readonly Dictionary<HttpConnection, HttpConnection> _unregistered;
private readonly object _unregisteredSync; private readonly object _unregisteredSync;
private List<HttpListenerPrefix> _all; // host == '+'
private Dictionary<HttpListenerPrefix, HttpListener> _prefixes;
private List<HttpListenerPrefix> _unhandled; // host == '*'
static EndPointListener() static EndPointListener()
{ {
_defaultCertFolderPath = _defaultCertFolderPath =
@ -93,293 +92,6 @@ namespace EonaCat.Network
/// </summary> /// </summary>
public SSLConfigServer SSL { get; } public SSLConfigServer SSL { get; }
private static void addSpecial(List<HttpListenerPrefix> 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<HttpListenerPrefix> 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<HttpListenerPrefix> 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) public void AddPrefix(HttpListenerPrefix prefix, HttpListener listener)
{ {
List<HttpListenerPrefix> current, future; List<HttpListenerPrefix> current, future;
@ -528,5 +240,292 @@ namespace EonaCat.Network
leaveIfNoPrefix(); 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<HttpListenerPrefix> 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<HttpListenerPrefix> 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<HttpListenerPrefix> 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);
}
} }
} }

View File

@ -30,6 +30,97 @@ namespace EonaCat.Network
{ {
} }
/// <summary>
/// Adds an HTTP listener and its associated prefixes.
/// </summary>
/// <param name="listener">The HTTP listener to be added.</param>
public static void AddListener(HttpListener listener)
{
var added = new List<string>();
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;
}
}
}
/// <summary>
/// Adds an HTTP listener prefix.
/// </summary>
/// <param name="uriPrefix">The URI prefix to be added.</param>
/// <param name="listener">The HTTP listener associated with the prefix.</param>
public static void AddPrefix(string uriPrefix, HttpListener listener)
{
lock (((ICollection)_endpoints).SyncRoot)
{
addPrefix(uriPrefix, listener);
}
}
/// <summary>
/// Removes an HTTP listener and its associated prefixes.
/// </summary>
/// <param name="listener">The HTTP listener to be removed.</param>
public static void RemoveListener(HttpListener listener)
{
lock (((ICollection)_endpoints).SyncRoot)
{
foreach (var pref in listener.Prefixes)
{
removePrefix(pref, listener);
}
}
}
/// <summary>
/// Removes an HTTP listener prefix.
/// </summary>
/// <param name="uriPrefix">The URI prefix to be removed.</param>
/// <param name="listener">The HTTP listener associated with the prefix.</param>
public static void RemovePrefix(string uriPrefix, HttpListener listener)
{
lock (((ICollection)_endpoints).SyncRoot)
{
removePrefix(uriPrefix, listener);
}
}
/// <summary>
/// Removes an endpoint and closes its associated listener.
/// </summary>
/// <param name="endpoint">The endpoint to be removed.</param>
/// <returns><c>true</c> if the endpoint is successfully removed; otherwise, <c>false</c>.</returns>
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) private static void addPrefix(string uriPrefix, HttpListener listener)
{ {
var pref = new HttpListenerPrefix(uriPrefix); var pref = new HttpListenerPrefix(uriPrefix);
@ -137,96 +228,5 @@ namespace EonaCat.Network
lsnr.RemovePrefix(pref, listener); lsnr.RemovePrefix(pref, listener);
} }
/// <summary>
/// Removes an endpoint and closes its associated listener.
/// </summary>
/// <param name="endpoint">The endpoint to be removed.</param>
/// <returns><c>true</c> if the endpoint is successfully removed; otherwise, <c>false</c>.</returns>
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;
}
}
/// <summary>
/// Adds an HTTP listener and its associated prefixes.
/// </summary>
/// <param name="listener">The HTTP listener to be added.</param>
public static void AddListener(HttpListener listener)
{
var added = new List<string>();
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;
}
}
}
/// <summary>
/// Adds an HTTP listener prefix.
/// </summary>
/// <param name="uriPrefix">The URI prefix to be added.</param>
/// <param name="listener">The HTTP listener associated with the prefix.</param>
public static void AddPrefix(string uriPrefix, HttpListener listener)
{
lock (((ICollection)_endpoints).SyncRoot)
{
addPrefix(uriPrefix, listener);
}
}
/// <summary>
/// Removes an HTTP listener and its associated prefixes.
/// </summary>
/// <param name="listener">The HTTP listener to be removed.</param>
public static void RemoveListener(HttpListener listener)
{
lock (((ICollection)_endpoints).SyncRoot)
{
foreach (var pref in listener.Prefixes)
{
removePrefix(pref, listener);
}
}
}
/// <summary>
/// Removes an HTTP listener prefix.
/// </summary>
/// <param name="uriPrefix">The URI prefix to be removed.</param>
/// <param name="listener">The HTTP listener associated with the prefix.</param>
public static void RemovePrefix(string uriPrefix, HttpListener listener)
{
lock (((ICollection)_endpoints).SyncRoot)
{
removePrefix(uriPrefix, listener);
}
}
} }
} }

View File

@ -17,10 +17,13 @@ namespace EonaCat.Network
/// </summary> /// </summary>
internal sealed class HttpConnection internal sealed class HttpConnection
{ {
private byte[] _buffer;
private const int _bufferLength = 8192; private const int _bufferLength = 8192;
private const int TIMEOUT_CONTINUE = 15000; private const int TIMEOUT_CONTINUE = 15000;
private const int TIMEOUT_INITIAL = 90000; private const int TIMEOUT_INITIAL = 90000;
private readonly EndPointListener _listener;
private readonly object _lock;
private readonly Dictionary<int, bool> _timeoutCanceled;
private byte[] _buffer;
private HttpListenerContext _context; private HttpListenerContext _context;
private bool _contextRegistered; private bool _contextRegistered;
private StringBuilder _currentLine; private StringBuilder _currentLine;
@ -28,14 +31,11 @@ namespace EonaCat.Network
private RequestStream _inputStream; private RequestStream _inputStream;
private HttpListener _lastListener; private HttpListener _lastListener;
private LineState _lineState; private LineState _lineState;
private readonly EndPointListener _listener;
private ResponseStream _outputStream; private ResponseStream _outputStream;
private int _position; private int _position;
private MemoryStream _requestBuffer; private MemoryStream _requestBuffer;
private Socket _socket; private Socket _socket;
private readonly object _lock;
private int _timeout; private int _timeout;
private readonly Dictionary<int, bool> _timeoutCanceled;
private Timer _timer; private Timer _timer;
/// <summary> /// <summary>
@ -106,375 +106,6 @@ namespace EonaCat.Network
/// </summary> /// </summary>
public Stream Stream { get; private set; } 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;
}
/// <summary>
/// Closes the connection.
/// </summary>
/// <param name="force">True to force close, false otherwise.</param>
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();
}
}
/// <summary> /// <summary>
/// Initiates reading the request. /// Initiates reading the request.
/// </summary> /// </summary>
@ -629,5 +260,373 @@ namespace EonaCat.Network
} }
} }
} }
/// <summary>
/// Closes the connection.
/// </summary>
/// <param name="force">True to force close, false otherwise.</param>
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;
}
} }
} }

View File

@ -19,16 +19,6 @@ namespace EonaCat.Network
Type = type; Type = type;
} }
/// <summary>
/// Gets a value indicating whether the header is multi-value in a request.
/// </summary>
internal bool IsMultiValueInRequest => (Type & HttpHeaderType.MultiValueInRequest) == HttpHeaderType.MultiValueInRequest;
/// <summary>
/// Gets a value indicating whether the header is multi-value in a response.
/// </summary>
internal bool IsMultiValueInResponse => (Type & HttpHeaderType.MultiValueInResponse) == HttpHeaderType.MultiValueInResponse;
/// <summary> /// <summary>
/// Gets a value indicating whether the header is for a request. /// Gets a value indicating whether the header is for a request.
/// </summary> /// </summary>
@ -49,6 +39,15 @@ namespace EonaCat.Network
/// </summary> /// </summary>
public HttpHeaderType Type { get; } public HttpHeaderType Type { get; }
/// <summary>
/// Gets a value indicating whether the header is multi-value in a request.
/// </summary>
internal bool IsMultiValueInRequest => (Type & HttpHeaderType.MultiValueInRequest) == HttpHeaderType.MultiValueInRequest;
/// <summary>
/// Gets a value indicating whether the header is multi-value in a response.
/// </summary>
internal bool IsMultiValueInResponse => (Type & HttpHeaderType.MultiValueInResponse) == HttpHeaderType.MultiValueInResponse;
/// <summary> /// <summary>
/// Gets a value indicating whether the header is multi-value. /// Gets a value indicating whether the header is multi-value.
/// </summary> /// </summary>

View File

@ -13,25 +13,25 @@ namespace EonaCat.Network
/// </summary> /// </summary>
public sealed class HttpListener : IDisposable public sealed class HttpListener : IDisposable
{ {
private AuthenticationSchemes _authSchemes; private static readonly string _defaultRealm;
private Func<HttpListenerRequest, AuthenticationSchemes> _authSchemeSelector;
private string _certFolderPath;
private readonly Dictionary<HttpConnection, HttpConnection> _connections; private readonly Dictionary<HttpConnection, HttpConnection> _connections;
private readonly object _connectionsSync; private readonly object _connectionsSync;
private readonly List<HttpListenerContext> contextQueue;
private readonly object _contextQueueLock; private readonly object _contextQueueLock;
private readonly Dictionary<HttpListenerContext, HttpListenerContext> _contextRegistry; private readonly Dictionary<HttpListenerContext, HttpListenerContext> _contextRegistry;
private readonly object _contextRegistryLock; private readonly object _contextRegistryLock;
private static readonly string _defaultRealm; private readonly HttpListenerPrefixCollection _prefixes;
private readonly List<HttpListenerAsyncResult> _waitQueue;
private readonly object _waitQueueLock;
private readonly List<HttpListenerContext> contextQueue;
private bool _allowForwardedRequest;
private AuthenticationSchemes _authSchemes;
private Func<HttpListenerRequest, AuthenticationSchemes> _authSchemeSelector;
private string _certFolderPath;
private bool _ignoreWriteExceptions; private bool _ignoreWriteExceptions;
private volatile bool _listening; private volatile bool _listening;
private readonly HttpListenerPrefixCollection _prefixes;
private string _realm; private string _realm;
private SSLConfigServer _sslConfig; private SSLConfigServer _sslConfig;
private Func<IIdentity, NetworkCredential> _userCredFinder; private Func<IIdentity, NetworkCredential> _userCredFinder;
private readonly List<HttpListenerAsyncResult> _waitQueue;
private readonly object _waitQueueLock;
static HttpListener() static HttpListener()
{ {
_defaultRealm = "SECRET AREA"; _defaultRealm = "SECRET AREA";
@ -59,9 +59,29 @@ namespace EonaCat.Network
_waitQueueLock = ((ICollection)_waitQueue).SyncRoot; _waitQueueLock = ((ICollection)_waitQueue).SyncRoot;
} }
internal bool IsDisposed { get; private set; } public static bool IsSupported => true;
/// <summary>
internal bool ReuseAddress { get; set; } /// Gets or sets a value indicating whether the server accepts every
/// handshake request without checking the request URI.
/// </summary>
/// <remarks>
/// The set operation does nothing if the server has already started or
/// it is shutting down.
/// </remarks>
/// <value>
/// <para>
/// <c>true</c> if the server accepts every handshake request without
/// checking the request URI; otherwise, <c>false</c>.
/// </para>
/// <para>
/// The default value is <c>false</c>.
/// </para>
/// </value>
public bool AllowForwardedRequest
{
get { return _allowForwardedRequest; }
set { _allowForwardedRequest = value; }
}
/// <summary> /// <summary>
/// Gets or sets the authentication schemes used by this listener. /// Gets or sets the authentication schemes used by this listener.
@ -127,9 +147,6 @@ namespace EonaCat.Network
} }
public bool IsListening => _listening; public bool IsListening => _listening;
public static bool IsSupported => true;
public HttpListenerPrefixCollection Prefixes public HttpListenerPrefixCollection Prefixes
{ {
get get
@ -197,6 +214,289 @@ namespace EonaCat.Network
} }
} }
internal bool IsDisposed { get; private set; }
internal bool ReuseAddress { get; set; }
/// <summary>
/// Aborts the listener and releases all resources associated with it.
/// </summary>
public void Abort()
{
if (IsDisposed)
{
return;
}
close(true);
}
/// <summary>
/// Begins asynchronously getting an HTTP context from the listener.
/// </summary>
/// <param name="callback">The method to call when the operation completes.</param>
/// <param name="state">A user-defined object that contains information about the asynchronous operation.</param>
/// <returns>An <see cref="IAsyncResult"/> that represents the asynchronous operation.</returns>
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));
}
/// <summary>
/// Closes the listener.
/// </summary>
public void Close()
{
if (IsDisposed)
{
return;
}
close(false);
}
/// <summary>
/// Disposes of the resources used by the <see cref="HttpListener"/>.
/// </summary>
void IDisposable.Dispose()
{
if (IsDisposed)
{
return;
}
close(true);
}
/// <summary>
/// Ends an asynchronous operation to get an HTTP context from the listener.
/// </summary>
/// <param name="asyncResult">The reference to the pending asynchronous request to finish.</param>
/// <returns>An <see cref="HttpListenerContext"/> that represents the context of the asynchronous operation.</returns>
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();
}
/// <summary>
/// Gets the next available HTTP context from the listener.
/// </summary>
/// <returns>An <see cref="HttpListenerContext"/> that represents the context of the HTTP request.</returns>
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);
}
/// <summary>
/// Starts listening for incoming requests.
/// </summary>
public void Start()
{
CheckDisposed();
if (_listening)
{
return;
}
EndPointManager.AddListener(this);
_listening = true;
}
/// <summary>
/// Stops listening for incoming requests.
/// </summary>
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<IIdentity, NetworkCredential> 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() private void CleanupConnections()
{ {
HttpConnection[] httpConnections = null; HttpConnection[] httpConnections = null;
@ -335,285 +635,5 @@ namespace EonaCat.Network
return ctx; 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<IIdentity, NetworkCredential> 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);
}
}
/// <summary>
/// Aborts the listener and releases all resources associated with it.
/// </summary>
public void Abort()
{
if (IsDisposed)
{
return;
}
close(true);
}
/// <summary>
/// Begins asynchronously getting an HTTP context from the listener.
/// </summary>
/// <param name="callback">The method to call when the operation completes.</param>
/// <param name="state">A user-defined object that contains information about the asynchronous operation.</param>
/// <returns>An <see cref="IAsyncResult"/> that represents the asynchronous operation.</returns>
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));
}
/// <summary>
/// Closes the listener.
/// </summary>
public void Close()
{
if (IsDisposed)
{
return;
}
close(false);
}
/// <summary>
/// Ends an asynchronous operation to get an HTTP context from the listener.
/// </summary>
/// <param name="asyncResult">The reference to the pending asynchronous request to finish.</param>
/// <returns>An <see cref="HttpListenerContext"/> that represents the context of the asynchronous operation.</returns>
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();
}
/// <summary>
/// Gets the next available HTTP context from the listener.
/// </summary>
/// <returns>An <see cref="HttpListenerContext"/> that represents the context of the HTTP request.</returns>
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);
}
/// <summary>
/// Starts listening for incoming requests.
/// </summary>
public void Start()
{
CheckDisposed();
if (_listening)
{
return;
}
EndPointManager.AddListener(this);
_listening = true;
}
/// <summary>
/// Stops listening for incoming requests.
/// </summary>
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."));
}
/// <summary>
/// Disposes of the resources used by the <see cref="HttpListener"/>.
/// </summary>
void IDisposable.Dispose()
{
if (IsDisposed)
{
return;
}
close(true);
}
} }
} }

View File

@ -12,10 +12,10 @@ namespace EonaCat.Network
internal class HttpListenerAsyncResult : IAsyncResult internal class HttpListenerAsyncResult : IAsyncResult
{ {
private readonly AsyncCallback _callback; private readonly AsyncCallback _callback;
private readonly object _sync;
private bool _completed; private bool _completed;
private HttpListenerContext _context; private HttpListenerContext _context;
private Exception _exception; private Exception _exception;
private readonly object _sync;
private ManualResetEvent _waitHandle; private ManualResetEvent _waitHandle;
/// <summary> /// <summary>
@ -30,16 +30,6 @@ namespace EonaCat.Network
_sync = new object(); _sync = new object();
} }
/// <summary>
/// Gets or sets a value indicating whether the <see cref="EndGetContext"/> method has been called.
/// </summary>
internal bool EndCalled { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the asynchronous operation is in progress.
/// </summary>
internal bool InGet { get; set; }
/// <summary> /// <summary>
/// Gets the user-defined object that contains information about the asynchronous operation. /// Gets the user-defined object that contains information about the asynchronous operation.
/// </summary> /// </summary>
@ -78,38 +68,15 @@ namespace EonaCat.Network
} }
} }
// Private method to complete the asynchronous operation /// <summary>
private static void complete(HttpListenerAsyncResult asyncResult) /// Gets or sets a value indicating whether the <see cref="EndGetContext"/> method has been called.
{ /// </summary>
lock (asyncResult._sync) internal bool EndCalled { get; set; }
{
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
);
}
/// <summary>
/// Gets or sets a value indicating whether the asynchronous operation is in progress.
/// </summary>
internal bool InGet { get; set; }
/// <summary> /// <summary>
/// Completes the asynchronous operation with the specified exception. /// Completes the asynchronous operation with the specified exception.
/// </summary> /// </summary>
@ -158,5 +125,37 @@ namespace EonaCat.Network
return _context; 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
);
}
} }
} }

View File

@ -25,6 +25,21 @@ namespace EonaCat.Network
Response = new HttpListenerResponse(this); Response = new HttpListenerResponse(this);
} }
/// <summary>
/// Gets the <see cref="HttpListenerRequest"/> associated with the context.
/// </summary>
public HttpListenerRequest Request { get; }
/// <summary>
/// Gets the <see cref="HttpListenerResponse"/> associated with the context.
/// </summary>
public HttpListenerResponse Response { get; }
/// <summary>
/// Gets or sets the <see cref="IPrincipal"/> associated with the user.
/// </summary>
public IPrincipal User { get; private set; }
/// <summary> /// <summary>
/// Gets the underlying <see cref="HttpConnection"/> for the context. /// Gets the underlying <see cref="HttpConnection"/> for the context.
/// </summary> /// </summary>
@ -49,21 +64,34 @@ namespace EonaCat.Network
/// Gets or sets the <see cref="HttpListener"/> associated with the context. /// Gets or sets the <see cref="HttpListener"/> associated with the context.
/// </summary> /// </summary>
internal HttpListener Listener { get; set; } internal HttpListener Listener { get; set; }
/// <summary> /// <summary>
/// Gets the <see cref="HttpListenerRequest"/> associated with the context. /// Accepts a WebSocket connection with the specified protocol.
/// </summary> /// </summary>
public HttpListenerRequest Request { get; } /// <param name="protocol">The WebSocket subprotocol to negotiate.</param>
/// <returns>The <see cref="HttpListenerWSContext"/> for the WebSocket connection.</returns>
public HttpListenerWSContext AcceptWebSocket(string protocol)
{
if (_websocketContext != null)
{
throw new InvalidOperationException("Accepting already in progress.");
}
/// <summary> if (protocol != null)
/// Gets the <see cref="HttpListenerResponse"/> associated with the context. {
/// </summary> if (protocol.Length == 0)
public HttpListenerResponse Response { get; } {
throw new ArgumentException("Empty string.", nameof(protocol));
}
/// <summary> if (!protocol.IsToken())
/// Gets or sets the <see cref="IPrincipal"/> associated with the user. {
/// </summary> throw new ArgumentException("Contains invalid characters", nameof(protocol));
public IPrincipal User { get; private set; } }
}
_websocketContext = new HttpListenerWSContext(this, protocol);
return _websocketContext;
}
/// <summary> /// <summary>
/// Authenticates the user based on the specified authentication scheme. /// Authenticates the user based on the specified authentication scheme.
@ -119,34 +147,5 @@ namespace EonaCat.Network
{ {
Listener.UnregisterContext(this); Listener.UnregisterContext(this);
} }
/// <summary>
/// Accepts a WebSocket connection with the specified protocol.
/// </summary>
/// <param name="protocol">The WebSocket subprotocol to negotiate.</param>
/// <returns>The <see cref="HttpListenerWSContext"/> for the WebSocket connection.</returns>
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;
}
} }
} }

View File

@ -13,15 +13,6 @@ namespace EonaCat.Network
[Serializable] [Serializable]
public class HttpListenerException : Win32Exception public class HttpListenerException : Win32Exception
{ {
/// <summary>
/// Initializes a new instance of the <see cref="HttpListenerException"/> class.
/// </summary>
protected HttpListenerException(
SerializationInfo serializationInfo, StreamingContext streamingContext)
: base(serializationInfo, streamingContext)
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HttpListenerException"/> class with no error message. /// Initializes a new instance of the <see cref="HttpListenerException"/> class with no error message.
/// </summary> /// </summary>
@ -48,6 +39,14 @@ namespace EonaCat.Network
{ {
} }
/// <summary>
/// Initializes a new instance of the <see cref="HttpListenerException"/> class.
/// </summary>
protected HttpListenerException(
SerializationInfo serializationInfo, StreamingContext streamingContext)
: base(serializationInfo, streamingContext)
{
}
/// <summary> /// <summary>
/// Gets the Win32 error code associated with this exception. /// Gets the Win32 error code associated with this exception.
/// </summary> /// </summary>

View File

@ -52,35 +52,6 @@ namespace EonaCat.Network
/// </summary> /// </summary>
public string Port { get; private set; } 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);
}
/// <summary> /// <summary>
/// Checks if the specified URI prefix is valid. /// Checks if the specified URI prefix is valid.
/// </summary> /// </summary>
@ -163,5 +134,34 @@ namespace EonaCat.Network
{ {
return _prefix; 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);
}
} }
} }

View File

@ -120,6 +120,15 @@ namespace EonaCat.Network
return _prefixes.GetEnumerator(); return _prefixes.GetEnumerator();
} }
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return _prefixes.GetEnumerator();
}
/// <summary> /// <summary>
/// Removes the specified URI prefix from the collection. /// Removes the specified URI prefix from the collection.
/// </summary> /// </summary>
@ -141,14 +150,5 @@ namespace EonaCat.Network
return ret; return ret;
} }
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
IEnumerator IEnumerable.GetEnumerator()
{
return _prefixes.GetEnumerator();
}
} }
} }

View File

@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Text; using System.Text;
namespace EonaCat.Network namespace EonaCat.Network
@ -17,12 +16,12 @@ namespace EonaCat.Network
public sealed class HttpListenerRequest public sealed class HttpListenerRequest
{ {
private static readonly byte[] _100continue; private static readonly byte[] _100continue;
private readonly HttpListenerContext _context;
private readonly WebHeaderCollection _headers;
private bool _chunked; private bool _chunked;
private Encoding _contentEncoding; private Encoding _contentEncoding;
private bool _contentLengthSet; private bool _contentLengthSet;
private readonly HttpListenerContext _context;
private CookieCollection _cookies; private CookieCollection _cookies;
private readonly WebHeaderCollection _headers;
private Stream _inputStream; private Stream _inputStream;
private bool _keepAlive; private bool _keepAlive;
private bool _keepAliveSet; private bool _keepAliveSet;
@ -162,7 +161,7 @@ namespace EonaCat.Network
/// <summary> /// <summary>
/// Gets the query string in the request. /// Gets the query string in the request.
/// </summary> /// </summary>
public NameValueCollection QueryString => _queryString ??= HttpUtility.InternalParseQueryString(Url.Query, Encoding.UTF8); public NameValueCollection QueryString => _queryString ??= HttpUtility.InternalParseQueryString(Url.Query, ContentEncoding);
/// <summary> /// <summary>
/// Gets the raw URL of the request. /// Gets the raw URL of the request.
@ -209,18 +208,13 @@ namespace EonaCat.Network
/// </summary> /// </summary>
public string[] UserLanguages { get; private set; } public string[] UserLanguages { get; private set; }
private static bool tryCreateVersion(string version, out Version result) public override string ToString()
{ {
try var buff = new StringBuilder(64);
{ buff.AppendFormat("{0} {1} HTTP/{2}\r\n", HttpMethod, _uri, _version);
result = new Version(version); buff.Append(_headers.ToString());
return true;
} return buff.ToString();
catch
{
result = null;
return false;
}
} }
internal void AddHeader(string header) 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); try
buff.AppendFormat("{0} {1} HTTP/{2}\r\n", HttpMethod, _uri, _version); {
buff.Append(_headers.ToString()); result = new Version(version);
return true;
return buff.ToString(); }
catch
{
result = null;
return false;
}
} }
} }
} }

View File

@ -1,12 +1,10 @@
// This file is part of the EonaCat project(s) which is released under the Apache License. // 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. // See the LICENSE file or go to https://EonaCat.com/License for full license details.
using EonaCat.Logger;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Reflection;
using System.Text; using System.Text;
namespace EonaCat.Network namespace EonaCat.Network
@ -16,10 +14,10 @@ namespace EonaCat.Network
/// </summary> /// </summary>
public sealed class HttpListenerResponse : IDisposable public sealed class HttpListenerResponse : IDisposable
{ {
private readonly HttpListenerContext _context;
private Encoding _contentEncoding; private Encoding _contentEncoding;
private long _contentLength; private long _contentLength;
private string _contentType; private string _contentType;
private readonly HttpListenerContext _context;
private CookieCollection _cookies; private CookieCollection _cookies;
private bool _disposed; private bool _disposed;
private WebHeaderCollection _headers; private WebHeaderCollection _headers;

View File

@ -9,8 +9,8 @@ namespace EonaCat.Network
internal class HttpStreamAsyncResult : IAsyncResult internal class HttpStreamAsyncResult : IAsyncResult
{ {
private readonly AsyncCallback _callback; private readonly AsyncCallback _callback;
private bool _isCompleted;
private readonly object _sync; private readonly object _sync;
private bool _isCompleted;
private ManualResetEvent _waitHandle; private ManualResetEvent _waitHandle;
internal HttpStreamAsyncResult(AsyncCallback callback, object state) internal HttpStreamAsyncResult(AsyncCallback callback, object state)
@ -20,6 +20,30 @@ namespace EonaCat.Network
_sync = new object(); _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 byte[] Buffer { get; set; }
internal int Count { get; set; } internal int Count { get; set; }
@ -31,33 +55,6 @@ namespace EonaCat.Network
internal int Offset { get; set; } internal int Offset { get; set; }
internal int SyncRead { 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() internal void Complete()
{ {
lock (_sync) lock (_sync)

File diff suppressed because it is too large Load Diff

View File

@ -25,62 +25,23 @@ namespace EonaCat.Network
internal class Logger internal class Logger
{ {
internal static string LoggingDirectory { get; private set; }
private static readonly LogManager _logManager; 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() static Logger()
{ {
_logManager = new LogManager(new LoggerSettings { RemoveMessagePrefix = true }); _logManager = new LogManager(new LoggerSettings { RemoveMessagePrefix = true });
_logManager.OnException += _logManager_OnException; _logManager.OnException += _logManager_OnException;
} }
internal static void Setup(string loggingDirectory = null) internal static bool DisableConsole { get; set; }
{ internal static bool IsLoggingDirectorySet => !string.IsNullOrWhiteSpace(LoggingDirectory);
LoggingDirectory = loggingDirectory; internal static bool IsLoggingEnabled { get; set; }
_logManager.Settings.FileLoggerOptions.FileNamePrefix = "EonaCat.Network"; internal static string LoggingDirectory { get; private set; }
private static bool HasBeenSetup { get; set; }
if (IsLoggingDirectorySet)
{
_logManager.Settings.FileLoggerOptions.LogDirectory = LoggingDirectory;
}
_logManager.Settings.UseLocalTime = true;
_logManager.Settings.FileLoggerOptions.UseLocalTime = true;
_logManager.Settings.SysLogServers = new List<SyslogServer>();
_logManager.Settings.SplunkServers = new List<SplunkServer>();
_logManager.Settings.GrayLogServers = new List<GrayLogServer>();
_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 void AddGrayLogServer(string hostname, int port) internal static void AddGrayLogServer(string hostname, int port)
{ {
_logManager.Settings.GrayLogServers.Add(new GrayLogServer(hostname, 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) internal static void AddSplunkServer(string splunkHecUrl, string splunkHecToken, bool disableSSL = false)
{ {
var splunkServer = new SplunkServer(splunkHecUrl, splunkHecToken); var splunkServer = new SplunkServer(splunkHecUrl, splunkHecToken);
@ -91,56 +52,14 @@ namespace EonaCat.Network
_logManager.Settings.SplunkServers.Add(splunkServer); _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) internal static void AddSyslogServer(string ipAddress, int port)
{ {
_logManager.Settings.SysLogServers.Add(new SyslogServer(ipAddress, 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); Write(message, ELogType.CRITICAL, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogSettings);
}
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);
}
} }
internal static void Debug(string message, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, GrayLogSettings grayLogSettings = null) 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<SyslogServer>();
_logManager.Settings.SplunkServers = new List<SplunkServer>();
_logManager.Settings.GrayLogServers = new List<GrayLogServer>();
_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) 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); 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);
}
} }
} }
} }

View File

@ -10,19 +10,19 @@ namespace EonaCat.Network
{ {
public class SSLConfigClient public class SSLConfigClient
{ {
private LocalCertificateSelectionCallback _clientCertSelectionCallback;
private X509CertificateCollection _clientCertificates; private X509CertificateCollection _clientCertificates;
private LocalCertificateSelectionCallback _clientCertSelectionCallback;
private RemoteCertificateValidationCallback _serverCertValidationCallback; private RemoteCertificateValidationCallback _serverCertValidationCallback;
public SSLConfigClient() public SSLConfigClient()
{ {
SslProtocols = SslProtocols.Tls12; SslProtocols = SslProtocols.None;
} }
public SSLConfigClient(string targetHost) public SSLConfigClient(string targetHost)
{ {
TargetHost = targetHost; TargetHost = targetHost;
SslProtocols = SslProtocols.Tls12; SslProtocols = SslProtocols.None;
} }
public SSLConfigClient(SSLConfigClient sslConfig) public SSLConfigClient(SSLConfigClient sslConfig)
@ -40,8 +40,6 @@ namespace EonaCat.Network
TargetHost = sslConfig.TargetHost; TargetHost = sslConfig.TargetHost;
} }
public bool CheckForCertificateRevocation { get; set; }
public X509CertificateCollection Certificates public X509CertificateCollection Certificates
{ {
get get
@ -56,6 +54,7 @@ namespace EonaCat.Network
} }
} }
public bool CheckForCertificateRevocation { get; set; }
public LocalCertificateSelectionCallback ClientCertificateSelectionCallback public LocalCertificateSelectionCallback ClientCertificateSelectionCallback
{ {
get get
@ -71,8 +70,6 @@ namespace EonaCat.Network
} }
} }
public SslProtocols SslProtocols { get; set; }
public RemoteCertificateValidationCallback ServerCertificateValidationCallback public RemoteCertificateValidationCallback ServerCertificateValidationCallback
{ {
get get
@ -88,6 +85,7 @@ namespace EonaCat.Network
} }
} }
public SslProtocols SslProtocols { get; set; }
public string TargetHost { get; set; } public string TargetHost { get; set; }
private static X509Certificate SelectClientCertificate( private static X509Certificate SelectClientCertificate(

View File

@ -14,13 +14,13 @@ namespace EonaCat.Network
public SSLConfigServer() public SSLConfigServer()
{ {
SslProtocols = SslProtocols.Tls12; SslProtocols = SslProtocols.None;
} }
public SSLConfigServer(X509Certificate2 certificate) public SSLConfigServer(X509Certificate2 certificate)
{ {
Certificate = certificate; Certificate = certificate;
SslProtocols = SslProtocols.Tls12; SslProtocols = SslProtocols.None;
} }
public SSLConfigServer(SSLConfigServer sslConfig) public SSLConfigServer(SSLConfigServer sslConfig)
@ -37,10 +37,9 @@ namespace EonaCat.Network
Certificate = sslConfig.Certificate; Certificate = sslConfig.Certificate;
} }
public X509Certificate2 Certificate { get; set; }
public bool CheckForCertificateRevocation { get; set; } public bool CheckForCertificateRevocation { get; set; }
public bool IsClientCertificateRequired { get; set; }
public RemoteCertificateValidationCallback ClientCertificateValidationCallback public RemoteCertificateValidationCallback ClientCertificateValidationCallback
{ {
get get
@ -56,10 +55,8 @@ namespace EonaCat.Network
} }
} }
public bool IsClientCertificateRequired { get; set; }
public SslProtocols SslProtocols { get; set; } public SslProtocols SslProtocols { get; set; }
public X509Certificate2 Certificate { get; set; }
private static bool ValidateClientCertificate( private static bool ValidateClientCertificate(
object sender, object sender,
X509Certificate certificate, X509Certificate certificate,

View File

@ -8,13 +8,12 @@ namespace EonaCat.Network
{ {
internal class RequestStream : Stream internal class RequestStream : Stream
{ {
private long _bodyLeft;
private readonly byte[] _buffer; private readonly byte[] _buffer;
private readonly Stream _stream;
private long _bodyLeft;
private int _count; private int _count;
private bool _disposed; private bool _disposed;
private int _offset; private int _offset;
private readonly Stream _stream;
internal RequestStream(Stream stream, byte[] buffer, int offset, int count) internal RequestStream(Stream stream, byte[] buffer, int offset, int count)
: this(stream, buffer, offset, count, -1) : this(stream, buffer, offset, count, -1)
{ {
@ -51,64 +50,6 @@ 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( public override IAsyncResult BeginRead(
byte[] buffer, int offset, int count, AsyncCallback callback, object state) byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{ {
@ -233,5 +174,63 @@ namespace EonaCat.Network
{ {
throw new NotSupportedException(); 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;
}
} }
} }

View File

@ -9,16 +9,15 @@ namespace EonaCat.Network
{ {
internal class ResponseStream : Stream internal class ResponseStream : Stream
{ {
private MemoryStream _body;
private static readonly byte[] _crlf = new byte[] { 13, 10 }; private static readonly byte[] _crlf = new byte[] { 13, 10 };
private readonly Action<byte[], int, int> _write;
private readonly Action<byte[], int, int> _writeChunked;
private MemoryStream _body;
private bool _disposed; private bool _disposed;
private HttpListenerResponse _response; private HttpListenerResponse _response;
private bool _sendChunked; private bool _sendChunked;
private Stream _stream; private Stream _stream;
private readonly Action<byte[], int, int> _write;
private Action<byte[], int, int> _writeBody; private Action<byte[], int, int> _writeBody;
private readonly Action<byte[], int, int> _writeChunked;
internal ResponseStream( internal ResponseStream(
Stream stream, HttpListenerResponse response, bool ignoreWriteExceptions) 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) private bool flush(bool closing)
{ {
if (!_response.HeadersSent) if (!_response.HeadersSent)
@ -137,12 +251,6 @@ namespace EonaCat.Network
return true; 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) private void writeChunked(byte[] buffer, int offset, int count)
{ {
var size = getChunkSizeBytes(count, false); 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);
}
} }
} }

View File

@ -37,12 +37,9 @@ namespace EonaCat.Network
{ {
} }
internal PayloadData PayloadData { get; }
public ushort Code => PayloadData.Code; public ushort Code => PayloadData.Code;
public string Reason => PayloadData.Reason ?? string.Empty; public string Reason => PayloadData.Reason ?? string.Empty;
public bool WasClean { get; internal set; } public bool WasClean { get; internal set; }
internal PayloadData PayloadData { get; }
} }
} }

View File

@ -7,10 +7,9 @@ namespace EonaCat.Network
{ {
public class MessageEventArgs : EventArgs public class MessageEventArgs : EventArgs
{ {
private readonly byte[] _rawData;
private string _data; private string _data;
private bool _dataSet; private bool _dataSet;
private readonly byte[] _rawData;
internal MessageEventArgs(WSFrame frame) internal MessageEventArgs(WSFrame frame)
{ {
Opcode = frame.Opcode; Opcode = frame.Opcode;
@ -28,8 +27,6 @@ namespace EonaCat.Network
_rawData = rawData; _rawData = rawData;
} }
internal OperationCode Opcode { get; }
public string Data public string Data
{ {
get get
@ -40,11 +37,8 @@ namespace EonaCat.Network
} }
public bool IsBinary => Opcode == OperationCode.Binary; public bool IsBinary => Opcode == OperationCode.Binary;
public bool IsPing => Opcode == OperationCode.Ping; public bool IsPing => Opcode == OperationCode.Ping;
public bool IsText => Opcode == OperationCode.Text; public bool IsText => Opcode == OperationCode.Text;
public byte[] RawData public byte[] RawData
{ {
get get
@ -54,6 +48,7 @@ namespace EonaCat.Network
} }
} }
internal OperationCode Opcode { get; }
private void setData() private void setData()
{ {
if (_dataSet) if (_dataSet)

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ namespace EonaCat.Network
{ {
internal enum OperationCode : byte internal enum OperationCode : byte
{ {
Cont = 0x0, Continue = 0x0,
Text = 0x1, Text = 0x1,

View File

@ -37,6 +37,21 @@ namespace EonaCat.Network
_reasonSet = true; _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) internal PayloadData(byte[] data)
: this(data, data.LongLength) : this(data, data.LongLength)
{ {

View File

@ -27,36 +27,6 @@ namespace EonaCat.Network
public IPrincipal User => _context.User; 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) public byte[] ReadFile(string path)
{ {
if (path == null) if (path == null)
@ -98,5 +68,35 @@ namespace EonaCat.Network
return tryReadFile(createFilePath(path), out contents); 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('\\', '/');
}
} }
} }

View File

@ -11,13 +11,13 @@ namespace EonaCat.Network
{ {
public class HttpServer public class HttpServer
{ {
private bool _allowForwardedRequest;
private string _docRootPath; private string _docRootPath;
private string _hostname; private string _hostname;
private HttpListener _listener; private HttpListener _listener;
private Thread _receiveThread; private Thread _receiveThread;
private volatile ServerState _state; private volatile ServerState _state;
private object _sync; private object _sync;
public HttpServer() public HttpServer()
{ {
init("*", System.Net.IPAddress.Any, 80, false); init("*", System.Net.IPAddress.Any, 80, false);
@ -100,8 +100,49 @@ namespace EonaCat.Network
init(address.ToString(true), address, port, secure); init(address.ToString(true), address, port, secure);
} }
public event EventHandler<HttpRequestEventArgs> OnConnect;
public event EventHandler<HttpRequestEventArgs> OnDelete;
public event EventHandler<HttpRequestEventArgs> OnGet;
public event EventHandler<HttpRequestEventArgs> OnHead;
public event EventHandler<HttpRequestEventArgs> OnOptions;
public event EventHandler<HttpRequestEventArgs> OnPatch;
public event EventHandler<HttpRequestEventArgs> OnPost;
public event EventHandler<HttpRequestEventArgs> OnPut;
public event EventHandler<HttpRequestEventArgs> OnTrace;
public System.Net.IPAddress Address { get; private set; } public System.Net.IPAddress Address { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether the server accepts every
/// handshake request without checking the request URI.
/// </summary>
/// <remarks>
/// The set operation does nothing if the server has already started or
/// it is shutting down.
/// </remarks>
/// <value>
/// <para>
/// <c>true</c> if the server accepts every handshake request without
/// checking the request URI; otherwise, <c>false</c>.
/// </para>
/// <para>
/// The default value is <c>false</c>.
/// </para>
/// </value>
public bool AllowForwardedRequest
{
get { return _allowForwardedRequest; }
set { _allowForwardedRequest = value; }
}
public AuthenticationSchemes AuthenticationSchemes public AuthenticationSchemes AuthenticationSchemes
{ {
get get
@ -208,6 +249,7 @@ namespace EonaCat.Network
public bool IsListening => _state == ServerState.Start; public bool IsListening => _state == ServerState.Start;
public bool IsLoggingEnabled { get; private set; }
public bool IsSecure { get; private set; } public bool IsSecure { get; private set; }
public bool KeepClean public bool KeepClean
@ -222,11 +264,7 @@ namespace EonaCat.Network
WebSocketServices.AutoCleanSessions = value; WebSocketServices.AutoCleanSessions = value;
} }
} }
public bool IsLoggingEnabled { get; private set; }
public int Port { get; private set; } public int Port { get; private set; }
public string Realm public string Realm
{ {
get get
@ -339,24 +377,174 @@ namespace EonaCat.Network
} }
public WSEndpointManager WebSocketServices { get; private set; } public WSEndpointManager WebSocketServices { get; private set; }
public void AddWebSocketService<TEndpoint>(string path)
where TEndpoint : WSEndpoint, new()
{
WebSocketServices.AddService<TEndpoint>(path, null);
}
public event EventHandler<HttpRequestEventArgs> OnConnect; public void AddWebSocketService<TEndpoint>(
string path, Action<TEndpoint> initializer
)
where TEndpoint : WSEndpoint, new()
{
WebSocketServices.AddService(path, initializer);
}
public event EventHandler<HttpRequestEventArgs> OnDelete; public bool RemoveWebSocketService(string path)
{
return WebSocketServices.RemoveService(path);
}
public event EventHandler<HttpRequestEventArgs> OnGet; public void Start()
{
if (IsSecure)
{
if (!checkCertificate(out string message))
{
throw new InvalidOperationException(message);
}
}
public event EventHandler<HttpRequestEventArgs> OnHead; start();
}
public event EventHandler<HttpRequestEventArgs> OnOptions; public void Stop()
{
stop((ushort)CloseStatusCode.NoStatus, string.Empty);
}
public event EventHandler<HttpRequestEventArgs> 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<HttpRequestEventArgs> OnPost; if (code == (ushort)CloseStatusCode.MandatoryExtension)
{
var message = $"{(ushort)CloseStatusCode.MandatoryExtension} cannot be used.";
throw new ArgumentException(message, nameof(code));
}
public event EventHandler<HttpRequestEventArgs> 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<HttpRequestEventArgs> 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() private void abort()
{ {
@ -432,20 +620,6 @@ namespace EonaCat.Network
return true; 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( private void init(
string hostname, System.Net.IPAddress address, int port, bool secure string hostname, System.Net.IPAddress address, int port, bool secure
) )
@ -703,161 +877,5 @@ namespace EonaCat.Network
_listener.Stop(); _listener.Stop();
_receiveThread.Join(millisecondsTimeout); _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<TEndpoint>(string path)
where TEndpoint : WSEndpoint, new()
{
WebSocketServices.AddService<TEndpoint>(path, null);
}
public void AddWebSocketService<TEndpoint>(
string path, Action<TEndpoint> 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);
}
} }
} }

View File

@ -17,12 +17,8 @@ namespace EonaCat.Network
StartTime = DateTime.MaxValue; StartTime = DateTime.MaxValue;
} }
protected WSSessionManager Sessions { get; private set; }
public WSContext Context { get; private set; } public WSContext Context { get; private set; }
public Func<CookieCollection, CookieCollection, bool> CookiesValidator { get; set; } public Func<CookieCollection, CookieCollection, bool> CookiesValidator { get; set; }
public bool EmitOnPing public bool EmitOnPing
{ {
get get
@ -43,11 +39,8 @@ namespace EonaCat.Network
} }
public string ID { get; private set; } public string ID { get; private set; }
public bool IgnoreExtensions { get; set; } public bool IgnoreExtensions { get; set; }
public Func<string, bool> OriginValidator { get; set; } public Func<string, bool> OriginValidator { get; set; }
public string Protocol public string Protocol
{ {
get get
@ -72,53 +65,8 @@ namespace EonaCat.Network
} }
public DateTime StartTime { get; private set; } public DateTime StartTime { get; private set; }
public WSState State => _websocket != null ? _websocket.ReadyState : WSState.Connecting; public WSState State => _websocket != null ? _websocket.ReadyState : WSState.Connecting;
protected WSSessionManager Sessions { get; private set; }
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();
}
internal void Start(WSContext context, WSSessionManager sessions) internal void Start(WSContext context, WSSessionManager sessions)
{ {
if (_websocket != null) if (_websocket != null)
@ -210,5 +158,49 @@ namespace EonaCat.Network
{ {
_websocket?.SendAsync(stream, length, completed); _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();
}
} }
} }

View File

@ -13,8 +13,7 @@ namespace EonaCat.Network
Sessions = new WSSessionManager(); Sessions = new WSSessionManager();
} }
internal ServerState State => Sessions.State; public abstract Type EndpointType { get; }
public bool KeepClean public bool KeepClean
{ {
get get
@ -29,11 +28,7 @@ namespace EonaCat.Network
} }
public string Path { get; } public string Path { get; }
public WSSessionManager Sessions { get; } public WSSessionManager Sessions { get; }
public abstract Type EndpointType { get; }
public TimeSpan WaitTime public TimeSpan WaitTime
{ {
get get
@ -47,6 +42,7 @@ namespace EonaCat.Network
} }
} }
internal ServerState State => Sessions.State;
internal void Start() internal void Start()
{ {
Sessions.Start(); Sessions.Start();

View File

@ -11,10 +11,10 @@ namespace EonaCat.Network
{ {
public class WSEndpointManager public class WSEndpointManager
{ {
private volatile bool _clean;
private readonly Dictionary<string, WSEndpointHost> _hosts; private readonly Dictionary<string, WSEndpointHost> _hosts;
private volatile ServerState _state;
private readonly object _sync; private readonly object _sync;
private volatile bool _clean;
private volatile ServerState _state;
private TimeSpan _waitTime; private TimeSpan _waitTime;
internal WSEndpointManager() internal WSEndpointManager()
@ -26,59 +26,6 @@ namespace EonaCat.Network
_waitTime = TimeSpan.FromSeconds(1); _waitTime = TimeSpan.FromSeconds(1);
} }
public int Count
{
get
{
lock (_sync)
{
return _hosts.Count;
}
}
}
public IEnumerable<WSEndpointHost> 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 public bool AutoCleanSessions
{ {
get get
@ -112,6 +59,28 @@ namespace EonaCat.Network
} }
} }
public int Count
{
get
{
lock (_sync)
{
return _hosts.Count;
}
}
}
public IEnumerable<WSEndpointHost> Hosts
{
get
{
lock (_sync)
{
return _hosts.Values.ToList();
}
}
}
public IEnumerable<string> Paths public IEnumerable<string> Paths
{ {
get 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<CompressionMethod, byte[]>(); get
try
{ {
foreach (var host in Hosts) if (path == null)
{ {
if (_state != ServerState.Start) throw new ArgumentNullException(nameof(path));
{
Logger.Error("The server is shutting down.");
break;
} }
host.Sessions.Broadcast(opcode, data, cache); if (path.Length == 0)
{
throw new ArgumentException("An empty string.", nameof(path));
} }
if (completed != null) if (path[0] != '/')
{ {
completed(); throw new ArgumentException("Not an absolute path.", nameof(path));
}
}
catch (Exception ex)
{
Logger.Error(ex, "Could not broadcast");
}
finally
{
cache.Clear();
}
} }
private void broadcast(OperationCode opcode, Stream stream, Action completed) if (path.IndexOfAny(new[] { '?', '#' }) > -1)
{ {
var cache = new Dictionary<CompressionMethod, Stream>(); var message = "It includes either or both query and fragment components.";
throw new ArgumentException(message, nameof(path));
try
{
foreach (var host in Hosts)
{
if (_state != ServerState.Start)
{
Logger.Error("The server is shutting down.");
break;
} }
host.Sessions.Broadcast(opcode, stream, cache); InternalTryGetServiceHost(path, out WSEndpointHost host);
}
if (completed != null) return host;
{
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<TEndpoint>(string path, Func<TEndpoint> 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<TEndpoint>(
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<TEndpoint>( public void AddService<TEndpoint>(
string path, Action<TEndpoint> initializer string path, Action<TEndpoint> initializer
) )
@ -470,5 +306,168 @@ namespace EonaCat.Network
return InternalTryGetServiceHost(path, out host); return InternalTryGetServiceHost(path, out host);
} }
internal void Add<TEndpoint>(string path, Func<TEndpoint> 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<TEndpoint>(
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<CompressionMethod, byte[]>();
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<CompressionMethod, Stream>();
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;
}
} }
} }

View File

@ -10,11 +10,9 @@ namespace EonaCat.Network
{ {
public class WSServer public class WSServer
{ {
public bool IsConsoleLoggingEnabled { get; set; } private static readonly string _defaultRealm;
public bool IsLoggingEnabled { get; set; }
private bool _allowForwardedRequest; private bool _allowForwardedRequest;
private AuthenticationSchemes _authSchemes; private AuthenticationSchemes _authSchemes;
private static readonly string _defaultRealm;
private bool _dnsStyle; private bool _dnsStyle;
private string _hostname; private string _hostname;
private TcpListener _listener; private TcpListener _listener;
@ -27,7 +25,6 @@ namespace EonaCat.Network
private volatile ServerState _state; private volatile ServerState _state;
private object _sync; private object _sync;
private Func<IIdentity, NetworkCredential> _userCredentialsFinder; private Func<IIdentity, NetworkCredential> _userCredentialsFinder;
static WSServer() static WSServer()
{ {
_defaultRealm = "SECRET AREA"; _defaultRealm = "SECRET AREA";
@ -35,8 +32,8 @@ namespace EonaCat.Network
public WSServer() public WSServer()
{ {
var addr = System.Net.IPAddress.Any; var address = System.Net.IPAddress.Any;
init(addr.ToString(), addr, 80, false); init(address.ToString(), address, 80, false);
} }
public WSServer(int port) public WSServer(int port)
@ -118,7 +115,6 @@ namespace EonaCat.Network
} }
public System.Net.IPAddress Address { get; private set; } public System.Net.IPAddress Address { get; private set; }
public bool AllowForwardedRequest public bool AllowForwardedRequest
{ {
get get
@ -175,10 +171,6 @@ namespace EonaCat.Network
} }
} }
public bool IsListening => _state == ServerState.Start;
public bool IsSecure { get; private set; }
/// <summary> /// <summary>
/// Determines if sessions need to be removed automatically /// Determines if sessions need to be removed automatically
/// </summary> /// </summary>
@ -195,6 +187,39 @@ namespace EonaCat.Network
} }
} }
public WSEndpointManager Endpoints { get; private set; }
public Func<IIdentity, NetworkCredential> 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 int Port { get; private set; }
public string Realm public string Realm
@ -266,35 +291,6 @@ namespace EonaCat.Network
return GetSSLConfig(); return GetSSLConfig();
} }
} }
public Func<IIdentity, NetworkCredential> 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 public TimeSpan WaitTime
{ {
get get
@ -307,8 +303,146 @@ namespace EonaCat.Network
Endpoints.WaitTime = value; Endpoints.WaitTime = value;
} }
} }
public void AddEndpoint<TEndpoint>(string path) where TEndpoint : WSEndpoint, new()
{
Endpoints.AddService<TEndpoint>(path, null);
}
public WSEndpointManager Endpoints { get; private set; } public void AddEndpoint<TEndpoint>(string path, Action<TEndpoint> 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() private void abort()
{ {
@ -365,21 +499,6 @@ namespace EonaCat.Network
|| Uri.CheckHostName(name) != UriHostNameType.Dns || Uri.CheckHostName(name) != UriHostNameType.Dns
|| name == _hostname; || 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() private string GetRealm()
{ {
var realm = _realm; var realm = _realm;
@ -662,132 +781,5 @@ namespace EonaCat.Network
_receiveThread.Join(millisecondsTimeout); _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<TEndpoint>(string path) where TEndpoint : WSEndpoint, new()
{
Endpoints.AddService<TEndpoint>(path, null);
}
public void AddEndpoint<TEndpoint>(string path, Action<TEndpoint> 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);
}
} }
} }

View File

@ -12,13 +12,13 @@ namespace EonaCat.Network
{ {
public class WSSessionManager public class WSSessionManager
{ {
private volatile bool _clean;
private readonly object _forSweep; private readonly object _forSweep;
private readonly Dictionary<string, IWSSession> _sessions; private readonly Dictionary<string, IWSSession> _sessions;
private readonly object _sync;
private volatile bool _clean;
private volatile ServerState _state; private volatile ServerState _state;
private volatile bool _sweeping; private volatile bool _sweeping;
private System.Timers.Timer _sweepTimer; private System.Timers.Timer _sweepTimer;
private readonly object _sync;
private TimeSpan _waitTime; private TimeSpan _waitTime;
internal WSSessionManager() internal WSSessionManager()
@ -33,8 +33,6 @@ namespace EonaCat.Network
setSweepTimer(60000); setSweepTimer(60000);
} }
internal ServerState State => _state;
public IEnumerable<string> ActiveIDs public IEnumerable<string> ActiveIDs
{ {
get 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 public bool KeepClean
{ {
get 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<CompressionMethod, byte[]>(); get
try
{ {
foreach (var session in Sessions) if (id == null)
{ {
if (_state != ServerState.Start) throw new ArgumentNullException(nameof(id));
{
Logger.Error("The service is shutting down.");
break;
} }
session.Context.WebSocket.Send(opcode, data, cache); if (id.Length == 0)
{
throw new ArgumentException("An empty string.", nameof(id));
} }
if (completed != null) tryGetSession(id, out IWSSession session);
{
completed();
}
}
catch (Exception ex)
{
Logger.Error(ex.Message);
Logger.Debug(ex.ToString());
}
finally
{
cache.Clear();
}
}
private void broadcast(OperationCode opcode, Stream stream, Action completed) return session;
{
var cache = new Dictionary<CompressionMethod, Stream>();
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<string, bool> broadping(byte[] frameAsBytes)
{
var ret = new Dictionary<string, bool>();
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<CompressionMethod, byte[]> 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<CompressionMethod, Stream> 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<string, bool> Broadping(
byte[] frameAsBytes, TimeSpan timeout
)
{
var ret = new Dictionary<string, bool>();
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) public void Broadcast(byte[] data)
{ {
if (_state != ServerState.Start) if (_state != ServerState.Start)
@ -872,5 +599,275 @@ namespace EonaCat.Network
return tryGetSession(id, out session); 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<CompressionMethod, byte[]> 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<CompressionMethod, Stream> 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<string, bool> Broadping(
byte[] frameAsBytes, TimeSpan timeout
)
{
var ret = new Dictionary<string, bool>();
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<CompressionMethod, byte[]>();
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<CompressionMethod, Stream>();
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<string, bool> broadping(byte[] frameAsBytes)
{
var ret = new Dictionary<string, bool>();
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);
}
}
} }
} }

View File

@ -29,6 +29,11 @@ namespace EonaCat.Network
public override Type EndpointType => typeof(TEndpoint); public override Type EndpointType => typeof(TEndpoint);
protected override WSEndpoint CreateSession()
{
return _creator();
}
private Func<TEndpoint> createCreator( private Func<TEndpoint> createCreator(
Func<TEndpoint> creator, Action<TEndpoint> initializer Func<TEndpoint> creator, Action<TEndpoint> initializer
) )
@ -46,10 +51,5 @@ namespace EonaCat.Network
return ret; return ret;
}; };
} }
protected override WSEndpoint CreateSession()
{
return _creator();
}
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -11,19 +11,13 @@ namespace EonaCat.Network
{ {
internal class WSFrame : IEnumerable<byte> internal class WSFrame : IEnumerable<byte>
{ {
private const int BUFFER_SIZE = 1024;
internal static readonly byte[] EmptyPingBytes; internal static readonly byte[] EmptyPingBytes;
private const int BUFFER_SIZE = 1024;
static WSFrame() static WSFrame()
{ {
EmptyPingBytes = CreatePingFrame(false).ToArray(); EmptyPingBytes = CreatePingFrame(false).ToArray();
} }
private WSFrame()
{
}
internal WSFrame(OperationCode opcode, PayloadData payloadData, bool mask) internal WSFrame(OperationCode opcode, PayloadData payloadData, bool mask)
: this(FinalFrame.Final, opcode, payloadData, false, mask) : this(FinalFrame.Final, opcode, payloadData, false, mask)
{ {
@ -42,8 +36,9 @@ namespace EonaCat.Network
Rsv2 = Rsv.Off; Rsv2 = Rsv.Off;
Rsv3 = Rsv.Off; Rsv3 = Rsv.Off;
Opcode = opcode; Opcode = opcode;
PayloadData = new PayloadData(payloadData);
var len = payloadData.Length; var len = PayloadData.Length;
if (len < 126) if (len < 126)
{ {
PayloadLength = (byte)len; PayloadLength = (byte)len;
@ -64,17 +59,41 @@ namespace EonaCat.Network
{ {
Mask = Mask.On; Mask = Mask.On;
MaskingKey = createMaskingKey(); MaskingKey = createMaskingKey();
payloadData.Mask(MaskingKey); PayloadData.Mask(MaskingKey);
} }
else else
{ {
Mask = Mask.Off; Mask = Mask.Off;
MaskingKey = WSClient.EmptyBytes; 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 int ExtendedPayloadLengthCount => PayloadLength < 126 ? 0 : (PayloadLength == 126 ? 2 : 8);
internal ulong FullPayloadLength => PayloadLength < 126 internal ulong FullPayloadLength => PayloadLength < 126
@ -82,52 +101,173 @@ namespace EonaCat.Network
: PayloadLength == 126 : PayloadLength == 126
? ExtendedPayloadLength.ToUInt16(ByteOrder.Big) ? ExtendedPayloadLength.ToUInt16(ByteOrder.Big)
: ExtendedPayloadLength.ToUInt64(ByteOrder.Big); : ExtendedPayloadLength.ToUInt64(ByteOrder.Big);
public IEnumerator<byte> 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<WSFrame> completed,
Action<Exception> 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; } Mask = Mask.Off;
PayloadData.Mask(MaskingKey);
public Rsv Rsv1 { get; private set; } MaskingKey = WSClient.EmptyBytes;
}
public Rsv Rsv2 { get; private set; }
public Rsv Rsv3 { get; private set; }
private static byte[] createMaskingKey() private static byte[] createMaskingKey()
{ {
@ -496,173 +636,5 @@ Extended Payload Length: {7}
stream.ReadBytesAsync(llen, BUFFER_SIZE, compl, error); 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<WSFrame> completed,
Action<Exception> 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<byte> 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();
}
} }
} }

View File

@ -12,11 +12,9 @@ namespace EonaCat.Network
{ {
internal abstract class WebBase internal abstract class WebBase
{ {
private const int _headersMaxLength = 8192;
internal byte[] EntityBodyData; internal byte[] EntityBodyData;
protected const string CrLf = "\r\n"; protected const string CrLf = "\r\n";
private const int _headersMaxLength = 8192;
protected WebBase(Version version, NameValueCollection headers) protected WebBase(Version version, NameValueCollection headers)
{ {
ProtocolVersion = version; ProtocolVersion = version;
@ -48,6 +46,60 @@ namespace EonaCat.Network
public Version ProtocolVersion { get; } public Version ProtocolVersion { get; }
public byte[] ToByteArray()
{
return Encoding.UTF8.GetBytes(ToString());
}
protected static T Read<T>(Stream stream, Func<string[], T> 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) private static byte[] readEntityBody(Stream stream, string length)
{ {
if (!long.TryParse(length, out long len)) if (!long.TryParse(length, out long len))
@ -105,59 +157,5 @@ namespace EonaCat.Network
.Replace(CrLf + "\t", " ") .Replace(CrLf + "\t", " ")
.Split(new[] { CrLf }, StringSplitOptions.RemoveEmptyEntries); .Split(new[] { CrLf }, StringSplitOptions.RemoveEmptyEntries);
} }
protected static T Read<T>(Stream stream, Func<string[], T> 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());
}
} }
} }

View File

@ -14,19 +14,18 @@ namespace EonaCat.Network
private bool _websocketRequest; private bool _websocketRequest;
private bool _websocketRequestSet; 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) internal WebRequest(string method, string uri)
: this(method, uri, HttpVersion.Version11, new NameValueCollection()) : this(method, uri, HttpVersion.Version11, new NameValueCollection())
{ {
Headers["User-Agent"] = $"EonaCat.Network/{Constants.Version}"; 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 public AuthenticationResponse AuthenticationResponse
{ {
get get
@ -63,67 +62,6 @@ namespace EonaCat.Network
public string RequestUri { get; } 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) public void SetCookies(CookieCollection cookies)
{ {
if (cookies == null || cookies.Count == 0) if (cookies == null || cookies.Count == 0)
@ -169,5 +107,66 @@ namespace EonaCat.Network
return output.ToString(); 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);
}
} }
} }

View File

@ -11,13 +11,6 @@ namespace EonaCat.Network
{ {
internal class WebResponse : WebBase 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) internal WebResponse(HttpStatusCode code)
: this(code, code.GetDescription()) : this(code, code.GetDescription())
{ {
@ -29,6 +22,12 @@ namespace EonaCat.Network
Headers["Server"] = $"EonaCat.Network/{Constants.Version}"; 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 CookieCollection Cookies => Headers.GetCookies(true);
public bool HasConnectionClose => Headers.Contains("Connection", "close"); public bool HasConnectionClose => Headers.Contains("Connection", "close");
@ -55,6 +54,42 @@ namespace EonaCat.Network
public string StatusCode { get; } 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) internal static WebResponse CreateCloseResponse(HttpStatusCode code)
{ {
var res = new WebResponse(code); var res = new WebResponse(code);
@ -104,41 +139,5 @@ namespace EonaCat.Network
{ {
return Read(stream, Parse, millisecondsTimeout); 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();
}
} }
} }