| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- // <copyright>
- // Copyright (c) Microsoft Corporation. All rights reserved.
- // </copyright>
- namespace System.ServiceModel.Channels
- {
- using System;
- using System.Collections.Generic;
- using System.Globalization;
- using System.Linq;
- using System.Net;
- using System.Net.WebSockets;
- using System.Runtime;
- using System.Runtime.InteropServices;
- using System.Security.Cryptography;
- using System.Text;
- using System.Threading;
- using System.Threading.Tasks;
- static class WebSocketHelper
- {
- internal const int OperationNotStarted = 0;
- internal const int OperationFinished = 1;
- internal const string SecWebSocketKey = "Sec-WebSocket-Key";
- internal const string SecWebSocketVersion = "Sec-WebSocket-Version";
- internal const string SecWebSocketProtocol = "Sec-WebSocket-Protocol";
- internal const string SecWebSocketAccept = "Sec-WebSocket-Accept";
- internal const string MaxPendingConnectionsString = "MaxPendingConnections";
- internal const string WebSocketTransportSettingsString = "WebSocketTransportSettings";
- internal const string CloseOperation = "CloseOperation";
- internal const string SendOperation = "SendOperation";
- internal const string ReceiveOperation = "ReceiveOperation";
- internal static readonly char[] ProtocolSeparators = new char[] { ',' };
- const string WebSocketKeyPostString = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
- const string SchemeWs = "ws";
- const string SchemeWss = "wss";
- static readonly int PropertyBufferSize = ((2 * Marshal.SizeOf(typeof(uint))) + Marshal.SizeOf(typeof(bool))) + IntPtr.Size;
- static readonly HashSet<char> InvalidSeparatorSet = new HashSet<char>(new char[] { '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}', ' ' });
- static string currentWebSocketVersion;
- internal static string ComputeAcceptHeader(string webSocketKey)
- {
- Fx.Assert(webSocketKey != null, "webSocketKey should not be null.");
- using (SHA1 sha = SHA1.Create())
- {
- string fullString = webSocketKey + WebSocketHelper.WebSocketKeyPostString;
- byte[] bytes = Encoding.UTF8.GetBytes(fullString);
- return Convert.ToBase64String(sha.ComputeHash(bytes));
- }
- }
- internal static int ComputeClientBufferSize(long maxReceivedMessageSize)
- {
- return ComputeInternalBufferSize(maxReceivedMessageSize, false);
- }
- internal static int ComputeServerBufferSize(long maxReceivedMessageSize)
- {
- return ComputeInternalBufferSize(maxReceivedMessageSize, true);
- }
- internal static int GetReceiveBufferSize(long maxReceivedMessageSize)
- {
- int effectiveMaxReceiveBufferSize = maxReceivedMessageSize <= WebSocketDefaults.BufferSize ? (int)maxReceivedMessageSize : WebSocketDefaults.BufferSize;
- return Math.Max(WebSocketDefaults.MinReceiveBufferSize, effectiveMaxReceiveBufferSize);
- }
- internal static bool UseWebSocketTransport(WebSocketTransportUsage transportUsage, bool isContractDuplex)
- {
- return transportUsage == WebSocketTransportUsage.Always
- || (transportUsage == WebSocketTransportUsage.WhenDuplex && isContractDuplex);
- }
- internal static Uri GetWebSocketUri(Uri httpUri)
- {
- Fx.Assert(httpUri != null, "RemoteAddress.Uri should not be null.");
- UriBuilder builder = new UriBuilder(httpUri);
- if (Uri.UriSchemeHttp.Equals(httpUri.Scheme, StringComparison.OrdinalIgnoreCase))
- {
- builder.Scheme = SchemeWs;
- }
- else
- {
- Fx.Assert(
- Uri.UriSchemeHttps.Equals(httpUri.Scheme, StringComparison.OrdinalIgnoreCase),
- "httpUri.Scheme should be http or https.");
- builder.Scheme = SchemeWss;
- }
- return builder.Uri;
- }
- internal static bool IsWebSocketUri(Uri uri)
- {
- return uri != null &&
- (WebSocketHelper.SchemeWs.Equals(uri.Scheme, StringComparison.OrdinalIgnoreCase) ||
- WebSocketHelper.SchemeWss.Equals(uri.Scheme, StringComparison.OrdinalIgnoreCase));
- }
- internal static Uri NormalizeWsSchemeWithHttpScheme(Uri uri)
- {
- Fx.Assert(uri != null, "RemoteAddress.Uri should not be null.");
- if (!IsWebSocketUri(uri))
- {
- return uri;
- }
- UriBuilder builder = new UriBuilder(uri);
- switch (uri.Scheme.ToLowerInvariant())
- {
- case SchemeWs:
- builder.Scheme = Uri.UriSchemeHttp;
- break;
- case SchemeWss:
- builder.Scheme = Uri.UriSchemeHttps;
- break;
- default:
- break;
- }
- return builder.Uri;
- }
- internal static bool TryParseSubProtocol(string subProtocolValue, out List<string> subProtocolList)
- {
- subProtocolList = new List<string>();
- if (subProtocolValue != null)
- {
- string[] parsedTokens = subProtocolValue.Split(ProtocolSeparators, StringSplitOptions.RemoveEmptyEntries);
- string invalidChar;
- for (int i = 0; i < parsedTokens.Length; i++)
- {
- string token = parsedTokens[i];
- if (!string.IsNullOrWhiteSpace(token))
- {
- token = token.Trim();
- if (!IsSubProtocolInvalid(token, out invalidChar))
- {
- // Note that we could be adding a duplicate to this list. According to the specification the header should not include
- // duplicates but we aim to be "robust in what we receive" so we will allow it. The matching code that consumes this list
- // will take the first match so duplicates will not affect the outcome of the negotiation process.
- subProtocolList.Add(token);
- }
- else
- {
- FxTrace.Exception.AsWarning(new WebException(
- SR.GetString(SR.WebSocketInvalidProtocolInvalidCharInProtocolString, token, invalidChar)));
- return false;
- }
- }
- }
- }
- return true;
- }
- internal static bool IsSubProtocolInvalid(string protocol, out string invalidChar)
- {
- Fx.Assert(protocol != null, "protocol should not be null");
- char[] chars = protocol.ToCharArray();
- for (int i = 0; i < chars.Length; i++)
- {
- char ch = chars[i];
- if (ch < 0x21 || ch > 0x7e)
- {
- invalidChar = string.Format(CultureInfo.InvariantCulture, "[{0}]", (int)ch);
- return true;
- }
- if (InvalidSeparatorSet.Contains(ch))
- {
- invalidChar = ch.ToString();
- return true;
- }
- }
- invalidChar = null;
- return false;
- }
- internal static string GetCurrentVersion()
- {
- if (currentWebSocketVersion == null)
- {
- WebSocket.RegisterPrefixes();
- HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("ws://localhost");
- string version = request.Headers[WebSocketHelper.SecWebSocketVersion];
- Fx.Assert(version != null, "version should not be null.");
- currentWebSocketVersion = version.Trim();
- }
- return currentWebSocketVersion;
- }
- internal static WebSocketTransportSettings GetRuntimeWebSocketSettings(WebSocketTransportSettings settings)
- {
- WebSocketTransportSettings runtimeSettings = settings.Clone();
- if (runtimeSettings.MaxPendingConnections == WebSocketDefaults.DefaultMaxPendingConnections)
- {
- runtimeSettings.MaxPendingConnections = WebSocketDefaults.MaxPendingConnectionsCpuCount;
- }
- return runtimeSettings;
- }
-
- internal static bool OSSupportsWebSockets()
- {
- return OSEnvironmentHelper.IsAtLeast(OSVersion.Win8);
- }
- [System.Diagnostics.CodeAnalysis.SuppressMessage(FxCop.Category.ReliabilityBasic, FxCop.Rule.WrapExceptionsRule,
- Justification = "The exceptions thrown here are already wrapped.")]
- internal static void ThrowCorrectException(Exception ex)
- {
- throw ConvertAndTraceException(ex);
- }
- [System.Diagnostics.CodeAnalysis.SuppressMessage(FxCop.Category.ReliabilityBasic, FxCop.Rule.WrapExceptionsRule,
- Justification = "The exceptions thrown here are already wrapped.")]
- internal static void ThrowCorrectException(Exception ex, TimeSpan timeout, string operation)
- {
- throw ConvertAndTraceException(ex, timeout, operation);
- }
- internal static Exception ConvertAndTraceException(Exception ex)
- {
- return ConvertAndTraceException(
- ex,
- TimeSpan.MinValue, // this is a dummy since operation type is null, so the timespan value won't be used
- null);
- }
- [System.Diagnostics.CodeAnalysis.SuppressMessage(FxCop.Category.ReliabilityBasic, "Reliability103:ThrowWrappedExceptionsRule",
- Justification = "The exceptions wrapped here will be thrown out later.")]
- internal static Exception ConvertAndTraceException(Exception ex, TimeSpan timeout, string operation)
- {
- ObjectDisposedException objectDisposedException = ex as ObjectDisposedException;
- if (objectDisposedException != null)
- {
- CommunicationObjectAbortedException communicationObjectAbortedException = new CommunicationObjectAbortedException(ex.Message, ex);
- FxTrace.Exception.AsWarning(communicationObjectAbortedException);
- return communicationObjectAbortedException;
- }
- AggregateException aggregationException = ex as AggregateException;
- if (aggregationException != null)
- {
- Exception exception = FxTrace.Exception.AsError<OperationCanceledException>(aggregationException);
- OperationCanceledException operationCanceledException = exception as OperationCanceledException;
- if (operationCanceledException != null)
- {
- TimeoutException timeoutException = GetTimeoutException(exception, timeout, operation);
- FxTrace.Exception.AsWarning(timeoutException);
- return timeoutException;
- }
- else
- {
- Exception communicationException = ConvertAggregateExceptionToCommunicationException(aggregationException);
- if (communicationException is CommunicationObjectAbortedException)
- {
- FxTrace.Exception.AsWarning(communicationException);
- return communicationException;
- }
- else
- {
- return FxTrace.Exception.AsError(communicationException);
- }
- }
- }
- WebSocketException webSocketException = ex as WebSocketException;
- if (webSocketException != null)
- {
- switch (webSocketException.WebSocketErrorCode)
- {
- case WebSocketError.InvalidMessageType:
- case WebSocketError.UnsupportedProtocol:
- case WebSocketError.UnsupportedVersion:
- ex = new ProtocolException(ex.Message, ex);
- break;
- default:
- ex = new CommunicationException(ex.Message, ex);
- break;
- }
- }
- return FxTrace.Exception.AsError(ex);
- }
- [System.Diagnostics.CodeAnalysis.SuppressMessage(FxCop.Category.ReliabilityBasic, "Reliability103",
- Justification = "The exceptions will be wrapped by the callers.")]
- internal static Exception ConvertAggregateExceptionToCommunicationException(AggregateException ex)
- {
- Exception exception = FxTrace.Exception.AsError<WebSocketException>(ex);
- WebSocketException webSocketException = exception as WebSocketException;
- if (webSocketException != null && webSocketException.InnerException != null)
- {
- HttpListenerException httpListenerException = webSocketException.InnerException as HttpListenerException;
- if (httpListenerException != null)
- {
- return HttpChannelUtilities.CreateCommunicationException(httpListenerException);
- }
- }
- ObjectDisposedException objectDisposedException = exception as ObjectDisposedException;
- if (objectDisposedException != null)
- {
- return new CommunicationObjectAbortedException(exception.Message, exception);
- }
- return new CommunicationException(exception.Message, exception);
- }
- internal static void ThrowExceptionOnTaskFailure(Task task, TimeSpan timeout, string operation)
- {
- if (task.IsFaulted)
- {
- throw FxTrace.Exception.AsError<CommunicationException>(task.Exception);
- }
- else if (task.IsCanceled)
- {
- throw FxTrace.Exception.AsError(GetTimeoutException(null, timeout, operation));
- }
- }
- internal static TimeoutException GetTimeoutException(Exception innerException, TimeSpan timeout, string operation)
- {
- string errorMsg = string.Empty;
- if (operation != null)
- {
- switch (operation)
- {
- case WebSocketHelper.CloseOperation:
- errorMsg = SR.GetString(SR.CloseTimedOut, timeout);
- break;
- case WebSocketHelper.SendOperation:
- errorMsg = SR.GetString(SR.WebSocketSendTimedOut, timeout);
- break;
- case WebSocketHelper.ReceiveOperation:
- errorMsg = SR.GetString(SR.WebSocketReceiveTimedOut, timeout);
- break;
- default:
- errorMsg = SR.GetString(SR.WebSocketOperationTimedOut, operation, timeout);
- break;
- }
- }
- return innerException == null ? new TimeoutException(errorMsg) : new TimeoutException(errorMsg, innerException);
- }
- private static int ComputeInternalBufferSize(long maxReceivedMessageSize, bool isServerBuffer)
- {
- const int NativeOverheadBufferSize = 144;
- /* LAYOUT:
- | Native buffer | PayloadReceiveBuffer | PropertyBuffer |
- | RBS + SBS + 144 | RBS | PBS |
- | Only WSPC may modify | Only WebSocketBase may modify |
- *RBS = ReceiveBufferSize, *SBS = SendBufferSize
- *PBS = PropertyBufferSize (32-bit: 16, 64 bit: 20 bytes) */
- int nativeSendBufferSize = isServerBuffer ? WebSocketDefaults.MinSendBufferSize : WebSocketDefaults.BufferSize;
- return (2 * GetReceiveBufferSize(maxReceivedMessageSize)) + nativeSendBufferSize + NativeOverheadBufferSize + PropertyBufferSize;
- }
- }
- }
|