예제 #1
0
        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;
예제 #6
0
        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);
        }