From 08fa8a948ffd75b02fd514c42a80ec910f092121 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Fri, 6 Jan 2012 16:18:12 -0500 Subject: [PATCH 1/7] Guzzle Implementing Guzzle to parse incoming handshake request Functional on RFC version --- lib/Ratchet/Application/WebSocket/App.php | 30 ++++++---- .../Application/WebSocket/Util/HTTP.php | 56 ------------------- .../Application/WebSocket/Version/Hixie76.php | 4 +- .../Application/WebSocket/Version/HyBi10.php | 6 +- .../Application/WebSocket/Version/RFC6455.php | 19 ++++--- .../Version/RFC6455/HandshakeVerifier.php | 19 ++++--- .../WebSocket/Version/VersionInterface.php | 3 +- tests/bootstrap.php | 4 +- 8 files changed, 51 insertions(+), 90 deletions(-) delete mode 100644 lib/Ratchet/Application/WebSocket/Util/HTTP.php diff --git a/lib/Ratchet/Application/WebSocket/App.php b/lib/Ratchet/Application/WebSocket/App.php index 362039c..75e7207 100644 --- a/lib/Ratchet/Application/WebSocket/App.php +++ b/lib/Ratchet/Application/WebSocket/App.php @@ -6,7 +6,8 @@ use Ratchet\Resource\Connection; use Ratchet\Resource\Command\Factory; use Ratchet\Resource\Command\CommandInterface; use Ratchet\Resource\Command\Action\SendMessage; -use Ratchet\Application\WebSocket\Util\HTTP; +use Guzzle\Http\Message\RequestInterface; +use Guzzle\Http\Message\RequestFactory; /** * The adapter to handle WebSocket requests/responses @@ -80,12 +81,14 @@ class App implements ApplicationInterface, ConfiguratorInterface { public function onMessage(Connection $from, $msg) { if (true !== $from->WebSocket->handshake) { if (!isset($from->WebSocket->version)) { - try { - $from->WebSocket->headers .= $msg; - $from->WebSocket->version = $this->getVersion($from->WebSocket->headers); - } catch (\UnderflowException $e) { + $from->WebSocket->headers .= $msg; + if (!$this->isMessageComplete($from->WebSocket->headers)) { return; } + + $headers = RequestFactory::fromMessage($from->WebSocket->headers); + $from->WebSocket->version = $this->getVersion($headers); + $from->WebSocket->headers = $headers; } $response = $from->WebSocket->version->handshake($from->WebSocket->headers); @@ -212,12 +215,8 @@ class App implements ApplicationInterface, ConfiguratorInterface { * @throws InvalidArgumentException If we can't understand protocol version request * @todo Verify the first line of the HTTP header as per page 16 of RFC 6455 */ - protected function getVersion($message) { - if (false === strstr($message, "\r\n\r\n")) { // This CAN fail with Hixie, depending on the TCP buffer in between - throw new \UnderflowException; - } - - $headers = HTTP::getHeaders($message); + protected function getVersion(RequestInterface $request) { + $headers = $request->getHeaders(); foreach ($this->_versions as $name => $instance) { if (null !== $instance) { @@ -236,6 +235,15 @@ class App implements ApplicationInterface, ConfiguratorInterface { throw new \InvalidArgumentException('Could not identify WebSocket protocol'); } + /** + * @param string + * @return bool + * @todo Method is flawed, this CAN result in an error with the Hixie protocol + */ + protected function isMessageComplete($message) { + return (boolean)strstr($message, "\r\n\r\n"); + } + /** * Disable a version of the WebSocket protocol *cough*Hixie76*cough* * @param string The name of the version to disable diff --git a/lib/Ratchet/Application/WebSocket/Util/HTTP.php b/lib/Ratchet/Application/WebSocket/Util/HTTP.php deleted file mode 100644 index c8f711f..0000000 --- a/lib/Ratchet/Application/WebSocket/Util/HTTP.php +++ /dev/null @@ -1,56 +0,0 @@ - null - , 'Upgrade' => null - , 'Connection' => null - , 'Sec-Websocket-Key' => null - , 'Origin' => null - , 'Sec-Websocket-Protocol' => null - , 'Sec-Websocket-Version' => null - , 'Sec-Websocket-Origin' => null - ); - } - - /** - * @param string - * @return array - * This is a fallback method for http_parse_headers as not all php installs have the HTTP module present - * @internal - */ - protected static function http_parse_headers($http_message) { - $retVal = array(); - $fields = explode("br", preg_replace("%(<|/\>|>)%", "", nl2br($http_message))); - - foreach ($fields as $field) { - if (preg_match('%^(GET|POST|PUT|DELETE|PATCH)(\s)(.*)%', $field, $matchReq)) { - $retVal["Request Method"] = $matchReq[1]; - $retVal["Request Url"] = $matchReq[3]; - } elseif (preg_match('/([^:]+): (.+)/m', $field, $match) ) { - $match[1] = preg_replace('/(?<=^|[\x09\x20\x2D])./e', 'strtoupper("\0")', strtolower(trim($match[1]))); - if (isset($retVal[$match[1]])) { - $retVal[$match[1]] = array($retVal[$match[1]], $match[2]); - } else { - $retVal[$match[1]] = trim($match[2]); - } - } - } - - return $retVal; - } -} \ No newline at end of file diff --git a/lib/Ratchet/Application/WebSocket/Version/Hixie76.php b/lib/Ratchet/Application/WebSocket/Version/Hixie76.php index 0777701..4540714 100644 --- a/lib/Ratchet/Application/WebSocket/Version/Hixie76.php +++ b/lib/Ratchet/Application/WebSocket/Version/Hixie76.php @@ -14,8 +14,8 @@ namespace Ratchet\Application\WebSocket\Version; * @link http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 */ class Hixie76 implements VersionInterface { - public static function isProtocol(array $headers) { - return isset($headers['Sec-Websocket-Key2']); + public static function isProtocol($headers) { + return isset($headers['Sec-WebSocket-Key2']); } /** diff --git a/lib/Ratchet/Application/WebSocket/Version/HyBi10.php b/lib/Ratchet/Application/WebSocket/Version/HyBi10.php index 39f20ea..ee24fe2 100644 --- a/lib/Ratchet/Application/WebSocket/Version/HyBi10.php +++ b/lib/Ratchet/Application/WebSocket/Version/HyBi10.php @@ -5,9 +5,9 @@ namespace Ratchet\Application\WebSocket\Version; * @todo Note: Even though this is the "legacy" HyBi version, it's using the RFC Message and Frame classes - change if needed */ class HyBi10 extends RFC6455 { - public static function isProtocol(array $headers) { - if (isset($headers['Sec-Websocket-Version'])) { - if ((int)$headers['Sec-Websocket-Version'] >= 6 && (int)$headers['Sec-Websocket-Version'] < 13) { + public static function isProtocol($headers) { + if (isset($headers['Sec-WebSocket-Version'])) { + if ((int)$headers['Sec-WebSocket-Version'] >= 6 && (int)$headers['Sec-WebSocket-Version'] < 13) { return true; } } diff --git a/lib/Ratchet/Application/WebSocket/Version/RFC6455.php b/lib/Ratchet/Application/WebSocket/Version/RFC6455.php index d1c7d83..fbb003f 100644 --- a/lib/Ratchet/Application/WebSocket/Version/RFC6455.php +++ b/lib/Ratchet/Application/WebSocket/Version/RFC6455.php @@ -1,7 +1,7 @@ _verifier = new HandshakeVerifier; } - public static function isProtocol(array $headers) { - if (isset($headers['Sec-Websocket-Version'])) { - if ((int)$headers['Sec-Websocket-Version'] == 13) { + /** + * @todo Change the request to be a Guzzle RequestInterface + */ + public static function isProtocol($headers) { + if (isset($headers['Sec-WebSocket-Version'])) { + if ((int)$headers['Sec-WebSocket-Version'] == 13) { return true; } } @@ -34,10 +37,10 @@ class RFC6455 implements VersionInterface { * @todo Decide what to do on failure...currently throwing an exception and I think socket connection is closed. Should be sending 40x error - but from where? */ public function handshake($message) { - $headers = HTTP::getHeaders($message); - $key = $this->sign($headers['Sec-Websocket-Key']); + $headers = $message->getHeaders(); + $key = $this->sign($headers['Sec-WebSocket-Key']); - if (true !== $this->_verifier->verifyAll($headers)) { + if (true !== $this->_verifier->verifyAll($message)) { throw new \InvalidArgumentException('Invalid HTTP header'); } @@ -45,7 +48,7 @@ class RFC6455 implements VersionInterface { '' => 'HTTP/1.1 101 Switching Protocols' , 'Upgrade' => 'websocket' , 'Connection' => 'Upgrade' - , 'Sec-WebSocket-Accept' => $this->sign($headers['Sec-Websocket-Key']) + , 'Sec-WebSocket-Accept' => $this->sign($headers['Sec-WebSocket-Key']) // , 'Sec-WebSocket-Protocol' => '' ); } diff --git a/lib/Ratchet/Application/WebSocket/Version/RFC6455/HandshakeVerifier.php b/lib/Ratchet/Application/WebSocket/Version/RFC6455/HandshakeVerifier.php index 224e870..331f62f 100644 --- a/lib/Ratchet/Application/WebSocket/Version/RFC6455/HandshakeVerifier.php +++ b/lib/Ratchet/Application/WebSocket/Version/RFC6455/HandshakeVerifier.php @@ -1,5 +1,6 @@ getHeaders(); + $passes = 0; - $passes += (int)$this->verifyMethod($headers['Request Method']); - //$passes += (int)$this->verifyHTTPVersion($headers['???']); // This isn't in the array! - $passes += (int)$this->verifyRequestURI($headers['Request Url']); + $passes += (int)$this->verifyMethod($request->getMethod()); + $passes += (int)$this->verifyHTTPVersion($request->getProtocolVersion()); + $passes += (int)$this->verifyRequestURI($request->getPath()); $passes += (int)$this->verifyHost($headers['Host']); $passes += (int)$this->verifyUpgradeRequest($headers['Upgrade']); $passes += (int)$this->verifyConnection($headers['Connection']); - $passes += (int)$this->verifyKey($headers['Sec-Websocket-Key']); - //$passes += (int)$this->verifyVersion($headers['Sec-Websocket-Version']); // Temporarily breaking functionality + $passes += (int)$this->verifyKey($headers['Sec-WebSocket-Key']); + //$passes += (int)$this->verifyVersion($headers['Sec-WebSocket-Version']); // Temporarily breaking functionality - return (6 === $passes); + return (7 === $passes); } /** diff --git a/lib/Ratchet/Application/WebSocket/Version/VersionInterface.php b/lib/Ratchet/Application/WebSocket/Version/VersionInterface.php index 53ce979..ebc9d8f 100644 --- a/lib/Ratchet/Application/WebSocket/Version/VersionInterface.php +++ b/lib/Ratchet/Application/WebSocket/Version/VersionInterface.php @@ -14,7 +14,7 @@ interface VersionInterface { * @return bool * @throws UnderflowException If the protocol thinks the headers are still fragmented */ - static function isProtocol(array $headers); + static function isProtocol($headers); /** * Perform the handshake and return the response headers @@ -22,6 +22,7 @@ interface VersionInterface { * @return array|string * @throws InvalidArgumentException If the HTTP handshake is mal-formed * @throws UnderflowException If the message hasn't finished buffering (not yet implemented, theoretically will only happen with Hixie version) + * @todo Change param to accept a Guzzle RequestInterface object */ function handshake($message); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 2f02d40..cb1e591 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -5,4 +5,6 @@ $app->register(); $app = new SplClassLoader('Ratchet', dirname(__DIR__) . DIRECTORY_SEPARATOR . 'lib'); - $app->register(); \ No newline at end of file + $app->register(); + + $app = new SplClassLoader('Guzzle', dirname(__DIR__) . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'guzzle' . DIRECTORY_SEPARATOR . 'src'); \ No newline at end of file From e20a52dacc99248ba407841bf0f1fec5c11e5652 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Fri, 6 Jan 2012 16:43:02 -0500 Subject: [PATCH 2/7] Guzzle Typecasting Updated all WebSocket protocol version to accept a Guzzle Request object --- lib/Ratchet/Application/WebSocket/App.php | 6 ++---- .../Application/WebSocket/Version/Hixie76.php | 9 ++++++--- .../Application/WebSocket/Version/HyBi10.php | 12 ++++------- .../Application/WebSocket/Version/RFC6455.php | 20 ++++++------------- .../WebSocket/Version/VersionInterface.php | 9 +++++---- 5 files changed, 23 insertions(+), 33 deletions(-) diff --git a/lib/Ratchet/Application/WebSocket/App.php b/lib/Ratchet/Application/WebSocket/App.php index 75e7207..9feb7f8 100644 --- a/lib/Ratchet/Application/WebSocket/App.php +++ b/lib/Ratchet/Application/WebSocket/App.php @@ -216,16 +216,14 @@ class App implements ApplicationInterface, ConfiguratorInterface { * @todo Verify the first line of the HTTP header as per page 16 of RFC 6455 */ protected function getVersion(RequestInterface $request) { - $headers = $request->getHeaders(); - foreach ($this->_versions as $name => $instance) { if (null !== $instance) { - if ($instance::isProtocol($headers)) { + if ($instance::isProtocol($request)) { return $instance; } } else { $ns = __NAMESPACE__ . "\\Version\\{$name}"; - if ($ns::isProtocol($headers)) { + if ($ns::isProtocol($request)) { $this->_versions[$name] = new $ns; return $this->_versions[$name]; } diff --git a/lib/Ratchet/Application/WebSocket/Version/Hixie76.php b/lib/Ratchet/Application/WebSocket/Version/Hixie76.php index 4540714..5f98e3c 100644 --- a/lib/Ratchet/Application/WebSocket/Version/Hixie76.php +++ b/lib/Ratchet/Application/WebSocket/Version/Hixie76.php @@ -1,5 +1,6 @@ getHeader('Sec-WebSocket-Key2')); } /** * @param string * @return string */ - public function handshake($message) { + public function handshake(RequestInterface $request) { + $message = $request->getRawHeaders() . $request->getResponse()->getBody(true); + $buffer = $message; $resource = $host = $origin = $key1 = $key2 = $protocol = $code = $handshake = null; diff --git a/lib/Ratchet/Application/WebSocket/Version/HyBi10.php b/lib/Ratchet/Application/WebSocket/Version/HyBi10.php index ee24fe2..920b966 100644 --- a/lib/Ratchet/Application/WebSocket/Version/HyBi10.php +++ b/lib/Ratchet/Application/WebSocket/Version/HyBi10.php @@ -1,18 +1,14 @@ = 6 && (int)$headers['Sec-WebSocket-Version'] < 13) { - return true; - } - } - - return false; + public static function isProtocol(RequestInterface $request) { + $version = (int)$request->getHeader('Sec-WebSocket-Version', -1); + return ($version >= 6 && $version < 13); } /** diff --git a/lib/Ratchet/Application/WebSocket/Version/RFC6455.php b/lib/Ratchet/Application/WebSocket/Version/RFC6455.php index fbb003f..ad41e66 100644 --- a/lib/Ratchet/Application/WebSocket/Version/RFC6455.php +++ b/lib/Ratchet/Application/WebSocket/Version/RFC6455.php @@ -21,14 +21,9 @@ class RFC6455 implements VersionInterface { /** * @todo Change the request to be a Guzzle RequestInterface */ - public static function isProtocol($headers) { - if (isset($headers['Sec-WebSocket-Version'])) { - if ((int)$headers['Sec-WebSocket-Version'] == 13) { - return true; - } - } - - return false; + public static function isProtocol(RequestInterface $request) { + $version = (int)$request->getHeader('Sec-WebSocket-Version', -1); + return (13 === $version); } /** @@ -36,11 +31,8 @@ class RFC6455 implements VersionInterface { * I kept this as an array and combined in App for future considerations...easier to add a subprotol as a key value than edit a string * @todo Decide what to do on failure...currently throwing an exception and I think socket connection is closed. Should be sending 40x error - but from where? */ - public function handshake($message) { - $headers = $message->getHeaders(); - $key = $this->sign($headers['Sec-WebSocket-Key']); - - if (true !== $this->_verifier->verifyAll($message)) { + public function handshake(RequestInterface $request) { + if (true !== $this->_verifier->verifyAll($request)) { throw new \InvalidArgumentException('Invalid HTTP header'); } @@ -48,7 +40,7 @@ class RFC6455 implements VersionInterface { '' => 'HTTP/1.1 101 Switching Protocols' , 'Upgrade' => 'websocket' , 'Connection' => 'Upgrade' - , 'Sec-WebSocket-Accept' => $this->sign($headers['Sec-WebSocket-Key']) + , 'Sec-WebSocket-Accept' => $this->sign($request->getHeader('Sec-WebSocket-Key')) // , 'Sec-WebSocket-Protocol' => '' ); } diff --git a/lib/Ratchet/Application/WebSocket/Version/VersionInterface.php b/lib/Ratchet/Application/WebSocket/Version/VersionInterface.php index ebc9d8f..8a13b70 100644 --- a/lib/Ratchet/Application/WebSocket/Version/VersionInterface.php +++ b/lib/Ratchet/Application/WebSocket/Version/VersionInterface.php @@ -1,5 +1,6 @@ Date: Fri, 6 Jan 2012 21:54:32 -0500 Subject: [PATCH 3/7] Passing Tests Finally able to do incomplete tests. Ratchet passes (finally). --- .../WebSocket/Version/RFC6455/FrameTest.php | 42 ++++++++++++++++--- .../WebSocket/Version/RFC6455Test.php | 24 ++++------- tests/bootstrap.php | 3 +- 3 files changed, 47 insertions(+), 22 deletions(-) diff --git a/tests/Ratchet/Tests/Application/WebSocket/Version/RFC6455/FrameTest.php b/tests/Ratchet/Tests/Application/WebSocket/Version/RFC6455/FrameTest.php index 90ab37d..c870d65 100644 --- a/tests/Ratchet/Tests/Application/WebSocket/Version/RFC6455/FrameTest.php +++ b/tests/Ratchet/Tests/Application/WebSocket/Version/RFC6455/FrameTest.php @@ -20,6 +20,17 @@ class FrameTest extends \PHPUnit_Framework_TestCase { } protected static function convert($in) { + if (strlen($in) > 8) { + $out = ''; + + while (strlen($in) > 8) { + $out .= static::convert(substr($in, 0, 8)); + $in = substr($in, 8); + } + + return $out; + } + return pack('C', bindec($in)); } @@ -37,6 +48,31 @@ class FrameTest extends \PHPUnit_Framework_TestCase { ); } + public static function underflowProvider() { + return array( + array('isFinal', '') + , array('getOpcode', '') + , array('isMasked', '10000001') + , array('getPayloadLength', '10000001') + , array('getPayloadLength', '1000000111111110') + , array('getMaskingKey', '1000000110000111') + , array('getPayload', '100000011000000100011100101010101001100111110100') + ); + } + + /** + * @dataProvider underflowProvider + */ + public function testUnderflowExceptionFromAllTheMethodsMimickingBuffering($method, $bin) { + $this->setExpectedException('\UnderflowException'); + + if (!empty($bin)) { + $this->_frame->addBuffer(static::convert($bin)); + } + + call_user_func(array($this->_frame, $method)); + } + /** * A data provider for testing the first byte of a WebSocket frame * @param bool Given, is the byte indicate this is the final frame @@ -53,12 +89,6 @@ class FrameTest extends \PHPUnit_Framework_TestCase { ); } - public function testUnderflowExceptionFromAllTheMethodsMimickingBuffering() { - return $this->markTestIncomplete(); - - $this->expectException('\UnderflowException'); - } - /** * @dataProvider firstByteProvider */ diff --git a/tests/Ratchet/Tests/Application/WebSocket/Version/RFC6455Test.php b/tests/Ratchet/Tests/Application/WebSocket/Version/RFC6455Test.php index 3be4b74..f32f59f 100644 --- a/tests/Ratchet/Tests/Application/WebSocket/Version/RFC6455Test.php +++ b/tests/Ratchet/Tests/Application/WebSocket/Version/RFC6455Test.php @@ -2,6 +2,7 @@ namespace Ratchet\Tests\Application\WebSocket\Version; use Ratchet\Application\WebSocket\Version\RFC6455; use Ratchet\Application\WebSocket\Version\RFC6455\Frame; +use Guzzle\Http\Message\RequestFactory; /** * @covers Ratchet\Application\WebSocket\Version\RFC6455 @@ -103,32 +104,25 @@ class RFC6455Test extends \PHPUnit_Framework_TestCase { public static function headerHandshakeProvider() { return array( array(false, "GET /test HTTP/1.0\r\n" . static::getAndSpliceHeader()) + , array(true, static::$good_rest . "\r\n" . static::getAndSpliceHeader()) + , array(false, "POST / HTTP:/1.1\r\n" . static::getAndSpliceHeader()) + , array(false, static::$good_rest . "\r\n" . static::getAndSpliceHeader('Upgrade', 'useless')) + , array(false, "GET /ಠ_ಠ HTTP/1.1\r\n" . static::getAndSpliceHeader()) + , array(true, static::$good_rest . "\r\n" . static::getAndSpliceHeader('Connection', 'Herp, Upgrade, Derp')) ); } -/* RFC example of a good header - GET /chat HTTP/1.1 - Host: server.example.com - Upgrade: websocket - Connection: Upgrade - Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== - Origin: http://example.com - Sec-WebSocket-Protocol: chat, superchat - Sec-WebSocket-Version: 13 -*/ - /** * @dataProvider headerHandshakeProvider - * @todo Can't finish this test until I rewrite headers */ public function testVariousHeadersToCheckHandshakeTolerance($pass, $header) { - return $this->markTestIncomplete(); + $request = RequestFactory::fromMessage($header); if ($pass) { - $this->assertTrue(is_array($this->_version->handshake($header))); + $this->assertTrue(is_array($this->_version->handshake($request))); } else { $this->setExpectedException('InvalidArgumentException'); - $this->_version->handshake($header); + $this->_version->handshake($request); } } } \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php index cb1e591..d8416df 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -7,4 +7,5 @@ $app = new SplClassLoader('Ratchet', dirname(__DIR__) . DIRECTORY_SEPARATOR . 'lib'); $app->register(); - $app = new SplClassLoader('Guzzle', dirname(__DIR__) . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'guzzle' . DIRECTORY_SEPARATOR . 'src'); \ No newline at end of file + $app = new SplClassLoader('Guzzle', dirname(__DIR__) . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'guzzle' . DIRECTORY_SEPARATOR . 'src'); + $app->register(); \ No newline at end of file From a1c6ee163f2d7bc7f7608a682fd32976d5d16983 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Fri, 6 Jan 2012 22:34:01 -0500 Subject: [PATCH 4/7] Guzzle Submodule Added Guzzle as a submodule --- .gitmodules | 3 +++ vendor/guzzle | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 vendor/guzzle diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..40105df --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vendor/guzzle"] + path = vendor/guzzle + url = git://github.com/guzzle/guzzle.git diff --git a/vendor/guzzle b/vendor/guzzle new file mode 160000 index 0000000..13ea2e7 --- /dev/null +++ b/vendor/guzzle @@ -0,0 +1 @@ +Subproject commit 13ea2e723eaba72d99eefb24ec36f95ff7ef0068 From 83f49f1f55be94a82785b60610a427ff36c55a28 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sat, 7 Jan 2012 00:40:31 -0500 Subject: [PATCH 5/7] Ugly Hacks Added some ugly hacks to fix Hixie: * Hixie now should work no matter how small the I/O buffer is * Hixie now works with the Guzzle library* --- lib/Ratchet/Application/WebSocket/App.php | 29 +++++++++++++++++-- .../Application/WebSocket/Version/Hixie76.php | 2 +- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/Ratchet/Application/WebSocket/App.php b/lib/Ratchet/Application/WebSocket/App.php index 9feb7f8..edda0ef 100644 --- a/lib/Ratchet/Application/WebSocket/App.php +++ b/lib/Ratchet/Application/WebSocket/App.php @@ -18,6 +18,8 @@ use Guzzle\Http\Message\RequestFactory; * @todo Consider chaning this class to a State Pattern. If a WS App interface is passed use different state for additional methods used */ class App implements ApplicationInterface, ConfiguratorInterface { + const CRLF = "\r\n\r\n"; + /** * Decorated application * @var Ratchet\Application\ApplicationInterface @@ -87,6 +89,7 @@ class App implements ApplicationInterface, ConfiguratorInterface { } $headers = RequestFactory::fromMessage($from->WebSocket->headers); + $headers->setHeader('X-Body', $this->getBody($from->WebSocket->headers)); $from->WebSocket->version = $this->getVersion($headers); $from->WebSocket->headers = $headers; } @@ -236,10 +239,32 @@ class App implements ApplicationInterface, ConfiguratorInterface { /** * @param string * @return bool - * @todo Method is flawed, this CAN result in an error with the Hixie protocol + * @todo Abstract, some hard coding done for (stupid) Hixie protocol */ protected function isMessageComplete($message) { - return (boolean)strstr($message, "\r\n\r\n"); + $headers = (boolean)strstr($message, static::CRLF); + if (!$headers) { + + return false; + } + + if (strstr($message, 'Sec-WebSocket-Key2')) { + if (8 !== strlen($this->getBody($message))) { + return false; + } + } + + return true; + } + + /** + * @param string + * @return string + * @deprecated + * @todo Remove soon, this is a temp hack for Hixie + */ + protected function getBody($message) { + return substr($message, strpos($message, static::CRLF) + strlen(static::CRLF)); } /** diff --git a/lib/Ratchet/Application/WebSocket/Version/Hixie76.php b/lib/Ratchet/Application/WebSocket/Version/Hixie76.php index 5f98e3c..7a04958 100644 --- a/lib/Ratchet/Application/WebSocket/Version/Hixie76.php +++ b/lib/Ratchet/Application/WebSocket/Version/Hixie76.php @@ -24,7 +24,7 @@ class Hixie76 implements VersionInterface { * @return string */ public function handshake(RequestInterface $request) { - $message = $request->getRawHeaders() . $request->getResponse()->getBody(true); + $message = $request->getRawHeaders() . "\r\n\r\n" . $request->getHeader('X-Body'); $buffer = $message; $resource = $host = $origin = $key1 = $key2 = $protocol = $code = $handshake = null; From 67267bfd946f04be446aa8c7e9af7184942d118d Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sat, 7 Jan 2012 01:20:34 -0500 Subject: [PATCH 6/7] Cleanup Using native Guzzle object in Hixie handshake instead of hack being done in App. Thanks to @mtdowling for the lead. --- lib/Ratchet/Application/WebSocket/App.php | 23 +++------- .../Guzzle/Http/Message/RequestFactory.php | 43 +++++++++++++++++++ .../Application/WebSocket/Version/Hixie76.php | 5 +-- 3 files changed, 51 insertions(+), 20 deletions(-) create mode 100644 lib/Ratchet/Application/WebSocket/Guzzle/Http/Message/RequestFactory.php diff --git a/lib/Ratchet/Application/WebSocket/App.php b/lib/Ratchet/Application/WebSocket/App.php index edda0ef..d56a6a8 100644 --- a/lib/Ratchet/Application/WebSocket/App.php +++ b/lib/Ratchet/Application/WebSocket/App.php @@ -7,7 +7,7 @@ use Ratchet\Resource\Command\Factory; use Ratchet\Resource\Command\CommandInterface; use Ratchet\Resource\Command\Action\SendMessage; use Guzzle\Http\Message\RequestInterface; -use Guzzle\Http\Message\RequestFactory; +use Ratchet\Application\WebSocket\Guzzle\Http\Message\RequestFactory; /** * The adapter to handle WebSocket requests/responses @@ -18,8 +18,6 @@ use Guzzle\Http\Message\RequestFactory; * @todo Consider chaning this class to a State Pattern. If a WS App interface is passed use different state for additional methods used */ class App implements ApplicationInterface, ConfiguratorInterface { - const CRLF = "\r\n\r\n"; - /** * Decorated application * @var Ratchet\Application\ApplicationInterface @@ -88,8 +86,7 @@ class App implements ApplicationInterface, ConfiguratorInterface { return; } - $headers = RequestFactory::fromMessage($from->WebSocket->headers); - $headers->setHeader('X-Body', $this->getBody($from->WebSocket->headers)); + $headers = RequestFactory::fromRequest($from->WebSocket->headers); $from->WebSocket->version = $this->getVersion($headers); $from->WebSocket->headers = $headers; } @@ -242,14 +239,16 @@ class App implements ApplicationInterface, ConfiguratorInterface { * @todo Abstract, some hard coding done for (stupid) Hixie protocol */ protected function isMessageComplete($message) { - $headers = (boolean)strstr($message, static::CRLF); + static $crlf = "\r\n\r\n"; + + $headers = (boolean)strstr($message, $crlf); if (!$headers) { return false; } if (strstr($message, 'Sec-WebSocket-Key2')) { - if (8 !== strlen($this->getBody($message))) { + if (8 !== strlen(substr($message, strpos($message, $crlf) + strlen($crlf)))) { return false; } } @@ -257,16 +256,6 @@ class App implements ApplicationInterface, ConfiguratorInterface { return true; } - /** - * @param string - * @return string - * @deprecated - * @todo Remove soon, this is a temp hack for Hixie - */ - protected function getBody($message) { - return substr($message, strpos($message, static::CRLF) + strlen(static::CRLF)); - } - /** * Disable a version of the WebSocket protocol *cough*Hixie76*cough* * @param string The name of the version to disable diff --git a/lib/Ratchet/Application/WebSocket/Guzzle/Http/Message/RequestFactory.php b/lib/Ratchet/Application/WebSocket/Guzzle/Http/Message/RequestFactory.php new file mode 100644 index 0000000..5184ce0 --- /dev/null +++ b/lib/Ratchet/Application/WebSocket/Guzzle/Http/Message/RequestFactory.php @@ -0,0 +1,43 @@ +setProtocolVersion($protocolVersion); + } + + protected static function requestCreate($method, $url, $headers = null, $body = null) { + $c = static::$entityEnclosingRequestClass; + $request = new $c($method, $url, $headers); + $request->setBody($body); + + return $request; + } +} \ No newline at end of file diff --git a/lib/Ratchet/Application/WebSocket/Version/Hixie76.php b/lib/Ratchet/Application/WebSocket/Version/Hixie76.php index 7a04958..bea5578 100644 --- a/lib/Ratchet/Application/WebSocket/Version/Hixie76.php +++ b/lib/Ratchet/Application/WebSocket/Version/Hixie76.php @@ -22,11 +22,10 @@ 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) { - $message = $request->getRawHeaders() . "\r\n\r\n" . $request->getHeader('X-Body'); - - $buffer = $message; + $buffer = $request->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]; From 4b2e731b6ffd6e5b6f8fb18d7ff69ceb0dc0b0fe Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sun, 8 Jan 2012 19:47:41 -0500 Subject: [PATCH 7/7] Added vendor info --- vendor/README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 vendor/README.md diff --git a/vendor/README.md b/vendor/README.md new file mode 100644 index 0000000..03f7f86 --- /dev/null +++ b/vendor/README.md @@ -0,0 +1,5 @@ +## External Libraries + +### Guzzle + +Used to parse the incoming HTTP handshake request. A Guzzle Request object is then passed around the application for a consistent API. \ No newline at end of file