Remove SocketHttpListener
This commit is contained in:
parent
848cfc32cc
commit
77addb2283
|
@ -9,7 +9,6 @@
|
|||
<ProjectReference Include="..\MediaBrowser.Providers\MediaBrowser.Providers.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.WebDashboard\MediaBrowser.WebDashboard.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.XbmcMetadata\MediaBrowser.XbmcMetadata.csproj" />
|
||||
<ProjectReference Include="..\SocketHttpListener\SocketHttpListener.csproj" />
|
||||
<ProjectReference Include="..\Emby.Dlna\Emby.Dlna.csproj" />
|
||||
<ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Api\MediaBrowser.Api.csproj" />
|
||||
|
@ -40,7 +39,6 @@
|
|||
<PackageReference Include="ServiceStack.Text.Core" Version="5.4.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.22.0" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="1.0.0" />
|
||||
<PackageReference Include="System.Buffers" Version="4.5.0" />
|
||||
<PackageReference Include="UTF.Unknown" Version="1.0.0-beta1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -23,9 +23,8 @@ namespace Emby.Server.Implementations.SocketSharp
|
|||
/// <value>The web socket.</value>
|
||||
private readonly WebSocket _webSocket;
|
||||
|
||||
private TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
private bool _disposed = false;
|
||||
private bool _disposed;
|
||||
|
||||
public SharpWebSocket(WebSocket socket, ILogger logger)
|
||||
{
|
||||
|
|
|
@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Http.Extensions;
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using SocketHttpListener.Net;
|
||||
using IHttpFile = MediaBrowser.Model.Services.IHttpFile;
|
||||
using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest;
|
||||
using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26730.3
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
|
@ -36,8 +37,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{CB7F2326-6497-4A3D-BA03-48513B17A7BE}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SocketHttpListener", "SocketHttpListener\SocketHttpListener.csproj", "{1D74413B-E7CF-455B-B021-F52BDF881542}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.Notifications\Emby.Notifications.csproj", "{2E030C33-6923-4530-9E54-FA29FA6AD1A9}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}"
|
||||
|
@ -56,7 +55,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||
SharedVersion.cs = SharedVersion.cs
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Drawing.Skia", "Jellyfin.Drawing.Skia\Jellyfin.Drawing.Skia.csproj", "{154872D9-6C12-4007-96E3-8F70A58386CE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
@ -132,10 +131,6 @@ Global
|
|||
{CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CB7F2326-6497-4A3D-BA03-48513B17A7BE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1D74413B-E7CF-455B-B021-F52BDF881542}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1D74413B-E7CF-455B-B021-F52BDF881542}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1D74413B-E7CF-455B-B021-F52BDF881542}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1D74413B-E7CF-455B-B021-F52BDF881542}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
namespace SocketHttpListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the values that indicate whether the byte order is a Little-endian or Big-endian.
|
||||
/// </summary>
|
||||
public enum ByteOrder : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates a Little-endian.
|
||||
/// </summary>
|
||||
Little,
|
||||
/// <summary>
|
||||
/// Indicates a Big-endian.
|
||||
/// </summary>
|
||||
Big
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the event data associated with a <see cref="WebSocket.OnClose"/> event.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A <see cref="WebSocket.OnClose"/> event occurs when the WebSocket connection has been closed.
|
||||
/// If you would like to get the reason for the close, you should access the <see cref="Code"/> or
|
||||
/// <see cref="Reason"/> property.
|
||||
/// </remarks>
|
||||
public class CloseEventArgs : EventArgs
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private bool _clean;
|
||||
private ushort _code;
|
||||
private string _reason;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Constructors
|
||||
|
||||
internal CloseEventArgs(PayloadData payload)
|
||||
{
|
||||
var data = payload.ApplicationData;
|
||||
var len = data.Length;
|
||||
_code = len > 1
|
||||
? data.SubArray(0, 2).ToUInt16(ByteOrder.Big)
|
||||
: (ushort)CloseStatusCode.NoStatusCode;
|
||||
|
||||
_reason = len > 2
|
||||
? GetUtf8String(data.SubArray(2, len - 2))
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
private static string GetUtf8String(byte[] bytes)
|
||||
{
|
||||
return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status code for the close.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A <see cref="ushort"/> that represents the status code for the close if any.
|
||||
/// </value>
|
||||
public ushort Code => _code;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the reason for the close.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A <see cref="string"/> that represents the reason for the close if any.
|
||||
/// </value>
|
||||
public string Reason => _reason;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the WebSocket connection has been closed cleanly.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if the WebSocket connection has been closed cleanly; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool WasClean
|
||||
{
|
||||
get => _clean;
|
||||
|
||||
internal set => _clean = value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
namespace SocketHttpListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the values of the status code for the WebSocket connection close.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The values of the status code are defined in
|
||||
/// <see href="http://tools.ietf.org/html/rfc6455#section-7.4">Section 7.4</see>
|
||||
/// of RFC 6455.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// "Reserved value" must not be set as a status code in a close control frame
|
||||
/// by an endpoint. It's designated for use in applications expecting a status
|
||||
/// code to indicate that the connection was closed due to the system grounds.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public enum CloseStatusCode : ushort
|
||||
{
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1000.
|
||||
/// Indicates a normal close.
|
||||
/// </summary>
|
||||
Normal = 1000,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1001.
|
||||
/// Indicates that an endpoint is going away.
|
||||
/// </summary>
|
||||
Away = 1001,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1002.
|
||||
/// Indicates that an endpoint is terminating the connection due to a protocol error.
|
||||
/// </summary>
|
||||
ProtocolError = 1002,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1003.
|
||||
/// Indicates that an endpoint is terminating the connection because it has received
|
||||
/// an unacceptable type message.
|
||||
/// </summary>
|
||||
IncorrectData = 1003,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1004.
|
||||
/// Still undefined. Reserved value.
|
||||
/// </summary>
|
||||
Undefined = 1004,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1005.
|
||||
/// Indicates that no status code was actually present. Reserved value.
|
||||
/// </summary>
|
||||
NoStatusCode = 1005,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1006.
|
||||
/// Indicates that the connection was closed abnormally. Reserved value.
|
||||
/// </summary>
|
||||
Abnormal = 1006,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1007.
|
||||
/// Indicates that an endpoint is terminating the connection because it has received
|
||||
/// a message that contains a data that isn't consistent with the type of the message.
|
||||
/// </summary>
|
||||
InconsistentData = 1007,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1008.
|
||||
/// Indicates that an endpoint is terminating the connection because it has received
|
||||
/// a message that violates its policy.
|
||||
/// </summary>
|
||||
PolicyViolation = 1008,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1009.
|
||||
/// Indicates that an endpoint is terminating the connection because it has received
|
||||
/// a message that is too big to process.
|
||||
/// </summary>
|
||||
TooBig = 1009,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1010.
|
||||
/// Indicates that the client is terminating the connection because it has expected
|
||||
/// the server to negotiate one or more extension, but the server didn't return them
|
||||
/// in the handshake response.
|
||||
/// </summary>
|
||||
IgnoreExtension = 1010,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1011.
|
||||
/// Indicates that the server is terminating the connection because it has encountered
|
||||
/// an unexpected condition that prevented it from fulfilling the request.
|
||||
/// </summary>
|
||||
ServerError = 1011,
|
||||
/// <summary>
|
||||
/// Equivalent to close status 1015.
|
||||
/// Indicates that the connection was closed due to a failure to perform a TLS handshake.
|
||||
/// Reserved value.
|
||||
/// </summary>
|
||||
TlsHandshakeFailure = 1015
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
namespace SocketHttpListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the values of the compression method used to compress the message on the WebSocket
|
||||
/// connection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The values of the compression method are defined in
|
||||
/// <see href="http://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-09">Compression
|
||||
/// Extensions for WebSocket</see>.
|
||||
/// </remarks>
|
||||
public enum CompressionMethod : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates non compression.
|
||||
/// </summary>
|
||||
None,
|
||||
/// <summary>
|
||||
/// Indicates using DEFLATE.
|
||||
/// </summary>
|
||||
Deflate
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the event data associated with a <see cref="WebSocket.OnError"/> event.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A <see cref="WebSocket.OnError"/> event occurs when the <see cref="WebSocket"/> gets an error.
|
||||
/// If you would like to get the error message, you should access the <see cref="Message"/>
|
||||
/// property.
|
||||
/// </remarks>
|
||||
public class ErrorEventArgs : EventArgs
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private string _message;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Constructors
|
||||
|
||||
internal ErrorEventArgs(string message)
|
||||
{
|
||||
_message = message;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error message.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A <see cref="string"/> that represents the error message.
|
||||
/// </value>
|
||||
public string Message => _message;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,946 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Services;
|
||||
using WebSocketState = System.Net.WebSockets.WebSocketState;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a set of static methods for the websocket-sharp.
|
||||
/// </summary>
|
||||
public static class Ext
|
||||
{
|
||||
#region Private Const Fields
|
||||
|
||||
private const string _tspecials = "()<>@,;:\\\"/[]?={} \t";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static MemoryStream compress(this Stream stream)
|
||||
{
|
||||
var output = new MemoryStream();
|
||||
if (stream.Length == 0)
|
||||
return output;
|
||||
|
||||
stream.Position = 0;
|
||||
using (var ds = new DeflateStream(output, CompressionMode.Compress, true))
|
||||
{
|
||||
stream.CopyTo(ds);
|
||||
//ds.Close(); // "BFINAL" set to 1.
|
||||
output.Position = 0;
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] decompress(this byte[] value)
|
||||
{
|
||||
if (value.Length == 0)
|
||||
return value;
|
||||
|
||||
using (var input = new MemoryStream(value))
|
||||
{
|
||||
return input.decompressToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static MemoryStream decompress(this Stream stream)
|
||||
{
|
||||
var output = new MemoryStream();
|
||||
if (stream.Length == 0)
|
||||
return output;
|
||||
|
||||
stream.Position = 0;
|
||||
using (var ds = new DeflateStream(stream, CompressionMode.Decompress, true))
|
||||
{
|
||||
ds.CopyTo(output, true);
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] decompressToArray(this Stream stream)
|
||||
{
|
||||
using (var decomp = stream.decompress())
|
||||
{
|
||||
return decomp.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<byte[]> ReadBytesAsync(this Stream stream, byte[] buffer, int offset, int length)
|
||||
{
|
||||
var len = await stream.ReadAsync(buffer, offset, length).ConfigureAwait(false);
|
||||
if (len < 1)
|
||||
return buffer.SubArray(0, offset);
|
||||
|
||||
var tmp = 0;
|
||||
while (len < length)
|
||||
{
|
||||
tmp = await stream.ReadAsync(buffer, offset + len, length - len).ConfigureAwait(false);
|
||||
if (tmp < 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
len += tmp;
|
||||
}
|
||||
|
||||
return len < length
|
||||
? buffer.SubArray(0, offset + len)
|
||||
: buffer;
|
||||
}
|
||||
|
||||
private static async Task<bool> ReadBytesAsync(this Stream stream, byte[] buffer, int offset, int length, Stream dest)
|
||||
{
|
||||
var bytes = await stream.ReadBytesAsync(buffer, offset, length).ConfigureAwait(false);
|
||||
var len = bytes.Length;
|
||||
dest.Write(bytes, 0, len);
|
||||
|
||||
return len == offset + length;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Methods
|
||||
|
||||
internal static async Task<byte[]> AppendAsync(this ushort code, string reason)
|
||||
{
|
||||
using (var buffer = new MemoryStream())
|
||||
{
|
||||
var tmp = code.ToByteArrayInternally(ByteOrder.Big);
|
||||
await buffer.WriteAsync(tmp, 0, 2).ConfigureAwait(false);
|
||||
if (reason != null && reason.Length > 0)
|
||||
{
|
||||
tmp = Encoding.UTF8.GetBytes(reason);
|
||||
await buffer.WriteAsync(tmp, 0, tmp.Length).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return buffer.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
internal static string CheckIfClosable(this WebSocketState state)
|
||||
{
|
||||
return state == WebSocketState.CloseSent
|
||||
? "While closing the WebSocket connection."
|
||||
: state == WebSocketState.Closed
|
||||
? "The WebSocket connection has already been closed."
|
||||
: null;
|
||||
}
|
||||
|
||||
internal static string CheckIfOpen(this WebSocketState state)
|
||||
{
|
||||
return state == WebSocketState.Connecting
|
||||
? "A WebSocket connection isn't established."
|
||||
: state == WebSocketState.CloseSent
|
||||
? "While closing the WebSocket connection."
|
||||
: state == WebSocketState.Closed
|
||||
? "The WebSocket connection has already been closed."
|
||||
: null;
|
||||
}
|
||||
|
||||
internal static string CheckIfValidControlData(this byte[] data, string paramName)
|
||||
{
|
||||
return data.Length > 125
|
||||
? string.Format("'{0}' length must be less.", paramName)
|
||||
: null;
|
||||
}
|
||||
|
||||
internal static Stream Compress(this Stream stream, CompressionMethod method)
|
||||
{
|
||||
return method == CompressionMethod.Deflate
|
||||
? stream.compress()
|
||||
: stream;
|
||||
}
|
||||
|
||||
internal static bool Contains<T>(this IEnumerable<T> source, Func<T, bool> condition)
|
||||
{
|
||||
foreach (T elm in source)
|
||||
if (condition(elm))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static void CopyTo(this Stream src, Stream dest, bool setDefaultPosition)
|
||||
{
|
||||
var readLen = 0;
|
||||
var bufferLen = 256;
|
||||
var buffer = new byte[bufferLen];
|
||||
while ((readLen = src.Read(buffer, 0, bufferLen)) > 0)
|
||||
{
|
||||
dest.Write(buffer, 0, readLen);
|
||||
}
|
||||
|
||||
if (setDefaultPosition)
|
||||
dest.Position = 0;
|
||||
}
|
||||
|
||||
internal static byte[] Decompress(this byte[] value, CompressionMethod method)
|
||||
{
|
||||
return method == CompressionMethod.Deflate
|
||||
? value.decompress()
|
||||
: value;
|
||||
}
|
||||
|
||||
internal static byte[] DecompressToArray(this Stream stream, CompressionMethod method)
|
||||
{
|
||||
return method == CompressionMethod.Deflate
|
||||
? stream.decompressToArray()
|
||||
: stream.ToByteArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="int"/> equals the specified <see cref="char"/>,
|
||||
/// and invokes the specified Action<int> delegate at the same time.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if <paramref name="value"/> equals <paramref name="c"/>;
|
||||
/// otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <param name="value">
|
||||
/// An <see cref="int"/> to compare.
|
||||
/// </param>
|
||||
/// <param name="c">
|
||||
/// A <see cref="char"/> to compare.
|
||||
/// </param>
|
||||
/// <param name="action">
|
||||
/// An Action<int> delegate that references the method(s) called at
|
||||
/// the same time as comparing. An <see cref="int"/> parameter to pass to
|
||||
/// the method(s) is <paramref name="value"/>.
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// <paramref name="value"/> isn't between 0 and 255.
|
||||
/// </exception>
|
||||
internal static bool EqualsWith(this int value, char c, Action<int> action)
|
||||
{
|
||||
if (value < 0 || value > 255)
|
||||
throw new ArgumentOutOfRangeException(nameof(value));
|
||||
|
||||
action(value);
|
||||
return value == c - 0;
|
||||
}
|
||||
|
||||
internal static string GetMessage(this CloseStatusCode code)
|
||||
{
|
||||
return code == CloseStatusCode.ProtocolError
|
||||
? "A WebSocket protocol error has occurred."
|
||||
: code == CloseStatusCode.IncorrectData
|
||||
? "An incorrect data has been received."
|
||||
: code == CloseStatusCode.Abnormal
|
||||
? "An exception has occurred."
|
||||
: code == CloseStatusCode.InconsistentData
|
||||
? "An inconsistent data has been received."
|
||||
: code == CloseStatusCode.PolicyViolation
|
||||
? "A policy violation has occurred."
|
||||
: code == CloseStatusCode.TooBig
|
||||
? "A too big data has been received."
|
||||
: code == CloseStatusCode.IgnoreExtension
|
||||
? "WebSocket client did not receive expected extension(s)."
|
||||
: code == CloseStatusCode.ServerError
|
||||
? "WebSocket server got an internal error."
|
||||
: code == CloseStatusCode.TlsHandshakeFailure
|
||||
? "An error has occurred while handshaking."
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
internal static string GetNameInternal(this string nameAndValue, string separator)
|
||||
{
|
||||
var i = nameAndValue.IndexOf(separator);
|
||||
return i > 0
|
||||
? nameAndValue.Substring(0, i).Trim()
|
||||
: null;
|
||||
}
|
||||
|
||||
internal static string GetValueInternal(this string nameAndValue, string separator)
|
||||
{
|
||||
var i = nameAndValue.IndexOf(separator);
|
||||
return i >= 0 && i < nameAndValue.Length - 1
|
||||
? nameAndValue.Substring(i + 1).Trim()
|
||||
: null;
|
||||
}
|
||||
|
||||
internal static bool IsCompressionExtension(this string value, CompressionMethod method)
|
||||
{
|
||||
return value.StartsWith(method.ToExtensionString());
|
||||
}
|
||||
|
||||
internal static bool IsPortNumber(this int value)
|
||||
{
|
||||
return value > 0 && value < 65536;
|
||||
}
|
||||
|
||||
internal static bool IsReserved(this ushort code)
|
||||
{
|
||||
return code == (ushort)CloseStatusCode.Undefined ||
|
||||
code == (ushort)CloseStatusCode.NoStatusCode ||
|
||||
code == (ushort)CloseStatusCode.Abnormal ||
|
||||
code == (ushort)CloseStatusCode.TlsHandshakeFailure;
|
||||
}
|
||||
|
||||
internal static bool IsReserved(this CloseStatusCode code)
|
||||
{
|
||||
return code == CloseStatusCode.Undefined ||
|
||||
code == CloseStatusCode.NoStatusCode ||
|
||||
code == CloseStatusCode.Abnormal ||
|
||||
code == CloseStatusCode.TlsHandshakeFailure;
|
||||
}
|
||||
|
||||
internal static bool IsText(this string value)
|
||||
{
|
||||
var len = value.Length;
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
char c = value[i];
|
||||
if (c < 0x20 && !"\r\n\t".Contains(c))
|
||||
return false;
|
||||
|
||||
if (c == 0x7f)
|
||||
return false;
|
||||
|
||||
if (c == '\n' && ++i < len)
|
||||
{
|
||||
c = value[i];
|
||||
if (!" \t".Contains(c))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool IsToken(this string value)
|
||||
{
|
||||
foreach (char c in value)
|
||||
if (c < 0x20 || c >= 0x7f || _tspecials.Contains(c))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static string Quote(this string value)
|
||||
{
|
||||
return value.IsToken()
|
||||
? value
|
||||
: string.Format("\"{0}\"", value.Replace("\"", "\\\""));
|
||||
}
|
||||
|
||||
internal static Task<byte[]> ReadBytesAsync(this Stream stream, int length)
|
||||
=> stream.ReadBytesAsync(new byte[length], 0, length);
|
||||
|
||||
internal static async Task<byte[]> ReadBytesAsync(this Stream stream, long length, int bufferLength)
|
||||
{
|
||||
using (var result = new MemoryStream())
|
||||
{
|
||||
var count = length / bufferLength;
|
||||
var rem = (int)(length % bufferLength);
|
||||
|
||||
var buffer = new byte[bufferLength];
|
||||
var end = false;
|
||||
for (long i = 0; i < count; i++)
|
||||
{
|
||||
if (!await stream.ReadBytesAsync(buffer, 0, bufferLength, result).ConfigureAwait(false))
|
||||
{
|
||||
end = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!end && rem > 0)
|
||||
{
|
||||
await stream.ReadBytesAsync(new byte[rem], 0, rem, result).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
internal static string RemovePrefix(this string value, params string[] prefixes)
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var prefix in prefixes)
|
||||
{
|
||||
if (value.StartsWith(prefix))
|
||||
{
|
||||
i = prefix.Length;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return i > 0
|
||||
? value.Substring(i)
|
||||
: value;
|
||||
}
|
||||
|
||||
internal static T[] Reverse<T>(this T[] array)
|
||||
{
|
||||
var len = array.Length;
|
||||
T[] reverse = new T[len];
|
||||
|
||||
var end = len - 1;
|
||||
for (var i = 0; i <= end; i++)
|
||||
reverse[i] = array[end - i];
|
||||
|
||||
return reverse;
|
||||
}
|
||||
|
||||
internal static IEnumerable<string> SplitHeaderValue(
|
||||
this string value, params char[] separator)
|
||||
{
|
||||
var len = value.Length;
|
||||
var separators = new string(separator);
|
||||
|
||||
var buffer = new StringBuilder(32);
|
||||
var quoted = false;
|
||||
var escaped = false;
|
||||
|
||||
char c;
|
||||
for (var i = 0; i < len; i++)
|
||||
{
|
||||
c = value[i];
|
||||
if (c == '"')
|
||||
{
|
||||
if (escaped)
|
||||
escaped = !escaped;
|
||||
else
|
||||
quoted = !quoted;
|
||||
}
|
||||
else if (c == '\\')
|
||||
{
|
||||
if (i < len - 1 && value[i + 1] == '"')
|
||||
escaped = true;
|
||||
}
|
||||
else if (separators.Contains(c))
|
||||
{
|
||||
if (!quoted)
|
||||
{
|
||||
yield return buffer.ToString();
|
||||
buffer.Length = 0;
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
|
||||
buffer.Append(c);
|
||||
}
|
||||
|
||||
if (buffer.Length > 0)
|
||||
yield return buffer.ToString();
|
||||
}
|
||||
|
||||
internal static byte[] ToByteArray(this Stream stream)
|
||||
{
|
||||
using (var output = new MemoryStream())
|
||||
{
|
||||
stream.Position = 0;
|
||||
stream.CopyTo(output);
|
||||
|
||||
return output.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
internal static byte[] ToByteArrayInternally(this ushort value, ByteOrder order)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
if (!order.IsHostOrder())
|
||||
Array.Reverse(bytes);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
internal static byte[] ToByteArrayInternally(this ulong value, ByteOrder order)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(value);
|
||||
if (!order.IsHostOrder())
|
||||
Array.Reverse(bytes);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
internal static string ToExtensionString(
|
||||
this CompressionMethod method, params string[] parameters)
|
||||
{
|
||||
if (method == CompressionMethod.None)
|
||||
return string.Empty;
|
||||
|
||||
var m = string.Format("permessage-{0}", method.ToString().ToLowerInvariant());
|
||||
if (parameters == null || parameters.Length == 0)
|
||||
return m;
|
||||
|
||||
return string.Format("{0}; {1}", m, parameters.ToString("; "));
|
||||
}
|
||||
|
||||
internal static ushort ToUInt16(this byte[] src, ByteOrder srcOrder)
|
||||
{
|
||||
src.ToHostOrder(srcOrder);
|
||||
return BitConverter.ToUInt16(src, 0);
|
||||
}
|
||||
|
||||
internal static ulong ToUInt64(this byte[] src, ByteOrder srcOrder)
|
||||
{
|
||||
src.ToHostOrder(srcOrder);
|
||||
return BitConverter.ToUInt64(src, 0);
|
||||
}
|
||||
|
||||
internal static string TrimEndSlash(this string value)
|
||||
{
|
||||
value = value.TrimEnd('/');
|
||||
return value.Length > 0
|
||||
? value
|
||||
: "/";
|
||||
}
|
||||
|
||||
internal static string Unquote(this string value)
|
||||
{
|
||||
var start = value.IndexOf('\"');
|
||||
var end = value.LastIndexOf('\"');
|
||||
if (start < end)
|
||||
value = value.Substring(start + 1, end - start - 1).Replace("\\\"", "\"");
|
||||
|
||||
return value.Trim();
|
||||
}
|
||||
|
||||
internal static void WriteBytes(this Stream stream, byte[] value)
|
||||
{
|
||||
using (var src = new MemoryStream(value))
|
||||
{
|
||||
src.CopyTo(stream);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="string"/> contains any of characters
|
||||
/// in the specified array of <see cref="char"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if <paramref name="value"/> contains any of <paramref name="chars"/>;
|
||||
/// otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <param name="value">
|
||||
/// A <see cref="string"/> to test.
|
||||
/// </param>
|
||||
/// <param name="chars">
|
||||
/// An array of <see cref="char"/> that contains characters to find.
|
||||
/// </param>
|
||||
public static bool Contains(this string value, params char[] chars)
|
||||
{
|
||||
return chars == null || chars.Length == 0
|
||||
? true
|
||||
: value == null || value.Length == 0
|
||||
? false
|
||||
: value.IndexOfAny(chars) != -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="QueryParamCollection"/> contains the entry
|
||||
/// with the specified <paramref name="name"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if <paramref name="collection"/> contains the entry
|
||||
/// with <paramref name="name"/>; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <param name="collection">
|
||||
/// A <see cref="QueryParamCollection"/> to test.
|
||||
/// </param>
|
||||
/// <param name="name">
|
||||
/// A <see cref="string"/> that represents the key of the entry to find.
|
||||
/// </param>
|
||||
public static bool Contains(this QueryParamCollection collection, string name)
|
||||
{
|
||||
return collection == null || collection.Count == 0
|
||||
? false
|
||||
: collection[name] != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="QueryParamCollection"/> contains the entry
|
||||
/// with the specified both <paramref name="name"/> and <paramref name="value"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if <paramref name="collection"/> contains the entry
|
||||
/// with both <paramref name="name"/> and <paramref name="value"/>;
|
||||
/// otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <param name="collection">
|
||||
/// A <see cref="QueryParamCollection"/> to test.
|
||||
/// </param>
|
||||
/// <param name="name">
|
||||
/// A <see cref="string"/> that represents the key of the entry to find.
|
||||
/// </param>
|
||||
/// <param name="value">
|
||||
/// A <see cref="string"/> that represents the value of the entry to find.
|
||||
/// </param>
|
||||
public static bool Contains(this QueryParamCollection collection, string name, string value)
|
||||
{
|
||||
if (collection == null || collection.Count == 0)
|
||||
return false;
|
||||
|
||||
var values = collection[name];
|
||||
if (values == null)
|
||||
return false;
|
||||
|
||||
foreach (var v in values.Split(','))
|
||||
if (v.Trim().Equals(value, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emits the specified <c>EventHandler<TEventArgs></c> delegate
|
||||
/// if it isn't <see langword="null"/>.
|
||||
/// </summary>
|
||||
/// <param name="eventHandler">
|
||||
/// An <c>EventHandler<TEventArgs></c> to emit.
|
||||
/// </param>
|
||||
/// <param name="sender">
|
||||
/// An <see cref="object"/> from which emits this <paramref name="eventHandler"/>.
|
||||
/// </param>
|
||||
/// <param name="e">
|
||||
/// A <c>TEventArgs</c> that represents the event data.
|
||||
/// </param>
|
||||
/// <typeparam name="TEventArgs">
|
||||
/// The type of the event data generated by the event.
|
||||
/// </typeparam>
|
||||
public static void Emit<TEventArgs>(
|
||||
this EventHandler<TEventArgs> eventHandler, object sender, TEventArgs e)
|
||||
where TEventArgs : EventArgs
|
||||
{
|
||||
if (eventHandler != null)
|
||||
eventHandler(sender, e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the specified HTTP status <paramref name="code"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="string"/> that represents the description of the HTTP status code.
|
||||
/// </returns>
|
||||
/// <param name="code">
|
||||
/// One of <see cref="HttpStatusCode"/> enum values, indicates the HTTP status codes.
|
||||
/// </param>
|
||||
public static string GetDescription(this HttpStatusCode code)
|
||||
{
|
||||
return ((int)code).GetStatusDescription();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the specified HTTP status <paramref name="code"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="string"/> that represents the description of the HTTP status code.
|
||||
/// </returns>
|
||||
/// <param name="code">
|
||||
/// An <see cref="int"/> that represents the HTTP status code.
|
||||
/// </param>
|
||||
public static string GetStatusDescription(this int code)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case 100: return "Continue";
|
||||
case 101: return "Switching Protocols";
|
||||
case 102: return "Processing";
|
||||
case 200: return "OK";
|
||||
case 201: return "Created";
|
||||
case 202: return "Accepted";
|
||||
case 203: return "Non-Authoritative Information";
|
||||
case 204: return "No Content";
|
||||
case 205: return "Reset Content";
|
||||
case 206: return "Partial Content";
|
||||
case 207: return "Multi-Status";
|
||||
case 300: return "Multiple Choices";
|
||||
case 301: return "Moved Permanently";
|
||||
case 302: return "Found";
|
||||
case 303: return "See Other";
|
||||
case 304: return "Not Modified";
|
||||
case 305: return "Use Proxy";
|
||||
case 307: return "Temporary Redirect";
|
||||
case 400: return "Bad Request";
|
||||
case 401: return "Unauthorized";
|
||||
case 402: return "Payment Required";
|
||||
case 403: return "Forbidden";
|
||||
case 404: return "Not Found";
|
||||
case 405: return "Method Not Allowed";
|
||||
case 406: return "Not Acceptable";
|
||||
case 407: return "Proxy Authentication Required";
|
||||
case 408: return "Request Timeout";
|
||||
case 409: return "Conflict";
|
||||
case 410: return "Gone";
|
||||
case 411: return "Length Required";
|
||||
case 412: return "Precondition Failed";
|
||||
case 413: return "Request Entity Too Large";
|
||||
case 414: return "Request-Uri Too Long";
|
||||
case 415: return "Unsupported Media Type";
|
||||
case 416: return "Requested Range Not Satisfiable";
|
||||
case 417: return "Expectation Failed";
|
||||
case 422: return "Unprocessable Entity";
|
||||
case 423: return "Locked";
|
||||
case 424: return "Failed Dependency";
|
||||
case 500: return "Internal Server Error";
|
||||
case 501: return "Not Implemented";
|
||||
case 502: return "Bad Gateway";
|
||||
case 503: return "Service Unavailable";
|
||||
case 504: return "Gateway Timeout";
|
||||
case 505: return "Http Version Not Supported";
|
||||
case 507: return "Insufficient Storage";
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="ByteOrder"/> is host
|
||||
/// (this computer architecture) byte order.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if <paramref name="order"/> is host byte order;
|
||||
/// otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <param name="order">
|
||||
/// One of the <see cref="ByteOrder"/> enum values, to test.
|
||||
/// </param>
|
||||
public static bool IsHostOrder(this ByteOrder order)
|
||||
{
|
||||
// true : !(true ^ true) or !(false ^ false)
|
||||
// false: !(true ^ false) or !(false ^ true)
|
||||
return !(BitConverter.IsLittleEndian ^ (order == ByteOrder.Little));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="string"/> is a predefined scheme.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if <paramref name="value"/> is a predefined scheme; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <param name="value">
|
||||
/// A <see cref="string"/> to test.
|
||||
/// </param>
|
||||
public static bool IsPredefinedScheme(this string value)
|
||||
{
|
||||
if (value == null || value.Length < 2)
|
||||
return false;
|
||||
|
||||
var c = value[0];
|
||||
if (c == 'h')
|
||||
return value == "http" || value == "https";
|
||||
|
||||
if (c == 'w')
|
||||
return value == "ws" || value == "wss";
|
||||
|
||||
if (c == 'f')
|
||||
return value == "file" || value == "ftp";
|
||||
|
||||
if (c == 'n')
|
||||
{
|
||||
c = value[1];
|
||||
return c == 'e'
|
||||
? value == "news" || value == "net.pipe" || value == "net.tcp"
|
||||
: value == "nntp";
|
||||
}
|
||||
|
||||
return (c == 'g' && value == "gopher") || (c == 'm' && value == "mailto");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="string"/> is a URI string.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// <c>true</c> if <paramref name="value"/> may be a URI string; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <param name="value">
|
||||
/// A <see cref="string"/> to test.
|
||||
/// </param>
|
||||
public static bool MaybeUri(this string value)
|
||||
{
|
||||
if (value == null || value.Length == 0)
|
||||
return false;
|
||||
|
||||
var i = value.IndexOf(':');
|
||||
if (i == -1)
|
||||
return false;
|
||||
|
||||
if (i >= 10)
|
||||
return false;
|
||||
|
||||
return value.Substring(0, i).IsPredefinedScheme();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a sub-array from the specified <paramref name="array"/>.
|
||||
/// A sub-array starts at the specified element position.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An array of T that receives a sub-array, or an empty array of T if any problems
|
||||
/// with the parameters.
|
||||
/// </returns>
|
||||
/// <param name="array">
|
||||
/// An array of T that contains the data to retrieve a sub-array.
|
||||
/// </param>
|
||||
/// <param name="startIndex">
|
||||
/// An <see cref="int"/> that contains the zero-based starting position of a sub-array
|
||||
/// in <paramref name="array"/>.
|
||||
/// </param>
|
||||
/// <param name="length">
|
||||
/// An <see cref="int"/> that contains the number of elements to retrieve a sub-array.
|
||||
/// </param>
|
||||
/// <typeparam name="T">
|
||||
/// The type of elements in the <paramref name="array"/>.
|
||||
/// </typeparam>
|
||||
public static T[] SubArray<T>(this T[] array, int startIndex, int length)
|
||||
{
|
||||
if (array == null || array.Length == 0)
|
||||
return new T[0];
|
||||
|
||||
if (startIndex < 0 || length <= 0)
|
||||
return new T[0];
|
||||
|
||||
if (startIndex + length > array.Length)
|
||||
return new T[0];
|
||||
|
||||
if (startIndex == 0 && array.Length == length)
|
||||
return array;
|
||||
|
||||
T[] subArray = new T[length];
|
||||
Array.Copy(array, startIndex, subArray, 0, length);
|
||||
|
||||
return subArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the order of the specified array of <see cref="byte"/> to the host byte order.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// An array of <see cref="byte"/> converted from <paramref name="src"/>.
|
||||
/// </returns>
|
||||
/// <param name="src">
|
||||
/// An array of <see cref="byte"/> to convert.
|
||||
/// </param>
|
||||
/// <param name="srcOrder">
|
||||
/// One of the <see cref="ByteOrder"/> enum values, indicates the byte order of
|
||||
/// <paramref name="src"/>.
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <paramref name="src"/> is <see langword="null"/>.
|
||||
/// </exception>
|
||||
public static void ToHostOrder(this byte[] src, ByteOrder srcOrder)
|
||||
{
|
||||
if (src == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(src));
|
||||
}
|
||||
|
||||
if (src.Length > 1 && !srcOrder.IsHostOrder())
|
||||
{
|
||||
Array.Reverse(src);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the specified <paramref name="array"/> to a <see cref="string"/> that
|
||||
/// concatenates the each element of <paramref name="array"/> across the specified
|
||||
/// <paramref name="separator"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="string"/> converted from <paramref name="array"/>,
|
||||
/// or <see cref="String.Empty"/> if <paramref name="array"/> is empty.
|
||||
/// </returns>
|
||||
/// <param name="array">
|
||||
/// An array of T to convert.
|
||||
/// </param>
|
||||
/// <param name="separator">
|
||||
/// A <see cref="string"/> that represents the separator string.
|
||||
/// </param>
|
||||
/// <typeparam name="T">
|
||||
/// The type of elements in <paramref name="array"/>.
|
||||
/// </typeparam>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <paramref name="array"/> is <see langword="null"/>.
|
||||
/// </exception>
|
||||
public static string ToString<T>(this T[] array, string separator)
|
||||
{
|
||||
if (array == null)
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
|
||||
var len = array.Length;
|
||||
if (len == 0)
|
||||
return string.Empty;
|
||||
|
||||
if (separator == null)
|
||||
separator = string.Empty;
|
||||
|
||||
var buff = new StringBuilder(64);
|
||||
(len - 1).Times(i => buff.AppendFormat("{0}{1}", array[i].ToString(), separator));
|
||||
|
||||
buff.Append(array[len - 1].ToString());
|
||||
return buff.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the specified <c>Action<int></c> delegate <paramref name="n"/> times.
|
||||
/// </summary>
|
||||
/// <param name="n">
|
||||
/// An <see cref="int"/> is the number of times to execute.
|
||||
/// </param>
|
||||
/// <param name="action">
|
||||
/// An <c>Action<int></c> delegate that references the method(s) to execute.
|
||||
/// An <see cref="int"/> parameter to pass to the method(s) is the zero-based count of
|
||||
/// iteration.
|
||||
/// </param>
|
||||
public static void Times(this int n, Action<int> action)
|
||||
{
|
||||
if (n > 0 && action != null)
|
||||
for (int i = 0; i < n; i++)
|
||||
action(i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the specified <see cref="string"/> to a <see cref="Uri"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="Uri"/> converted from <paramref name="uriString"/>, or <see langword="null"/>
|
||||
/// if <paramref name="uriString"/> isn't successfully converted.
|
||||
/// </returns>
|
||||
/// <param name="uriString">
|
||||
/// A <see cref="string"/> to convert.
|
||||
/// </param>
|
||||
public static Uri ToUri(this string uriString)
|
||||
{
|
||||
return Uri.TryCreate(
|
||||
uriString, uriString.MaybeUri() ? UriKind.Absolute : UriKind.Relative, out var res)
|
||||
? res
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// URL-decodes the specified <see cref="string"/>.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A <see cref="string"/> that receives the decoded string, or the <paramref name="value"/>
|
||||
/// if it's <see langword="null"/> or empty.
|
||||
/// </returns>
|
||||
/// <param name="value">
|
||||
/// A <see cref="string"/> to decode.
|
||||
/// </param>
|
||||
public static string UrlDecode(this string value)
|
||||
{
|
||||
return value == null || value.Length == 0
|
||||
? value
|
||||
: WebUtility.UrlDecode(value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
namespace SocketHttpListener
|
||||
{
|
||||
internal enum Fin : byte
|
||||
{
|
||||
More = 0x0,
|
||||
Final = 0x1
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using MediaBrowser.Model.Services;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
internal abstract class HttpBase
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private QueryParamCollection _headers;
|
||||
private Version _version;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protected Fields
|
||||
|
||||
protected const string CrLf = "\r\n";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protected Constructors
|
||||
|
||||
protected HttpBase(Version version, QueryParamCollection headers)
|
||||
{
|
||||
_version = version;
|
||||
_headers = headers;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
public QueryParamCollection Headers => _headers;
|
||||
|
||||
public Version ProtocolVersion => _version;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static Encoding getEncoding(string contentType)
|
||||
{
|
||||
if (contentType == null || contentType.Length == 0)
|
||||
return Encoding.UTF8;
|
||||
|
||||
var i = contentType.IndexOf("charset=", StringComparison.Ordinal);
|
||||
if (i == -1)
|
||||
return Encoding.UTF8;
|
||||
|
||||
var charset = contentType.Substring(i + 8);
|
||||
i = charset.IndexOf(';');
|
||||
if (i != -1)
|
||||
charset = charset.Substring(0, i).TrimEnd();
|
||||
|
||||
return Encoding.GetEncoding(charset.Trim('"'));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public byte[] ToByteArray()
|
||||
{
|
||||
return Encoding.UTF8.GetBytes(ToString());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using MediaBrowser.Model.Services;
|
||||
using SocketHttpListener.Net;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
// TODO what is the point of this class?
|
||||
internal class HttpResponse : HttpBase
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private string _code;
|
||||
private string _reason;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Constructors
|
||||
|
||||
private HttpResponse(string code, string reason, Version version, QueryParamCollection headers)
|
||||
: base(version, headers)
|
||||
{
|
||||
_code = code;
|
||||
_reason = reason;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Constructors
|
||||
|
||||
internal HttpResponse(HttpStatusCode code)
|
||||
: this(code, code.GetDescription())
|
||||
{
|
||||
}
|
||||
|
||||
internal HttpResponse(HttpStatusCode code, string reason)
|
||||
: this(((int)code).ToString(), reason, HttpVersion.Version11, new QueryParamCollection())
|
||||
{
|
||||
Headers["Server"] = "websocket-sharp/1.0";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
public CookieCollection Cookies => GetCookies(Headers, true);
|
||||
|
||||
private static CookieCollection GetCookies(QueryParamCollection headers, bool response)
|
||||
{
|
||||
var name = response ? "Set-Cookie" : "Cookie";
|
||||
return headers == null || !headers.Contains(name)
|
||||
? new CookieCollection()
|
||||
: CookieHelper.Parse(headers[name], response);
|
||||
}
|
||||
|
||||
public bool IsProxyAuthenticationRequired => _code == "407";
|
||||
|
||||
public bool IsUnauthorized => _code == "401";
|
||||
|
||||
public bool IsWebSocketResponse
|
||||
{
|
||||
get
|
||||
{
|
||||
var headers = Headers;
|
||||
return ProtocolVersion > HttpVersion.Version10 &&
|
||||
_code == "101" &&
|
||||
headers.Contains("Upgrade", "websocket") &&
|
||||
headers.Contains("Connection", "Upgrade");
|
||||
}
|
||||
}
|
||||
|
||||
public string Reason => _reason;
|
||||
|
||||
public string StatusCode => _code;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Methods
|
||||
|
||||
internal static HttpResponse CreateCloseResponse(HttpStatusCode code)
|
||||
{
|
||||
var res = new HttpResponse(code);
|
||||
res.Headers["Connection"] = "close";
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public void SetCookies(CookieCollection cookies)
|
||||
{
|
||||
if (cookies == null || cookies.Count == 0)
|
||||
return;
|
||||
|
||||
var headers = Headers;
|
||||
var sorted = cookies.OfType<Cookie>().OrderBy(i => i.Name).ToList();
|
||||
|
||||
foreach (var cookie in sorted)
|
||||
headers.Add("Set-Cookie", cookie.ToString());
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var output = new StringBuilder(64);
|
||||
output.AppendFormat("HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf);
|
||||
|
||||
var headers = Headers;
|
||||
foreach (var key in headers.Keys)
|
||||
output.AppendFormat("{0}: {1}{2}", key, headers[key], CrLf);
|
||||
|
||||
output.Append(CrLf);
|
||||
|
||||
return output.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
namespace SocketHttpListener
|
||||
{
|
||||
internal enum Mask : byte
|
||||
{
|
||||
Unmask = 0x0,
|
||||
Mask = 0x1
|
||||
}
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the event data associated with a <see cref="WebSocket.OnMessage"/> event.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A <see cref="WebSocket.OnMessage"/> event occurs when the <see cref="WebSocket"/> receives
|
||||
/// a text or binary data frame.
|
||||
/// If you want to get the received data, you access the <see cref="Data"/> or
|
||||
/// <see cref="RawData"/> property.
|
||||
/// </remarks>
|
||||
public class MessageEventArgs : EventArgs
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private string _data;
|
||||
private Opcode _opcode;
|
||||
private byte[] _rawData;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Constructors
|
||||
|
||||
internal MessageEventArgs(Opcode opcode, byte[] data)
|
||||
{
|
||||
_opcode = opcode;
|
||||
_rawData = data;
|
||||
_data = convertToString(opcode, data);
|
||||
}
|
||||
|
||||
internal MessageEventArgs(Opcode opcode, PayloadData payload)
|
||||
{
|
||||
_opcode = opcode;
|
||||
_rawData = payload.ApplicationData;
|
||||
_data = convertToString(opcode, _rawData);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the received data as a <see cref="string"/>.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// A <see cref="string"/> that contains the received data.
|
||||
/// </value>
|
||||
public string Data => _data;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the received data as an array of <see cref="byte"/>.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// An array of <see cref="byte"/> that contains the received data.
|
||||
/// </value>
|
||||
public byte[] RawData => _rawData;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the received data.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// One of the <see cref="Opcode"/> values, indicates the type of the received data.
|
||||
/// </value>
|
||||
public Opcode Type => _opcode;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static string convertToString(Opcode opcode, byte[] data)
|
||||
{
|
||||
return data.Length == 0
|
||||
? string.Empty
|
||||
: opcode == Opcode.Text
|
||||
? Encoding.UTF8.GetString(data, 0, data.Length)
|
||||
: opcode.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,141 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace SocketHttpListener.Net
|
||||
{
|
||||
public static class CookieHelper
|
||||
{
|
||||
internal static CookieCollection Parse(string value, bool response)
|
||||
{
|
||||
return response
|
||||
? parseResponse(value)
|
||||
: null;
|
||||
}
|
||||
|
||||
private static string[] splitCookieHeaderValue(string value)
|
||||
{
|
||||
return new List<string>(value.SplitHeaderValue(',', ';')).ToArray();
|
||||
}
|
||||
|
||||
private static CookieCollection parseResponse(string value)
|
||||
{
|
||||
var cookies = new CookieCollection();
|
||||
|
||||
Cookie cookie = null;
|
||||
var pairs = splitCookieHeaderValue(value);
|
||||
for (int i = 0; i < pairs.Length; i++)
|
||||
{
|
||||
var pair = pairs[i].Trim();
|
||||
if (pair.Length == 0)
|
||||
continue;
|
||||
|
||||
if (pair.StartsWith("version", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (cookie != null)
|
||||
cookie.Version = int.Parse(pair.GetValueInternal("=").Trim('"'));
|
||||
}
|
||||
else if (pair.StartsWith("expires", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var buffer = new StringBuilder(pair.GetValueInternal("="), 32);
|
||||
if (i < pairs.Length - 1)
|
||||
buffer.AppendFormat(", {0}", pairs[++i].Trim());
|
||||
|
||||
if (!DateTime.TryParseExact(
|
||||
buffer.ToString(),
|
||||
new[] { "ddd, dd'-'MMM'-'yyyy HH':'mm':'ss 'GMT'", "r" },
|
||||
new CultureInfo("en-US"),
|
||||
DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal,
|
||||
out var expires))
|
||||
expires = DateTime.Now;
|
||||
|
||||
if (cookie != null && cookie.Expires == DateTime.MinValue)
|
||||
cookie.Expires = expires.ToLocalTime();
|
||||
}
|
||||
else if (pair.StartsWith("max-age", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var max = int.Parse(pair.GetValueInternal("=").Trim('"'));
|
||||
var expires = DateTime.Now.AddSeconds((double)max);
|
||||
if (cookie != null)
|
||||
cookie.Expires = expires;
|
||||
}
|
||||
else if (pair.StartsWith("path", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (cookie != null)
|
||||
cookie.Path = pair.GetValueInternal("=");
|
||||
}
|
||||
else if (pair.StartsWith("domain", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (cookie != null)
|
||||
cookie.Domain = pair.GetValueInternal("=");
|
||||
}
|
||||
else if (pair.StartsWith("port", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var port = pair.Equals("port", StringComparison.OrdinalIgnoreCase)
|
||||
? "\"\""
|
||||
: pair.GetValueInternal("=");
|
||||
|
||||
if (cookie != null)
|
||||
cookie.Port = port;
|
||||
}
|
||||
else if (pair.StartsWith("comment", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (cookie != null)
|
||||
cookie.Comment = pair.GetValueInternal("=").UrlDecode();
|
||||
}
|
||||
else if (pair.StartsWith("commenturl", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (cookie != null)
|
||||
cookie.CommentUri = pair.GetValueInternal("=").Trim('"').ToUri();
|
||||
}
|
||||
else if (pair.StartsWith("discard", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (cookie != null)
|
||||
cookie.Discard = true;
|
||||
}
|
||||
else if (pair.StartsWith("secure", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (cookie != null)
|
||||
cookie.Secure = true;
|
||||
}
|
||||
else if (pair.StartsWith("httponly", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (cookie != null)
|
||||
cookie.HttpOnly = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cookie != null)
|
||||
cookies.Add(cookie);
|
||||
|
||||
string name;
|
||||
string val = string.Empty;
|
||||
|
||||
var pos = pair.IndexOf('=');
|
||||
if (pos == -1)
|
||||
{
|
||||
name = pair;
|
||||
}
|
||||
else if (pos == pair.Length - 1)
|
||||
{
|
||||
name = pair.Substring(0, pos).TrimEnd(' ');
|
||||
}
|
||||
else
|
||||
{
|
||||
name = pair.Substring(0, pos).TrimEnd(' ');
|
||||
val = pair.Substring(pos + 1).TrimStart(' ');
|
||||
}
|
||||
|
||||
cookie = new Cookie(name, val);
|
||||
}
|
||||
}
|
||||
|
||||
if (cookie != null)
|
||||
cookies.Add(cookie);
|
||||
|
||||
return cookies;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
namespace SocketHttpListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains the values of the opcode that indicates the type of a WebSocket frame.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The values of the opcode are defined in
|
||||
/// <see href="http://tools.ietf.org/html/rfc6455#section-5.2">Section 5.2</see> of RFC 6455.
|
||||
/// </remarks>
|
||||
public enum Opcode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Equivalent to numeric value 0.
|
||||
/// Indicates a continuation frame.
|
||||
/// </summary>
|
||||
Cont = 0x0,
|
||||
/// <summary>
|
||||
/// Equivalent to numeric value 1.
|
||||
/// Indicates a text frame.
|
||||
/// </summary>
|
||||
Text = 0x1,
|
||||
/// <summary>
|
||||
/// Equivalent to numeric value 2.
|
||||
/// Indicates a binary frame.
|
||||
/// </summary>
|
||||
Binary = 0x2,
|
||||
/// <summary>
|
||||
/// Equivalent to numeric value 8.
|
||||
/// Indicates a connection close frame.
|
||||
/// </summary>
|
||||
Close = 0x8,
|
||||
/// <summary>
|
||||
/// Equivalent to numeric value 9.
|
||||
/// Indicates a ping frame.
|
||||
/// </summary>
|
||||
Ping = 0x9,
|
||||
/// <summary>
|
||||
/// Equivalent to numeric value 10.
|
||||
/// Indicates a pong frame.
|
||||
/// </summary>
|
||||
Pong = 0xa
|
||||
}
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
internal class PayloadData : IEnumerable<byte>
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private byte[] _applicationData;
|
||||
private byte[] _extensionData;
|
||||
private bool _masked;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Const Fields
|
||||
|
||||
public const ulong MaxLength = long.MaxValue;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Constructors
|
||||
|
||||
public PayloadData()
|
||||
: this(new byte[0], new byte[0], false)
|
||||
{
|
||||
}
|
||||
|
||||
public PayloadData(byte[] applicationData)
|
||||
: this(new byte[0], applicationData, false)
|
||||
{
|
||||
}
|
||||
|
||||
public PayloadData(string applicationData)
|
||||
: this(new byte[0], Encoding.UTF8.GetBytes(applicationData), false)
|
||||
{
|
||||
}
|
||||
|
||||
public PayloadData(byte[] applicationData, bool masked)
|
||||
: this(new byte[0], applicationData, masked)
|
||||
{
|
||||
}
|
||||
|
||||
public PayloadData(byte[] extensionData, byte[] applicationData, bool masked)
|
||||
{
|
||||
_extensionData = extensionData;
|
||||
_applicationData = applicationData;
|
||||
_masked = masked;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Properties
|
||||
|
||||
internal bool ContainsReservedCloseStatusCode =>
|
||||
_applicationData.Length > 1 &&
|
||||
_applicationData.SubArray(0, 2).ToUInt16(ByteOrder.Big).IsReserved();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
public byte[] ApplicationData => _applicationData;
|
||||
|
||||
public byte[] ExtensionData => _extensionData;
|
||||
|
||||
public bool IsMasked => _masked;
|
||||
|
||||
public ulong Length => (ulong)(_extensionData.Length + _applicationData.Length);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private static void mask(byte[] src, byte[] key)
|
||||
{
|
||||
for (long i = 0; i < src.Length; i++)
|
||||
src[i] = (byte)(src[i] ^ key[i % 4]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public IEnumerator<byte> GetEnumerator()
|
||||
{
|
||||
foreach (byte b in _extensionData)
|
||||
yield return b;
|
||||
|
||||
foreach (byte b in _applicationData)
|
||||
yield return b;
|
||||
}
|
||||
|
||||
public void Mask(byte[] maskingKey)
|
||||
{
|
||||
if (_extensionData.Length > 0)
|
||||
mask(_extensionData, maskingKey);
|
||||
|
||||
if (_applicationData.Length > 0)
|
||||
mask(_applicationData, maskingKey);
|
||||
|
||||
_masked = !_masked;
|
||||
}
|
||||
|
||||
public byte[] ToByteArray()
|
||||
{
|
||||
return _extensionData.Length > 0
|
||||
? new List<byte>(this).ToArray()
|
||||
: _applicationData;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return BitConverter.ToString(ToByteArray());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Explicitly Implemented Interface Members
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("SocketHttpListener")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: NeutralResourcesLanguage("en")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
|
@ -1,8 +0,0 @@
|
|||
namespace SocketHttpListener
|
||||
{
|
||||
internal enum Rsv : byte
|
||||
{
|
||||
Off = 0x0,
|
||||
On = 0x1
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\SharedVersion.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
|
@ -1,74 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
public class SocketStream : Stream
|
||||
{
|
||||
private readonly Socket _socket;
|
||||
|
||||
public SocketStream(Socket socket, bool ownsSocket)
|
||||
{
|
||||
_socket = socket;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => true;
|
||||
|
||||
public override long Length => throw new NotImplementedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotImplementedException();
|
||||
set => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
_socket.Send(buffer, offset, count, SocketFlags.None);
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
{
|
||||
return _socket.BeginSend(buffer, offset, count, SocketFlags.None, callback, state);
|
||||
}
|
||||
|
||||
public override void EndWrite(IAsyncResult asyncResult)
|
||||
{
|
||||
_socket.EndSend(asyncResult);
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return _socket.Receive(buffer, offset, count, SocketFlags.None);
|
||||
}
|
||||
|
||||
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
|
||||
{
|
||||
return _socket.BeginReceive(buffer, offset, count, SocketFlags.None, callback, state);
|
||||
}
|
||||
|
||||
public override int EndRead(IAsyncResult asyncResult)
|
||||
{
|
||||
return _socket.EndReceive(asyncResult);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,777 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using WebSocketState = System.Net.WebSockets.WebSocketState;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements the WebSocket interface.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The WebSocket class provides a set of methods and properties for two-way communication using
|
||||
/// the WebSocket protocol (<see href="http://tools.ietf.org/html/rfc6455">RFC 6455</see>).
|
||||
/// </remarks>
|
||||
public class WebSocket : IDisposable
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private Action _closeContext;
|
||||
private CompressionMethod _compression;
|
||||
private WebSocketContext _context;
|
||||
private CookieCollection _cookies;
|
||||
private AutoResetEvent _exitReceiving;
|
||||
private object _forConn;
|
||||
private readonly SemaphoreSlim _forEvent = new SemaphoreSlim(1, 1);
|
||||
private object _forMessageEventQueue;
|
||||
private readonly SemaphoreSlim _forSend = new SemaphoreSlim(1, 1);
|
||||
private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
private Queue<MessageEventArgs> _messageEventQueue;
|
||||
private string _protocol;
|
||||
private volatile WebSocketState _readyState;
|
||||
private AutoResetEvent _receivePong;
|
||||
private bool _secure;
|
||||
private Stream _stream;
|
||||
private const string _version = "13";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Fields
|
||||
|
||||
internal const int FragmentLength = 1016; // Max value is int.MaxValue - 14.
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Constructors
|
||||
|
||||
// As server
|
||||
internal WebSocket(string protocol)
|
||||
{
|
||||
_protocol = protocol;
|
||||
}
|
||||
|
||||
public void SetContext(HttpListenerWebSocketContext context, Action closeContextFn, Stream stream)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
_closeContext = closeContextFn;
|
||||
_secure = context.IsSecureConnection;
|
||||
_stream = stream;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
// In the .NET Framework, this pulls the value from a P/Invoke. Here we just hardcode it to a reasonable default.
|
||||
public static TimeSpan DefaultKeepAliveInterval => TimeSpan.FromSeconds(30);
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state of the WebSocket connection.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// One of the <see cref="WebSocketState"/> enum values, indicates the state of the WebSocket
|
||||
/// connection. The default value is <see cref="WebSocketState.Connecting"/>.
|
||||
/// </value>
|
||||
public WebSocketState ReadyState => _readyState;
|
||||
|
||||
#region Public Events
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the WebSocket connection has been closed.
|
||||
/// </summary>
|
||||
public event EventHandler<CloseEventArgs> OnClose;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the <see cref="WebSocket"/> gets an error.
|
||||
/// </summary>
|
||||
public event EventHandler<ErrorEventArgs> OnError;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the <see cref="WebSocket"/> receives a message.
|
||||
/// </summary>
|
||||
public event EventHandler<MessageEventArgs> OnMessage;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when the WebSocket connection has been established.
|
||||
/// </summary>
|
||||
public event EventHandler OnOpen;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private async Task CloseAsync(CloseStatusCode code, string reason, bool wait)
|
||||
{
|
||||
await CloseAsync(new PayloadData(
|
||||
await ((ushort)code).AppendAsync(reason).ConfigureAwait(false)),
|
||||
!code.IsReserved(),
|
||||
wait).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task CloseAsync(PayloadData payload, bool send, bool wait)
|
||||
{
|
||||
lock (_forConn)
|
||||
{
|
||||
if (_readyState == WebSocketState.CloseSent || _readyState == WebSocketState.Closed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_readyState = WebSocketState.CloseSent;
|
||||
}
|
||||
|
||||
var e = new CloseEventArgs(payload)
|
||||
{
|
||||
WasClean = await CloseHandshakeAsync(
|
||||
send ? WebSocketFrame.CreateCloseFrame(Mask.Unmask, payload).ToByteArray() : null,
|
||||
wait ? 1000 : 0).ConfigureAwait(false)
|
||||
};
|
||||
|
||||
_readyState = WebSocketState.Closed;
|
||||
try
|
||||
{
|
||||
OnClose.Emit(this, e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error("An exception has occurred while OnClose.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> CloseHandshakeAsync(byte[] frameAsBytes, int millisecondsTimeout)
|
||||
{
|
||||
var sent = frameAsBytes != null && await WriteBytesAsync(frameAsBytes).ConfigureAwait(false);
|
||||
var received =
|
||||
millisecondsTimeout == 0 ||
|
||||
(sent && _exitReceiving != null && _exitReceiving.WaitOne(millisecondsTimeout));
|
||||
|
||||
closeServerResources();
|
||||
|
||||
if (_receivePong != null)
|
||||
{
|
||||
_receivePong.Dispose();
|
||||
_receivePong = null;
|
||||
}
|
||||
|
||||
if (_exitReceiving != null)
|
||||
{
|
||||
_exitReceiving.Dispose();
|
||||
_exitReceiving = null;
|
||||
}
|
||||
|
||||
var result = sent && received;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// As server
|
||||
private void closeServerResources()
|
||||
{
|
||||
if (_closeContext == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_closeContext();
|
||||
}
|
||||
catch (SocketException)
|
||||
{
|
||||
// it could be unable to send the handshake response
|
||||
}
|
||||
|
||||
_closeContext = null;
|
||||
_stream = null;
|
||||
_context = null;
|
||||
}
|
||||
|
||||
private async Task<bool> ConcatenateFragmentsIntoAsync(Stream dest)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var frame = await WebSocketFrame.ReadAsync(_stream, true).ConfigureAwait(false);
|
||||
if (frame.IsFinal)
|
||||
{
|
||||
/* FINAL */
|
||||
|
||||
// CONT
|
||||
if (frame.IsContinuation)
|
||||
{
|
||||
dest.WriteBytes(frame.PayloadData.ApplicationData);
|
||||
break;
|
||||
}
|
||||
|
||||
// PING
|
||||
if (frame.IsPing)
|
||||
{
|
||||
processPingFrame(frame);
|
||||
continue;
|
||||
}
|
||||
|
||||
// PONG
|
||||
if (frame.IsPong)
|
||||
{
|
||||
processPongFrame(frame);
|
||||
continue;
|
||||
}
|
||||
|
||||
// CLOSE
|
||||
if (frame.IsClose)
|
||||
return await ProcessCloseFrameAsync(frame).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* MORE */
|
||||
|
||||
// CONT
|
||||
if (frame.IsContinuation)
|
||||
{
|
||||
dest.WriteBytes(frame.PayloadData.ApplicationData);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// ?
|
||||
return await ProcessUnsupportedFrameAsync(
|
||||
frame,
|
||||
CloseStatusCode.IncorrectData,
|
||||
"An incorrect data has been received while receiving fragmented data.").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// As server
|
||||
private HttpResponse createHandshakeCloseResponse(HttpStatusCode code)
|
||||
{
|
||||
var res = HttpResponse.CreateCloseResponse(code);
|
||||
res.Headers["Sec-WebSocket-Version"] = _version;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private MessageEventArgs dequeueFromMessageEventQueue()
|
||||
{
|
||||
lock (_forMessageEventQueue)
|
||||
return _messageEventQueue.Count > 0
|
||||
? _messageEventQueue.Dequeue()
|
||||
: null;
|
||||
}
|
||||
|
||||
private void enqueueToMessageEventQueue(MessageEventArgs e)
|
||||
{
|
||||
lock (_forMessageEventQueue)
|
||||
_messageEventQueue.Enqueue(e);
|
||||
}
|
||||
|
||||
private void error(string message, Exception exception)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (exception != null)
|
||||
{
|
||||
message += ". Exception.Message: " + exception.Message;
|
||||
}
|
||||
OnError.Emit(this, new ErrorEventArgs(message));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void error(string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnError.Emit(this, new ErrorEventArgs(message));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void init()
|
||||
{
|
||||
_compression = CompressionMethod.None;
|
||||
_cookies = new CookieCollection();
|
||||
_forConn = new object();
|
||||
_messageEventQueue = new Queue<MessageEventArgs>();
|
||||
_forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot;
|
||||
_readyState = WebSocketState.Connecting;
|
||||
}
|
||||
|
||||
private async Task OpenAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
startReceiving();
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ProcessExceptionAsync(ex, "An exception has occurred while opening.").ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await _forEvent.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
OnOpen?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ProcessExceptionAsync(ex, "An exception has occurred while OnOpen.").ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_forEvent.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> ProcessCloseFrameAsync(WebSocketFrame frame)
|
||||
{
|
||||
var payload = frame.PayloadData;
|
||||
await CloseAsync(payload, !payload.ContainsReservedCloseStatusCode, false).ConfigureAwait(false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool processDataFrame(WebSocketFrame frame)
|
||||
{
|
||||
var e = frame.IsCompressed
|
||||
? new MessageEventArgs(
|
||||
frame.Opcode, frame.PayloadData.ApplicationData.Decompress(_compression))
|
||||
: new MessageEventArgs(frame.Opcode, frame.PayloadData);
|
||||
|
||||
enqueueToMessageEventQueue(e);
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task ProcessExceptionAsync(Exception exception, string message)
|
||||
{
|
||||
var code = CloseStatusCode.Abnormal;
|
||||
var reason = message;
|
||||
if (exception is WebSocketException)
|
||||
{
|
||||
var wsex = (WebSocketException)exception;
|
||||
code = wsex.Code;
|
||||
reason = wsex.Message;
|
||||
}
|
||||
|
||||
error(message ?? code.GetMessage(), exception);
|
||||
if (_readyState == WebSocketState.Connecting)
|
||||
{
|
||||
await CloseAsync(HttpStatusCode.BadRequest).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await CloseAsync(code, reason ?? code.GetMessage(), false).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private Task<bool> ProcessFragmentedFrameAsync(WebSocketFrame frame)
|
||||
{
|
||||
return frame.IsContinuation // Not first fragment
|
||||
? Task.FromResult(true)
|
||||
: ProcessFragmentsAsync(frame);
|
||||
}
|
||||
|
||||
private async Task<bool> ProcessFragmentsAsync(WebSocketFrame first)
|
||||
{
|
||||
using (var buff = new MemoryStream())
|
||||
{
|
||||
buff.WriteBytes(first.PayloadData.ApplicationData);
|
||||
if (!await ConcatenateFragmentsIntoAsync(buff).ConfigureAwait(false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] data;
|
||||
if (_compression != CompressionMethod.None)
|
||||
{
|
||||
data = buff.DecompressToArray(_compression);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = buff.ToArray();
|
||||
}
|
||||
|
||||
enqueueToMessageEventQueue(new MessageEventArgs(first.Opcode, data));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private bool processPingFrame(WebSocketFrame frame)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool processPongFrame(WebSocketFrame frame)
|
||||
{
|
||||
_receivePong.Set();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task<bool> ProcessUnsupportedFrameAsync(WebSocketFrame frame, CloseStatusCode code, string reason)
|
||||
{
|
||||
await ProcessExceptionAsync(new WebSocketException(code, reason), null).ConfigureAwait(false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Task<bool> ProcessWebSocketFrameAsync(WebSocketFrame frame)
|
||||
{
|
||||
// TODO: @bond change to if/else chain
|
||||
return frame.IsCompressed && _compression == CompressionMethod.None
|
||||
? ProcessUnsupportedFrameAsync(
|
||||
frame,
|
||||
CloseStatusCode.IncorrectData,
|
||||
"A compressed data has been received without available decompression method.")
|
||||
: frame.IsFragmented
|
||||
? ProcessFragmentedFrameAsync(frame)
|
||||
: frame.IsData
|
||||
? Task.FromResult(processDataFrame(frame))
|
||||
: frame.IsPing
|
||||
? Task.FromResult(processPingFrame(frame))
|
||||
: frame.IsPong
|
||||
? Task.FromResult(processPongFrame(frame))
|
||||
: frame.IsClose
|
||||
? ProcessCloseFrameAsync(frame)
|
||||
: ProcessUnsupportedFrameAsync(frame, CloseStatusCode.PolicyViolation, null);
|
||||
}
|
||||
|
||||
private async Task<bool> SendAsync(Opcode opcode, Stream stream)
|
||||
{
|
||||
await _forSend.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var src = stream;
|
||||
var compressed = false;
|
||||
var sent = false;
|
||||
try
|
||||
{
|
||||
if (_compression != CompressionMethod.None)
|
||||
{
|
||||
stream = stream.Compress(_compression);
|
||||
compressed = true;
|
||||
}
|
||||
|
||||
sent = await SendAsync(opcode, Mask.Unmask, stream, compressed).ConfigureAwait(false);
|
||||
if (!sent)
|
||||
error("Sending a data has been interrupted.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error("An exception has occurred while sending a data.", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (compressed)
|
||||
stream.Dispose();
|
||||
|
||||
src.Dispose();
|
||||
}
|
||||
|
||||
return sent;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_forSend.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> SendAsync(Opcode opcode, Mask mask, Stream stream, bool compressed)
|
||||
{
|
||||
var len = stream.Length;
|
||||
|
||||
/* Not fragmented */
|
||||
|
||||
if (len == 0)
|
||||
return await SendAsync(Fin.Final, opcode, mask, new byte[0], compressed).ConfigureAwait(false);
|
||||
|
||||
var quo = len / FragmentLength;
|
||||
var rem = (int)(len % FragmentLength);
|
||||
|
||||
byte[] buff = null;
|
||||
if (quo == 0)
|
||||
{
|
||||
buff = new byte[rem];
|
||||
return await stream.ReadAsync(buff, 0, rem).ConfigureAwait(false) == rem &&
|
||||
await SendAsync(Fin.Final, opcode, mask, buff, compressed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
buff = new byte[FragmentLength];
|
||||
if (quo == 1 && rem == 0)
|
||||
return await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) == FragmentLength &&
|
||||
await SendAsync(Fin.Final, opcode, mask, buff, compressed).ConfigureAwait(false);
|
||||
|
||||
/* Send fragmented */
|
||||
|
||||
// Begin
|
||||
if (await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) != FragmentLength ||
|
||||
!await SendAsync(Fin.More, opcode, mask, buff, compressed).ConfigureAwait(false))
|
||||
return false;
|
||||
|
||||
var n = rem == 0 ? quo - 2 : quo - 1;
|
||||
for (long i = 0; i < n; i++)
|
||||
if (await stream.ReadAsync(buff, 0, FragmentLength).ConfigureAwait(false) != FragmentLength ||
|
||||
!await SendAsync(Fin.More, Opcode.Cont, mask, buff, compressed).ConfigureAwait(false))
|
||||
return false;
|
||||
|
||||
// End
|
||||
if (rem == 0)
|
||||
rem = FragmentLength;
|
||||
else
|
||||
buff = new byte[rem];
|
||||
|
||||
return await stream.ReadAsync(buff, 0, rem).ConfigureAwait(false) == rem &&
|
||||
await SendAsync(Fin.Final, Opcode.Cont, mask, buff, compressed).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Task<bool> SendAsync(Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed)
|
||||
{
|
||||
lock (_forConn)
|
||||
{
|
||||
if (_readyState != WebSocketState.Open)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
return WriteBytesAsync(
|
||||
WebSocketFrame.CreateWebSocketFrame(fin, opcode, mask, data, compressed).ToByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
// As server
|
||||
private Task<bool> SendHttpResponseAsync(HttpResponse response)
|
||||
=> WriteBytesAsync(response.ToByteArray());
|
||||
|
||||
private void startReceiving()
|
||||
{
|
||||
if (_messageEventQueue.Count > 0)
|
||||
{
|
||||
_messageEventQueue.Clear();
|
||||
}
|
||||
|
||||
_exitReceiving = new AutoResetEvent(false);
|
||||
_receivePong = new AutoResetEvent(false);
|
||||
|
||||
Action receive = null;
|
||||
receive = async () => await WebSocketFrame.ReadAsync(
|
||||
_stream,
|
||||
true,
|
||||
async frame =>
|
||||
{
|
||||
if (await ProcessWebSocketFrameAsync(frame).ConfigureAwait(false) && _readyState != WebSocketState.Closed)
|
||||
{
|
||||
receive();
|
||||
|
||||
if (!frame.IsData)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _forEvent.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var e = dequeueFromMessageEventQueue();
|
||||
if (e != null && _readyState == WebSocketState.Open)
|
||||
{
|
||||
OnMessage.Emit(this, e);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ProcessExceptionAsync(ex, "An exception has occurred while OnMessage.").ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_forEvent.Release();
|
||||
}
|
||||
|
||||
}
|
||||
else if (_exitReceiving != null)
|
||||
{
|
||||
_exitReceiving.Set();
|
||||
}
|
||||
},
|
||||
async ex => await ProcessExceptionAsync(ex, "An exception has occurred while receiving a message.")).ConfigureAwait(false);
|
||||
|
||||
receive();
|
||||
}
|
||||
|
||||
private async Task<bool> WriteBytesAsync(byte[] data)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _stream.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Methods
|
||||
|
||||
// As server
|
||||
internal async Task CloseAsync(HttpResponse response)
|
||||
{
|
||||
_readyState = WebSocketState.CloseSent;
|
||||
await SendHttpResponseAsync(response).ConfigureAwait(false);
|
||||
|
||||
closeServerResources();
|
||||
|
||||
_readyState = WebSocketState.Closed;
|
||||
}
|
||||
|
||||
// As server
|
||||
internal Task CloseAsync(HttpStatusCode code)
|
||||
=> CloseAsync(createHandshakeCloseResponse(code));
|
||||
|
||||
// As server
|
||||
public async Task ConnectAsServer()
|
||||
{
|
||||
try
|
||||
{
|
||||
_readyState = WebSocketState.Open;
|
||||
await OpenAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await ProcessExceptionAsync(ex, "An exception has occurred while connecting.").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Closes the WebSocket connection, and releases all associated resources.
|
||||
/// </summary>
|
||||
public Task CloseAsync()
|
||||
{
|
||||
var msg = _readyState.CheckIfClosable();
|
||||
if (msg != null)
|
||||
{
|
||||
error(msg);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var send = _readyState == WebSocketState.Open;
|
||||
return CloseAsync(new PayloadData(), send, send);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the WebSocket connection with the specified <see cref="CloseStatusCode"/>
|
||||
/// and <see cref="string"/>, and releases all associated resources.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method emits a <see cref="OnError"/> event if the size
|
||||
/// of <paramref name="reason"/> is greater than 123 bytes.
|
||||
/// </remarks>
|
||||
/// <param name="code">
|
||||
/// One of the <see cref="CloseStatusCode"/> enum values, represents the status code
|
||||
/// indicating the reason for the close.
|
||||
/// </param>
|
||||
/// <param name="reason">
|
||||
/// A <see cref="string"/> that represents the reason for the close.
|
||||
/// </param>
|
||||
public async Task CloseAsync(CloseStatusCode code, string reason)
|
||||
{
|
||||
byte[] data = null;
|
||||
var msg = _readyState.CheckIfClosable() ??
|
||||
(data = await ((ushort)code).AppendAsync(reason).ConfigureAwait(false)).CheckIfValidControlData("reason");
|
||||
|
||||
if (msg != null)
|
||||
{
|
||||
error(msg);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var send = _readyState == WebSocketState.Open && !code.IsReserved();
|
||||
await CloseAsync(new PayloadData(data), send, send).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a binary <paramref name="data"/> asynchronously using the WebSocket connection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method doesn't wait for the send to be complete.
|
||||
/// </remarks>
|
||||
/// <param name="data">
|
||||
/// An array of <see cref="byte"/> that represents the binary data to send.
|
||||
/// </param>
|
||||
public Task SendAsync(byte[] data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(data));
|
||||
}
|
||||
|
||||
var msg = _readyState.CheckIfOpen();
|
||||
if (msg != null)
|
||||
{
|
||||
throw new Exception(msg);
|
||||
}
|
||||
|
||||
return SendAsync(Opcode.Binary, new MemoryStream(data));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a text <paramref name="data"/> asynchronously using the WebSocket connection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method doesn't wait for the send to be complete.
|
||||
/// </remarks>
|
||||
/// <param name="data">
|
||||
/// A <see cref="string"/> that represents the text data to send.
|
||||
/// </param>
|
||||
public Task SendAsync(string data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(data));
|
||||
}
|
||||
|
||||
var msg = _readyState.CheckIfOpen();
|
||||
if (msg != null)
|
||||
{
|
||||
throw new Exception(msg);
|
||||
}
|
||||
|
||||
return SendAsync(Opcode.Text, new MemoryStream(Encoding.UTF8.GetBytes(data)));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Explicit Interface Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Closes the WebSocket connection, and releases all associated resources.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method closes the WebSocket connection with <see cref="CloseStatusCode.Away"/>.
|
||||
/// </remarks>
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
CloseAsync(CloseStatusCode.Away, null).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
/// <summary>
|
||||
/// The exception that is thrown when a <see cref="WebSocket"/> gets a fatal error.
|
||||
/// </summary>
|
||||
public class WebSocketException : Exception
|
||||
{
|
||||
#region Internal Constructors
|
||||
|
||||
internal WebSocketException()
|
||||
: this(CloseStatusCode.Abnormal, null, null)
|
||||
{
|
||||
}
|
||||
|
||||
internal WebSocketException(string message)
|
||||
: this(CloseStatusCode.Abnormal, message, null)
|
||||
{
|
||||
}
|
||||
|
||||
internal WebSocketException(CloseStatusCode code)
|
||||
: this(code, null, null)
|
||||
{
|
||||
}
|
||||
|
||||
internal WebSocketException(string message, Exception innerException)
|
||||
: this(CloseStatusCode.Abnormal, message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
internal WebSocketException(CloseStatusCode code, string message)
|
||||
: this(code, message, null)
|
||||
{
|
||||
}
|
||||
|
||||
internal WebSocketException(CloseStatusCode code, string message, Exception innerException)
|
||||
: base(message ?? code.GetMessage(), innerException)
|
||||
{
|
||||
Code = code;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status code indicating the cause for the exception.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// One of the <see cref="CloseStatusCode"/> enum values, represents the status code indicating
|
||||
/// the cause for the exception.
|
||||
/// </value>
|
||||
public CloseStatusCode Code
|
||||
{
|
||||
get; private set;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,432 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SocketHttpListener
|
||||
{
|
||||
internal class WebSocketFrame : IEnumerable<byte>
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private byte[] _extPayloadLength;
|
||||
private Fin _fin;
|
||||
private Mask _mask;
|
||||
private byte[] _maskingKey;
|
||||
private Opcode _opcode;
|
||||
private PayloadData _payloadData;
|
||||
private byte _payloadLength;
|
||||
private Rsv _rsv1;
|
||||
private Rsv _rsv2;
|
||||
private Rsv _rsv3;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Fields
|
||||
|
||||
internal static readonly byte[] EmptyUnmaskPingData;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Constructor
|
||||
|
||||
static WebSocketFrame()
|
||||
{
|
||||
EmptyUnmaskPingData = CreatePingFrame(Mask.Unmask).ToByteArray();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Constructors
|
||||
|
||||
private WebSocketFrame()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Constructors
|
||||
|
||||
internal WebSocketFrame(Opcode opcode, PayloadData payload)
|
||||
: this(Fin.Final, opcode, Mask.Mask, payload, false)
|
||||
{
|
||||
}
|
||||
|
||||
internal WebSocketFrame(Opcode opcode, Mask mask, PayloadData payload)
|
||||
: this(Fin.Final, opcode, mask, payload, false)
|
||||
{
|
||||
}
|
||||
|
||||
internal WebSocketFrame(Fin fin, Opcode opcode, Mask mask, PayloadData payload)
|
||||
: this(fin, opcode, mask, payload, false)
|
||||
{
|
||||
}
|
||||
|
||||
internal WebSocketFrame(
|
||||
Fin fin, Opcode opcode, Mask mask, PayloadData payload, bool compressed)
|
||||
{
|
||||
_fin = fin;
|
||||
_rsv1 = isData(opcode) && compressed ? Rsv.On : Rsv.Off;
|
||||
_rsv2 = Rsv.Off;
|
||||
_rsv3 = Rsv.Off;
|
||||
_opcode = opcode;
|
||||
_mask = mask;
|
||||
|
||||
var len = payload.Length;
|
||||
if (len < 126)
|
||||
{
|
||||
_payloadLength = (byte)len;
|
||||
_extPayloadLength = new byte[0];
|
||||
}
|
||||
else if (len < 0x010000)
|
||||
{
|
||||
_payloadLength = (byte)126;
|
||||
_extPayloadLength = ((ushort)len).ToByteArrayInternally(ByteOrder.Big);
|
||||
}
|
||||
else
|
||||
{
|
||||
_payloadLength = (byte)127;
|
||||
_extPayloadLength = len.ToByteArrayInternally(ByteOrder.Big);
|
||||
}
|
||||
|
||||
if (mask == Mask.Mask)
|
||||
{
|
||||
_maskingKey = createMaskingKey();
|
||||
payload.Mask(_maskingKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
_maskingKey = new byte[0];
|
||||
}
|
||||
|
||||
_payloadData = payload;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Properties
|
||||
|
||||
public byte[] ExtendedPayloadLength => _extPayloadLength;
|
||||
|
||||
public Fin Fin => _fin;
|
||||
|
||||
public bool IsBinary => _opcode == Opcode.Binary;
|
||||
|
||||
public bool IsClose => _opcode == Opcode.Close;
|
||||
|
||||
public bool IsCompressed => _rsv1 == Rsv.On;
|
||||
|
||||
public bool IsContinuation => _opcode == Opcode.Cont;
|
||||
|
||||
public bool IsControl => _opcode == Opcode.Close || _opcode == Opcode.Ping || _opcode == Opcode.Pong;
|
||||
|
||||
public bool IsData => _opcode == Opcode.Binary || _opcode == Opcode.Text;
|
||||
|
||||
public bool IsFinal => _fin == Fin.Final;
|
||||
|
||||
public bool IsFragmented => _fin == Fin.More || _opcode == Opcode.Cont;
|
||||
|
||||
public bool IsMasked => _mask == Mask.Mask;
|
||||
|
||||
public bool IsPerMessageCompressed => (_opcode == Opcode.Binary || _opcode == Opcode.Text) && _rsv1 == Rsv.On;
|
||||
|
||||
public bool IsPing => _opcode == Opcode.Ping;
|
||||
|
||||
public bool IsPong => _opcode == Opcode.Pong;
|
||||
|
||||
public bool IsText => _opcode == Opcode.Text;
|
||||
|
||||
public ulong Length => 2 + (ulong)(_extPayloadLength.Length + _maskingKey.Length) + _payloadData.Length;
|
||||
|
||||
public Mask Mask => _mask;
|
||||
|
||||
public byte[] MaskingKey => _maskingKey;
|
||||
|
||||
public Opcode Opcode => _opcode;
|
||||
|
||||
public PayloadData PayloadData => _payloadData;
|
||||
|
||||
public byte PayloadLength => _payloadLength;
|
||||
|
||||
public Rsv Rsv1 => _rsv1;
|
||||
|
||||
public Rsv Rsv2 => _rsv2;
|
||||
|
||||
public Rsv Rsv3 => _rsv3;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private byte[] createMaskingKey()
|
||||
{
|
||||
var key = new byte[4];
|
||||
var rand = new Random();
|
||||
rand.NextBytes(key);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
private static bool isControl(Opcode opcode)
|
||||
{
|
||||
return opcode == Opcode.Close || opcode == Opcode.Ping || opcode == Opcode.Pong;
|
||||
}
|
||||
|
||||
private static bool isData(Opcode opcode)
|
||||
{
|
||||
return opcode == Opcode.Text || opcode == Opcode.Binary;
|
||||
}
|
||||
|
||||
private static async Task<WebSocketFrame> ReadAsync(byte[] header, Stream stream, bool unmask)
|
||||
{
|
||||
/* Header */
|
||||
|
||||
// FIN
|
||||
var fin = (header[0] & 0x80) == 0x80 ? Fin.Final : Fin.More;
|
||||
// RSV1
|
||||
var rsv1 = (header[0] & 0x40) == 0x40 ? Rsv.On : Rsv.Off;
|
||||
// RSV2
|
||||
var rsv2 = (header[0] & 0x20) == 0x20 ? Rsv.On : Rsv.Off;
|
||||
// RSV3
|
||||
var rsv3 = (header[0] & 0x10) == 0x10 ? Rsv.On : Rsv.Off;
|
||||
// Opcode
|
||||
var opcode = (Opcode)(header[0] & 0x0f);
|
||||
// MASK
|
||||
var mask = (header[1] & 0x80) == 0x80 ? Mask.Mask : Mask.Unmask;
|
||||
// Payload Length
|
||||
var payloadLen = (byte)(header[1] & 0x7f);
|
||||
|
||||
// Check if correct frame.
|
||||
var incorrect = isControl(opcode) && fin == Fin.More
|
||||
? "A control frame is fragmented."
|
||||
: !isData(opcode) && rsv1 == Rsv.On
|
||||
? "A non data frame is compressed."
|
||||
: null;
|
||||
|
||||
if (incorrect != null)
|
||||
throw new WebSocketException(CloseStatusCode.IncorrectData, incorrect);
|
||||
|
||||
// Check if consistent frame.
|
||||
if (isControl(opcode) && payloadLen > 125)
|
||||
throw new WebSocketException(
|
||||
CloseStatusCode.InconsistentData,
|
||||
"The length of payload data of a control frame is greater than 125 bytes.");
|
||||
|
||||
var frame = new WebSocketFrame();
|
||||
frame._fin = fin;
|
||||
frame._rsv1 = rsv1;
|
||||
frame._rsv2 = rsv2;
|
||||
frame._rsv3 = rsv3;
|
||||
frame._opcode = opcode;
|
||||
frame._mask = mask;
|
||||
frame._payloadLength = payloadLen;
|
||||
|
||||
/* Extended Payload Length */
|
||||
|
||||
var size = payloadLen < 126
|
||||
? 0
|
||||
: payloadLen == 126
|
||||
? 2
|
||||
: 8;
|
||||
|
||||
var extPayloadLen = size > 0 ? await stream.ReadBytesAsync(size).ConfigureAwait(false) : Array.Empty<byte>();
|
||||
if (size > 0 && extPayloadLen.Length != size)
|
||||
throw new WebSocketException(
|
||||
"The 'Extended Payload Length' of a frame cannot be read from the data source.");
|
||||
|
||||
frame._extPayloadLength = extPayloadLen;
|
||||
|
||||
/* Masking Key */
|
||||
|
||||
var masked = mask == Mask.Mask;
|
||||
var maskingKey = masked ? await stream.ReadBytesAsync(4).ConfigureAwait(false) : Array.Empty<byte>();
|
||||
if (masked && maskingKey.Length != 4)
|
||||
throw new WebSocketException(
|
||||
"The 'Masking Key' of a frame cannot be read from the data source.");
|
||||
|
||||
frame._maskingKey = maskingKey;
|
||||
|
||||
/* Payload Data */
|
||||
|
||||
ulong len = payloadLen < 126
|
||||
? payloadLen
|
||||
: payloadLen == 126
|
||||
? extPayloadLen.ToUInt16(ByteOrder.Big)
|
||||
: extPayloadLen.ToUInt64(ByteOrder.Big);
|
||||
|
||||
byte[] data = null;
|
||||
if (len > 0)
|
||||
{
|
||||
// Check if allowable payload data length.
|
||||
if (payloadLen > 126 && len > PayloadData.MaxLength)
|
||||
throw new WebSocketException(
|
||||
CloseStatusCode.TooBig,
|
||||
"The length of 'Payload Data' of a frame is greater than the allowable length.");
|
||||
|
||||
data = payloadLen > 126
|
||||
? await stream.ReadBytesAsync((long)len, 1024).ConfigureAwait(false)
|
||||
: await stream.ReadBytesAsync((int)len).ConfigureAwait(false);
|
||||
|
||||
//if (data.LongLength != (long)len)
|
||||
// throw new WebSocketException(
|
||||
// "The 'Payload Data' of a frame cannot be read from the data source.");
|
||||
}
|
||||
else
|
||||
{
|
||||
data = Array.Empty<byte>();
|
||||
}
|
||||
|
||||
var payload = new PayloadData(data, masked);
|
||||
if (masked && unmask)
|
||||
{
|
||||
payload.Mask(maskingKey);
|
||||
frame._mask = Mask.Unmask;
|
||||
frame._maskingKey = Array.Empty<byte>();
|
||||
}
|
||||
|
||||
frame._payloadData = payload;
|
||||
return frame;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Methods
|
||||
|
||||
internal static WebSocketFrame CreateCloseFrame(Mask mask, byte[] data)
|
||||
{
|
||||
return new WebSocketFrame(Opcode.Close, mask, new PayloadData(data));
|
||||
}
|
||||
|
||||
internal static WebSocketFrame CreateCloseFrame(Mask mask, PayloadData payload)
|
||||
{
|
||||
return new WebSocketFrame(Opcode.Close, mask, payload);
|
||||
}
|
||||
|
||||
internal static async Task<WebSocketFrame> CreateCloseFrameAsync(Mask mask, CloseStatusCode code, string reason)
|
||||
{
|
||||
return new WebSocketFrame(
|
||||
Opcode.Close, mask, new PayloadData(await ((ushort)code).AppendAsync(reason).ConfigureAwait(false)));
|
||||
}
|
||||
|
||||
internal static WebSocketFrame CreatePingFrame(Mask mask)
|
||||
{
|
||||
return new WebSocketFrame(Opcode.Ping, mask, new PayloadData());
|
||||
}
|
||||
|
||||
internal static WebSocketFrame CreatePingFrame(Mask mask, byte[] data)
|
||||
{
|
||||
return new WebSocketFrame(Opcode.Ping, mask, new PayloadData(data));
|
||||
}
|
||||
|
||||
internal static WebSocketFrame CreatePongFrame(Mask mask, PayloadData payload)
|
||||
{
|
||||
return new WebSocketFrame(Opcode.Pong, mask, payload);
|
||||
}
|
||||
|
||||
internal static WebSocketFrame CreateWebSocketFrame(
|
||||
Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed)
|
||||
{
|
||||
return new WebSocketFrame(fin, opcode, mask, new PayloadData(data), compressed);
|
||||
}
|
||||
|
||||
internal static Task<WebSocketFrame> ReadAsync(Stream stream)
|
||||
=> ReadAsync(stream, true);
|
||||
|
||||
internal static async Task<WebSocketFrame> ReadAsync(Stream stream, bool unmask)
|
||||
{
|
||||
var header = await stream.ReadBytesAsync(2).ConfigureAwait(false);
|
||||
if (header.Length != 2)
|
||||
{
|
||||
throw new WebSocketException(
|
||||
"The header part of a frame cannot be read from the data source.");
|
||||
}
|
||||
|
||||
return await ReadAsync(header, stream, unmask).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task ReadAsync(
|
||||
Stream stream, bool unmask, Action<WebSocketFrame> completed, Action<Exception> error)
|
||||
{
|
||||
try
|
||||
{
|
||||
var header = await stream.ReadBytesAsync(2).ConfigureAwait(false);
|
||||
if (header.Length != 2)
|
||||
{
|
||||
throw new WebSocketException(
|
||||
"The header part of a frame cannot be read from the data source.");
|
||||
}
|
||||
|
||||
var frame = await ReadAsync(header, stream, unmask).ConfigureAwait(false);
|
||||
completed?.Invoke(frame);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error.Invoke(ex);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public IEnumerator<byte> GetEnumerator()
|
||||
{
|
||||
foreach (var b in ToByteArray())
|
||||
yield return b;
|
||||
}
|
||||
|
||||
public void Print(bool dumped)
|
||||
{
|
||||
//Console.WriteLine(dumped ? dump(this) : print(this));
|
||||
}
|
||||
|
||||
public byte[] ToByteArray()
|
||||
{
|
||||
using (var buff = new MemoryStream())
|
||||
{
|
||||
var header = (int)_fin;
|
||||
header = (header << 1) + (int)_rsv1;
|
||||
header = (header << 1) + (int)_rsv2;
|
||||
header = (header << 1) + (int)_rsv3;
|
||||
header = (header << 4) + (int)_opcode;
|
||||
header = (header << 1) + (int)_mask;
|
||||
header = (header << 7) + (int)_payloadLength;
|
||||
buff.Write(((ushort)header).ToByteArrayInternally(ByteOrder.Big), 0, 2);
|
||||
|
||||
if (_payloadLength > 125)
|
||||
buff.Write(_extPayloadLength, 0, _extPayloadLength.Length);
|
||||
|
||||
if (_mask == Mask.Mask)
|
||||
buff.Write(_maskingKey, 0, _maskingKey.Length);
|
||||
|
||||
if (_payloadLength > 0)
|
||||
{
|
||||
var payload = _payloadData.ToByteArray();
|
||||
if (_payloadLength < 127)
|
||||
buff.Write(payload, 0, payload.Length);
|
||||
else
|
||||
buff.WriteBytes(payload);
|
||||
}
|
||||
|
||||
return buff.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return BitConverter.ToString(ToByteArray());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Explicitly Implemented Interface Members
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user