protected override int ProcessHeadersAndUpgrade(NetContext context, Connection connection, Stream input, Stream additionalData, int additionalOffset)
        {
            string requestLine;
            var    headers = ParseHeaders(input, out requestLine);

            if (!string.Equals(headers["Upgrade"], "WebSocket", StringComparison.InvariantCultureIgnoreCase) ||
                !string.Equals(headers["Connection"], "Upgrade", StringComparison.InvariantCultureIgnoreCase) ||
                headers["Sec-WebSocket-Accept"] != expected)
            {
                throw new InvalidOperationException();
            }

            lock (this)
            {
                AddMatchingExtensions(headers, connection, context.Extensions);
                var protocol = new WebSocketsProcessor_RFC6455_13(true);
                connection.SetProtocol(protocol);
                if (pending != null)
                {
                    while (pending.Count != 0)
                    {
                        connection.Send(context, pending.Dequeue());
                    }
                }
            }
            return(0);
        }
Example #2
0
        }                                                              // doesn't need to be 100% accurate

        void IFrame.Write(NetContext context, Connection connection, Stream stream)
        {
            //Debug.WriteLine("write:" + this);
            byte[] buffer = null;
            try
            {
                buffer = context.GetBuffer();
                int offset = 0;
                buffer[offset++] = (byte)(((int)flags & 240) | ((int)OpCode & 15));
                if (PayloadLength > ushort.MaxValue)
                { // write as a 64-bit length
                    buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | 127);
                    buffer[offset++] = 0;
                    buffer[offset++] = 0;
                    buffer[offset++] = 0;
                    buffer[offset++] = 0;
                    buffer[offset++] = (byte)(PayloadLength >> 24);
                    buffer[offset++] = (byte)(PayloadLength >> 16);
                    buffer[offset++] = (byte)(PayloadLength >> 8);
                    buffer[offset++] = (byte)(PayloadLength);
                }
                else if (PayloadLength > 125)
                { // write as a 16-bit length
                    buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | 126);
                    buffer[offset++] = (byte)(PayloadLength >> 8);
                    buffer[offset++] = (byte)(PayloadLength);
                }
                else
                { // write in the header
                    buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | PayloadLength);
                }
                if (Mask.HasValue)
                {
                    int mask = Mask.Value;
                    buffer[offset++] = (byte)(mask >> 24);
                    buffer[offset++] = (byte)(mask >> 16);
                    buffer[offset++] = (byte)(mask >> 8);
                    buffer[offset++] = (byte)(mask);
                }
                stream.Write(buffer, 0, offset);

                if (PayloadLength != 0)
                {
                    int totalRead = WebSocketsProcessor_RFC6455_13.Copy(Payload, stream, buffer, Mask, PayloadLength);
                    if (totalRead != PayloadLength)
                    {
                        throw new EndOfStreamException("Wrong payload length sent");
                    }
                }
            }
            finally
            {
                context.Recycle(buffer);
            }
        }
        protected override int ProcessHeadersAndUpgrade(NetContext context, Connection connection, Stream input, Stream additionalData, int additionalOffset)
        {
            string requestLine;
            var headers = ParseHeaders(input, out requestLine);
            if (!string.Equals(headers["Upgrade"], "WebSocket", StringComparison.InvariantCultureIgnoreCase)
                || !string.Equals(headers["Connection"], "Upgrade", StringComparison.InvariantCultureIgnoreCase)
                || headers["Sec-WebSocket-Accept"] != expected)
            {
                throw new InvalidOperationException();
            }

            lock (this)
            {
                AddMatchingExtensions(headers, connection, context.Extensions);
                var protocol = new WebSocketsProcessor_RFC6455_13(true);
                connection.SetProtocol(protocol);
                if (pending != null)
                {
                    while (pending.Count != 0)
                    {
                        connection.Send(context, pending.Dequeue());
                    }
                }
            }
            return 0;
        }
        protected override int ProcessHeadersAndUpgrade(NetContext context, Connection conn, Stream input, Stream additionalData, int additionalOffset)
        {
            string requestLine;
            var    headers = ParseHeaders(input, out requestLine);
            string host    = headers["Host"];

            // context.Handler.WriteLog(host + ": " + requestLine, conn);

            //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 connection = (WebSocketConnection)conn;

            connection.Host   = host;
            connection.Origin = headers["Origin"] ?? headers["Sec-WebSocket-Origin"]; // 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!
            connection.Protocol    = headers["Sec-WebSocket-Protocol"];
            connection.RequestLine = requestLine;
            if (string.IsNullOrEmpty(connection.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.InvariantCultureIgnoreCase);

            if (headers.ContainsKey("Connection"))
            {
                // so for mozilla, this will be the set {"keep-alive", "Upgrade"}
                var parts = headers["Connection"].Split(Comma);
                foreach (var part in parts)
                {
                    connectionParts.Add(part.Trim());
                }
            }
            if (connectionParts.Contains("Upgrade") && string.Equals(headers["Upgrade"], "websocket", StringComparison.InvariantCultureIgnoreCase))
            {
                //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
                WebSocketsProcessor newProtocol;
                string version           = headers["Sec-WebSocket-Version"];
                byte[] body              = null;
                int    bodyRead          = 0;
                bool   processExtensions = false;
                if (version == null)
                {
                    if (headers.ContainsKey("Sec-WebSocket-Key1") && headers.ContainsKey("Sec-WebSocket-Key2"))
                    { // smells like hixie-76/hybi-00
                        newProtocol = new WebSocketsProcessor_Hixie76_00.Server();
                    }
                    else
                    {
                        throw new NotSupportedException();
                    }
                }
                else
                {
                    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
                        newProtocol       = new WebSocketsProcessor_RFC6455_13(false);
                        processExtensions = true;
                        break;

                    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));
                    }
                }

                //if(headers.ContainsKey("x-forwarded-for"))
                //{
                //    context.Handler.WriteLog("forwarded for: " + headers["x-forwarded-for"], conn);
                //}

                if (processExtensions)
                {
                    AddMatchingExtensions(headers, connection, context.Extensions);
                }
                connection.HighPriority = false; // regular priority when we've got as far as switching protocols
                connection.SetProtocol(newProtocol);
                context.Handler.OnAuthenticate(connection, headers);
                newProtocol.CompleteHandshake(context, connection, input, requestLine, headers, body);
                context.Handler.OnAfterAuthenticate(connection);
                connection.PromptToSend(context);
                return(bodyRead);
            }
            else
            {
                var            responseHeaders = new StringDictionary();
                HttpStatusCode code;
                string         body;

                if (!TryBasicResponse(context, headers, requestLine, responseHeaders, out code, out body))
                {
                    // does it look a lot like a proxy server?
                    bool isProxy = headers.ContainsKey("via");
                    if (!isProxy)
                    {
                        foreach (string key in headers.Keys)
                        {
                            if (key.EndsWith("-via"))
                            {
                                isProxy = true;
                                break;
                            }
                        }
                    }
                    if (isProxy)
                    {
                        throw new CloseSocketException(); // no need to log this
                    }


                    if ((failSockets++ % 100) == 0)
                    {
                        // DUMP HEADERS
                        var sb = new StringBuilder("Failing request: ").AppendLine(requestLine);
                        foreach (string key in headers.Keys)
                        {
                            sb.AppendFormat("{0}:\t{1}", key, headers[key]).AppendLine();
                        }
                        context?.Handler?.ErrorLog?.WriteLine(sb);
                    }
                    throw new InvalidOperationException("Request was not a web-socket upgrade request; connection: " + headers["Connection"] + ", upgrade: " + headers["Upgrade"]);
                }
                else
                {
                    var newProcessor = new BasicHttpResponseProcessor();
                    connection.SetProtocol(newProcessor);
                    var resp = new StringBuilder();
                    resp.Append("HTTP/1.0 ").Append((int)code).Append(' ').Append(code).AppendLine();
                    foreach (string key in responseHeaders.Keys)
                    {
                        string val = responseHeaders[key];
                        if (string.IsNullOrEmpty(val))
                        {
                            continue;
                        }
                        resp.Append(key).Append(": ").Append(val).AppendLine();
                    }
                    resp.Append("Connection: close").AppendLine();
                    resp.AppendLine();
                    if (!string.IsNullOrEmpty(body))
                    {
                        resp.AppendLine(body);
                    }
                    connection.Send(context, resp.ToString());
                    newProcessor.SendShutdown(context);
                    connection.PromptToSend(context);
                    throw new CloseSocketException();
                }
            }
        }
        protected override void InitializeClientHandshake(NetContext context, Connection conn)
        {
            var connection = (WebSocketConnection)conn;

            byte[] nonce = new byte[16];
            NetContext.GetRandomBytes(nonce);
            var outgoing = Convert.ToBase64String(nonce);

            expected = WebSocketsProcessor_RFC6455_13.ComputeReply(context, outgoing);

            if (string.IsNullOrEmpty(connection.Host))
            {
                throw new InvalidOperationException("Host must be specified");
            }
            if (string.IsNullOrEmpty(connection.RequestLine))
            {
                throw new InvalidOperationException("RequestLine must be specified");
            }

            StringBuilder req = new StringBuilder(connection.RequestLine).Append("\r\n" +
                                                                                 "Upgrade: websocket\r\n" +
                                                                                 "Connection: Upgrade\r\n" +
                                                                                 "Sec-WebSocket-Version: 13\r\n" +
                                                                                 "Sec-WebSocket-Key: ").Append(outgoing).Append("\r\n" +
                                                                                                                                "Host: ").Append(connection.Host).Append("\r\n");

            if (!string.IsNullOrEmpty(connection.Origin))
            {
                req.Append("Origin: ").Append(connection.Origin).Append("\r\n");
            }
            if (!string.IsNullOrEmpty(connection.Protocol))
            {
                req.Append("Sec-WebSocket-Protocol: ").Append(connection.Protocol).Append("\r\n");
            }
            var extnArray = context.Extensions;

            if (extnArray != null && extnArray.Length != 0)
            {
                List <string> extnHeaders = null;
                for (int i = 0; i < extnArray.Length; i++)
                {
                    var extn = extnArray[i] as IExtensionFactory;
                    if (extn != null)
                    {
                        string extnHeader = extn.GetRequestHeader();
                        if (!string.IsNullOrEmpty(extnHeader))
                        {
                            if (extnHeaders == null)
                            {
                                extnHeaders = new List <string>();
                            }
                            extnHeaders.Add(extnHeader);
                        }
                    }
                }
                if (extnHeaders != null)
                {
                    req.Append("Sec-WebSocket-Extensions: ").Append(extnHeaders[0]);
                    for (int i = 1; i < extnHeaders.Count; i++)
                    {
                        req.Append(", ").Append(extnHeaders[i]);
                    }
                    req.Append("\r\n");
                }
            }

            req.Append("\r\n");
            EnqueueFrame(context, new StringFrame(req.ToString(), Encoding.ASCII));
            connection.PromptToSend(context);
        }
        protected override int ProcessHeadersAndUpgrade(NetContext context, Connection conn, Stream input, Stream additionalData, int additionalOffset)
        {
            string requestLine;
            var headers = ParseHeaders(input, out requestLine);
            string host = headers["Host"];

            // context.Handler.WriteLog(host + ": " + requestLine, conn);

            //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 connection = (WebSocketConnection) conn;
            connection.Host = host;
            connection.Origin = headers["Origin"] ?? headers["Sec-WebSocket-Origin"]; // 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!
            connection.Protocol = headers["Sec-WebSocket-Protocol"];
            connection.RequestLine = requestLine;
            if (string.IsNullOrEmpty(connection.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.InvariantCultureIgnoreCase);
            if (headers.ContainsKey("Connection"))
            {
                // so for mozilla, this will be the set {"keep-alive", "Upgrade"}
                var parts = headers["Connection"].Split(Comma);
                foreach (var part in parts) connectionParts.Add(part.Trim());
            }
            if (connectionParts.Contains("Upgrade") && string.Equals(headers["Upgrade"], "websocket", StringComparison.InvariantCultureIgnoreCase))
            {
                //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
                WebSocketsProcessor newProtocol;
                string version = headers["Sec-WebSocket-Version"];
                byte[] body = null;
                int bodyRead = 0;
                bool processExtensions = false;
                if(version == null)
                {
                    if (headers.ContainsKey("Sec-WebSocket-Key1") && headers.ContainsKey("Sec-WebSocket-Key2"))
                    { // smells like hixie-76/hybi-00
                        newProtocol = new WebSocketsProcessor_Hixie76_00.Server();
                    }
                    else
                    {
                        throw new NotSupportedException();
                    }
                }
                else
                {
                    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
                            newProtocol = new WebSocketsProcessor_RFC6455_13(false);
                            processExtensions = true;
                            break;
                        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));
                    }
                }
                
                //if(headers.ContainsKey("x-forwarded-for"))
                //{
                //    context.Handler.WriteLog("forwarded for: " + headers["x-forwarded-for"], conn);
                //}
                
                if (processExtensions)
                {
                    AddMatchingExtensions(headers, connection, context.Extensions);
                }
                connection.HighPriority = false; // regular priority when we've got as far as switching protocols
                connection.SetProtocol(newProtocol);
                context.Handler.OnAuthenticate(connection, headers);
                newProtocol.CompleteHandshake(context, connection, input, requestLine, headers, body);
                context.Handler.OnAfterAuthenticate(connection);
                connection.PromptToSend(context);
                return bodyRead;
            }
            else
            {
                var responseHeaders = new StringDictionary();
                HttpStatusCode code;
                string body;

                if (!TryBasicResponse(context, headers, requestLine, responseHeaders, out code, out body))
                {
                    // does it look a lot like a proxy server?
                    bool isProxy = headers.ContainsKey("via");
                    if(!isProxy)
                    {
                        foreach(string key in headers.Keys)
                        {
                            if(key.EndsWith("-via"))
                            {
                                isProxy = true;
                                break;
                            }
                        }
                    }
                    if(isProxy)
                    {
                        throw new CloseSocketException(); // no need to log this
                    }


                    if ((failSockets++ % 100) == 0)
                    {
                        // DUMP HEADERS
                        var sb = new StringBuilder("Failing request: ").AppendLine(requestLine);
                        foreach (string key in headers.Keys)
                        {
                            sb.AppendFormat("{0}:\t{1}", key, headers[key]).AppendLine();
                        }
                        Console.Error.WriteLine(sb);
                    }
                    throw new InvalidOperationException("Request was not a web-socket upgrade request; connection: " + headers["Connection"] + ", upgrade: " + headers["Upgrade"]);
                }
                else
                {
                    var newProcessor = new BasicHttpResponseProcessor();
                    connection.SetProtocol(newProcessor);
                    var resp = new StringBuilder();
                    resp.Append("HTTP/1.0 ").Append((int)code).Append(' ').Append(code).AppendLine();
                    foreach (string key in responseHeaders.Keys)
                    {
                        string val = responseHeaders[key];
                        if(string.IsNullOrEmpty(val)) continue;
                        resp.Append(key).Append(": ").Append(val).AppendLine();
                    }
                    resp.Append("Connection: close").AppendLine();
                    resp.AppendLine();
                    if (!string.IsNullOrEmpty(body)) resp.AppendLine(body);
                    connection.Send(context, resp.ToString());
                    newProcessor.SendShutdown(context);
                    connection.PromptToSend(context);
                    throw new CloseSocketException();
                }
            }
        }