| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System.Buffers; |
| | | 5 | | using System.Diagnostics; |
| | | 6 | | using System.Text; |
| | | 7 | | |
| | | 8 | | namespace System.Net.WebSockets |
| | | 9 | | { |
| | | 10 | | internal static partial class WebSocketValidate |
| | | 11 | | { |
| | | 12 | | /// <summary> |
| | | 13 | | /// The minimum value for window bits that the websocket per-message-deflate extension can support.<para /> |
| | | 14 | | /// For the current implementation of deflate(), a windowBits value of 8 (a window size of 256 bytes) is not sup |
| | | 15 | | /// We cannot use silently 9 instead of 8, because the websocket produces raw deflate stream |
| | | 16 | | /// and thus it needs to know the window bits in advance. |
| | | 17 | | /// </summary> |
| | | 18 | | internal const int MinDeflateWindowBits = 9; |
| | | 19 | | |
| | | 20 | | /// <summary> |
| | | 21 | | /// The maximum value for window bits that the websocket per-message-deflate extension can support. |
| | | 22 | | /// </summary> |
| | | 23 | | internal const int MaxDeflateWindowBits = 15; |
| | | 24 | | |
| | | 25 | | internal const int MaxControlFramePayloadLength = 123; |
| | | 26 | | #if TARGET_BROWSER |
| | | 27 | | private const int ValidCloseStatusCodesFrom = 3000; |
| | | 28 | | private const int ValidCloseStatusCodesTo = 4999; |
| | | 29 | | #else |
| | | 30 | | private const int CloseStatusCodeAbort = 1006; |
| | | 31 | | private const int CloseStatusCodeFailedTLSHandshake = 1015; |
| | | 32 | | private const int InvalidCloseStatusCodesFrom = 0; |
| | | 33 | | private const int InvalidCloseStatusCodesTo = 999; |
| | | 34 | | #endif |
| | | 35 | | |
| | | 36 | | // [0x21, 0x7E] except separators "()<>@,;:\\\"/[]?={} ". |
| | 0 | 37 | | private static readonly SearchValues<char> s_validSubprotocolChars = |
| | 0 | 38 | | SearchValues.Create("!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz|~"); |
| | | 39 | | |
| | | 40 | | internal static void ValidateSubprotocol(string subProtocol) |
| | 0 | 41 | | { |
| | 0 | 42 | | ArgumentException.ThrowIfNullOrWhiteSpace(subProtocol); |
| | | 43 | | |
| | 0 | 44 | | int indexOfInvalidChar = subProtocol.AsSpan().IndexOfAnyExcept(s_validSubprotocolChars); |
| | 0 | 45 | | if (indexOfInvalidChar >= 0) |
| | 0 | 46 | | { |
| | 0 | 47 | | char invalidChar = subProtocol[indexOfInvalidChar]; |
| | | 48 | | |
| | 0 | 49 | | string invalidCharDescription = char.IsBetween(invalidChar, (char)0x21, (char)0x7E) |
| | 0 | 50 | | ? invalidChar.ToString() // ASCII separator |
| | 0 | 51 | | : $"[{(int)invalidChar}]"; |
| | | 52 | | |
| | 0 | 53 | | throw new ArgumentException(SR.Format(SR.net_WebSockets_InvalidCharInProtocolString, subProtocol, invali |
| | | 54 | | } |
| | 0 | 55 | | } |
| | | 56 | | |
| | | 57 | | internal static void ValidateCloseStatus(WebSocketCloseStatus closeStatus, string? statusDescription) |
| | 5744 | 58 | | { |
| | 5744 | 59 | | if (closeStatus == WebSocketCloseStatus.Empty && !string.IsNullOrEmpty(statusDescription)) |
| | 0 | 60 | | { |
| | 0 | 61 | | throw new ArgumentException(SR.Format(SR.net_WebSockets_ReasonNotNull, |
| | 0 | 62 | | statusDescription, |
| | 0 | 63 | | WebSocketCloseStatus.Empty), |
| | 0 | 64 | | nameof(statusDescription)); |
| | | 65 | | } |
| | | 66 | | |
| | 5744 | 67 | | int closeStatusCode = (int)closeStatus; |
| | | 68 | | #if TARGET_BROWSER |
| | | 69 | | // as defined in https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#code |
| | | 70 | | if (closeStatus != WebSocketCloseStatus.NormalClosure && (closeStatusCode < ValidCloseStatusCodesFrom || clo |
| | | 71 | | #else |
| | 5744 | 72 | | if ((closeStatusCode >= InvalidCloseStatusCodesFrom && |
| | 5744 | 73 | | closeStatusCode <= InvalidCloseStatusCodesTo) || |
| | 5744 | 74 | | closeStatusCode == CloseStatusCodeAbort || |
| | 5744 | 75 | | closeStatusCode == CloseStatusCodeFailedTLSHandshake) |
| | | 76 | | #endif |
| | 0 | 77 | | { |
| | | 78 | | // CloseStatus 1006 means Aborted - this will never appear on the wire and is reflected by calling WebSo |
| | 0 | 79 | | throw new ArgumentException(SR.Format(SR.net_WebSockets_InvalidCloseStatusCode, |
| | 0 | 80 | | closeStatusCode), |
| | 0 | 81 | | nameof(closeStatus)); |
| | | 82 | | } |
| | | 83 | | |
| | 5744 | 84 | | if (!string.IsNullOrEmpty(statusDescription) && |
| | 5744 | 85 | | Encoding.UTF8.GetMaxByteCount(statusDescription.Length) > MaxControlFramePayloadLength && |
| | 5744 | 86 | | Encoding.UTF8.GetByteCount(statusDescription) > MaxControlFramePayloadLength) |
| | 0 | 87 | | { |
| | 0 | 88 | | throw new ArgumentException(SR.Format(SR.net_WebSockets_InvalidCloseStatusDescription, |
| | 0 | 89 | | statusDescription, |
| | 0 | 90 | | MaxControlFramePayloadLength), |
| | 0 | 91 | | nameof(statusDescription)); |
| | | 92 | | } |
| | 5744 | 93 | | } |
| | | 94 | | |
| | | 95 | | internal static void ValidateArraySegment(ArraySegment<byte> arraySegment, string parameterName) |
| | 0 | 96 | | { |
| | 0 | 97 | | Debug.Assert(!string.IsNullOrEmpty(parameterName), "'parameterName' MUST NOT be NULL or string.Empty"); |
| | | 98 | | |
| | 0 | 99 | | if (arraySegment.Array == null) |
| | 0 | 100 | | { |
| | 0 | 101 | | throw new ArgumentNullException(parameterName + "." + nameof(arraySegment.Array)); |
| | | 102 | | } |
| | 0 | 103 | | if (arraySegment.Offset < 0 || arraySegment.Offset > arraySegment.Array.Length) |
| | 0 | 104 | | { |
| | 0 | 105 | | throw new ArgumentOutOfRangeException(parameterName + "." + nameof(arraySegment.Offset)); |
| | | 106 | | } |
| | 0 | 107 | | if (arraySegment.Count < 0 || arraySegment.Count > (arraySegment.Array.Length - arraySegment.Offset)) |
| | 0 | 108 | | { |
| | 0 | 109 | | throw new ArgumentOutOfRangeException(parameterName + "." + nameof(arraySegment.Count)); |
| | | 110 | | } |
| | 0 | 111 | | } |
| | | 112 | | |
| | | 113 | | internal static void ValidateBuffer(byte[] buffer, int offset, int count) |
| | | 114 | | { |
| | | 115 | | ArgumentNullException.ThrowIfNull(buffer); |
| | | 116 | | |
| | | 117 | | ArgumentOutOfRangeException.ThrowIfNegative(offset); |
| | | 118 | | ArgumentOutOfRangeException.ThrowIfGreaterThan(offset, buffer.Length); |
| | | 119 | | |
| | | 120 | | ArgumentOutOfRangeException.ThrowIfNegative(count); |
| | | 121 | | ArgumentOutOfRangeException.ThrowIfGreaterThan(count, buffer.Length - offset); |
| | | 122 | | } |
| | | 123 | | } |
| | | 124 | | } |
| | | 125 | | |