diff options
| author | stefan <stefan@hegedues.at> | 2018-09-12 19:26:21 +0200 |
|---|---|---|
| committer | stefan <stefan@hegedues.at> | 2018-09-12 19:26:21 +0200 |
| commit | 48facb797ed912e4ea6b04b17d1ff190ac2daac4 (patch) | |
| tree | 8dae77a31670a888d733484cb17dd4077d5444e8 /SocketHttpListener/Net/WebSockets | |
| parent | c32d8656382a0eacb301692e0084377fc433ae9b (diff) | |
Update to 3.5.2 and .net core 2.1
Diffstat (limited to 'SocketHttpListener/Net/WebSockets')
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)); + } + } + } +} |
