| File: System\Net\WebSockets\HttpWebSocket.cs | Web Access |
| Project: src\runtime\src\libraries\System.Net.HttpListener\src\System.Net.HttpListener.csproj (System.Net.HttpListener) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; using System.Security.Cryptography; using System.Text; using System.Threading; namespace System.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 acceptString = string.Concat(secWebSocketKey, HttpWebSocket.SecWebSocketKeyGuid); byte[] toHash = Encoding.UTF8.GetBytes(acceptString); // SHA1 used only for hashing purposes, not for crypto. Check here for FIPS compat. byte[] hash = SHA1.HashData(toHash); return Convert.ToBase64String(hash); } // 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(WebSocketError.UnsupportedProtocol, SR.Format(SR.net_WebSockets_ClientAcceptingNoProtocols, subProtocol)); } // 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(',', StringSplitOptions.TrimEntries | 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]; if (string.Equals(acceptProtocol, currentRequestProtocol, StringComparison.OrdinalIgnoreCase)) { return true; } } throw new WebSocketException(WebSocketError.UnsupportedProtocol, SR.Format(SR.net_WebSockets_AcceptUnsupportedProtocol, clientSecWebSocketProtocol, subProtocol)); } internal static void ValidateOptions(string? subProtocol, int receiveBufferSize, int sendBufferSize, TimeSpan keepAliveInterval) { if (subProtocol != null) { WebSocketValidate.ValidateSubprotocol(subProtocol); } ArgumentOutOfRangeException.ThrowIfLessThan(receiveBufferSize, MinReceiveBufferSize); ArgumentOutOfRangeException.ThrowIfLessThan(sendBufferSize, MinSendBufferSize); ArgumentOutOfRangeException.ThrowIfGreaterThan(receiveBufferSize, MaxBufferSize); ArgumentOutOfRangeException.ThrowIfGreaterThan(sendBufferSize, MaxBufferSize); ArgumentOutOfRangeException.ThrowIfLessThan(keepAliveInterval, Timeout.InfiniteTimeSpan); } 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(SR.net_WebSockets_UnsupportedPlatform); } if (!context.Request.IsWebSocketRequest) { throw new WebSocketException(WebSocketError.NotAWebSocket, SR.Format(SR.net_WebSockets_AcceptNotAWebSocket, nameof(ValidateWebSocketHeaders), HttpKnownHeaderNames.Connection, HttpKnownHeaderNames.Upgrade, HttpWebSocket.WebSocketUpgradeToken, context.Request.Headers[HttpKnownHeaderNames.Upgrade])); } string? secWebSocketVersion = context.Request.Headers[HttpKnownHeaderNames.SecWebSocketVersion]; if (string.IsNullOrEmpty(secWebSocketVersion)) { throw new WebSocketException(WebSocketError.HeaderError, SR.Format(SR.net_WebSockets_AcceptHeaderNotFound, nameof(ValidateWebSocketHeaders), HttpKnownHeaderNames.SecWebSocketVersion)); } if (!string.Equals(secWebSocketVersion, SupportedVersion, StringComparison.OrdinalIgnoreCase)) { throw new WebSocketException(WebSocketError.UnsupportedVersion, SR.Format(SR.net_WebSockets_AcceptUnsupportedWebSocketVersion, nameof(ValidateWebSocketHeaders), secWebSocketVersion, SupportedVersion)); } 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(WebSocketError.HeaderError, SR.Format(SR.net_WebSockets_AcceptHeaderNotFound, nameof(ValidateWebSocketHeaders), HttpKnownHeaderNames.SecWebSocketKey)); } } } }