File: src\libraries\Common\src\System\Net\WebSockets\WebSocketValidate.cs
Web Access
Project: src\src\libraries\System.Net.WebSockets\src\System.Net.WebSockets.csproj (System.Net.WebSockets)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers;
using System.Diagnostics;
using System.Text;
 
namespace System.Net.WebSockets
{
    internal static partial class WebSocketValidate
    {
        /// <summary>
        /// The minimum value for window bits that the websocket per-message-deflate extension can support.<para />
        /// For the current implementation of deflate(), a windowBits value of 8 (a window size of 256 bytes) is not supported.
        /// We cannot use silently 9 instead of 8, because the websocket produces raw deflate stream
        /// and thus it needs to know the window bits in advance.
        /// </summary>
        internal const int MinDeflateWindowBits = 9;
 
        /// <summary>
        /// The maximum value for window bits that the websocket per-message-deflate extension can support.
        /// </summary>
        internal const int MaxDeflateWindowBits = 15;
 
        internal const int MaxControlFramePayloadLength = 123;
#if TARGET_BROWSER
        private const int ValidCloseStatusCodesFrom = 3000;
        private const int ValidCloseStatusCodesTo = 4999;
#else
        private const int CloseStatusCodeAbort = 1006;
        private const int CloseStatusCodeFailedTLSHandshake = 1015;
        private const int InvalidCloseStatusCodesFrom = 0;
        private const int InvalidCloseStatusCodesTo = 999;
#endif
 
        // [0x21, 0x7E] except separators "()<>@,;:\\\"/[]?={} ".
        private static readonly SearchValues<char> s_validSubprotocolChars =
            SearchValues.Create("!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~");
 
        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.
                        ObjectDisposedException.ThrowIf(isDisposed, typeof(WebSocket));
                        return;
                    }
                }
 
                validStatesText = string.Join(", ", validStates);
            }
 
            throw new WebSocketException(
                WebSocketError.InvalidState,
                SR.Format(SR.net_WebSockets_InvalidState, currentState, validStatesText));
        }
 
        internal static void ValidateSubprotocol(string subProtocol)
        {
            ArgumentException.ThrowIfNullOrWhiteSpace(subProtocol);
 
            int indexOfInvalidChar = subProtocol.AsSpan().IndexOfAnyExcept(s_validSubprotocolChars);
            if (indexOfInvalidChar >= 0)
            {
                char invalidChar = subProtocol[indexOfInvalidChar];
 
                string invalidCharDescription = char.IsBetween(invalidChar, (char)0x21, (char)0x7E)
                    ? invalidChar.ToString() // ASCII separator
                    : $"[{(int)invalidChar}]";
 
                throw new ArgumentException(SR.Format(SR.net_WebSockets_InvalidCharInProtocolString, subProtocol, invalidCharDescription), nameof(subProtocol));
            }
        }
 
        internal static void ValidateCloseStatus(WebSocketCloseStatus closeStatus, string? statusDescription)
        {
            if (closeStatus == WebSocketCloseStatus.Empty && !string.IsNullOrEmpty(statusDescription))
            {
                throw new ArgumentException(SR.Format(SR.net_WebSockets_ReasonNotNull,
                    statusDescription,
                    WebSocketCloseStatus.Empty),
                    nameof(statusDescription));
            }
 
            int closeStatusCode = (int)closeStatus;
#if TARGET_BROWSER
            // as defined in https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#code
            if (closeStatus != WebSocketCloseStatus.NormalClosure && (closeStatusCode < ValidCloseStatusCodesFrom || closeStatusCode > ValidCloseStatusCodesTo))
#else
            if ((closeStatusCode >= InvalidCloseStatusCodesFrom &&
                closeStatusCode <= InvalidCloseStatusCodesTo) ||
                closeStatusCode == CloseStatusCodeAbort ||
                closeStatusCode == CloseStatusCodeFailedTLSHandshake)
#endif
            {
                // CloseStatus 1006 means Aborted - this will never appear on the wire and is reflected by calling WebSocket.Abort
                throw new ArgumentException(SR.Format(SR.net_WebSockets_InvalidCloseStatusCode,
                    closeStatusCode),
                    nameof(closeStatus));
            }
 
            if (!string.IsNullOrEmpty(statusDescription) &&
                Encoding.UTF8.GetMaxByteCount(statusDescription.Length) > MaxControlFramePayloadLength &&
                Encoding.UTF8.GetByteCount(statusDescription) > MaxControlFramePayloadLength)
            {
                throw new ArgumentException(SR.Format(SR.net_WebSockets_InvalidCloseStatusDescription,
                    statusDescription,
                    MaxControlFramePayloadLength),
                    nameof(statusDescription));
            }
        }
 
        internal static void ValidateArraySegment(ArraySegment<byte> arraySegment, string parameterName)
        {
            Debug.Assert(!string.IsNullOrEmpty(parameterName), "'parameterName' MUST NOT be NULL or string.Empty");
 
            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)
        {
            ArgumentNullException.ThrowIfNull(buffer);
 
            ArgumentOutOfRangeException.ThrowIfNegative(offset);
            ArgumentOutOfRangeException.ThrowIfGreaterThan(offset, buffer.Length);
 
            ArgumentOutOfRangeException.ThrowIfNegative(count);
            ArgumentOutOfRangeException.ThrowIfGreaterThan(count, buffer.Length - offset);
        }
    }
}