From 27347880c9c9b3a3610ce8ee2b7c2ad2221b4c26 Mon Sep 17 00:00:00 2001 From: Ben Morse Date: Mon, 17 Oct 2011 14:36:52 -0700 Subject: [PATCH 1/4] * applying updated code from http://code.google.com/p/phpwebsocket/ --- chatbot.demo.php | 3 +- server.php | 65 +++++++++++++++++++++++--------------- websocket.class.php | 77 ++++++++++++++++++++++++++++----------------- websocket.demo.php | 3 +- 4 files changed, 90 insertions(+), 58 deletions(-) diff --git a/chatbot.demo.php b/chatbot.demo.php index fa5ff8a..cd8973a 100644 --- a/chatbot.demo.php +++ b/chatbot.demo.php @@ -21,5 +21,4 @@ function process($user,$msg){ } } -$ws = new ChatBot("localhost",12345); -$ws->listen(); +$master = new ChatBot("localhost",12345); diff --git a/server.php b/server.php index 9fa3ef3..e79c2f4 100644 --- a/server.php +++ b/server.php @@ -47,11 +47,11 @@ function process($user,$msg){ } } -function send($client,$msg){ +function send($client,$msg){ say("> ".$msg); $msg = wrap($msg); socket_write($client,$msg,strlen($msg)); -} +} function WebSocket($address,$port){ $master=socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() failed"); @@ -91,25 +91,39 @@ function disconnect($socket){ function dohandshake($user,$buffer){ console("\nRequesting handshake..."); console($buffer); - /* - GET {resource} HTTP/1.1 - Upgrade: WebSocket - Connection: Upgrade - Host: {host} - Origin: {origin} - \r\n - */ - list($resource,$host,$origin) = getheaders($buffer); - //$resource = "/phpwebsocketchat/server.php"; - //$host = "localhost:12345"; - //$origin = "http://localhost"; + list($resource,$host,$origin,$strkey1,$strkey2,$data) = getheaders($buffer); console("Handshaking..."); - $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" . + + $pattern = '/[^\d]*/'; + $replacement = ''; + $numkey1 = preg_replace($pattern, $replacement, $strkey1); + $numkey2 = preg_replace($pattern, $replacement, $strkey2); + + $pattern = '/[^ ]*/'; + $replacement = ''; + $spaces1 = strlen(preg_replace($pattern, $replacement, $strkey1)); + $spaces2 = strlen(preg_replace($pattern, $replacement, $strkey2)); + + if ($spaces1 == 0 || $spaces2 == 0 || $numkey1 % $spaces1 != 0 || $numkey2 % $spaces2 != 0) { + socket_close($user->socket); + console('failed'); + return false; + } + + $ctx = hash_init('md5'); + hash_update($ctx, pack("N", $numkey1/$spaces1)); + hash_update($ctx, pack("N", $numkey2/$spaces2)); + hash_update($ctx, $data); + $hash_data = hash_final($ctx,true); + + $upgrade = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" . "Upgrade: WebSocket\r\n" . "Connection: Upgrade\r\n" . - "WebSocket-Origin: " . $origin . "\r\n" . - "WebSocket-Location: ws://" . $host . $resource . "\r\n" . - "\r\n"; + "Sec-WebSocket-Origin: " . $origin . "\r\n" . + "Sec-WebSocket-Location: ws://" . $host . $resource . "\r\n" . + "\r\n" . + $hash_data; + socket_write($user->socket,$upgrade.chr(0),strlen($upgrade.chr(0))); $user->handshake=true; console($upgrade); @@ -118,13 +132,14 @@ function dohandshake($user,$buffer){ } function getheaders($req){ - $req = substr($req,4); /* RegEx kill babies */ - $res = substr($req,0,strpos($req," HTTP")); - $req = substr($req,strpos($req,"Host:")+6); - $host = substr($req,0,strpos($req,"\r\n")); - $req = substr($req,strpos($req,"Origin:")+8); - $ori = substr($req,0,strpos($req,"\r\n")); - return array($res,$host,$ori); + $r=$h=$o=null; + if(preg_match("/GET (.*) HTTP/" ,$req,$match)){ $r=$match[1]; } + if(preg_match("/Host: (.*)\r\n/" ,$req,$match)){ $h=$match[1]; } + if(preg_match("/Origin: (.*)\r\n/",$req,$match)){ $o=$match[1]; } + if(preg_match("/Sec-WebSocket-Key2: (.*)\r\n/",$req,$match)){ $key2=$match[1]; } + if(preg_match("/Sec-WebSocket-Key1: (.*)\r\n/",$req,$match)){ $key1=$match[1]; } + if(preg_match("/\r\n(.*?)\$/",$req,$match)){ $data=$match[1]; } + return array($r,$h,$o,$key1,$key2,$data); } function getuserbysocket($socket){ diff --git a/websocket.class.php b/websocket.class.php index 2874f7e..09133b7 100644 --- a/websocket.class.php +++ b/websocket.class.php @@ -1,8 +1,6 @@ listen(); +// Usage: $master=new WebSocket("localhost",12345); class WebSocket{ var $master; @@ -20,14 +18,10 @@ function __construct($address,$port){ socket_bind($this->master, $address, $port) or die("socket_bind() failed"); socket_listen($this->master,20) or die("socket_listen() failed"); $this->sockets[] = $this->master; - $this->say("---------------\n"); $this->say("Server Started : ".date('Y-m-d H:i:s')); $this->say("Listening on : ".$address." port ".$port); - $this->say("Master socket : ".$this->master); - $this->say("---------------\n"); - } - - function listen(){ + $this->say("Master socket : ".$this->master."\n"); + while(true){ $changed = $this->sockets; socket_select($changed,$write=NULL,$except=NULL,NULL); @@ -60,17 +54,9 @@ function send($client,$msg){ $this->say("> ".$msg); $msg = $this->wrap($msg); socket_write($client,$msg,strlen($msg)); + $this->say("! ".strlen($msg)); } - function broadcast($msg,$except=null){ - $this->say(">>".$msg); - foreach($this->users as $user){ - if($user->socket==$except){ continue; } /* exclude from broadcast */ - $msg = $this->wrap($msg); - socket_write($user->socket,$msg,strlen($msg)); - } - } - function connect($socket){ $user = new User(); $user->id = uniqid(); @@ -78,6 +64,7 @@ function connect($socket){ array_push($this->users,$user); array_push($this->sockets,$socket); $this->log($socket." CONNECTED!"); + $this->log(date("d/n/Y ")."at ".date("H:i:s T")); } function disconnect($socket){ @@ -96,27 +83,59 @@ function disconnect($socket){ function dohandshake($user,$buffer){ $this->log("\nRequesting handshake..."); $this->log($buffer); - list($resource,$host,$origin) = $this->getheaders($buffer); + list($resource,$host,$origin,$key1,$key2,$l8b) = $this->getheaders($buffer); $this->log("Handshaking..."); - $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" . + //$port = explode(":",$host); + //$port = $port[1]; + //$this->log($origin."\r\n".$host); + $upgrade = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" . "Upgrade: WebSocket\r\n" . "Connection: Upgrade\r\n" . - "WebSocket-Origin: " . $origin . "\r\n" . - "WebSocket-Location: ws://" . $host . $resource . "\r\n" . - "\r\n"; + //"WebSocket-Origin: " . $origin . "\r\n" . + //"WebSocket-Location: ws://" . $host . $resource . "\r\n" . + "Sec-WebSocket-Origin: " . $origin . "\r\n" . + "Sec-WebSocket-Location: ws://" . $host . $resource . "\r\n" . + //"Sec-WebSocket-Protocol: icbmgame\r\n" . //Client doesn't send this + "\r\n" . + $this->calcKey($key1,$key2,$l8b) . "\r\n";// . + //"\r\n"; socket_write($user->socket,$upgrade.chr(0),strlen($upgrade.chr(0))); $user->handshake=true; $this->log($upgrade); $this->log("Done handshaking..."); return true; } - + + function calcKey($key1,$key2,$l8b){ + //Get the numbers + preg_match_all('/([\d]+)/', $key1, $key1_num); + preg_match_all('/([\d]+)/', $key2, $key2_num); + //Number crunching [/bad pun] + $this->log("Key1: " . $key1_num = implode($key1_num[0]) ); + $this->log("Key2: " . $key2_num = implode($key2_num[0]) ); + //Count spaces + preg_match_all('/([ ]+)/', $key1, $key1_spc); + preg_match_all('/([ ]+)/', $key2, $key2_spc); + //How many spaces did it find? + $this->log("Key1 Spaces: " . $key1_spc = strlen(implode($key1_spc[0])) ); + $this->log("Key2 Spaces: " . $key2_spc = strlen(implode($key2_spc[0])) ); + if($key1_spc==0|$key2_spc==0){ $this->log("Invalid key");return; } + //Some math + $key1_sec = pack("N",$key1_num / $key1_spc); //Get the 32bit secret key, minus the other thing + $key2_sec = pack("N",$key2_num / $key2_spc); + //This needs checking, I'm not completely sure it should be a binary string + return md5($key1_sec.$key2_sec.$l8b,1); //The result, I think + } + function getheaders($req){ $r=$h=$o=null; - if(preg_match("/GET (.*) HTTP/" ,$req,$match)){ $r=$match[1]; } - if(preg_match("/Host: (.*)\r\n/" ,$req,$match)){ $h=$match[1]; } - if(preg_match("/Origin: (.*)\r\n/",$req,$match)){ $o=$match[1]; } - return array($r,$h,$o); + if(preg_match("/GET (.*) HTTP/" ,$req,$match)){ $r=$match[1]; } + if(preg_match("/Host: (.*)\r\n/" ,$req,$match)){ $h=$match[1]; } + if(preg_match("/Origin: (.*)\r\n/" ,$req,$match)){ $o=$match[1]; } + if(preg_match("/Sec-WebSocket-Key1: (.*)\r\n/",$req,$match)){ $this->log("Sec Key1: ".$sk1=$match[1]); } + if(preg_match("/Sec-WebSocket-Key2: (.*)\r\n/",$req,$match)){ $this->log("Sec Key2: ".$sk2=$match[1]); } + if($match=substr($req,-8)) { $this->log("Last 8 bytes: ".$l8b=$match); } + return array($r,$h,$o,$sk1,$sk2,$l8b); } function getuserbysocket($socket){ @@ -140,4 +159,4 @@ class User{ var $handshake; } -?> \ No newline at end of file +?> diff --git a/websocket.demo.php b/websocket.demo.php index 2302ee3..4506461 100644 --- a/websocket.demo.php +++ b/websocket.demo.php @@ -4,5 +4,4 @@ // Basic WebSocket demo echoes msg back to client include "websocket.class.php"; -$ws = new WebSocket("localhost",12345); -$ws->listen(); +$master = new WebSocket("localhost",12345); From 43da58892dff81cda8cdf01ae74de5338e706e1f Mon Sep 17 00:00:00 2001 From: Ben Morse Date: Mon, 17 Oct 2011 14:53:28 -0700 Subject: [PATCH 2/4] * deleting server.php because I don't want to keep a non extendable copy of this DRAFT standard * this file is pretty much identical to websocket.class.php anyways --- server.php | 165 ----------------------------------------------------- 1 file changed, 165 deletions(-) delete mode 100644 server.php diff --git a/server.php b/server.php deleted file mode 100644 index e79c2f4..0000000 --- a/server.php +++ /dev/null @@ -1,165 +0,0 @@ -#!/php -q -php -q server.php */ -error_reporting(E_ALL); -set_time_limit(0); -ob_implicit_flush(); - -$master = WebSocket("localhost",12345); -$sockets = array($master); -$users = array(); -$debug = false; - -while(true){ - $changed = $sockets; - socket_select($changed,$write=NULL,$except=NULL,NULL); - foreach($changed as $socket){ - if($socket==$master){ - $client=socket_accept($master); - if($client<0){ console("socket_accept() failed"); continue; } - else{ connect($client); } - } - else{ - $bytes = @socket_recv($socket,$buffer,2048,0); - if($bytes==0){ disconnect($socket); } - else{ - $user = getuserbysocket($socket); - if(!$user->handshake){ dohandshake($user,$buffer); } - else{ process($user,$buffer); } - } - } - } -} - -//--------------------------------------------------------------- -function process($user,$msg){ - $action = unwrap($msg); - say("< ".$action); - switch($action){ - case "hello" : send($user->socket,"hello human"); break; - case "hi" : send($user->socket,"zup human"); break; - case "name" : send($user->socket,"my name is Multivac, silly I know"); break; - case "age" : send($user->socket,"I am older than time itself"); break; - case "date" : send($user->socket,"today is ".date("Y.m.d")); break; - case "time" : send($user->socket,"server time is ".date("H:i:s")); break; - case "thanks": send($user->socket,"you're welcome"); break; - case "bye" : send($user->socket,"bye"); break; - default : send($user->socket,$action." not understood"); break; - } -} - -function send($client,$msg){ - say("> ".$msg); - $msg = wrap($msg); - socket_write($client,$msg,strlen($msg)); -} - -function WebSocket($address,$port){ - $master=socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() failed"); - socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1) or die("socket_option() failed"); - socket_bind($master, $address, $port) or die("socket_bind() failed"); - socket_listen($master,20) or die("socket_listen() failed"); - echo "Server Started : ".date('Y-m-d H:i:s')."\n"; - echo "Master socket : ".$master."\n"; - echo "Listening on : ".$address." port ".$port."\n\n"; - return $master; -} - -function connect($socket){ - global $sockets,$users; - $user = new User(); - $user->id = uniqid(); - $user->socket = $socket; - array_push($users,$user); - array_push($sockets,$socket); - console($socket." CONNECTED!"); -} - -function disconnect($socket){ - global $sockets,$users; - $found=null; - $n=count($users); - for($i=0;$i<$n;$i++){ - if($users[$i]->socket==$socket){ $found=$i; break; } - } - if(!is_null($found)){ array_splice($users,$found,1); } - $index = array_search($socket,$sockets); - socket_close($socket); - console($socket." DISCONNECTED!"); - if($index>=0){ array_splice($sockets,$index,1); } -} - -function dohandshake($user,$buffer){ - console("\nRequesting handshake..."); - console($buffer); - list($resource,$host,$origin,$strkey1,$strkey2,$data) = getheaders($buffer); - console("Handshaking..."); - - $pattern = '/[^\d]*/'; - $replacement = ''; - $numkey1 = preg_replace($pattern, $replacement, $strkey1); - $numkey2 = preg_replace($pattern, $replacement, $strkey2); - - $pattern = '/[^ ]*/'; - $replacement = ''; - $spaces1 = strlen(preg_replace($pattern, $replacement, $strkey1)); - $spaces2 = strlen(preg_replace($pattern, $replacement, $strkey2)); - - if ($spaces1 == 0 || $spaces2 == 0 || $numkey1 % $spaces1 != 0 || $numkey2 % $spaces2 != 0) { - socket_close($user->socket); - console('failed'); - return false; - } - - $ctx = hash_init('md5'); - hash_update($ctx, pack("N", $numkey1/$spaces1)); - hash_update($ctx, pack("N", $numkey2/$spaces2)); - hash_update($ctx, $data); - $hash_data = hash_final($ctx,true); - - $upgrade = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" . - "Upgrade: WebSocket\r\n" . - "Connection: Upgrade\r\n" . - "Sec-WebSocket-Origin: " . $origin . "\r\n" . - "Sec-WebSocket-Location: ws://" . $host . $resource . "\r\n" . - "\r\n" . - $hash_data; - - socket_write($user->socket,$upgrade.chr(0),strlen($upgrade.chr(0))); - $user->handshake=true; - console($upgrade); - console("Done handshaking..."); - return true; -} - -function getheaders($req){ - $r=$h=$o=null; - if(preg_match("/GET (.*) HTTP/" ,$req,$match)){ $r=$match[1]; } - if(preg_match("/Host: (.*)\r\n/" ,$req,$match)){ $h=$match[1]; } - if(preg_match("/Origin: (.*)\r\n/",$req,$match)){ $o=$match[1]; } - if(preg_match("/Sec-WebSocket-Key2: (.*)\r\n/",$req,$match)){ $key2=$match[1]; } - if(preg_match("/Sec-WebSocket-Key1: (.*)\r\n/",$req,$match)){ $key1=$match[1]; } - if(preg_match("/\r\n(.*?)\$/",$req,$match)){ $data=$match[1]; } - return array($r,$h,$o,$key1,$key2,$data); -} - -function getuserbysocket($socket){ - global $users; - $found=null; - foreach($users as $user){ - if($user->socket==$socket){ $found=$user; break; } - } - return $found; -} - -function say($msg=""){ echo $msg."\n"; } -function wrap($msg=""){ return chr(0).$msg.chr(255); } -function unwrap($msg=""){ return substr($msg,1,strlen($msg)-2); } -function console($msg=""){ global $debug; if($debug){ echo $msg."\n"; } } - -class User{ - var $id; - var $socket; - var $handshake; -} - -?> \ No newline at end of file From 244401eee2e30aec5f96d9de16a8aba433b6833a Mon Sep 17 00:00:00 2001 From: Ben Morse Date: Mon, 17 Oct 2011 15:14:47 -0700 Subject: [PATCH 3/4] * updating readme file for hybi-10 standard * updating websocket.class.php for hybi-standard * commit previous to this is some previous standard (which works on ios 4.2) * this version breaks ios 4.2. maybe in a future revision I will detect which keys are used and respond accordingly (this is 100% feasible) --- README.md | 40 +++------- websocket.class.php | 180 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 152 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index de20ab5..d176a4e 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,11 @@ PHPWEBSOCKET ============ -So here is a quick hack to implement websockets in php. -As of Feb/10 the only browsers that support websockets are [Google Chrome](http://www.google.com/chrome) and [Safari nightlies](http://nightly.webkit.org/) +So here is a quick hack to implement hybi-10 websockets in php! +Check out the [hybi-10 standard here](http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10#section-4.2) +Check [This Wikipedia Page](http://en.wikipedia.org/wiki/WebSocket#Browser_support) for suppored list of browsers. -Changelog ---------- -* 2010.02.18 - Chatbot demo extending websocket class -* 2010.02.18 - Websocket class for better reuse -* 2010.02.15 - Added Users list to keep track of handshakes -* 2010.02.15 - Minor cosmetic changes - Client side ----------- @@ -25,30 +19,19 @@ Client side } catch(ex){ log(ex); } -View source code of [client.html](http://github.com/GeorgeNava/phpwebsocket/blob/master/client.html) +View source code of [client.html](http://github.com/esromneb/phpwebsocket/blob/master/client.html) Server side ----------- - log("Handshaking..."); - list($resource,$host,$origin) = getheaders($buffer); - $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" . - "Upgrade: WebSocket\r\n" . - "Connection: Upgrade\r\n" . - "WebSocket-Origin: " . $origin . "\r\n" . - "WebSocket-Location: ws://" . $host . $resource . "\r\n" . - "\r\n"; - $handshake = true; - socket_write($socket,$upgrade.chr(0),strlen($upgrade.chr(0))); - -View source code of [server.php](http://github.com/GeorgeNava/phpwebsocket/blob/master/server.php) + View source code of [chatbot.demo.php](http://github.com/esromneb/phpwebsocket/blob/master/chatbot.demo.php) Steps to run the test: ---------------------- - -* Save both files, client.html and server.php, in a folder in your local server running Apache and PHP. -* From the command line, run the server.php program to listen for socket connections. +* [See these instructions](http://net.tutsplus.com/tutorials/javascript-ajax/start-using-html5-websockets-today/) for info on how to run this under windows ( xampp: Apache + PHP ) +* Save both files, client.html and chatbot.demo.php, in a folder in your local server running Apache and PHP. +* From the command line, run the "php -q chatbot.demo.php" program to listen for socket connections. * Open Google Chrome and point to the client.html page * Done, your browser now has a full-duplex channel with the server. * Start sending commands to the server to get some responses. @@ -58,8 +41,9 @@ WebSockets for the masses! Author ------ +Ben Morse George Nava -[http://georgenava.appspot.com](http://georgenava.appspot.com) -[http://mylittlehacks.appspot.com](http://mylittlehacks.appspot.com) -[http://twitter.com/georgenava](http://twitter.com/georgenava) + +[http://portforwardpodcast.com/](http://portforwardpodcast.com/) +[http://twitter.com/PortFwdPodcast](http://twitter.com/PortFwdPodcast) diff --git a/websocket.class.php b/websocket.class.php index 09133b7..6e98260 100644 --- a/websocket.class.php +++ b/websocket.class.php @@ -6,7 +6,7 @@ class WebSocket{ var $master; var $sockets = array(); var $users = array(); - var $debug = false; + var $debug = true; function __construct($address,$port){ error_reporting(E_ALL); @@ -21,6 +21,10 @@ function __construct($address,$port){ $this->say("Server Started : ".date('Y-m-d H:i:s')); $this->say("Listening on : ".$address." port ".$port); $this->say("Master socket : ".$this->master."\n"); + if( $this->debug ) + { + $this->say("Debugging on\n"); + } while(true){ $changed = $this->sockets; @@ -50,12 +54,74 @@ function process($user,$msg){ $this->send($user->socket,$msg); } - function send($client,$msg){ - $this->say("> ".$msg); - $msg = $this->wrap($msg); - socket_write($client,$msg,strlen($msg)); - $this->say("! ".strlen($msg)); - } + + + +// unit test for message lengths of 124, 65535, 2^64 +// FIXME hook this up + function test_send($client) + { + $base = "Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + for( $i =0; $i < 10; $i++ ) + { + $this->send($client,$base); + $this->send($client,"".strlen($base)); + $base = $base."a"; + } + } + +// FIXME throw error if message length is longer than 0x7FFFFFFFFFFFFFFF chracters + function send($client,$data){ + $this->say("> ".$data); + + + $header = " "; + $header[0] = chr(0x81); + $header_length = 1; + + //Payload length: 7 bits, 7+16 bits, or 7+64 bits + $dataLength = strlen($data); + + //The length of the payload data, in bytes: if 0-125, that is the payload length. + if($dataLength <= 125) + { + $header[1] = chr($dataLength); + $header_length = 2; + } + elseif ($dataLength <= 65535) + { + // If 126, the following 2 bytes interpreted as a 16 + // bit unsigned integer are the payload length. + + $header[1] = chr(126); + $header[2] = chr($dataLength >> 8); + $header[3] = chr($dataLength & 0xFF); + $header_length = 4; + } + else + { + // If 127, the following 8 bytes interpreted as a 64-bit unsigned integer (the + // most significant bit MUST be 0) are the payload length. + $header[1] = chr(127); + $header[2] = chr(($dataLength & 0xFF00000000000000) >> 56); + $header[3] = chr(($dataLength & 0xFF000000000000) >> 48); + $header[4] = chr(($dataLength & 0xFF0000000000) >> 40); + $header[5] = chr(($dataLength & 0xFF00000000) >> 32); + $header[6] = chr(($dataLength & 0xFF000000) >> 24); + $header[7] = chr(($dataLength & 0xFF0000) >> 16); + $header[8] = chr(($dataLength & 0xFF00 ) >> 8); + $header[9] = chr( $dataLength & 0xFF ); + $header_length = 10; + } + + $result = socket_write($client, $header . $data, strlen($data) + $header_length); + //$result = socket_write($client, chr(0x81) . chr(strlen($data)) . $data, strlen($data) + 2); + if ( !$result ) { + $this->disconnect($client); + $client = false; + } + $this->say("len(".strlen($data).")"); + } function connect($socket){ $user = new User(); @@ -79,11 +145,11 @@ function disconnect($socket){ $this->log($socket." DISCONNECTED!"); if($index>=0){ array_splice($this->sockets,$index,1); } } - + function dohandshake($user,$buffer){ $this->log("\nRequesting handshake..."); $this->log($buffer); - list($resource,$host,$origin,$key1,$key2,$l8b) = $this->getheaders($buffer); + list($resource,$host,$origin,$key1,$key2,$l8b,$key0) = $this->getheaders($buffer); $this->log("Handshaking..."); //$port = explode(":",$host); //$port = $port[1]; @@ -91,40 +157,20 @@ function dohandshake($user,$buffer){ $upgrade = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n" . "Upgrade: WebSocket\r\n" . "Connection: Upgrade\r\n" . - //"WebSocket-Origin: " . $origin . "\r\n" . - //"WebSocket-Location: ws://" . $host . $resource . "\r\n" . "Sec-WebSocket-Origin: " . $origin . "\r\n" . - "Sec-WebSocket-Location: ws://" . $host . $resource . "\r\n" . - //"Sec-WebSocket-Protocol: icbmgame\r\n" . //Client doesn't send this - "\r\n" . - $this->calcKey($key1,$key2,$l8b) . "\r\n";// . - //"\r\n"; - socket_write($user->socket,$upgrade.chr(0),strlen($upgrade.chr(0))); + "Sec-WebSocket-Accept: " . $this->calcKeyHybi10($key0) . "\r\n" . "\r\n" ; + + socket_write($user->socket,$upgrade,strlen($upgrade)); $user->handshake=true; $this->log($upgrade); $this->log("Done handshaking..."); return true; } - - function calcKey($key1,$key2,$l8b){ - //Get the numbers - preg_match_all('/([\d]+)/', $key1, $key1_num); - preg_match_all('/([\d]+)/', $key2, $key2_num); - //Number crunching [/bad pun] - $this->log("Key1: " . $key1_num = implode($key1_num[0]) ); - $this->log("Key2: " . $key2_num = implode($key2_num[0]) ); - //Count spaces - preg_match_all('/([ ]+)/', $key1, $key1_spc); - preg_match_all('/([ ]+)/', $key2, $key2_spc); - //How many spaces did it find? - $this->log("Key1 Spaces: " . $key1_spc = strlen(implode($key1_spc[0])) ); - $this->log("Key2 Spaces: " . $key2_spc = strlen(implode($key2_spc[0])) ); - if($key1_spc==0|$key2_spc==0){ $this->log("Invalid key");return; } - //Some math - $key1_sec = pack("N",$key1_num / $key1_spc); //Get the 32bit secret key, minus the other thing - $key2_sec = pack("N",$key2_num / $key2_spc); - //This needs checking, I'm not completely sure it should be a binary string - return md5($key1_sec.$key2_sec.$l8b,1); //The result, I think + + function calcKeyHybi10($key){ + $CRAZY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + $sha = sha1($key.$CRAZY,true); + return base64_encode($sha); } function getheaders($req){ @@ -134,8 +180,9 @@ function getheaders($req){ if(preg_match("/Origin: (.*)\r\n/" ,$req,$match)){ $o=$match[1]; } if(preg_match("/Sec-WebSocket-Key1: (.*)\r\n/",$req,$match)){ $this->log("Sec Key1: ".$sk1=$match[1]); } if(preg_match("/Sec-WebSocket-Key2: (.*)\r\n/",$req,$match)){ $this->log("Sec Key2: ".$sk2=$match[1]); } + if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/" ,$req,$match)){ $this->log("new Sec Key2: ".$sk0=$match[1]); } if($match=substr($req,-8)) { $this->log("Last 8 bytes: ".$l8b=$match); } - return array($r,$h,$o,$sk1,$sk2,$l8b); + return array($r,$h,$o,$sk1,$sk2,$l8b,$sk0); } function getuserbysocket($socket){ @@ -149,9 +196,62 @@ function getuserbysocket($socket){ function say($msg=""){ echo $msg."\n"; } function log($msg=""){ if($this->debug){ echo $msg."\n"; } } function wrap($msg=""){ return chr(0).$msg.chr(255); } - function unwrap($msg=""){ return substr($msg,1,strlen($msg)-2); } -} + // copied from http://lemmingzshadow.net/386/php-websocket-serverclient-nach-draft-hybi-10/ + function unwrap($data="") + { + $bytes = $data; + $dataLength = ''; + $mask = ''; + $coded_data = ''; + $decodedData = ''; + $secondByte = sprintf('%08b', ord($bytes[1])); + $masked = ($secondByte[0] == '1') ? true : false; + $dataLength = ($masked === true) ? ord($bytes[1]) & 127 : ord($bytes[1]); + if($masked === true) + { + if($dataLength === 126) + { + $mask = substr($bytes, 4, 4); + $coded_data = substr($bytes, 8); + } + elseif($dataLength === 127) + { + $mask = substr($bytes, 10, 4); + $coded_data = substr($bytes, 14); + } + else + { + $mask = substr($bytes, 2, 4); + $coded_data = substr($bytes, 6); + } + for($i = 0; $i < strlen($coded_data); $i++) + { + $decodedData .= $coded_data[$i] ^ $mask[$i % 4]; + } + } + else + { + if($dataLength === 126) + { + $decodedData = substr($bytes, 4); + } + elseif($dataLength === 127) + { + $decodedData = substr($bytes, 10); + } + else + { + $decodedData = substr($bytes, 2); + } + } + + return $decodedData; + } + + + +} //class WebSocket class User{ var $id; From d7dd77673d493d1265c7049d3f906f782d1e6e0d Mon Sep 17 00:00:00 2001 From: Ben Morse Date: Mon, 17 Oct 2011 15:40:34 -0700 Subject: [PATCH 4/4] * adding double spaces after README.md for actual line breaks --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d176a4e..f5d1928 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ PHPWEBSOCKET ============ -So here is a quick hack to implement hybi-10 websockets in php! -Check out the [hybi-10 standard here](http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10#section-4.2) -Check [This Wikipedia Page](http://en.wikipedia.org/wiki/WebSocket#Browser_support) for suppored list of browsers. +So here is a quick hack to implement hybi-10 websockets in php! +Check out the [hybi-10 standard here](http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10#section-4.2) +Check [This Wikipedia Page](http://en.wikipedia.org/wiki/WebSocket#Browser_support) for suppored list of browsers. Client side @@ -39,11 +39,11 @@ Steps to run the test: WebSockets for the masses! ========================== -Author +Author ------ -Ben Morse -George Nava +Ben Morse +George Nava -[http://portforwardpodcast.com/](http://portforwardpodcast.com/) +[http://portforwardpodcast.com/](http://portforwardpodcast.com/) [http://twitter.com/PortFwdPodcast](http://twitter.com/PortFwdPodcast)