in src/Microsoft.Azure.Relay/WebSockets/NetStandard20/WebSocketHandle.Managed.cs [604:701]
private async Task<string> ParseAndValidateConnectResponseAsync(
Stream stream, ClientWebSocket webSocket, ClientWebSocketOptions options, string expectedSecWebSocketAccept, CancellationToken cancellationToken)
{
// Read the first line of the response
string statusLine = await ReadResponseHeaderLineAsync(stream, cancellationToken).ConfigureAwait(false);
// Depending on the underlying sockets implementation and timing, connecting to a server that then
// immediately closes the connection may either result in an exception getting thrown from the connect
// earlier, or it may result in getting to here but reading 0 bytes. If we read 0 bytes and thus have
// an empty status line, treat it as a connect failure.
if (string.IsNullOrEmpty(statusLine))
{
// "The response status line value '' is invalid."
throw new WebSocketException(WebSocketError.HeaderError, SR.Format(SR.net_WebSockets_ResponseStatusInvalid, string.Empty));
}
const string ExpectedStatusVersion = "HTTP/1.1";
const HttpStatusCode ExpectedStatusCode = HttpStatusCode.SwitchingProtocols;
webSocket.Response = new HttpResponseMessage();
string[] statusLineParts = statusLine.Split(new[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries);
string statusVerion = statusLineParts[0];
// If the status line doesn't begin with "HTTP/1.1", fail.
if (!string.Equals(statusVerion, ExpectedStatusVersion, StringComparison.Ordinal) || statusLineParts.Length < 2)
{
// "The response status line value 'HTTP/1.0' is invalid."
throw new WebSocketException(WebSocketError.HeaderError, SR.Format(SR.net_WebSockets_ResponseStatusInvalid, statusLine));
}
int statusCode;
if (!int.TryParse(statusLineParts[1], out statusCode))
{
// "The response status line value 'HTTP/1.0 BADSTUFF' is invalid."
throw new WebSocketException(WebSocketError.HeaderError, SR.Format(SR.net_WebSockets_ResponseStatusInvalid, statusLine));
}
webSocket.Response.StatusCode = (HttpStatusCode)statusCode;
if (statusLineParts.Length > 2)
{
webSocket.Response.ReasonPhrase = statusLineParts[2];
}
// If the status line doesn't contain a status code 101, or if it's long enough to have a status description
// but doesn't contain whitespace after the 101, fail.
if (webSocket.Response.StatusCode != ExpectedStatusCode)
{
throw CreateExceptionForUnexpectedStatus(webSocket.Response);
}
// Read each response header. Be liberal in parsing the response header, treating
// everything to the left of the colon as the key and everything to the right as the value, trimming both.
// For each header, validate that we got the expected value.
bool foundUpgrade = false, foundConnection = false, foundSecWebSocketAccept = false;
string subprotocol = null;
string line;
while (!string.IsNullOrEmpty(line = await ReadResponseHeaderLineAsync(stream, cancellationToken).ConfigureAwait(false)))
{
int colonIndex = line.IndexOf(':');
if (colonIndex == -1)
{
// $"The '{line}' header value '(Missing separator)' is invalid."
throw new WebSocketException(WebSocketError.HeaderError, SR.Format(SR.net_WebSockets_InvalidResponseHeader, line, "(Missing separator)"));
}
string headerName = line.SubstringTrim(0, colonIndex);
string headerValue = line.SubstringTrim(colonIndex + 1);
webSocket.Response.Headers.Add(headerName, headerValue);
// The Connection, Upgrade, and SecWebSocketAccept headers are required and with specific values.
ValidateAndTrackHeader(HttpKnownHeaderNames.Connection, "Upgrade", headerName, headerValue, ref foundConnection);
ValidateAndTrackHeader(HttpKnownHeaderNames.Upgrade, "websocket", headerName, headerValue, ref foundUpgrade);
ValidateAndTrackHeader(HttpKnownHeaderNames.SecWebSocketAccept, expectedSecWebSocketAccept, headerName, headerValue, ref foundSecWebSocketAccept);
// The SecWebSocketProtocol header is optional. We should only get it with a non-empty value if we requested subprotocols,
// and then it must only be one of the ones we requested. If we got a subprotocol other than one we requested (or if we
// already got one in a previous header), fail. Otherwise, track which one we got.
if (string.Equals(HttpKnownHeaderNames.SecWebSocketProtocol, headerName, StringComparison.OrdinalIgnoreCase) &&
!string.IsNullOrWhiteSpace(headerValue))
{
string newSubprotocol = options.RequestedSubProtocols.Find(requested => string.Equals(requested, headerValue, StringComparison.OrdinalIgnoreCase));
if (newSubprotocol == null || subprotocol != null)
{
throw new WebSocketException(
WebSocketError.UnsupportedProtocol,
SR.Format(SR.net_WebSockets_AcceptUnsupportedProtocol, string.Join(", ", options.RequestedSubProtocols), subprotocol));
}
subprotocol = newSubprotocol;
}
}
if (!foundUpgrade || !foundConnection || !foundSecWebSocketAccept)
{
// The response header 'Upgrade|Connection|Sec-WebSocket-Accept' was not found.
string missingHeaderName = !foundUpgrade ? HttpKnownHeaderNames.Upgrade : !foundConnection ? HttpKnownHeaderNames.Connection : HttpKnownHeaderNames.SecWebSocketAccept;
throw new WebSocketException(WebSocketError.HeaderError, SR.Format(SR.net_WebSockets_ResponseHeaderMissing, missingHeaderName));
}
return subprotocol;
}