How to fix Websocket Handshake code?

14,797

If any of you are beating your head against a wall, this is the offending piece of code:

$response.= "Sec-WebSocket-Protocol: " . substr($path, 1) .

I am sure there is a way to actually set the desired/possible protocols, but I'm not sure yet what they are; and I'm not sure if its necessary for my purposes. If someone has an explanation of what the protocol switching is even for, I'd love to read it, but for now I'm just taking it out of my code.

Lots of googling to find this small problem.

I also dumped the pack(H*) code in the handshake which didn't seem to be necessary based on what I was reading. I'm not sure if that did anything or not, but it wasn't necessary to get the program to work.

Share:
14,797
Kirk
Author by

Kirk

I write, be it code or prose. Creative Writing: I am published, but not professionally; because there is a difference. One day, maybe.

Updated on June 04, 2022

Comments

  • Kirk
    Kirk almost 2 years

    This is likely a familiar sob story. But there are so many of them out there, and I'm such a n00b I can't find the answer, so I'd like your help if you can help me.

    So, I'm using phpwebsocket by lemmingzshadow (google brings this up pretty easily if you are unfamiliar). As far as I can tell the version he has out has a bug where it doesn't follow the standards that Chrome 20.+ now uses. Its got something to do with the hand shake & security keys but that's where I'm stuck at. I know I need to provide the following based on other questions, hopefully you can help me understand and fix this issue:

    The header Chrome receives is (Edited; I apparently posted the message to the server twice.):

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: aWXrpLOnEm15mE8+w1zG05ad01k=
    Sec-WebSocket-Protocol: QuatroDuo
    

    The header my server receives is:

    Upgrade: websocket
    Connection: Upgrade
    Host: gumonshoe.net:8000
    Origin: http://gumonshoe.net
    Sec-WebSocket-Key: v3+iw0U78qkwZnp+RWTu3A
    Sec-WebSocket-Version: 13
    Sec-WebSocket-Extensions: x-webkit-deflate-frame
    

    I don't think the cookies are necessary, correct me if I'm wrong though.

    I hate to do this next part, but I figure pasting it all is better than doing nothing and needing to come back later. Here is the portion of code that reads & interprets the handshake and sends the new one.

    Help is appreaciated:

    <?PHP
    private function handshake($data)
        {   
            $this->log('Performing handshake\r\n\r\n' . $data);  
            $lines = preg_split("/\r\n/", $data);
    
            // check for valid http-header:
            if(!preg_match('/\AGET (\S+) HTTP\/1.1\z/', $lines[0], $matches)) {
                $this->log('Invalid request: ' . $lines[0]);
                $this->sendHttpResponse(400);
                stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
                return false;
            }
    
            // check for valid application:
            $path = $matches[1];
            $this->application = $this->server->getApplication(substr($path, 1));
                if(!$this->application) {
                    $this->log('Invalid application: ' . $path);
                    $this->sendHttpResponse(404);           
                    stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
                    $this->server->removeClientOnError($this);
                    return false;
                }
    
            // generate headers array:
            $headers = array();
            foreach($lines as $line)
            {
                $line = chop($line);
                if(preg_match('/\A(\S+): (.*)\z/', $line, $matches))
                {
                    $headers[$matches[1]] = $matches[2];
                }
            }
    
            // check for supported websocket version:       
            if(!isset($headers['Sec-WebSocket-Version']) || $headers['Sec-WebSocket-Version'] < 6)
            {
                $this->log('Unsupported websocket version.');
                $this->sendHttpResponse(501);
                stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
                $this->server->removeClientOnError($this);
                return false;
            }
    
            // check origin:
            if($this->server->getCheckOrigin() === true)
            {
                $origin = (isset($headers['Sec-WebSocket-Origin'])) ? $headers['Sec-WebSocket-Origin'] : false;
                $origin = (isset($headers['Origin'])) ? $headers['Origin'] : $origin;
                if($origin === false)
                {
                    $this->log('No origin provided.');
                    $this->sendHttpResponse(401);
                    stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
                    $this->server->removeClientOnError($this);
                    return false;
                }
    
                if(empty($origin))
                {
                    $this->log('Empty origin provided.');
                    $this->sendHttpResponse(401);
                    stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
                    $this->server->removeClientOnError($this);
                    return false;
                }
    
                if($this->server->checkOrigin($origin) === false)
                {
                    $this->log('Invalid origin provided. : ' . $origin . ' Legal options were:');
                    $gumk = 0;
                    foreach(array_keys($this->server->getAllowedOrigins()) as $lo) {
                        $this->log( '[' . $gumk++ . '] : ' . $lo);
                    }
                    $this->sendHttpResponse(401);
                    stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
                    $this->server->removeClientOnError($this);
                    return false;
                }
            }       
    
            // do handyshake: (hybi-10)
            $secKey = $headers['Sec-WebSocket-Key'];
            $secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
            $response = "HTTP/1.1 101 Switching Protocols\r\n";
            $response.= "Upgrade: websocket\r\n";
            $response.= "Connection: Upgrade\r\n";
            $response.= "Sec-WebSocket-Accept: " . $secAccept . "\r\n";
            $response.= "Sec-WebSocket-Protocol: " . substr($path, 1) . "\r\n\r\n";     
            if(false === ($this->server->writeBuffer($this->socket, $response)))
            {
                return false;
            }
            $this->handshaked = true;
            $this->log('Handshake sent');
            $this->application->onConnect($this);
    
            // trigger status application:
            if($this->server->getApplication('status') !== false)
            {
                $this->server->getApplication('status')->clientConnected($this->ip, $this->port);
            }
    
            return true;            
        }
    

    Receiving the following error,

    Error during WebSocket handshake: Sec-WebSocket-Protocol mismatch
    

    As I'm largely inexperienced in this level of server debugging, a more detailed answer than linking me to documentation/specifications would be appreciated.