Beispiel #1
0
        public BinaryFrame(ArraySegment <byte> segment, bool isMasked = true)
        {
            BufferValidator.ValidateArraySegment(segment, "segment");

            this.Data     = segment.Array;
            this.Offset   = segment.Offset;
            this.Count    = segment.Count;
            this.IsMasked = isMasked;
        }
Beispiel #2
0
        public BinaryFrame(byte[] data, int offset, int count, bool isMasked = true)
        {
            BufferValidator.ValidateBuffer(data, offset, count, "data");

            this.Data     = data;
            this.Offset   = offset;
            this.Count    = count;
            this.IsMasked = isMasked;
        }
Beispiel #3
0
        public BinaryFragmentationFrame(OpCode opCode, byte[] data, int offset, int count, bool isFin = false, bool isMasked = true)
        {
            BufferValidator.ValidateBuffer(data, offset, count, "data");

            _opCode       = opCode;
            this.Data     = data;
            this.Offset   = offset;
            this.Count    = count;
            this.IsFin    = isFin;
            this.IsMasked = isMasked;
        }
        internal static bool HandleOpenningHandshakeRequest(AsyncWebSocketSession session, byte[] buffer, int offset, int count,
                                                            out string secWebSocketKey,
                                                            out string path,
                                                            out string query)
        {
            BufferValidator.ValidateBuffer(buffer, offset, count, "buffer");

            var request = Encoding.UTF8.GetString(buffer, offset, count);

#if DEBUG
            _log.DebugFormat("[{0}]{1}{2}", session.RemoteEndPoint, Environment.NewLine, request);
#endif
            try
            {
                // GET /chat HTTP/1.1
                // Host: server.example.com
                // Upgrade: websocket
                // Connection: Upgrade
                // Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
                // Origin: http://example.com
                // Sec-WebSocket-Protocol: chat, superchat
                // Sec-WebSocket-Version: 13
                Dictionary <string, string> headers;
                List <string> extensions;
                List <string> protocols;
                ParseOpenningHandshakeRequestHeaders(request, out headers, out extensions, out protocols);
                if (headers == null)
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to invalid headers.", session.RemoteEndPoint));
                }

                // An HTTP/1.1 or higher GET request, including a "Request-URI"
                // [RFC2616] that should be interpreted as a /resource name/
                // defined in Section 3 (or an absolute HTTP/HTTPS URI containing the /resource name/).
                // A |Host| header field containing the server's authority.
                if (!headers.ContainsKey(Consts.HttpGetMethodName))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to lack of get method.", session.RemoteEndPoint));
                }
                if (!headers.ContainsKey(HttpKnownHeaderNames.Host))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to lack of host authority.", session.RemoteEndPoint));
                }
                string uriString  = string.Format("ws://{0}{1}", headers[HttpKnownHeaderNames.Host], headers[Consts.HttpGetMethodName]);
                Uri    requestUri = null;
                if (!Uri.TryCreate(uriString, UriKind.RelativeOrAbsolute, out requestUri))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to invalid requested resource name.", session.RemoteEndPoint));
                }
                path  = requestUri.AbsolutePath;
                query = requestUri.Query;

                // A |Connection| header field that includes the token "Upgrade",
                // treated as an ASCII case-insensitive value.
                if (!headers.ContainsKey(HttpKnownHeaderNames.Connection))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to lack of connection header item.", session.RemoteEndPoint));
                }
                if (headers[HttpKnownHeaderNames.Connection].ToLowerInvariant() != Consts.WebSocketConnectionToken.ToLowerInvariant())
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to invalid connection header item value [{1}].",
                                                              session.RemoteEndPoint, headers[HttpKnownHeaderNames.Connection]));
                }

                // An |Upgrade| header field containing the value "websocket",
                // treated as an ASCII case-insensitive value.
                if (!headers.ContainsKey(HttpKnownHeaderNames.Upgrade))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to lack of upgrade header item.", session.RemoteEndPoint));
                }
                if (headers[HttpKnownHeaderNames.Upgrade].ToLowerInvariant() != Consts.WebSocketUpgradeToken.ToLowerInvariant())
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to invalid upgrade header item value [{1}].",
                                                              session.RemoteEndPoint, headers[HttpKnownHeaderNames.Upgrade]));
                }

                // A |Sec-WebSocket-Key| header field with a base64-encoded (see
                // Section 4 of [RFC4648]) value that, when decoded, is 16 bytes in length.
                if (!headers.ContainsKey(HttpKnownHeaderNames.SecWebSocketKey))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to lack of Sec-WebSocket-Key header item.", session.RemoteEndPoint));
                }
                if (string.IsNullOrWhiteSpace(headers[HttpKnownHeaderNames.SecWebSocketKey]))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to invalid Sec-WebSocket-Key header item value [{1}].",
                                                              session.RemoteEndPoint, headers[HttpKnownHeaderNames.SecWebSocketKey]));
                }
                secWebSocketKey = headers[HttpKnownHeaderNames.SecWebSocketKey];

                // A |Sec-WebSocket-Version| header field, with a value of 13.
                if (!headers.ContainsKey(HttpKnownHeaderNames.SecWebSocketVersion))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to lack of Sec-WebSocket-Version header item.", session.RemoteEndPoint));
                }
                if (headers[HttpKnownHeaderNames.SecWebSocketVersion].ToLowerInvariant() != Consts.WebSocketVersion.ToLowerInvariant())
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to invalid Sec-WebSocket-Version header item value [{1}].",
                                                              session.RemoteEndPoint, headers[HttpKnownHeaderNames.SecWebSocketVersion]));
                }

                // Optionally, a |Sec-WebSocket-Extensions| header field, with a
                // list of values indicating which extensions the client would like
                // to speak.  The interpretation of this header field is discussed in Section 9.1.
                if (extensions != null)
                {
                    if (!extensions.Any())
                    {
                        throw new WebSocketHandshakeException(string.Format(
                                                                  "Handshake with remote [{0}] failed due to empty extension.", session.RemoteEndPoint));
                    }
                    foreach (var extension in extensions)
                    {
                        // The empty string is not the same as the null value for these
                        // purposes and is not a legal value for this field.
                        if (string.IsNullOrWhiteSpace(extension))
                        {
                            throw new WebSocketHandshakeException(string.Format(
                                                                      "Handshake with remote [{0}] failed due to empty extension.", session.RemoteEndPoint));
                        }
                    }

                    session.AgreeExtensions(extensions);
                }

                // Optionally, a |Sec-WebSocket-Protocol| header field, with a list
                // of values indicating which protocols the client would like to
                // speak, ordered by preference.
                if (protocols != null)
                {
                    if (!protocols.Any())
                    {
                        throw new WebSocketHandshakeException(string.Format(
                                                                  "Handshake with remote [{0}] failed due to empty sub-protocol.", session.RemoteEndPoint));
                    }
                    foreach (var protocol in protocols)
                    {
                        // The empty string is not the same as the null value for these
                        // purposes and is not a legal value for this field.
                        if (string.IsNullOrWhiteSpace(protocol))
                        {
                            throw new WebSocketHandshakeException(string.Format(
                                                                      "Handshake with remote [{0}] failed due to empty sub-protocol.", session.RemoteEndPoint));
                        }
                    }

                    session.AgreeSubProtocols(string.Join(",", protocols));
                }

                // Optionally, an |Origin| header field.  This header field is sent
                // by all browser clients.  A connection attempt lacking this
                // header field SHOULD NOT be interpreted as coming from a browser client.
                //
                // Servers that are not intended to process input from any web page but
                // only for certain sites SHOULD verify the |Origin| field is an origin
                // they expect.  If the origin indicated is unacceptable to the server,
                // then it SHOULD respond to the WebSocket handshake with a reply
                // containing HTTP 403 Forbidden status code.
                //
                // The |Origin| header field protects from the attack cases when the
                // untrusted party is typically the author of a JavaScript application
                // that is executing in the context of the trusted client.  The client
                // itself can contact the server and, via the mechanism of the |Origin|
                // header field, determine whether to extend those communication
                // privileges to the JavaScript application.  The intent is not to
                // prevent non-browsers from establishing connections but rather to
                // ensure that trusted browsers under the control of potentially
                // malicious JavaScript cannot fake a WebSocket handshake.

                // Optionally, other header fields, such as those used to send
                // cookies or request authentication to a server.  Unknown header
                // fields are ignored, as per [RFC2616].
            }
            catch (Exception ex)
            {
                _log.ErrorFormat("{0}{1}{2}", session, Environment.NewLine, request);
                _log.Error(ex.Message, ex);
                throw;
            }

            return(true);
        }
        internal static bool VerifyOpenningHandshakeResponse(AsyncWebSocketClient client, byte[] buffer, int offset, int count, string secWebSocketKey)
        {
            BufferValidator.ValidateBuffer(buffer, offset, count, "buffer");
            if (string.IsNullOrEmpty(secWebSocketKey))
            {
                throw new ArgumentNullException("secWebSocketKey");
            }

            var response = Encoding.UTF8.GetString(buffer, offset, count);

#if DEBUG
            _log.DebugFormat("[{0}]{1}{2}", client.RemoteEndPoint, Environment.NewLine, response);
#endif
            try
            {
                // HTTP/1.1 101 Switching Protocols
                // Upgrade: websocket
                // Connection: Upgrade
                // Sec-WebSocket-Accept: 1tGBmA9p0DQDgmFll6P0/UcVS/E=
                // Sec-WebSocket-Protocol: chat
                Dictionary <string, string> headers;
                List <string> extensions;
                List <string> protocols;
                ParseOpenningHandshakeResponseHeaders(response, out headers, out extensions, out protocols);
                if (headers == null)
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to invalid headers.", client.RemoteEndPoint));
                }

                // If the status code received from the server is not 101, the
                // client handles the response per HTTP [RFC2616] procedures.  In
                // particular, the client might perform authentication if it
                // receives a 401 status code; the server might redirect the client
                // using a 3xx status code (but clients are not required to follow them), etc.
                if (!headers.ContainsKey(Consts.HttpStatusCodeName))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to lack of status code.", client.RemoteEndPoint));
                }
                if (!headers.ContainsKey(Consts.HttpStatusCodeDescription))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to lack of status description.", client.RemoteEndPoint));
                }
                if (headers[Consts.HttpStatusCodeName] == ((int)HttpStatusCode.BadRequest).ToString())
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to bad request [{1}].",
                                                              client.RemoteEndPoint, headers[Consts.HttpStatusCodeName]));
                }
                if (headers[Consts.HttpStatusCodeName] != ((int)HttpStatusCode.SwitchingProtocols).ToString())
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to expected 101 Switching Protocols but received [{1}].",
                                                              client.RemoteEndPoint, headers[Consts.HttpStatusCodeName]));
                }

                // If the response lacks an |Upgrade| header field or the |Upgrade|
                // header field contains a value that is not an ASCII case-
                // insensitive match for the value "websocket", the client MUST
                // _Fail the WebSocket Connection_.
                if (!headers.ContainsKey(HttpKnownHeaderNames.Connection))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to lack of connection header item.", client.RemoteEndPoint));
                }
                if (headers[HttpKnownHeaderNames.Connection].ToLowerInvariant() != Consts.WebSocketConnectionToken.ToLowerInvariant())
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to invalid connection header item value [{1}].",
                                                              client.RemoteEndPoint, headers[HttpKnownHeaderNames.Connection]));
                }

                // If the response lacks a |Connection| header field or the
                // |Connection| header field doesn't contain a token that is an
                // ASCII case-insensitive match for the value "Upgrade", the client
                // MUST _Fail the WebSocket Connection_.
                if (!headers.ContainsKey(HttpKnownHeaderNames.Upgrade))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to lack of upgrade header item.", client.RemoteEndPoint));
                }
                if (headers[HttpKnownHeaderNames.Upgrade].ToLowerInvariant() != Consts.WebSocketUpgradeToken.ToLowerInvariant())
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to invalid upgrade header item value [{1}].",
                                                              client.RemoteEndPoint, headers[HttpKnownHeaderNames.Upgrade]));
                }

                // If the response lacks a |Sec-WebSocket-Accept| header field or
                // the |Sec-WebSocket-Accept| contains a value other than the
                // base64-encoded SHA-1 of the concatenation of the |Sec-WebSocket-
                // Key| (as a string, not base64-decoded) with the string "258EAFA5-
                // E914-47DA-95CA-C5AB0DC85B11" but ignoring any leading and
                // trailing whitespace, the client MUST _Fail the WebSocket Connection_.
                if (!headers.ContainsKey(HttpKnownHeaderNames.SecWebSocketAccept))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to lack of Sec-WebSocket-Accept header item.", client.RemoteEndPoint));
                }
                string challenge = GetSecWebSocketAcceptString(secWebSocketKey);
                if (!headers[HttpKnownHeaderNames.SecWebSocketAccept].Equals(challenge, StringComparison.OrdinalIgnoreCase))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to invalid Sec-WebSocket-Accept header item value [{1}].",
                                                              client.RemoteEndPoint, headers[HttpKnownHeaderNames.SecWebSocketAccept]));
                }

                // If the response includes a |Sec-WebSocket-Extensions| header
                // field and this header field indicates the use of an extension
                // that was not present in the client's handshake (the server has
                // indicated an extension not requested by the client), the client
                // MUST _Fail the WebSocket Connection_.
                if (extensions != null)
                {
                    foreach (var extension in extensions)
                    {
                        // The empty string is not the same as the null value for these
                        // purposes and is not a legal value for this field.
                        if (string.IsNullOrWhiteSpace(extension))
                        {
                            throw new WebSocketHandshakeException(string.Format(
                                                                      "Handshake with remote [{0}] failed due to empty extension.", client.RemoteEndPoint));
                        }
                    }

                    client.AgreeExtensions(extensions);
                }

                // If the response includes a |Sec-WebSocket-Protocol| header field
                // and this header field indicates the use of a subprotocol that was
                // not present in the client's handshake (the server has indicated a
                // subprotocol not requested by the client), the client MUST _Fail
                // the WebSocket Connection_.
                if (protocols != null)
                {
                    if (!protocols.Any())
                    {
                        throw new WebSocketHandshakeException(string.Format(
                                                                  "Handshake with remote [{0}] failed due to empty sub-protocol.", client.RemoteEndPoint));
                    }
                    if (protocols.Count > 1)
                    {
                        throw new WebSocketHandshakeException(string.Format(
                                                                  "Handshake with remote [{0}] failed due to suggest to use multiple sub-protocols.", client.RemoteEndPoint));
                    }
                    foreach (var protocol in protocols)
                    {
                        // The empty string is not the same as the null value for these
                        // purposes and is not a legal value for this field.
                        if (string.IsNullOrWhiteSpace(protocol))
                        {
                            throw new WebSocketHandshakeException(string.Format(
                                                                      "Handshake with remote [{0}] failed due to empty sub-protocol.", client.RemoteEndPoint));
                        }
                    }

                    var suggestedProtocols = protocols.First().Split(',')
                                             .Select(p => p.TrimStart().TrimEnd()).Where(p => !string.IsNullOrWhiteSpace(p));

                    if (!suggestedProtocols.Any())
                    {
                        throw new WebSocketHandshakeException(string.Format(
                                                                  "Handshake with remote [{0}] failed due to invalid sub-protocol.", client.RemoteEndPoint));
                    }
                    if (suggestedProtocols.Count() > 1)
                    {
                        throw new WebSocketHandshakeException(string.Format(
                                                                  "Handshake with remote [{0}] failed due to suggest to use multiple sub-protocols.", client.RemoteEndPoint));
                    }

                    // The value chosen MUST be derived
                    // from the client's handshake, specifically by selecting one of
                    // the values from the |Sec-WebSocket-Protocol| field that the
                    // server is willing to use for this connection (if any).
                    client.UseSubProtocol(suggestedProtocols.First());
                }
            }
            catch (Exception ex)
            {
                _log.ErrorFormat("{0}{1}{2}", client, Environment.NewLine, response);
                _log.Error(ex.Message, ex);
                throw;
            }

            return(true);
        }
