internal static Task CompleteServerHandshakeAsync(ref HttpRequest request, WebSocketConnection socket) { var key = request.Headers.GetRaw("Sec-WebSocket-Key"); var connection = socket.Connection; var buffer = connection.Output.Alloc(StandardPrefixBytes.Length + SecResponseLength + StandardPostfixBytes.Length); buffer.Write(StandardPrefixBytes); // RFC6455 logic to prove that we know how to web-socket buffer.Ensure(SecResponseLength); int bytes = ComputeReply(key.Buffer, buffer.Buffer.Span); if (bytes != SecResponseLength) { throw new InvalidOperationException($"Incorrect response token length; expected {SecResponseLength}, got {bytes}"); } buffer.Advance(SecResponseLength); buffer.Write(StandardPostfixBytes); return(buffer.FlushAsync().AsTask()); }
private WebSocketConnection GetProtocol(IPipeConnection connection, ref HttpRequest request) { var headers = request.Headers; string host = headers.GetAsciiString("Host"); if (string.IsNullOrEmpty(host)) { //4. The request MUST contain a |Host| header field whose value //contains /host/ plus optionally ":" followed by /port/ (when not //using the default port). throw new InvalidOperationException("host required"); } bool looksGoodEnough = false; // mozilla sends "keep-alive, Upgrade"; let's make it more forgiving var connectionParts = new HashSet <string>(StringComparer.OrdinalIgnoreCase); if (headers.ContainsKey("Connection")) { // so for mozilla, this will be the set {"keep-alive", "Upgrade"} var parts = headers.GetAsciiString("Connection").Split(Comma); foreach (var part in parts) { connectionParts.Add(part.Trim()); } } if (connectionParts.Contains("Upgrade") && IsCaseInsensitiveAsciiMatch(headers.GetRaw("Upgrade").Buffer, "websocket")) { //5. The request MUST contain an |Upgrade| header field whose value //MUST include the "websocket" keyword. //6. The request MUST contain a |Connection| header field whose value //MUST include the "Upgrade" token. looksGoodEnough = true; } if (!looksGoodEnough && AllowClientsMissingConnectionHeaders) { if ((headers.ContainsKey("Sec-WebSocket-Version") && headers.ContainsKey("Sec-WebSocket-Key")) || (headers.ContainsKey("Sec-WebSocket-Key1") && headers.ContainsKey("Sec-WebSocket-Key2"))) { looksGoodEnough = true; } } if (looksGoodEnough) { //9. The request MUST include a header field with the name //|Sec-WebSocket-Version|. The value of this header field MUST be if (!headers.ContainsKey("Sec-WebSocket-Version")) { throw new NotSupportedException(); } else { var version = headers.GetRaw("Sec-WebSocket-Version").Buffer.GetUInt32(); switch (version) { case 4: case 5: case 6: case 7: case 8: // these are all early drafts case 13: // this is later drafts and RFC6455 break; // looks ok default: // should issues a 400 "upgrade required" and specify Sec-WebSocket-Version - see 4.4 throw new InvalidOperationException(string.Format("Sec-WebSocket-Version {0} is not supported", version)); } } } else { throw new InvalidOperationException("Request was not a web-socket upgrade request"); } //The "Request-URI" of the GET method [RFC2616] is used to identify the //endpoint of the WebSocket connection, both to allow multiple domains //to be served from one IP address and to allow multiple WebSocket //endpoints to be served by a single server. var socket = new WebSocketConnection(connection, ConnectionType.Server); socket.Host = host; socket.BufferFragments = BufferFragments; // Some early drafts used the latter, so we'll allow it as a fallback // in particular, two drafts of version "8" used (separately) **both**, // so we can't rely on the version for this (hybi-10 vs hybi-11). // To make it even worse, hybi-00 used Origin, so it is all over the place! socket.Origin = headers.GetAsciiString("Origin") ?? headers.GetAsciiString("Sec-WebSocket-Origin"); socket.Protocol = headers.GetAsciiString("Sec-WebSocket-Protocol"); socket.RequestLine = request.Path.Buffer.GetAsciiString(); return(socket); }
protected virtual Task OnHandshakeCompleteAsync(WebSocketConnection connection) => Task.CompletedTask;
protected virtual ValueTask <bool> OnAuthenticateAsync(WebSocketConnection connection, ref HttpRequestHeaders headers) => new ValueTask <bool>(true);
protected internal virtual Task OnTextAsync(WebSocketConnection connection, ref Message message) => Task.CompletedTask;
internal static async Task <WebSocketConnection> ClientHandshake(IPipeConnection connection, Uri uri, string origin, string protocol) { WebSocketServer.WriteStatus(ConnectionType.Client, "Writing client handshake..."); // write the outbound request portion of the handshake var output = connection.Output.Alloc(); output.Write(GET); output.WriteAsciiString(uri.PathAndQuery); output.Write(HTTP_Host); output.WriteAsciiString(uri.Host); output.Write(UpgradeConnectionKey); byte[] challengeKey = new byte[WebSocketProtocol.SecResponseLength]; GetRandomBytes(challengeKey); // we only want the first 16 for entropy, but... meh output.Ensure(WebSocketProtocol.SecRequestLength); int bytesWritten = Base64.Encode(new ReadOnlySpan <byte>(challengeKey, 0, 16), output.Buffer.Span); // now cheekily use that data we just wrote the the output buffer // as a source to compute the expected bytes, and store them back // into challengeKey; sneaky! WebSocketProtocol.ComputeReply( output.Buffer.Slice(0, WebSocketProtocol.SecRequestLength).Span, challengeKey); output.Advance(bytesWritten); output.Write(CRLF); if (!string.IsNullOrWhiteSpace(origin)) { output.WriteAsciiString("Origin: "); output.WriteAsciiString(origin); output.Write(CRLF); } if (!string.IsNullOrWhiteSpace(protocol)) { output.WriteAsciiString("Sec-WebSocket-Protocol: "); output.WriteAsciiString(protocol); output.Write(CRLF); } output.Write(WebSocketVersion); output.Write(CRLF); // final CRLF to end the HTTP request await output.FlushAsync(); WebSocketServer.WriteStatus(ConnectionType.Client, "Parsing response to client handshake..."); using (var resp = await WebSocketServer.ParseHttpResponse(connection.Input)) { if (!resp.HttpVersion.Equals(ExpectedHttpVersion) || !resp.StatusCode.Equals(ExpectedStatusCode) || !resp.StatusText.Equals(ExpectedStatusText) || !resp.Headers.GetRaw("Upgrade").Equals(ExpectedUpgrade) || !resp.Headers.GetRaw("Connection").Equals(ExpectedConnection)) { throw new InvalidOperationException("Not a web-socket server"); } var accept = resp.Headers.GetRaw("Sec-WebSocket-Accept"); if (!accept.Equals(challengeKey)) { throw new InvalidOperationException("Sec-WebSocket-Accept mismatch"); } protocol = resp.Headers.GetAsciiString("Sec-WebSocket-Protocol"); } var webSocket = new WebSocketConnection(connection, ConnectionType.Client) { Host = uri.Host, RequestLine = uri.OriginalString, Origin = origin, Protocol = protocol }; webSocket.StartProcessingIncomingFrames(); return(webSocket); }