[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/stratdev3/NetCoreServer/master/source/NetCoreServer/WebSocket.cs [Back]  [Original]

using System;
using System.Text;
using System.Security.Cryptography;
using System.Collections.Generic;
using System.Threading;
using System.Linq;

namespace NetCoreServer
{
    /// 
    /// WebSocket utility class
    /// 
    public class WebSocket : IWebSocket
    {
        private readonly IWebSocket _wsHandler;

        /// 
        /// Initialize a new WebSocket
        /// 
        /// WebSocket handler
        public WebSocket(IWebSocket wsHandler) { _wsHandler = wsHandler; ClearWsBuffers(); InitWsNonce(); }

        /// 
        /// Final frame
        /// 
        public const byte WS_FIN = 0x80;
        /// 
        /// Text frame
        /// 
        public const byte WS_TEXT = 0x01;
        /// 
        /// Binary frame
        /// 
        public const byte WS_BINARY = 0x02;
        /// 
        /// Close frame
        /// 
        public const byte WS_CLOSE = 0x08;
        /// 
        /// Ping frame
        /// 
        public const byte WS_PING = 0x09;
        /// 
        /// Pong frame
        /// 
        public const byte WS_PONG = 0x0A;

        /// 
        /// Perform WebSocket client upgrade
        /// 
        /// WebSocket upgrade HTTP response
        /// WebSocket client Id
        /// 'true' if the WebSocket was successfully upgrade, 'false' if the WebSocket was not upgrade
        public bool PerformClientUpgrade(HttpResponse response, Guid id)
        {
            if (response.Status != 101)
                return false;

            bool error = false;
            bool accept = false;
            bool connection = false;
            bool upgrade = false;

            // Validate WebSocket handshake headers
            for (int i = 0; i < response.Headers; i++)
            {
                var header = response.Header(i);
                var key = header.Item1;
                var value = header.Item2;

                if (string.Compare(key, "Connection", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    if (string.Compare(value, "Upgrade", StringComparison.OrdinalIgnoreCase) != 0)
                    {
                        error = true;
                        _wsHandler.OnWsError("Invalid WebSocket handshaked response: 'Connection' header value must be 'Upgrade'");
                        break;
                    }

                    connection = true;
                }
                else if (string.Compare(key, "Upgrade", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    if (string.Compare(value, "websocket", StringComparison.OrdinalIgnoreCase) != 0)
                    {
                        error = true;
                        _wsHandler.OnWsError("Invalid WebSocket handshaked response: 'Upgrade' header value must be 'websocket'");
                        break;
                    }

                    upgrade = true;
                }
                else if (string.Compare(key, "Sec-WebSocket-Accept", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    // Calculate the original WebSocket hash
                    string wskey = Convert.ToBase64String(WsNonce) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
                    string wshash;
                    using (SHA1 sha1 = SHA1.Create())
                    {
                        wshash = Encoding.UTF8.GetString(sha1.ComputeHash(Encoding.UTF8.GetBytes(wskey)));
                    }

                    // Get the received WebSocket hash
                    wskey = Encoding.UTF8.GetString(Convert.FromBase64String(value));

                    // Compare original and received hashes
                    if (string.Compare(wskey, wshash, StringComparison.InvariantCulture) != 0)
                    {
                        error = true;
                        _wsHandler.OnWsError("Invalid WebSocket handshaked response: 'Sec-WebSocket-Accept' value validation failed");
                        break;
                    }

                    accept = true;
                }
            }

            // Failed to perform WebSocket handshake
            if (!accept || !connection || !upgrade)
            {
                if (!error)
                    _wsHandler.OnWsError("Invalid WebSocket response");
                return false;
            }

            // WebSocket successfully handshaked!
            WsHandshaked = true;
            WsRandom.NextBytes(WsSendMask);
            _wsHandler.OnWsConnected(response);

            return true;
        }

        /// 
        /// Perform WebSocket server upgrade
        /// 
        /// WebSocket upgrade HTTP request
        /// WebSocket upgrade HTTP response
        /// 'true' if the WebSocket was successfully upgrade, 'false' if the WebSocket was not upgrade
        public bool PerformServerUpgrade(HttpRequest request, HttpResponse response)
        {
            if (request.Method != "GET")
                return false;

            bool error = false;
            bool connection = false;
            bool upgrade = false;
            bool wsKey = false;
            bool wsVersion = false;

            string accept = "";

            // Validate WebSocket handshake headers
            for (int i = 0; i < request.Headers; i++)
            {
                var header = request.Header(i);
                var key = header.Item1;
                var value = header.Item2;

                if (string.Compare(key, "Connection", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    if ((string.Compare(value, "Upgrade", StringComparison.OrdinalIgnoreCase) != 0) && (string.Compare(value.RemoveWhiteSpace(), "keep-alive,Upgrade", StringComparison.OrdinalIgnoreCase) != 0))
                    {
                        error = true;
                        response.MakeErrorResponse(400, "Invalid WebSocket handshaked request: 'Connection' header value must be 'Upgrade' or 'keep-alive, Upgrade'");
                        break;
                    }

                    connection = true;
                }
                else if (string.Compare(key, "Upgrade", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    if (string.Compare(value, "websocket", StringComparison.OrdinalIgnoreCase) != 0)
                    {
                        error = true;
                        response.MakeErrorResponse(400, "Invalid WebSocket handshaked request: 'Upgrade' header value must be 'websocket'");
                        break;
                    }

                    upgrade = true;
                }
                else if (string.Compare(key, "Sec-WebSocket-Key", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    if (string.IsNullOrEmpty(value))
                    {
                        error = true;
                        response.MakeErrorResponse(400, "Invalid WebSocket handshaked request: 'Sec-WebSocket-Key' header value must be non empty");
                        break;
                    }

                    // Calculate the original WebSocket hash
                    string wskey = value + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
                    byte[] wshash;
                    using (SHA1 sha1 = SHA1.Create())
                    {
                        wshash = sha1.ComputeHash(Encoding.UTF8.GetBytes(wskey));
                    }

                    accept = Convert.ToBase64String(wshash);

                    wsKey = true;
                }
                else if (string.Compare(key, "Sec-WebSocket-Version", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    if (string.Compare(value, "13", StringComparison.OrdinalIgnoreCase) != 0)
                    {
                        error = true;
                        response.MakeErrorResponse(400, "Invalid WebSocket handshaked request: 'Sec-WebSocket-Version' header value must be '13'");
                        break;
                    }

                    wsVersion = true;
                }
            }

            // Filter out non WebSocket handshake requests
            if (!connection && !upgrade && !wsKey && !wsVersion)
                return false;

            // Failed to perform WebSocket handshake
            if (!connection || !upgrade || !wsKey || !wsVersion)
            {
                if (!error)
                    response.MakeErrorResponse(400, "Invalid WebSocket response");
                _wsHandler.SendUpgrade(response);
                return false;
            }

            // Prepare WebSocket upgrade success response
            response.Clear();
            response.SetBegin(101);
            response.SetHeader("Connection", "Upgrade");
            response.SetHeader("Upgrade", "websocket");
            response.SetHeader("Sec-WebSocket-Accept", accept);
            response.SetBody();

            // Validate WebSocket upgrade request and response
            if (!_wsHandler.OnWsConnecting(request, response))
                return false;

            // Send WebSocket upgrade response
            _wsHandler.SendUpgrade(response);

            // WebSocket successfully handshaked!
            WsHandshaked = true;
            Array.Fill(WsSendMask, (byte)0);
            _wsHandler.OnWsConnected(request);

            return true;
        }

        /// 
        /// Prepare WebSocket send frame
        /// 
        /// WebSocket opcode
        /// WebSocket mask
        /// Buffer to send as a span of bytes
        /// WebSocket status (default is 0)
        public void PrepareSendFrame(byte opcode, bool mask, ReadOnlySpan buffer, int status = 0)
        {
            // Check if we need to store additional 2 bytes of close status frame
            bool storeStatus = ((opcode & WS_CLOSE) == WS_CLOSE) && ((buffer.Length > 0) || (status != 0));
            long size = storeStatus ? (buffer.Length + 2) : buffer.Length;

            // Clear the previous WebSocket send buffer
            WsSendBuffer.Clear();

            // Append WebSocket frame opcode
            WsSendBuffer.Append(opcode);

            // Append WebSocket frame size
            if (size  8) & 0xFF));
                WsSendBuffer.Append((byte)(size & 0xFF));
            }
            else
            {
                WsSendBuffer.Append((byte)(127 | (mask ? 0x80 : 0)));
                for (int i = 7; i >= 0; i--)
                    WsSendBuffer.Append((byte)((size >> (8 * i)) & 0xFF));
            }

            if (mask)
            {
                // Append WebSocket frame mask
                WsSendBuffer.Append(WsSendMask);
            }

            // Resize WebSocket frame buffer
            long offset = WsSendBuffer.Size;
            WsSendBuffer.Resize(WsSendBuffer.Size + size);

            int index = 0;

            // Append WebSocket close status
            // RFC 6455: If there is a body, the first two bytes of the body MUST
            // be a 2-byte unsigned integer (in network byte order) representing
            // a status code with value code.
            if (storeStatus)
            {
                index += 2;
                WsSendBuffer.Data[offset + 0] = (byte)(((status >> 8) & 0xFF) ^ WsSendMask[0]);
                WsSendBuffer.Data[offset + 1] = (byte)((status & 0xFF) ^ WsSendMask[1]);
            }

            // Mask WebSocket frame content
            for (int i = index; i < size; i++)
                WsSendBuffer.Data[offset + i] = (byte)(buffer[i - index] ^ WsSendMask[i % 4]);
        }

        /// 
        /// Prepare WebSocket send frame
        /// 
        /// Buffer to send
        /// Buffer offset
        /// Buffer size
        public void PrepareReceiveFrame(byte[] buffer, long offset, long size)
        {
            lock (WsReceiveLock)
            {
                int index = 0;

                // Clear received data after WebSocket frame was processed
                if (WsFrameReceived)
                {
                    WsFrameReceived = false;
                    WsHeaderSize = 0;
                    WsPayloadSize = 0;
                    WsReceiveFrameBuffer.Clear();
                    Array.Clear(WsReceiveMask, 0, WsReceiveMask.Length);
                }
                if (WsFinalReceived)
                {
                    WsFinalReceived = false;
                    WsReceiveFinalBuffer.Clear();
                }

                while (size > 0)
                {
                    // Clear received data after WebSocket frame was processed
                    if (WsFrameReceived)
                    {
                        WsFrameReceived = false;
                        WsHeaderSize = 0;
                        WsPayloadSize = 0;
                        WsReceiveFrameBuffer.Clear();
                        Array.Clear(WsReceiveMask, 0, WsReceiveMask.Length);
                    }
                    if (WsFinalReceived)
                    {
                        WsFinalReceived = false;
                        WsReceiveFinalBuffer.Clear();
                    }

                    // Prepare WebSocket frame opcode and mask flag
                    if (WsReceiveFrameBuffer.Size < 2)
                    {
                        for (long i = 0; i < 2; i++, index++, size--)
                        {
                            if (size == 0)
                                return;
                            WsReceiveFrameBuffer.Append(buffer[offset + index]);
                        }
                    }

                    byte opcode = (byte)(WsReceiveFrameBuffer[0] & 0x0F);
                    bool fin = ((WsReceiveFrameBuffer[0] >> 7) & 0x01) != 0;
                    bool mask = ((WsReceiveFrameBuffer[1] >> 7) & 0x01) != 0;
                    long payload = WsReceiveFrameBuffer[1] & (~0x80);

                    // Prepare WebSocket opcode
                    WsOpcode = (opcode != 0) ? opcode : WsOpcode;

                    // Prepare WebSocket frame size
                    if (payload 

Web Proxy Viewer  |  New URL  |  Original Page