// Validate the response headers and return the sub-protocol. private string ValidateResponse(HttpWebRequest request, HttpWebResponse response) { // 101 if (response.StatusCode != HttpStatusCode.SwitchingProtocols) { throw new WebSocketException(SR.GetString(SR.net_WebSockets_Connect101Expected, (int)response.StatusCode)); } // Upgrade: websocket string upgradeHeader = response.Headers[HttpKnownHeaderNames.Upgrade]; if (!string.Equals(upgradeHeader, WebSocketHelpers.WebSocketUpgradeToken, StringComparison.OrdinalIgnoreCase)) { throw new WebSocketException(SR.GetString(SR.net_WebSockets_InvalidResponseHeader, HttpKnownHeaderNames.Upgrade, upgradeHeader)); } // Connection: Upgrade string connectionHeader = response.Headers[HttpKnownHeaderNames.Connection]; if (!string.Equals(connectionHeader, HttpKnownHeaderNames.Upgrade, StringComparison.OrdinalIgnoreCase)) { throw new WebSocketException(SR.GetString(SR.net_WebSockets_InvalidResponseHeader, HttpKnownHeaderNames.Connection, connectionHeader)); } // Sec-WebSocket-Accept derived from request Sec-WebSocket-Key string websocketAcceptHeader = response.Headers[HttpKnownHeaderNames.SecWebSocketAccept]; string expectedAcceptHeader = WebSocketHelpers.GetSecWebSocketAcceptString( request.Headers[HttpKnownHeaderNames.SecWebSocketKey]); if (!string.Equals(websocketAcceptHeader, expectedAcceptHeader, StringComparison.OrdinalIgnoreCase)) { throw new WebSocketException(SR.GetString(SR.net_WebSockets_InvalidResponseHeader, HttpKnownHeaderNames.SecWebSocketAccept, websocketAcceptHeader)); } // Sec-WebSocket-Protocol matches one from request // A missing header is ok. It's also ok if the client didn't specify any. string subProtocol = response.Headers[HttpKnownHeaderNames.SecWebSocketProtocol]; if (!string.IsNullOrWhiteSpace(subProtocol) && options.RequestedSubProtocols.Count > 0) { bool foundMatch = false; foreach (string requestedSubProtocol in options.RequestedSubProtocols) { if (string.Equals(requestedSubProtocol, subProtocol, StringComparison.OrdinalIgnoreCase)) { foundMatch = true; break; } } if (!foundMatch) { throw new WebSocketException(SR.GetString(SR.net_WebSockets_AcceptUnsupportedProtocol, string.Join(", ", options.RequestedSubProtocols), subProtocol)); } } return(string.IsNullOrWhiteSpace(subProtocol) ? null : subProtocol); // May be null or valid. }
private async Task ConnectAsyncCore(Uri uri, CancellationToken cancellationToken) { HttpWebResponse response = null; CancellationTokenRegistration connectCancellation = new CancellationTokenRegistration(); // Any errors from here on out are fatal and this instance will be disposed. try { HttpWebRequest request = CreateAndConfigureRequest(uri); if (Logging.On) { Logging.Associate(Logging.WebSockets, this, request); } connectCancellation = cancellationToken.Register(AbortRequest, request, false); response = await request.GetResponseAsync().SuppressContextFlow() as HttpWebResponse; Contract.Assert(response != null, "Not an HttpWebResponse"); this.RequestId = response.Headers.Get("X-RequestId"); if (Logging.On) { Logging.Associate(Logging.WebSockets, this, response); } string subprotocol = ValidateResponse(request, response); innerWebSocket = WebSocket.CreateClientWebSocket(response.GetResponseStream(), subprotocol, options.ReceiveBufferSize, options.SendBufferSize, options.KeepAliveInterval, false, options.GetOrCreateBuffer()); if (Logging.On) { Logging.Associate(Logging.WebSockets, this, innerWebSocket); } // Change internal state to 'connected' to enable the other methods if (Interlocked.CompareExchange(ref state, connected, connecting) != connecting) { // Aborted/Disposed during connect. throw new ObjectDisposedException(GetType().FullName); } } catch (WebException ex) { ConnectExceptionCleanup(response); WebSocketException wex = new WebSocketException(SR.GetString(SR.net_webstatus_ConnectFailure), ex); if (Logging.On) { Logging.Exception(Logging.WebSockets, this, "ConnectAsync", wex); } throw wex; } catch (Exception ex) { ConnectExceptionCleanup(response); if (Logging.On) { Logging.Exception(Logging.WebSockets, this, "ConnectAsync", ex); } throw; } finally { // We successfully connected (or failed trying), disengage from this token. // Otherwise any timeout/cancellation would apply to the full session. // In the failure case we need to release the reference to HWR. connectCancellation.Dispose(); } }