From a18af41a746a880f061c6030051c32da278a7520 Mon Sep 17 00:00:00 2001 From: Mike Almond Date: Tue, 31 Jan 2012 22:13:20 -0500 Subject: [PATCH 1/7] [REFACTOR] Changing the handshake to return a guzzle object Changing Hixie and RFC to return the same guzzle object instead of an array and a string --- lib/Ratchet/Application/WebSocket/App.php | 30 +--------- .../Application/WebSocket/Version/Hixie76.php | 55 +++++++++---------- .../Application/WebSocket/Version/RFC6455.php | 14 +++-- 3 files changed, 40 insertions(+), 59 deletions(-) diff --git a/lib/Ratchet/Application/WebSocket/App.php b/lib/Ratchet/Application/WebSocket/App.php index 8753c93..386f178 100644 --- a/lib/Ratchet/Application/WebSocket/App.php +++ b/lib/Ratchet/Application/WebSocket/App.php @@ -95,33 +95,9 @@ class App implements ApplicationInterface, ConfiguratorInterface { $response = $from->WebSocket->version->handshake($from->WebSocket->headers); $from->WebSocket->handshake = true; - - if (is_array($response)) { - // This block is to be moved/changed later - $agreed_protocols = array(); - $requested_protocols = $from->WebSocket->headers->getTokenizedHeader('Sec-WebSocket-Protocol', ','); - foreach ($this->accepted_subprotocols as $sub_protocol) { - if (false !== $requested_protocols->hasValue($sub_protocol)) { - $agreed_protocols[] = $sub_protocol; - } - } - if (count($agreed_protocols) > 0) { - $response['Sec-WebSocket-Protocol'] = implode(',', $agreed_protocols); - } - - $header = ''; - foreach ($response as $key => $val) { - if (!empty($key)) { - $header .= "{$key}: "; - } - - $header .= "{$val}\r\n"; - } - $header .= "\r\n"; - } else { - $header = $response; - } - + + $header = (string)$response; + $comp = $this->_factory->newComposite(); $comp->enqueue($this->_factory->newCommand('SendMessage', $from)->setMessage($header)); $comp->enqueue($this->prepareCommand($this->_app->onOpen($from, $msg))); // Need to send headers/handshake to application, let it have the cookies, etc diff --git a/lib/Ratchet/Application/WebSocket/Version/Hixie76.php b/lib/Ratchet/Application/WebSocket/Version/Hixie76.php index bea5578..ce5ad73 100644 --- a/lib/Ratchet/Application/WebSocket/Version/Hixie76.php +++ b/lib/Ratchet/Application/WebSocket/Version/Hixie76.php @@ -1,6 +1,7 @@ getRawHeaders() . "\r\n\r\n" . $request->getBody(); - $resource = $host = $origin = $key1 = $key2 = $protocol = $code = $handshake = null; - - preg_match('#GET (.*?) HTTP#', $buffer, $match) && $resource = $match[1]; - preg_match("#Host: (.*?)\r\n#", $buffer, $match) && $host = $match[1]; - preg_match("#Sec-WebSocket-Key1: (.*?)\r\n#", $buffer, $match) && $key1 = $match[1]; - preg_match("#Sec-WebSocket-Key2: (.*?)\r\n#", $buffer, $match) && $key2 = $match[1]; - preg_match("#Sec-WebSocket-Protocol: (.*?)\r\n#", $buffer, $match) && $protocol = $match[1]; - preg_match("#Origin: (.*?)\r\n#", $buffer, $match) && $origin = $match[1]; - preg_match("#\r\n(.*?)\$#", $buffer, $match) && $code = $match[1]; - - 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-Location: ws://{$host}{$resource}\r\n" - . ($protocol ? "Sec-WebSocket-Protocol: {$protocol}\r\n" : "") - . "\r\n" - . $this->_createHandshakeThingy($key1, $key2, $code) - ; + $body = $this->sign($request->getHeader('Sec-WebSocket-Key1'), $request->getHeader('Sec-WebSocket-Key2'), $request->getBody()); + + $headers = array( + 'Upgrade' => 'WebSocket' + , 'Connection' => 'Upgrade' + , 'Sec-WebSocket-Origin' => $request->getHeader('Origin') + , 'Sec-WebSocket-Location' => 'ws://' . $request->getHeader('Host') . $request->getPath() + ); + if ($request->getHeader('Sec-WebSocket-Protocol')) { + $headers['Sec-WebSocket-Protocol'] = $request->getHeader('Sec-WebSocket-Protocol'); + } + + $response = new \Guzzle\Http\Message\Response(101, $headers, $body); + return $response; + + } public function newMessage() { @@ -59,17 +56,19 @@ class Hixie76 implements VersionInterface { return chr(0) . $message . chr(255); } - protected function _doStuffToObtainAnInt32($key) { - return preg_match_all('#[0-9]#', $key, $number) && preg_match_all('# #', $key, $space) ? - implode('', $number[0]) / count($space[0]) : - '' - ; + protected function generateKeyNumber($key) { + + $int = preg_replace('[\D]', '', $key) / substr_count($key, ' '); + return (is_int($int) && substr_count($key, ' ') > 0) ? $int : ''; + + } - protected function _createHandshakeThingy($key1, $key2, $code) { + protected function sign($key1, $key2, $code) { + return md5( - pack('N', $this->_doStuffToObtainAnInt32($key1)) - . pack('N', $this->_doStuffToObtainAnInt32($key2)) + pack('N', $this->generateKeyNumber($key1)) + . pack('N', $this->generateKeyNumber($key2)) . $code , true); } diff --git a/lib/Ratchet/Application/WebSocket/Version/RFC6455.php b/lib/Ratchet/Application/WebSocket/Version/RFC6455.php index bb579cb..577506e 100644 --- a/lib/Ratchet/Application/WebSocket/Version/RFC6455.php +++ b/lib/Ratchet/Application/WebSocket/Version/RFC6455.php @@ -2,6 +2,8 @@ namespace Ratchet\Application\WebSocket\Version; use Ratchet\Application\WebSocket\Version\RFC6455\HandshakeVerifier; use Guzzle\Http\Message\RequestInterface; +use Guzzle\Http\Message\Response; + /** * @link http://tools.ietf.org/html/rfc6455 @@ -36,13 +38,17 @@ class RFC6455 implements VersionInterface { throw new \InvalidArgumentException('Invalid HTTP header'); } - return array( - '' => 'HTTP/1.1 101 Switching Protocols' - , 'Upgrade' => 'websocket' + $headers = array( + 'Upgrade' => 'websocket' , 'Connection' => 'Upgrade' , 'Sec-WebSocket-Accept' => $this->sign($request->getHeader('Sec-WebSocket-Key')) -// , 'Sec-WebSocket-Protocol' => '' ); + + + $response = new \Guzzle\Http\Message\Response(101, $headers); + return $response; + + } /** From 2996c08728fa54a798f53e2dad84682a9c6c407c Mon Sep 17 00:00:00 2001 From: Mike Almond Date: Wed, 1 Feb 2012 13:16:59 -0500 Subject: [PATCH 2/7] [REFACTOR] Fixing some code based on unit tests Change the response to send the proper header and fix the generate key method to fail properly when no spaces are present --- lib/Ratchet/Application/WebSocket/Version/Hixie76.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/Ratchet/Application/WebSocket/Version/Hixie76.php b/lib/Ratchet/Application/WebSocket/Version/Hixie76.php index ce5ad73..ad70daa 100644 --- a/lib/Ratchet/Application/WebSocket/Version/Hixie76.php +++ b/lib/Ratchet/Application/WebSocket/Version/Hixie76.php @@ -38,10 +38,8 @@ class Hixie76 implements VersionInterface { $headers['Sec-WebSocket-Protocol'] = $request->getHeader('Sec-WebSocket-Protocol'); } - $response = new \Guzzle\Http\Message\Response(101, $headers, $body); + $response = new \Guzzle\Http\Message\Response('HTTP/1.1 101 WebSocket Protocol Handshake', $headers, $body); return $response; - - } public function newMessage() { @@ -56,10 +54,13 @@ class Hixie76 implements VersionInterface { return chr(0) . $message . chr(255); } - protected function generateKeyNumber($key) { + public function generateKeyNumber($key) { + if (substr_count($key, ' ') == 0) { + return ''; + } $int = preg_replace('[\D]', '', $key) / substr_count($key, ' '); - return (is_int($int) && substr_count($key, ' ') > 0) ? $int : ''; + return (is_int($int)) ? $int : ''; } From 10da7e49206ab2a6a3a68027afbad6e5a019e0f8 Mon Sep 17 00:00:00 2001 From: Mike Almond Date: Wed, 1 Feb 2012 13:17:51 -0500 Subject: [PATCH 3/7] [TESTS] Fixing unit tests for versions Adding a unit test for Hixie and fixing the unit test for RFC --- .../WebSocket/Version/Hixie76Test.php | 19 +++++++++++++++++++ .../WebSocket/Version/RFC6455Test.php | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/Ratchet/Tests/Application/WebSocket/Version/Hixie76Test.php b/tests/Ratchet/Tests/Application/WebSocket/Version/Hixie76Test.php index 58a6245..bf5aa4d 100644 --- a/tests/Ratchet/Tests/Application/WebSocket/Version/Hixie76Test.php +++ b/tests/Ratchet/Tests/Application/WebSocket/Version/Hixie76Test.php @@ -30,4 +30,23 @@ class Hixie76Test extends \PHPUnit_Framework_TestCase { , array('', '') ); } + + /** + * @dataProvider KeyProvider + */ + public function testKeySigningForHandshake($accept, $key) { + $this->assertEquals($accept, $this->_version->generateKeyNumber($key)); + } + + + public static function KeyProvider() { + return array( + array('179922739', '17 9 G`ZD9 2 2b 7X 3 /r90') + , array('', '17 9 G`ZD9 2 2b 7X 3 /r91') + , array('906585445', '3e6b263 4 17 80') + , array('', '3e6b263 4 17 80') + , array('', '3e6b63 4 17 80') + , array('', '3e6b6341780') + ); + } } \ No newline at end of file diff --git a/tests/Ratchet/Tests/Application/WebSocket/Version/RFC6455Test.php b/tests/Ratchet/Tests/Application/WebSocket/Version/RFC6455Test.php index f32f59f..4e696ab 100644 --- a/tests/Ratchet/Tests/Application/WebSocket/Version/RFC6455Test.php +++ b/tests/Ratchet/Tests/Application/WebSocket/Version/RFC6455Test.php @@ -119,7 +119,7 @@ class RFC6455Test extends \PHPUnit_Framework_TestCase { $request = RequestFactory::fromMessage($header); if ($pass) { - $this->assertTrue(is_array($this->_version->handshake($request))); + $this->assertInstanceOf('\\Guzzle\\Http\\Message\\Response', $this->_version->handshake($request)); } else { $this->setExpectedException('InvalidArgumentException'); $this->_version->handshake($request); From 576eee08161e16fd75b306020f096fc897c75a48 Mon Sep 17 00:00:00 2001 From: Mike Almond Date: Wed, 1 Feb 2012 21:21:11 -0500 Subject: [PATCH 4/7] [HIXIE] Reverting back to the proper response code. Silly hixie, tricks are for kids --- lib/Ratchet/Application/WebSocket/Version/Hixie76.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Ratchet/Application/WebSocket/Version/Hixie76.php b/lib/Ratchet/Application/WebSocket/Version/Hixie76.php index ad70daa..eac76ff 100644 --- a/lib/Ratchet/Application/WebSocket/Version/Hixie76.php +++ b/lib/Ratchet/Application/WebSocket/Version/Hixie76.php @@ -38,7 +38,7 @@ class Hixie76 implements VersionInterface { $headers['Sec-WebSocket-Protocol'] = $request->getHeader('Sec-WebSocket-Protocol'); } - $response = new \Guzzle\Http\Message\Response('HTTP/1.1 101 WebSocket Protocol Handshake', $headers, $body); + $response = new \Guzzle\Http\Message\Response('101', $headers, $body); return $response; } From c6a801f1efd87c04d158f6acdaf2da21e544310a Mon Sep 17 00:00:00 2001 From: Mike Almond Date: Tue, 7 Feb 2012 17:14:52 -0500 Subject: [PATCH 5/7] [Refactor] Fixing stuff for Chris Fixing sub protocol agreement between server and client --- .../Component/WebSocket/Version/Hixie76.php | 19 +++++++------------ .../WebSocket/WebSocketComponent.php | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/Ratchet/Component/WebSocket/Version/Hixie76.php b/src/Ratchet/Component/WebSocket/Version/Hixie76.php index 6ad1eb5..c4f081e 100644 --- a/src/Ratchet/Component/WebSocket/Version/Hixie76.php +++ b/src/Ratchet/Component/WebSocket/Version/Hixie76.php @@ -23,23 +23,18 @@ class Hixie76 implements VersionInterface { /** * @param string * @return string - * @todo Unhack this mess...or wait for Hixie to die (HURRY UP APPLE) */ public function handshake(RequestInterface $request) { $body = $this->sign($request->getHeader('Sec-WebSocket-Key1'), $request->getHeader('Sec-WebSocket-Key2'), $request->getBody()); - + $headers = array( 'Upgrade' => 'WebSocket' , 'Connection' => 'Upgrade' , 'Sec-WebSocket-Origin' => $request->getHeader('Origin') , 'Sec-WebSocket-Location' => 'ws://' . $request->getHeader('Host') . $request->getPath() ); - if ($request->getHeader('Sec-WebSocket-Protocol')) { - $headers['Sec-WebSocket-Protocol'] = $request->getHeader('Sec-WebSocket-Protocol'); - } - - $response = new \Guzzle\Http\Message\Response('101', $headers, $body); - return $response; + + return new \Guzzle\Http\Message\Response('101', $headers, $body); } public function newMessage() { @@ -55,18 +50,18 @@ class Hixie76 implements VersionInterface { } public function generateKeyNumber($key) { - + if (substr_count($key, ' ') == 0) { return ''; } $int = preg_replace('[\D]', '', $key) / substr_count($key, ' '); return (is_int($int)) ? $int : ''; - - + + } protected function sign($key1, $key2, $code) { - + return md5( pack('N', $this->generateKeyNumber($key1)) . pack('N', $this->generateKeyNumber($key2)) diff --git a/src/Ratchet/Component/WebSocket/WebSocketComponent.php b/src/Ratchet/Component/WebSocket/WebSocketComponent.php index 4286800..192894c 100644 --- a/src/Ratchet/Component/WebSocket/WebSocketComponent.php +++ b/src/Ratchet/Component/WebSocket/WebSocketComponent.php @@ -79,6 +79,25 @@ class WebSocketComponent implements MessageComponentInterface { $response = $from->WebSocket->version->handshake($from->WebSocket->headers); $from->WebSocket->handshake = true; + + + // This block is to be moved/changed later + + $agreed_protocols = array(); + + $requested_protocols = $from->WebSocket->headers->getTokenizedHeader('Sec-WebSocket-Protocol', ','); + + foreach ($this->accepted_subprotocols as $sub_protocol) { + if (false !== $requested_protocols->hasValue($sub_protocol)) { + $agreed_protocols[] = $sub_protocol; + } + } + + if (count($agreed_protocols) > 0) { + + // $response['Sec-WebSocket-Protocol'] = implode(',', $agreed_protocols); + $response->setHeader('Sec-WebSocket-Protocol', implode(',', $agreed_protocols)); + } $header = (string)$response; $comp = $this->_factory->newComposite(); From f6fef1a390be31136248b43c6c8cd589f3f824c2 Mon Sep 17 00:00:00 2001 From: Mike Almond Date: Wed, 8 Feb 2012 13:19:42 -0500 Subject: [PATCH 6/7] [WebSocket] Refactor Fixing bugs in hixieeeeeeee --- .../Component/WebSocket/Version/Hixie76.php | 5 ++++- .../Component/WebSocket/Version/RFC6455.php | 22 ++++++++----------- .../WebSocket/WebSocketComponent.php | 22 +++++++------------ 3 files changed, 21 insertions(+), 28 deletions(-) diff --git a/src/Ratchet/Component/WebSocket/Version/Hixie76.php b/src/Ratchet/Component/WebSocket/Version/Hixie76.php index c4f081e..e2034d8 100644 --- a/src/Ratchet/Component/WebSocket/Version/Hixie76.php +++ b/src/Ratchet/Component/WebSocket/Version/Hixie76.php @@ -34,7 +34,10 @@ class Hixie76 implements VersionInterface { , 'Sec-WebSocket-Location' => 'ws://' . $request->getHeader('Host') . $request->getPath() ); - return new \Guzzle\Http\Message\Response('101', $headers, $body); + $response = new \Guzzle\Http\Message\Response('101', $headers, $body); + $response->setStatus('101', 'WebSocket Protocol Handshake'); + + return $response; } public function newMessage() { diff --git a/src/Ratchet/Component/WebSocket/Version/RFC6455.php b/src/Ratchet/Component/WebSocket/Version/RFC6455.php index 6734198..9d3c82d 100644 --- a/src/Ratchet/Component/WebSocket/Version/RFC6455.php +++ b/src/Ratchet/Component/WebSocket/Version/RFC6455.php @@ -40,13 +40,9 @@ class RFC6455 implements VersionInterface { , 'Connection' => 'Upgrade' , 'Sec-WebSocket-Accept' => $this->sign($request->getHeader('Sec-WebSocket-Key')) ); - - - $response = new \Guzzle\Http\Message\Response(101, $headers); - return $response; - - - } + + return new \Guzzle\Http\Message\Response('101', $headers); + } /** * @return RFC6455\Message @@ -81,8 +77,8 @@ class RFC6455 implements VersionInterface { switch($type) { case 'text': // first byte indicates FIN, Text-Frame (10000001): - $frameHead[0] = 129; - break; + $frameHead[0] = 129; + break; case 'close': // first byte indicates FIN, Close Frame(10001000): @@ -100,7 +96,7 @@ class RFC6455 implements VersionInterface { break; } - // set mask and payload length (using 1, 3 or 9 bytes) + // set mask and payload length (using 1, 3 or 9 bytes) if($payloadLength > 65535) { $payloadLengthBin = str_split(sprintf('%064b', $payloadLength), 8); $frameHead[1] = ($masked === true) ? 255 : 127; @@ -131,13 +127,13 @@ class RFC6455 implements VersionInterface { $mask[$i] = chr(rand(0, 255)); } - $frameHead = array_merge($frameHead, $mask); - } + $frameHead = array_merge($frameHead, $mask); + } $frame = implode('', $frameHead); // append payload to frame: $framePayload = array(); - for($i = 0; $i < $payloadLength; $i++) { + for($i = 0; $i < $payloadLength; $i++) { $frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i]; } diff --git a/src/Ratchet/Component/WebSocket/WebSocketComponent.php b/src/Ratchet/Component/WebSocket/WebSocketComponent.php index 192894c..b4775a0 100644 --- a/src/Ratchet/Component/WebSocket/WebSocketComponent.php +++ b/src/Ratchet/Component/WebSocket/WebSocketComponent.php @@ -78,28 +78,22 @@ class WebSocketComponent implements MessageComponentInterface { $response = $from->WebSocket->version->handshake($from->WebSocket->headers); $from->WebSocket->handshake = true; - - - - // This block is to be moved/changed later - + + // This block is to be moved/changed later $agreed_protocols = array(); - $requested_protocols = $from->WebSocket->headers->getTokenizedHeader('Sec-WebSocket-Protocol', ','); - - foreach ($this->accepted_subprotocols as $sub_protocol) { - if (false !== $requested_protocols->hasValue($sub_protocol)) { + + foreach ($this->accepted_subprotocols as $sub_protocol) { + if (null !== $requested_protocols && false !== $requested_protocols->hasValue($sub_protocol)) { $agreed_protocols[] = $sub_protocol; } } - + if (count($agreed_protocols) > 0) { - - // $response['Sec-WebSocket-Protocol'] = implode(',', $agreed_protocols); $response->setHeader('Sec-WebSocket-Protocol', implode(',', $agreed_protocols)); } $header = (string)$response; - + $comp = $this->_factory->newComposite(); $comp->enqueue($this->_factory->newCommand('SendMessage', $from)->setMessage($header)); $comp->enqueue($this->prepareCommand($this->_decorating->onOpen($from, $msg))); // Need to send headers/handshake to application, let it have the cookies, etc @@ -176,7 +170,7 @@ class WebSocketComponent implements MessageComponentInterface { $hash = md5($command->getMessage()) . '-' . spl_object_hash($version); if (!isset($cache[$hash])) { - $cache[$hash] = $version->frame($command->getMessage(), $this->_mask_payload); + $cache[$hash] = $version->frame($command->getMessage(), $this->_mask_payload); } return $command->setMessage($cache[$hash]); From f4c1cb110f83cbe9e3611c29a8b7c5944b5d7fdb Mon Sep 17 00:00:00 2001 From: Mike Almond Date: Wed, 8 Feb 2012 16:41:32 -0500 Subject: [PATCH 7/7] [WebSocket] Fixing name spacing and a bug Fixing the namespacing for the Guzzle response and the bug was the if statement should have been outside the loop. --- src/Ratchet/Component/WebSocket/Version/Hixie76.php | 2 +- src/Ratchet/Component/WebSocket/Version/RFC6455.php | 2 +- src/Ratchet/Component/WebSocket/WebSocketComponent.php | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Ratchet/Component/WebSocket/Version/Hixie76.php b/src/Ratchet/Component/WebSocket/Version/Hixie76.php index e2034d8..5f2bf5b 100644 --- a/src/Ratchet/Component/WebSocket/Version/Hixie76.php +++ b/src/Ratchet/Component/WebSocket/Version/Hixie76.php @@ -34,7 +34,7 @@ class Hixie76 implements VersionInterface { , 'Sec-WebSocket-Location' => 'ws://' . $request->getHeader('Host') . $request->getPath() ); - $response = new \Guzzle\Http\Message\Response('101', $headers, $body); + $response = new Response('101', $headers, $body); $response->setStatus('101', 'WebSocket Protocol Handshake'); return $response; diff --git a/src/Ratchet/Component/WebSocket/Version/RFC6455.php b/src/Ratchet/Component/WebSocket/Version/RFC6455.php index 9d3c82d..1c31cd5 100644 --- a/src/Ratchet/Component/WebSocket/Version/RFC6455.php +++ b/src/Ratchet/Component/WebSocket/Version/RFC6455.php @@ -41,7 +41,7 @@ class RFC6455 implements VersionInterface { , 'Sec-WebSocket-Accept' => $this->sign($request->getHeader('Sec-WebSocket-Key')) ); - return new \Guzzle\Http\Message\Response('101', $headers); + return new Response('101', $headers); } /** diff --git a/src/Ratchet/Component/WebSocket/WebSocketComponent.php b/src/Ratchet/Component/WebSocket/WebSocketComponent.php index b4775a0..454128d 100644 --- a/src/Ratchet/Component/WebSocket/WebSocketComponent.php +++ b/src/Ratchet/Component/WebSocket/WebSocketComponent.php @@ -82,13 +82,13 @@ class WebSocketComponent implements MessageComponentInterface { // This block is to be moved/changed later $agreed_protocols = array(); $requested_protocols = $from->WebSocket->headers->getTokenizedHeader('Sec-WebSocket-Protocol', ','); - - foreach ($this->accepted_subprotocols as $sub_protocol) { - if (null !== $requested_protocols && false !== $requested_protocols->hasValue($sub_protocol)) { - $agreed_protocols[] = $sub_protocol; + if (null !== $requested_protocols) { + foreach ($this->accepted_subprotocols as $sub_protocol) { + if (false !== $requested_protocols->hasValue($sub_protocol)) { + $agreed_protocols[] = $sub_protocol; + } } } - if (count($agreed_protocols) > 0) { $response->setHeader('Sec-WebSocket-Protocol', implode(',', $agreed_protocols)); }