Beispiel #6
0
        internal static bool VerifyOpenningHandshakeResponse(WebSocketClient client, byte[] buffer, int offset, int count, string secWebSocketKey)
        {
            BufferValidator.ValidateBuffer(buffer, offset, count, "buffer");
            if (string.IsNullOrEmpty(secWebSocketKey))
            {
                throw new ArgumentNullException("secWebSocketKey");
            }

            var response = Encoding.UTF8.GetString(buffer, offset, count);

            try
            {
                // HTTP/1.1 101 Switching Protocols
                // Upgrade: websocket
                // Connection: Upgrade
                // Sec-WebSocket-Accept: 1tGBmA9p0DQDgmFll6P0/UcVS/E=
                // Sec-WebSocket-Protocol: chat
                Dictionary <string, string> headers;
                List <string> extensions;
                List <string> protocols;
                ParseOpenningHandshakeResponseHeaders(response, out headers, out extensions, out protocols);
                if (headers == null)
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to invalid headers.", client.RemoteEndPoint));
                }

                // If the status code received from the server is not 101, the
                // client handles the response per HTTP [RFC2616] procedures.  In
                // particular, the client might perform authentication if it
                // receives a 401 status code; the server might redirect the client
                // using a 3xx status code (but clients are not required to follow them), etc.
                if (!headers.ContainsKey(Consts.HttpStatusCodeName))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to lack of status code.", client.RemoteEndPoint));
                }
                if (!headers.ContainsKey(Consts.HttpStatusCodeDescription))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to lack of status description.", client.RemoteEndPoint));
                }
                if (headers[Consts.HttpStatusCodeName] == ((int)HttpStatusCode.BadRequest).ToString())
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to bad request [{1}].",
                                                              client.RemoteEndPoint, headers[Consts.HttpStatusCodeName]));
                }
                if (headers[Consts.HttpStatusCodeName] != ((int)HttpStatusCode.SwitchingProtocols).ToString())
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to expected 101 Switching Protocols but received [{1}].",
                                                              client.RemoteEndPoint, headers[Consts.HttpStatusCodeName]));
                }

                // If the response lacks an |Upgrade| header field or the |Upgrade|
                // header field contains a value that is not an ASCII case-
                // insensitive match for the value "websocket", the client MUST
                // _Fail the WebSocket Connection_.
                if (!headers.ContainsKey(HttpKnownHeaderNames.Connection))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to lack of connection header item.", client.RemoteEndPoint));
                }
                if (headers[HttpKnownHeaderNames.Connection].ToLowerInvariant() != Consts.WebSocketConnectionToken.ToLowerInvariant())
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to invalid connection header item value [{1}].",
                                                              client.RemoteEndPoint, headers[HttpKnownHeaderNames.Connection]));
                }

                // If the response lacks a |Connection| header field or the
                // |Connection| header field doesn't contain a token that is an
                // ASCII case-insensitive match for the value "Upgrade", the client
                // MUST _Fail the WebSocket Connection_.
                if (!headers.ContainsKey(HttpKnownHeaderNames.Upgrade))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to lack of upgrade header item.", client.RemoteEndPoint));
                }
                if (headers[HttpKnownHeaderNames.Upgrade].ToLowerInvariant() != Consts.WebSocketUpgradeToken.ToLowerInvariant())
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to invalid upgrade header item value [{1}].",
                                                              client.RemoteEndPoint, headers[HttpKnownHeaderNames.Upgrade]));
                }

                // If the response lacks a |Sec-WebSocket-Accept| header field or
                // the |Sec-WebSocket-Accept| contains a value other than the
                // base64-encoded SHA-1 of the concatenation of the |Sec-WebSocket-
                // Key| (as a string, not base64-decoded) with the string "258EAFA5-
                // E914-47DA-95CA-C5AB0DC85B11" but ignoring any leading and
                // trailing whitespace, the client MUST _Fail the WebSocket Connection_.
                if (!headers.ContainsKey(HttpKnownHeaderNames.SecWebSocketAccept))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to lack of Sec-WebSocket-Accept header item.", client.RemoteEndPoint));
                }
                string challenge = GetSecWebSocketAcceptString(secWebSocketKey);
                if (!headers[HttpKnownHeaderNames.SecWebSocketAccept].Equals(challenge, StringComparison.OrdinalIgnoreCase))
                {
                    throw new WebSocketHandshakeException(string.Format(
                                                              "Handshake with remote [{0}] failed due to invalid Sec-WebSocket-Accept header item value [{1}].",
                                                              client.RemoteEndPoint, headers[HttpKnownHeaderNames.SecWebSocketAccept]));
                }

                // If the response includes a |Sec-WebSocket-Extensions| header
                // field and this header field indicates the use of an extension
                // that was not present in the client's handshake (the server has
                // indicated an extension not requested by the client), the client
                // MUST _Fail the WebSocket Connection_.

                // If the response includes a |Sec-WebSocket-Protocol| header field
                // and this header field indicates the use of a subprotocol that was
                // not present in the client's handshake (the server has indicated a
                // subprotocol not requested by the client), the client MUST _Fail
                // the WebSocket Connection_.
            }
            catch (Exception)
            {
                throw;
            }

            return(true);
        }