From c0672acb7f37975b7e64716092e529d8e251214b Mon Sep 17 00:00:00 2001 From: bramvdbogaerde Date: Sat, 21 Jul 2012 17:23:09 +0200 Subject: [PATCH 1/2] Added support for the newest browser,changed handchaking by adding Sec-WebSocketAccept in the header response --- helpers.class.php | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/helpers.class.php b/helpers.class.php index e2e28d5..734b0ef 100644 --- a/helpers.class.php +++ b/helpers.class.php @@ -11,7 +11,7 @@ class WSHelpers{ * response header */ static function getResponseHeaders($buffer = '', $uniqueOrigin = FALSE){ - list($resource, $host, $origin, $strkey1, $strkey2, $data) = self::getRequestHeaders($buffer); + list($resource, $host, $origin, $strkey1, $strkey2, $data,$key) = self::getRequestHeaders($buffer); if(!self::validOrigin($origin, $uniqueOrigin)){ self::console('Refusing connection from origin %s. Allowed origin(s): %s', array($origin, implode(', ', $uniqueOrigin))); return FALSE; @@ -29,23 +29,22 @@ static function getResponseHeaders($buffer = '', $uniqueOrigin = FALSE){ $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) { - WSHelpers::console('Handshake 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); - + if ($spaces1 == 0 || $spaces2 == 0 || $numkey1 % $spaces1 != 0 || $numkey2 % $spaces2 != 0) { + $hash_data = self::calcKey($key); + }else{ + $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); + } return "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-Accept: ".$hash_data. "\r\n". "Sec-WebSocket-Location: ws://" . $host . $resource . "\r\n" . - "\r\n" . $hash_data; + "\r\n"; } static function validOrigin($origin, $uniqueOrigin){ @@ -65,17 +64,17 @@ static function validOrigin($origin, $uniqueOrigin){ * resource, host, origin, key1, key2, data */ static function getRequestHeaders($req){ - $r = $h = $o = $key1 = $key2 = $data = null; + $r = $h = $o = $key1 = $key2 = $data = $key = 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); + if(preg_match("/Sec-WebSocket-Key: (.*)\r\n/",$req,$match)){$key = $match[1]; } + + return array($r, $h, $o, $key1, $key2, $data,$key); } - - /** * Verify if a class exists and if it extends a base class * @@ -110,13 +109,13 @@ static function validateClass($className, $parentName){ * @return * The message wrapped up for sending */ - static function wrap($msg = ''){ + static function wrap($msg= ''){ if(is_object($msg) || is_array($msg)){ $msg = json_encode($msg); } return chr(0) . $msg . chr(255); } - + /** * Remove wrapper characters from received message * @@ -142,4 +141,9 @@ static function unwrap($msg = ''){ static function console($msg = '', $vars = array()){ vprintf($msg . "\n", $vars); } -} \ No newline at end of file + function calcKey($key){ + $CRAZY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + $sha = sha1($key.$CRAZY,true); + return base64_encode($sha); + } +} From 5c01be4004c7744cbd32e7919a4ab07d4046ee40 Mon Sep 17 00:00:00 2001 From: bramvdbogaerde Date: Sat, 21 Jul 2012 21:15:56 +0200 Subject: [PATCH 2/2] Server can now recieve messages with HyBi-10 decode --- helpers.class.php | 177 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 174 insertions(+), 3 deletions(-) diff --git a/helpers.class.php b/helpers.class.php index 734b0ef..a0a2758 100644 --- a/helpers.class.php +++ b/helpers.class.php @@ -10,6 +10,8 @@ class WSHelpers{ * @return * response header */ + private $initFrame; + private $masks; static function getResponseHeaders($buffer = '', $uniqueOrigin = FALSE){ list($resource, $host, $origin, $strkey1, $strkey2, $data,$key) = self::getRequestHeaders($buffer); if(!self::validOrigin($origin, $uniqueOrigin)){ @@ -113,7 +115,7 @@ static function wrap($msg= ''){ if(is_object($msg) || is_array($msg)){ $msg = json_encode($msg); } - return chr(0) . $msg . chr(255); + return self::encode($msg); } /** @@ -125,10 +127,15 @@ static function wrap($msg= ''){ * The unwrapped string */ static function unwrap($msg = ''){ - $msg = substr($msg, 1, strlen($msg) - 2); + $msg = self::decode($msg); + if(isset($msg['type']) AND $msg['type'] == "text"){ + $msg = $msg['payload']; + } + else{ if(json_decode($msg) !== null){ $msg = json_decode($msg); } + } return $msg; } @@ -146,4 +153,168 @@ function calcKey($key){ $sha = sha1($key.$CRAZY,true); return base64_encode($sha); } -} + private function encode($data) + { + $databuffer = array(); + $sendlength = strlen($data); + $rawBytesSend = $sendlength + 2; + $packet; + if ($sendlength > 65535) { + // 64bit + array_pad($databuffer, 10, 0); + $databuffer[1] = 127; + $lo = $sendlength | 0; + $hi = ($sendlength - $lo) / 4294967296; + + $databuffer[2] = ($hi >> 24) & 255; + $databuffer[3] = ($hi >> 16) & 255; + $databuffer[4] = ($hi >> 8) & 255; + $databuffer[5] = $hi & 255; + + $databuffer[6] = ($lo >> 24) & 255; + $databuffer[7] = ($lo >> 16) & 255; + $databuffer[8] = ($lo >> 8) & 255; + $databuffer[9] = $lo & 255; + + $rawBytesSend += 8; + } else if ($sendlength > 125) { + // 16 bit + array_pad($databuffer, 4, 0); + $databuffer[1] = 126; + $databuffer[2] = ($sendlength >> 8) & 255; + $databuffer[3] = $sendlength & 255; + + $rawBytesSend += 2; + } else { + array_pad($databuffer, 2, 0); + $databuffer[1] = $sendlength; + } + + // Set op and find + $databuffer[0] = (128 + ($binary ? 2 : 1)); + $packet = pack('c', $databuffer[0]); + // Clear masking bit + $databuffer[1] &= ~128; + // write out the packet header + for ($i = 1; $i < count($databuffer); $i++) { + //$packet .= $databuffer[$i]; + $packet .= pack('c', $databuffer[$i]); + } + + // write out the packet data + for ($i = 0; $i < $sendlength; $i++) { + $packet .= $data[$i]; + } + return $packet; + } + private function decode($data) + /// Decoding + + { + $payloadLength = ''; + $mask = ''; + $unmaskedPayload = ''; + $decodedData = array(); + + // estimate frame type: + $firstByteBinary = sprintf('%08b', ord($data[0])); + $secondByteBinary = sprintf('%08b', ord($data[1])); + $opcode = bindec(substr($firstByteBinary, 4, 4)); + $isMasked = ($secondByteBinary[0] == '1') ? true : false; + $payloadLength = ord($data[1]) & 127; + + // close connection if unmasked frame is received: + if($isMasked === false) + { + $this->close(1002); + } + + switch($opcode) + { + // text frame: + case 1: + $decodedData['type'] = 'text'; + break; + + case 2: + $decodedData['type'] = 'binary'; + break; + + // connection close frame: + case 8: + $decodedData['type'] = 'close'; + break; + + // ping frame: + case 9: + $decodedData['type'] = 'ping'; + break; + + // pong frame: + case 10: + $decodedData['type'] = 'pong'; + break; + + default: + // Close connection on unknown opcode: + $this->close(1003); + break; + } + + if($payloadLength === 126) + { + $mask = substr($data, 4, 4); + $payloadOffset = 8; + $dataLength = bindec(sprintf('%08b', ord($data[2])) . sprintf('%08b', ord($data[3]))) + $payloadOffset; + } + elseif($payloadLength === 127) + { + $mask = substr($data, 10, 4); + $payloadOffset = 14; + $tmp = ''; + for($i = 0; $i < 8; $i++) + { + $tmp .= sprintf('%08b', ord($data[$i+2])); + } + $dataLength = bindec($tmp) + $payloadOffset; + unset($tmp); + } + else + { + $mask = substr($data, 2, 4); + $payloadOffset = 6; + $dataLength = $payloadLength + $payloadOffset; + } + + /** + * We have to check for large frames here. socket_recv cuts at 1024 bytes + * so if websocket-frame is > 1024 bytes we have to wait until whole + * data is transferd. + */ + if(strlen($data) < $dataLength) + { + return false; + } + + if($isMasked === true) + { + for($i = $payloadOffset; $i < $dataLength; $i++) + { + $j = $i - $payloadOffset; + if(isset($data[$i])) + { + $unmaskedPayload .= $data[$i] ^ $mask[$j % 4]; + } + } + $decodedData['payload'] = $unmaskedPayload; + } + else + { + $payloadOffset = $payloadOffset - 4; + $decodedData['payload'] = substr($data, $payloadOffset); + } + + return $decodedData; + } + +} \ No newline at end of file