aboutsummaryrefslogtreecommitdiff
path: root/SocketHttpListener/Net/WebSockets
diff options
context:
space:
mode:
authorstefan <stefan@hegedues.at>2018-09-12 19:26:21 +0200
committerstefan <stefan@hegedues.at>2018-09-12 19:26:21 +0200
commit48facb797ed912e4ea6b04b17d1ff190ac2daac4 (patch)
tree8dae77a31670a888d733484cb17dd4077d5444e8 /SocketHttpListener/Net/WebSockets
parentc32d8656382a0eacb301692e0084377fc433ae9b (diff)
Update to 3.5.2 and .net core 2.1
Diffstat (limited to 'SocketHttpListener/Net/WebSockets')
-rw-r--r--SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs358
-rw-r--r--SocketHttpListener/Net/WebSockets/HttpWebSocket.Managed.cs84
-rw-r--r--SocketHttpListener/Net/WebSockets/HttpWebSocket.cs160
-rw-r--r--SocketHttpListener/Net/WebSockets/WebSocketCloseStatus.cs31
-rw-r--r--SocketHttpListener/Net/WebSockets/WebSocketContext.cs169
-rw-r--r--SocketHttpListener/Net/WebSockets/WebSocketValidate.cs143
6 files changed, 478 insertions, 467 deletions
diff --git a/SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs b/SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs
index 803c67b83..49375678d 100644
--- a/SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs
+++ b/SocketHttpListener/Net/WebSockets/HttpListenerWebSocketContext.cs
@@ -12,337 +12,87 @@ using SocketHttpListener.Primitives;
namespace SocketHttpListener.Net.WebSockets
{
- /// <summary>
- /// Provides the properties used to access the information in a WebSocket connection request
- /// received by the <see cref="HttpListener"/>.
- /// </summary>
- /// <remarks>
- /// </remarks>
public class HttpListenerWebSocketContext : WebSocketContext
{
- #region Private Fields
+ private readonly Uri _requestUri;
+ private readonly QueryParamCollection _headers;
+ private readonly CookieCollection _cookieCollection;
+ private readonly IPrincipal _user;
+ private readonly bool _isAuthenticated;
+ private readonly bool _isLocal;
+ private readonly bool _isSecureConnection;
- private HttpListenerContext _context;
- private WebSocket _websocket;
+ private readonly string _origin;
+ private readonly IEnumerable<string> _secWebSocketProtocols;
+ private readonly string _secWebSocketVersion;
+ private readonly string _secWebSocketKey;
- #endregion
-
- #region Internal Constructors
+ private readonly WebSocket _webSocket;
internal HttpListenerWebSocketContext(
- HttpListenerContext context, string protocol, ICryptoProvider cryptoProvider, IMemoryStreamFactory memoryStreamFactory)
+ Uri requestUri,
+ QueryParamCollection headers,
+ CookieCollection cookieCollection,
+ IPrincipal user,
+ bool isAuthenticated,
+ bool isLocal,
+ bool isSecureConnection,
+ string origin,
+ IEnumerable<string> secWebSocketProtocols,
+ string secWebSocketVersion,
+ string secWebSocketKey,
+ WebSocket webSocket)
{
- _context = context;
- _websocket = new WebSocket(this, protocol, cryptoProvider, memoryStreamFactory);
- }
-
- #endregion
+ _cookieCollection = new CookieCollection();
+ _cookieCollection.Add(cookieCollection);
- #region Internal Properties
+ //_headers = new NameValueCollection(headers);
+ _headers = headers;
+ _user = CopyPrincipal(user);
- internal Stream Stream
- {
- get
- {
- return _context.Connection.Stream;
- }
+ _requestUri = requestUri;
+ _isAuthenticated = isAuthenticated;
+ _isLocal = isLocal;
+ _isSecureConnection = isSecureConnection;
+ _origin = origin;
+ _secWebSocketProtocols = secWebSocketProtocols;
+ _secWebSocketVersion = secWebSocketVersion;
+ _secWebSocketKey = secWebSocketKey;
+ _webSocket = webSocket;
}
- #endregion
+ public override Uri RequestUri => _requestUri;
- #region Public Properties
+ public override QueryParamCollection Headers => _headers;
- /// <summary>
- /// Gets the HTTP cookies included in the request.
- /// </summary>
- /// <value>
- /// A <see cref="System.Net.CookieCollection"/> that contains the cookies.
- /// </value>
- public override CookieCollection CookieCollection
- {
- get
- {
- return _context.Request.Cookies;
- }
- }
+ public override string Origin => _origin;
- /// <summary>
- /// Gets the HTTP headers included in the request.
- /// </summary>
- /// <value>
- /// A <see cref="QueryParamCollection"/> that contains the headers.
- /// </value>
- public override QueryParamCollection Headers
- {
- get
- {
- return _context.Request.Headers;
- }
- }
+ public override IEnumerable<string> SecWebSocketProtocols => _secWebSocketProtocols;
- /// <summary>
- /// Gets the value of the Host header included in the request.
- /// </summary>
- /// <value>
- /// A <see cref="string"/> that represents the value of the Host header.
- /// </value>
- public override string Host
- {
- get
- {
- return _context.Request.Headers["Host"];
- }
- }
+ public override string SecWebSocketVersion => _secWebSocketVersion;
- /// <summary>
- /// Gets a value indicating whether the client is authenticated.
- /// </summary>
- /// <value>
- /// <c>true</c> if the client is authenticated; otherwise, <c>false</c>.
- /// </value>
- public override bool IsAuthenticated
- {
- get
- {
- return _context.Request.IsAuthenticated;
- }
- }
+ public override string SecWebSocketKey => _secWebSocketKey;
- /// <summary>
- /// Gets a value indicating whether the client connected from the local computer.
- /// </summary>
- /// <value>
- /// <c>true</c> if the client connected from the local computer; otherwise, <c>false</c>.
- /// </value>
- public override bool IsLocal
- {
- get
- {
- return _context.Request.IsLocal;
- }
- }
+ public override CookieCollection CookieCollection => _cookieCollection;
- /// <summary>
- /// Gets a value indicating whether the WebSocket connection is secured.
- /// </summary>
- /// <value>
- /// <c>true</c> if the connection is secured; otherwise, <c>false</c>.
- /// </value>
- public override bool IsSecureConnection
- {
- get
- {
- return _context.Connection.IsSecure;
- }
- }
+ public override IPrincipal User => _user;
- /// <summary>
- /// Gets a value indicating whether the request is a WebSocket connection request.
- /// </summary>
- /// <value>
- /// <c>true</c> if the request is a WebSocket connection request; otherwise, <c>false</c>.
- /// </value>
- public override bool IsWebSocketRequest
- {
- get
- {
- return _context.Request.IsWebSocketRequest;
- }
- }
+ public override bool IsAuthenticated => _isAuthenticated;
- /// <summary>
- /// Gets the value of the Origin header included in the request.
- /// </summary>
- /// <value>
- /// A <see cref="string"/> that represents the value of the Origin header.
- /// </value>
- public override string Origin
- {
- get
- {
- return _context.Request.Headers["Origin"];
- }
- }
+ public override bool IsLocal => _isLocal;
- /// <summary>
- /// Gets the query string included in the request.
- /// </summary>
- /// <value>
- /// A <see cref="QueryParamCollection"/> that contains the query string parameters.
- /// </value>
- public override QueryParamCollection QueryString
- {
- get
- {
- return _context.Request.QueryString;
- }
- }
+ public override bool IsSecureConnection => _isSecureConnection;
- /// <summary>
- /// Gets the URI requested by the client.
- /// </summary>
- /// <value>
- /// A <see cref="Uri"/> that represents the requested URI.
- /// </value>
- public override Uri RequestUri
- {
- get
- {
- return _context.Request.Url;
- }
- }
+ public override WebSocket WebSocket => _webSocket;
- /// <summary>
- /// Gets the value of the Sec-WebSocket-Key header included in the request.
- /// </summary>
- /// <remarks>
- /// This property provides a part of the information used by the server to prove that it
- /// received a valid WebSocket connection request.
- /// </remarks>
- /// <value>
- /// A <see cref="string"/> that represents the value of the Sec-WebSocket-Key header.
- /// </value>
- public override string SecWebSocketKey
+ private static IPrincipal CopyPrincipal(IPrincipal user)
{
- get
+ if (user != null)
{
- return _context.Request.Headers["Sec-WebSocket-Key"];
+ throw new NotImplementedException();
}
- }
- /// <summary>
- /// Gets the values of the Sec-WebSocket-Protocol header included in the request.
- /// </summary>
- /// <remarks>
- /// This property represents the subprotocols requested by the client.
- /// </remarks>
- /// <value>
- /// An <see cref="T:System.Collections.Generic.IEnumerable{string}"/> instance that provides
- /// an enumerator which supports the iteration over the values of the Sec-WebSocket-Protocol
- /// header.
- /// </value>
- public override IEnumerable<string> SecWebSocketProtocols
- {
- get
- {
- var protocols = _context.Request.Headers["Sec-WebSocket-Protocol"];
- if (protocols != null)
- foreach (var protocol in protocols.Split(','))
- yield return protocol.Trim();
- }
- }
-
- /// <summary>
- /// Gets the value of the Sec-WebSocket-Version header included in the request.
- /// </summary>
- /// <remarks>
- /// This property represents the WebSocket protocol version.
- /// </remarks>
- /// <value>
- /// A <see cref="string"/> that represents the value of the Sec-WebSocket-Version header.
- /// </value>
- public override string SecWebSocketVersion
- {
- get
- {
- return _context.Request.Headers["Sec-WebSocket-Version"];
- }
- }
-
- /// <summary>
- /// Gets the server endpoint as an IP address and a port number.
- /// </summary>
- /// <value>
- /// </value>
- public override IPEndPoint ServerEndPoint
- {
- get
- {
- return _context.Connection.LocalEndPoint;
- }
- }
-
- /// <summary>
- /// Gets the client information (identity, authentication, and security roles).
- /// </summary>
- /// <value>
- /// A <see cref="IPrincipal"/> that represents the client information.
- /// </value>
- public override IPrincipal User
- {
- get
- {
- return _context.User;
- }
- }
-
- /// <summary>
- /// Gets the client endpoint as an IP address and a port number.
- /// </summary>
- /// <value>
- /// </value>
- public override IPEndPoint UserEndPoint
- {
- get
- {
- return _context.Connection.RemoteEndPoint;
- }
+ return null;
}
-
- /// <summary>
- /// Gets the <see cref="SocketHttpListener.WebSocket"/> instance used for two-way communication
- /// between client and server.
- /// </summary>
- /// <value>
- /// A <see cref="SocketHttpListener.WebSocket"/>.
- /// </value>
- public override WebSocket WebSocket
- {
- get
- {
- return _websocket;
- }
- }
-
- #endregion
-
- #region Internal Methods
-
- internal void Close()
- {
- try
- {
- _context.Connection.Close(true);
- }
- catch
- {
- // catch errors sending the closing handshake
- }
- }
-
- internal void Close(HttpStatusCode code)
- {
- _context.Response.StatusCode = (int)code;
- _context.Response.OutputStream.Dispose();
- }
-
- #endregion
-
- #region Public Methods
-
- /// <summary>
- /// Returns a <see cref="string"/> that represents the current
- /// <see cref="HttpListenerWebSocketContext"/>.
- /// </summary>
- /// <returns>
- /// A <see cref="string"/> that represents the current
- /// <see cref="HttpListenerWebSocketContext"/>.
- /// </returns>
- public override string ToString()
- {
- return _context.Request.ToString();
- }
-
- #endregion
}
}
diff --git a/SocketHttpListener/Net/WebSockets/HttpWebSocket.Managed.cs b/SocketHttpListener/Net/WebSockets/HttpWebSocket.Managed.cs
new file mode 100644
index 000000000..571e4bdba
--- /dev/null
+++ b/SocketHttpListener/Net/WebSockets/HttpWebSocket.Managed.cs
@@ -0,0 +1,84 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SocketHttpListener.Net.WebSockets
+{
+ internal static partial class HttpWebSocket
+ {
+ private const string SupportedVersion = "13";
+
+ internal static async Task<HttpListenerWebSocketContext> AcceptWebSocketAsyncCore(HttpListenerContext context,
+ string subProtocol,
+ int receiveBufferSize,
+ TimeSpan keepAliveInterval,
+ ArraySegment<byte>? internalBuffer = null)
+ {
+ ValidateOptions(subProtocol, receiveBufferSize, MinSendBufferSize, keepAliveInterval);
+
+ // get property will create a new response if one doesn't exist.
+ HttpListenerResponse response = context.Response;
+ HttpListenerRequest request = context.Request;
+ ValidateWebSocketHeaders(context);
+
+ string secWebSocketVersion = request.Headers[HttpKnownHeaderNames.SecWebSocketVersion];
+
+ // Optional for non-browser client
+ string origin = request.Headers[HttpKnownHeaderNames.Origin];
+
+ string[] secWebSocketProtocols = null;
+ string outgoingSecWebSocketProtocolString;
+ bool shouldSendSecWebSocketProtocolHeader =
+ ProcessWebSocketProtocolHeader(
+ request.Headers[HttpKnownHeaderNames.SecWebSocketProtocol],
+ subProtocol,
+ out outgoingSecWebSocketProtocolString);
+
+ if (shouldSendSecWebSocketProtocolHeader)
+ {
+ secWebSocketProtocols = new string[] { outgoingSecWebSocketProtocolString };
+ response.Headers.Add(HttpKnownHeaderNames.SecWebSocketProtocol, outgoingSecWebSocketProtocolString);
+ }
+
+ // negotiate the websocket key return value
+ string secWebSocketKey = request.Headers[HttpKnownHeaderNames.SecWebSocketKey];
+ string secWebSocketAccept = HttpWebSocket.GetSecWebSocketAcceptString(secWebSocketKey);
+
+ response.Headers.Add(HttpKnownHeaderNames.Connection, HttpKnownHeaderNames.Upgrade);
+ response.Headers.Add(HttpKnownHeaderNames.Upgrade, WebSocketUpgradeToken);
+ response.Headers.Add(HttpKnownHeaderNames.SecWebSocketAccept, secWebSocketAccept);
+
+ response.StatusCode = (int)HttpStatusCode.SwitchingProtocols; // HTTP 101
+ response.StatusDescription = HttpStatusDescription.Get(HttpStatusCode.SwitchingProtocols);
+
+ HttpResponseStream responseStream = response.OutputStream as HttpResponseStream;
+
+ // Send websocket handshake headers
+ await responseStream.WriteWebSocketHandshakeHeadersAsync().ConfigureAwait(false);
+
+ //WebSocket webSocket = WebSocket.CreateFromStream(context.Connection.ConnectedStream, isServer: true, subProtocol, keepAliveInterval);
+ WebSocket webSocket = new WebSocket(subProtocol);
+
+ HttpListenerWebSocketContext webSocketContext = new HttpListenerWebSocketContext(
+ request.Url,
+ request.Headers,
+ request.Cookies,
+ context.User,
+ request.IsAuthenticated,
+ request.IsLocal,
+ request.IsSecureConnection,
+ origin,
+ secWebSocketProtocols != null ? secWebSocketProtocols : Array.Empty<string>(),
+ secWebSocketVersion,
+ secWebSocketKey,
+ webSocket);
+
+ webSocket.SetContext(webSocketContext, context.Connection.Close, context.Connection.Stream);
+
+ return webSocketContext;
+ }
+
+ private const bool WebSocketsSupported = true;
+ }
+}
diff --git a/SocketHttpListener/Net/WebSockets/HttpWebSocket.cs b/SocketHttpListener/Net/WebSockets/HttpWebSocket.cs
new file mode 100644
index 000000000..9dc9143f8
--- /dev/null
+++ b/SocketHttpListener/Net/WebSockets/HttpWebSocket.cs
@@ -0,0 +1,160 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Diagnostics.CodeAnalysis;
+using System.Security.Cryptography;
+using System.Threading;
+
+namespace SocketHttpListener.Net.WebSockets
+{
+ internal static partial class HttpWebSocket
+ {
+ internal const string SecWebSocketKeyGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ internal const string WebSocketUpgradeToken = "websocket";
+ internal const int DefaultReceiveBufferSize = 16 * 1024;
+ internal const int DefaultClientSendBufferSize = 16 * 1024;
+
+ [SuppressMessage("Microsoft.Security", "CA5350", Justification = "SHA1 used only for hashing purposes, not for crypto.")]
+ internal static string GetSecWebSocketAcceptString(string secWebSocketKey)
+ {
+ string retVal;
+
+ // SHA1 used only for hashing purposes, not for crypto. Check here for FIPS compat.
+ using (SHA1 sha1 = SHA1.Create())
+ {
+ string acceptString = string.Concat(secWebSocketKey, HttpWebSocket.SecWebSocketKeyGuid);
+ byte[] toHash = Encoding.UTF8.GetBytes(acceptString);
+ retVal = Convert.ToBase64String(sha1.ComputeHash(toHash));
+ }
+
+ return retVal;
+ }
+
+ // return value here signifies if a Sec-WebSocket-Protocol header should be returned by the server.
+ internal static bool ProcessWebSocketProtocolHeader(string clientSecWebSocketProtocol,
+ string subProtocol,
+ out string acceptProtocol)
+ {
+ acceptProtocol = string.Empty;
+ if (string.IsNullOrEmpty(clientSecWebSocketProtocol))
+ {
+ // client hasn't specified any Sec-WebSocket-Protocol header
+ if (subProtocol != null)
+ {
+ // If the server specified _anything_ this isn't valid.
+ throw new WebSocketException("UnsupportedProtocol");
+ }
+ // Treat empty and null from the server as the same thing here, server should not send headers.
+ return false;
+ }
+
+ // here, we know the client specified something and it's non-empty.
+
+ if (subProtocol == null)
+ {
+ // client specified some protocols, server specified 'null'. So server should send headers.
+ return true;
+ }
+
+ // here, we know that the client has specified something, it's not empty
+ // and the server has specified exactly one protocol
+
+ string[] requestProtocols = clientSecWebSocketProtocol.Split(new char[] { ',' },
+ StringSplitOptions.RemoveEmptyEntries);
+ acceptProtocol = subProtocol;
+
+ // client specified protocols, serverOptions has exactly 1 non-empty entry. Check that
+ // this exists in the list the client specified.
+ for (int i = 0; i < requestProtocols.Length; i++)
+ {
+ string currentRequestProtocol = requestProtocols[i].Trim();
+ if (string.Equals(acceptProtocol, currentRequestProtocol, StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+
+ throw new WebSocketException("net_WebSockets_AcceptUnsupportedProtocol");
+ }
+
+ internal static void ValidateOptions(string subProtocol, int receiveBufferSize, int sendBufferSize, TimeSpan keepAliveInterval)
+ {
+ if (subProtocol != null)
+ {
+ WebSocketValidate.ValidateSubprotocol(subProtocol);
+ }
+
+ if (receiveBufferSize < MinReceiveBufferSize)
+ {
+ throw new ArgumentOutOfRangeException("net_WebSockets_ArgumentOutOfRange_TooSmall");
+ }
+
+ if (sendBufferSize < MinSendBufferSize)
+ {
+ throw new ArgumentOutOfRangeException("net_WebSockets_ArgumentOutOfRange_TooSmall");
+ }
+
+ if (receiveBufferSize > MaxBufferSize)
+ {
+ throw new ArgumentOutOfRangeException("net_WebSockets_ArgumentOutOfRange_TooBig");
+ }
+
+ if (sendBufferSize > MaxBufferSize)
+ {
+ throw new ArgumentOutOfRangeException("net_WebSockets_ArgumentOutOfRange_TooBig");
+ }
+
+ if (keepAliveInterval < Timeout.InfiniteTimeSpan) // -1 millisecond
+ {
+ throw new ArgumentOutOfRangeException("net_WebSockets_ArgumentOutOfRange_TooSmall");
+ }
+ }
+
+ internal const int MinSendBufferSize = 16;
+ internal const int MinReceiveBufferSize = 256;
+ internal const int MaxBufferSize = 64 * 1024;
+
+ private static void ValidateWebSocketHeaders(HttpListenerContext context)
+ {
+ if (!WebSocketsSupported)
+ {
+ throw new PlatformNotSupportedException("net_WebSockets_UnsupportedPlatform");
+ }
+
+ if (!context.Request.IsWebSocketRequest)
+ {
+ throw new WebSocketException("net_WebSockets_AcceptNotAWebSocket");
+ }
+
+ string secWebSocketVersion = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion];
+ if (string.IsNullOrEmpty(secWebSocketVersion))
+ {
+ throw new WebSocketException("net_WebSockets_AcceptHeaderNotFound");
+ }
+
+ if (!string.Equals(secWebSocketVersion, SupportedVersion, StringComparison.OrdinalIgnoreCase))
+ {
+ throw new WebSocketException("net_WebSockets_AcceptUnsupportedWebSocketVersion");
+ }
+
+ string secWebSocketKey = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketKey];
+ bool isSecWebSocketKeyInvalid = string.IsNullOrWhiteSpace(secWebSocketKey);
+ if (!isSecWebSocketKeyInvalid)
+ {
+ try
+ {
+ // key must be 16 bytes then base64-encoded
+ isSecWebSocketKeyInvalid = Convert.FromBase64String(secWebSocketKey).Length != 16;
+ }
+ catch
+ {
+ isSecWebSocketKeyInvalid = true;
+ }
+ }
+ if (isSecWebSocketKeyInvalid)
+ {
+ throw new WebSocketException("net_WebSockets_AcceptHeaderNotFound");
+ }
+ }
+ }
+}
diff --git a/SocketHttpListener/Net/WebSockets/WebSocketCloseStatus.cs b/SocketHttpListener/Net/WebSockets/WebSocketCloseStatus.cs
new file mode 100644
index 000000000..0f43b7b80
--- /dev/null
+++ b/SocketHttpListener/Net/WebSockets/WebSocketCloseStatus.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SocketHttpListener.Net.WebSockets
+{
+ public enum WebSocketCloseStatus
+ {
+ NormalClosure = 1000,
+ EndpointUnavailable = 1001,
+ ProtocolError = 1002,
+ InvalidMessageType = 1003,
+ Empty = 1005,
+ // AbnormalClosure = 1006, // 1006 is reserved and should never be used by user
+ InvalidPayloadData = 1007,
+ PolicyViolation = 1008,
+ MessageTooBig = 1009,
+ MandatoryExtension = 1010,
+ InternalServerError = 1011
+ // TLSHandshakeFailed = 1015, // 1015 is reserved and should never be used by user
+
+ // 0 - 999 Status codes in the range 0-999 are not used.
+ // 1000 - 1999 Status codes in the range 1000-1999 are reserved for definition by this protocol.
+ // 2000 - 2999 Status codes in the range 2000-2999 are reserved for use by extensions.
+ // 3000 - 3999 Status codes in the range 3000-3999 MAY be used by libraries and frameworks. The
+ // interpretation of these codes is undefined by this protocol. End applications MUST
+ // NOT use status codes in this range.
+ // 4000 - 4999 Status codes in the range 4000-4999 MAY be used by application code. The interpretation
+ // of these codes is undefined by this protocol.
+ }
+}
diff --git a/SocketHttpListener/Net/WebSockets/WebSocketContext.cs b/SocketHttpListener/Net/WebSockets/WebSocketContext.cs
index 9665ab789..071b5fe05 100644
--- a/SocketHttpListener/Net/WebSockets/WebSocketContext.cs
+++ b/SocketHttpListener/Net/WebSockets/WebSocketContext.cs
@@ -8,176 +8,19 @@ using MediaBrowser.Model.Services;
namespace SocketHttpListener.Net.WebSockets
{
- /// <summary>
- /// Exposes the properties used to access the information in a WebSocket connection request.
- /// </summary>
- /// <remarks>
- /// The WebSocketContext class is an abstract class.
- /// </remarks>
public abstract class WebSocketContext
{
- #region Protected Constructors
-
- /// <summary>
- /// Initializes a new instance of the <see cref="WebSocketContext"/> class.
- /// </summary>
- protected WebSocketContext()
- {
- }
-
- #endregion
-
- #region Public Properties
-
- /// <summary>
- /// Gets the HTTP cookies included in the request.
- /// </summary>
- /// <value>
- /// A <see cref="System.Net.CookieCollection"/> that contains the cookies.
- /// </value>
- public abstract CookieCollection CookieCollection { get; }
-
- /// <summary>
- /// Gets the HTTP headers included in the request.
- /// </summary>
- /// <value>
- /// A <see cref="QueryParamCollection"/> that contains the headers.
- /// </value>
+ public abstract Uri RequestUri { get; }
public abstract QueryParamCollection Headers { get; }
-
- /// <summary>
- /// Gets the value of the Host header included in the request.
- /// </summary>
- /// <value>
- /// A <see cref="string"/> that represents the value of the Host header.
- /// </value>
- public abstract string Host { get; }
-
- /// <summary>
- /// Gets a value indicating whether the client is authenticated.
- /// </summary>
- /// <value>
- /// <c>true</c> if the client is authenticated; otherwise, <c>false</c>.
- /// </value>
- public abstract bool IsAuthenticated { get; }
-
- /// <summary>
- /// Gets a value indicating whether the client connected from the local computer.
- /// </summary>
- /// <value>
- /// <c>true</c> if the client connected from the local computer; otherwise, <c>false</c>.
- /// </value>
- public abstract bool IsLocal { get; }
-
- /// <summary>
- /// Gets a value indicating whether the WebSocket connection is secured.
- /// </summary>
- /// <value>
- /// <c>true</c> if the connection is secured; otherwise, <c>false</c>.
- /// </value>
- public abstract bool IsSecureConnection { get; }
-
- /// <summary>
- /// Gets a value indicating whether the request is a WebSocket connection request.
- /// </summary>
- /// <value>
- /// <c>true</c> if the request is a WebSocket connection request; otherwise, <c>false</c>.
- /// </value>
- public abstract bool IsWebSocketRequest { get; }
-
- /// <summary>
- /// Gets the value of the Origin header included in the request.
- /// </summary>
- /// <value>
- /// A <see cref="string"/> that represents the value of the Origin header.
- /// </value>
public abstract string Origin { get; }
-
- /// <summary>
- /// Gets the query string included in the request.
- /// </summary>
- /// <value>
- /// A <see cref="QueryParamCollection"/> that contains the query string parameters.
- /// </value>
- public abstract QueryParamCollection QueryString { get; }
-
- /// <summary>
- /// Gets the URI requested by the client.
- /// </summary>
- /// <value>
- /// A <see cref="Uri"/> that represents the requested URI.
- /// </value>
- public abstract Uri RequestUri { get; }
-
- /// <summary>
- /// Gets the value of the Sec-WebSocket-Key header included in the request.
- /// </summary>
- /// <remarks>
- /// This property provides a part of the information used by the server to prove that it
- /// received a valid WebSocket connection request.
- /// </remarks>
- /// <value>
- /// A <see cref="string"/> that represents the value of the Sec-WebSocket-Key header.
- /// </value>
- public abstract string SecWebSocketKey { get; }
-
- /// <summary>
- /// Gets the values of the Sec-WebSocket-Protocol header included in the request.
- /// </summary>
- /// <remarks>
- /// This property represents the subprotocols requested by the client.
- /// </remarks>
- /// <value>
- /// An <see cref="T:System.Collections.Generic.IEnumerable{string}"/> instance that provides
- /// an enumerator which supports the iteration over the values of the Sec-WebSocket-Protocol
- /// header.
- /// </value>
public abstract IEnumerable<string> SecWebSocketProtocols { get; }
-
- /// <summary>
- /// Gets the value of the Sec-WebSocket-Version header included in the request.
- /// </summary>
- /// <remarks>
- /// This property represents the WebSocket protocol version.
- /// </remarks>
- /// <value>
- /// A <see cref="string"/> that represents the value of the Sec-WebSocket-Version header.
- /// </value>
public abstract string SecWebSocketVersion { get; }
-
- /// <summary>
- /// Gets the server endpoint as an IP address and a port number.
- /// </summary>
- /// <value>
- /// A <see cref="System.Net.IPEndPoint"/> that represents the server endpoint.
- /// </value>
- public abstract IPEndPoint ServerEndPoint { get; }
-
- /// <summary>
- /// Gets the client information (identity, authentication, and security roles).
- /// </summary>
- /// <value>
- /// A <see cref="IPrincipal"/> that represents the client information.
- /// </value>
+ public abstract string SecWebSocketKey { get; }
+ public abstract CookieCollection CookieCollection { get; }
public abstract IPrincipal User { get; }
-
- /// <summary>
- /// Gets the client endpoint as an IP address and a port number.
- /// </summary>
- /// <value>
- /// A <see cref="System.Net.IPEndPoint"/> that represents the client endpoint.
- /// </value>
- public abstract IPEndPoint UserEndPoint { get; }
-
- /// <summary>
- /// Gets the <see cref="SocketHttpListener.WebSocket"/> instance used for two-way communication
- /// between client and server.
- /// </summary>
- /// <value>
- /// A <see cref="SocketHttpListener.WebSocket"/>.
- /// </value>
+ public abstract bool IsAuthenticated { get; }
+ public abstract bool IsLocal { get; }
+ public abstract bool IsSecureConnection { get; }
public abstract WebSocket WebSocket { get; }
-
- #endregion
}
}
diff --git a/SocketHttpListener/Net/WebSockets/WebSocketValidate.cs b/SocketHttpListener/Net/WebSockets/WebSocketValidate.cs
new file mode 100644
index 000000000..00895ea01
--- /dev/null
+++ b/SocketHttpListener/Net/WebSockets/WebSocketValidate.cs
@@ -0,0 +1,143 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using MediaBrowser.Model.Net;
+using System.Globalization;
+using WebSocketState = System.Net.WebSockets.WebSocketState;
+
+namespace SocketHttpListener.Net.WebSockets
+{
+ internal static partial class WebSocketValidate
+ {
+ internal const int MaxControlFramePayloadLength = 123;
+ private const int CloseStatusCodeAbort = 1006;
+ private const int CloseStatusCodeFailedTLSHandshake = 1015;
+ private const int InvalidCloseStatusCodesFrom = 0;
+ private const int InvalidCloseStatusCodesTo = 999;
+ private const string Separators = "()<>@,;:\\\"/[]?={} ";
+
+ internal static void ThrowIfInvalidState(WebSocketState currentState, bool isDisposed, WebSocketState[] validStates)
+ {
+ string validStatesText = string.Empty;
+
+ if (validStates != null && validStates.Length > 0)
+ {
+ foreach (WebSocketState validState in validStates)
+ {
+ if (currentState == validState)
+ {
+ // Ordering is important to maintain .NET 4.5 WebSocket implementation exception behavior.
+ if (isDisposed)
+ {
+ throw new ObjectDisposedException(nameof(WebSocket));
+ }
+
+ return;
+ }
+ }
+
+ validStatesText = string.Join(", ", validStates);
+ }
+
+ throw new WebSocketException("net_WebSockets_InvalidState");
+ }
+
+ internal static void ValidateSubprotocol(string subProtocol)
+ {
+ if (string.IsNullOrWhiteSpace(subProtocol))
+ {
+ throw new ArgumentException("net_WebSockets_InvalidEmptySubProtocol");
+ }
+
+ string invalidChar = null;
+ int i = 0;
+ while (i < subProtocol.Length)
+ {
+ char ch = subProtocol[i];
+ if (ch < 0x21 || ch > 0x7e)
+ {
+ invalidChar = string.Format(CultureInfo.InvariantCulture, "[{0}]", (int)ch);
+ break;
+ }
+
+ if (!char.IsLetterOrDigit(ch) &&
+ Separators.IndexOf(ch) >= 0)
+ {
+ invalidChar = ch.ToString();
+ break;
+ }
+
+ i++;
+ }
+
+ if (invalidChar != null)
+ {
+ throw new ArgumentException("net_WebSockets_InvalidCharInProtocolString");
+ }
+ }
+
+ internal static void ValidateCloseStatus(WebSocketCloseStatus closeStatus, string statusDescription)
+ {
+ if (closeStatus == WebSocketCloseStatus.Empty && !string.IsNullOrEmpty(statusDescription))
+ {
+ throw new ArgumentException("net_WebSockets_ReasonNotNull");
+ }
+
+ int closeStatusCode = (int)closeStatus;
+
+ if ((closeStatusCode >= InvalidCloseStatusCodesFrom &&
+ closeStatusCode <= InvalidCloseStatusCodesTo) ||
+ closeStatusCode == CloseStatusCodeAbort ||
+ closeStatusCode == CloseStatusCodeFailedTLSHandshake)
+ {
+ // CloseStatus 1006 means Aborted - this will never appear on the wire and is reflected by calling WebSocket.Abort
+ throw new ArgumentException("net_WebSockets_InvalidCloseStatusCode");
+ }
+
+ int length = 0;
+ if (!string.IsNullOrEmpty(statusDescription))
+ {
+ length = Encoding.UTF8.GetByteCount(statusDescription);
+ }
+
+ if (length > MaxControlFramePayloadLength)
+ {
+ throw new ArgumentException("net_WebSockets_InvalidCloseStatusDescription");
+ }
+ }
+
+ internal static void ValidateArraySegment(ArraySegment<byte> arraySegment, string parameterName)
+ {
+ if (arraySegment.Array == null)
+ {
+ throw new ArgumentNullException(parameterName + "." + nameof(arraySegment.Array));
+ }
+ if (arraySegment.Offset < 0 || arraySegment.Offset > arraySegment.Array.Length)
+ {
+ throw new ArgumentOutOfRangeException(parameterName + "." + nameof(arraySegment.Offset));
+ }
+ if (arraySegment.Count < 0 || arraySegment.Count > (arraySegment.Array.Length - arraySegment.Offset))
+ {
+ throw new ArgumentOutOfRangeException(parameterName + "." + nameof(arraySegment.Count));
+ }
+ }
+
+ internal static void ValidateBuffer(byte[] buffer, int offset, int count)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer));
+ }
+
+ if (offset < 0 || offset > buffer.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+
+ if (count < 0 || count > (buffer.Length - offset))
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ }
+ }
+}