From d075b99c264f08da857adbc619e0477c1ee377c4 Mon Sep 17 00:00:00 2001 From: Chris Boden Date: Sat, 19 May 2012 23:36:32 -0400 Subject: [PATCH] [WebSockets] Handshake encoding + case insensitivity Updated RFC6455 handshaker to check values case insensitively Made sure RFC6455 handshaker matches encoding properly Added mbstring as a requirement for Ratchet Refs #28, #30 --- README.md | 3 +- composer.json | 1 + composer.lock | 16 +----- .../WebSocket/Version/FrameInterface.php | 6 --- .../Version/RFC6455/HandshakeVerifier.php | 14 ++--- .../Version/RFC6455/HandshakeVerifierTest.php | 11 ++-- .../Tests/WebSocket/Version/RFC6455Test.php | 51 ++++++++++++------- 7 files changed, 52 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 776668b..ba5e54b 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,14 @@ Build up your application through simple interfaces and re-use your application ##WebSocket Compliance * Supports the RFC6455, HyBi-10+, and Hixie76 protocol versions (at the same time) -* Tested on Chrome 18 - 16, Firefox 6 - 12, Safari 5, iOS 4.2, iOS 5 +* Tested on Chrome 13 - 19, Firefox 6 - 12, Safari 5.0.1+, iOS 4.2, iOS 5 ##Requirements Shell access is required and a dedicated machine with root access is recommended. To avoid proxy/firewall blockage it's recommended WebSockets are run on port 80, which requires root access. Note that you can not run two applications (Apache and Ratchet) on the same port, thus the requirement for a separate machine (for now). +PHP 5.3.2 (or higher) is required with mbstring enabled (*--enable-mbstring* flag during compile time). PHP5.4 is recommended for its performance improvements. Cookies from your domain will be passed to the socket server, allowing you to identify users. Accessing your website's session data in Ratchet requires you to use [Symfony2 Sessions](http://symfony.com/doc/master/components/http_foundation/sessions.html) on your website. diff --git a/composer.json b/composer.json index a0a09a8..3e2c07f 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ } , "require": { "php": ">=5.3.2" + , "ext-mbstring": "*" , "guzzle/guzzle": "2.5.*" , "symfony/http-foundation": "2.1.*" , "react/socket": "dev-master" diff --git a/composer.lock b/composer.lock index 378a998..d85b62a 100644 --- a/composer.lock +++ b/composer.lock @@ -1,5 +1,5 @@ { - "hash": "cbea4e3e4d74a22ba34d4edf2ce44df3", + "hash": "253370657f067dacf104d5fae531f20a", "packages": [ { "package": "evenement/evenement", @@ -32,13 +32,6 @@ "version": "dev-master", "source-reference": "eb82542e8ec9506096caf7c528564c740a214f56" }, - { - "package": "symfony/event-dispatcher", - "version": "dev-master", - "source-reference": "0b58a4019befc0bd038bc0ec0165101d5dd31754", - "alias-pretty-version": "2.1.x-dev", - "alias-version": "2.1.9999999.9999999-dev" - }, { "package": "symfony/http-foundation", "version": "dev-master", @@ -50,13 +43,6 @@ "package": "symfony/http-foundation", "version": "dev-master", "source-reference": "3d9f4ce435f6322b9720c209ad610202526373c0" - }, - { - "package": "symfony/http-foundation", - "version": "dev-master", - "source-reference": "cf8e8324c68ce584525502702866485f17f1c8a5", - "alias-pretty-version": "2.1.x-dev", - "alias-version": "2.1.9999999.9999999-dev" } ], "packages-dev": null, diff --git a/src/Ratchet/WebSocket/Version/FrameInterface.php b/src/Ratchet/WebSocket/Version/FrameInterface.php index 57b27ea..61451cc 100644 --- a/src/Ratchet/WebSocket/Version/FrameInterface.php +++ b/src/Ratchet/WebSocket/Version/FrameInterface.php @@ -2,12 +2,6 @@ namespace Ratchet\WebSocket\Version; interface FrameInterface { - /** - * Dunno if I'll use this - * Thinking could be used if a control frame? - */ -// function __invoke(); - /** * @return bool */ diff --git a/src/Ratchet/WebSocket/Version/RFC6455/HandshakeVerifier.php b/src/Ratchet/WebSocket/Version/RFC6455/HandshakeVerifier.php index 6898e2e..e9172ef 100644 --- a/src/Ratchet/WebSocket/Version/RFC6455/HandshakeVerifier.php +++ b/src/Ratchet/WebSocket/Version/RFC6455/HandshakeVerifier.php @@ -32,10 +32,9 @@ class HandshakeVerifier { * Test the HTTP method. MUST be "GET" * @param string * @return bool - * @todo Look into STD if "get" is valid (am I supposed to do case conversion?) */ public function verifyMethod($val) { - return ('GET' === $val); + return ('get' === mb_strtolower($val, 'ASCII')); } /** @@ -50,7 +49,6 @@ class HandshakeVerifier { /** * @param string * @return bool - * @todo Verify the logic here is correct */ public function verifyRequestURI($val) { if ($val[0] != '/') { @@ -80,7 +78,7 @@ class HandshakeVerifier { * @return bool */ public function verifyUpgradeRequest($val) { - return ('websocket' === $val); + return ('websocket' === mb_strtolower($val, 'ASCII')); } /** @@ -89,12 +87,16 @@ class HandshakeVerifier { * @return bool */ public function verifyConnection($val) { - if ('Upgrade' === $val) { + $val = mb_strtolower($val, 'ASCII'); + + if ('upgrade' === $val) { return true; } + // todo change this to mb_eregi_replace $vals = explode(',', str_replace(', ', ',', $val)); - return (false !== array_search('Upgrade', $vals)); + + return (false !== array_search('upgrade', $vals)); } /** diff --git a/tests/Ratchet/Tests/WebSocket/Version/RFC6455/HandshakeVerifierTest.php b/tests/Ratchet/Tests/WebSocket/Version/RFC6455/HandshakeVerifierTest.php index 2e51648..3822ff3 100644 --- a/tests/Ratchet/Tests/WebSocket/Version/RFC6455/HandshakeVerifierTest.php +++ b/tests/Ratchet/Tests/WebSocket/Version/RFC6455/HandshakeVerifierTest.php @@ -18,7 +18,8 @@ class HandshakeVerifierTest extends \PHPUnit_Framework_TestCase { public static function methodProvider() { return array( array(true, 'GET') - , array(false, 'get') // I'm not sure if this is valid or not, need to check standard + , array(true, 'get') + , array(true, 'Get') , array(false, 'POST') , array(false, 'DELETE') , array(false, 'PUT') @@ -64,6 +65,7 @@ class HandshakeVerifierTest extends \PHPUnit_Framework_TestCase { , array(false, '/chat#bad') , array(false, 'nope') , array(false, '/ ಠ_ಠ ') + , array(false, '/✖') ); } @@ -91,7 +93,8 @@ class HandshakeVerifierTest extends \PHPUnit_Framework_TestCase { public static function upgradeProvider() { return array( array(true, 'websocket') - , array(false, 'Websocket') + , array(true, 'Websocket') + , array(true, 'webSocket') , array(false, null) , array(false, '') ); @@ -107,7 +110,7 @@ class HandshakeVerifierTest extends \PHPUnit_Framework_TestCase { public static function connectionProvider() { return array( array(true, 'Upgrade') - , array(false, 'upgrade') + , array(true, 'upgrade') , array(true, 'keep-alive, Upgrade') , array(true, 'Upgrade, keep-alive') , array(true, 'keep-alive, Upgrade, something') @@ -133,6 +136,8 @@ class HandshakeVerifierTest extends \PHPUnit_Framework_TestCase { , array(false, 'Hello World') , array(false, '1234567890123456') , array(false, '123456789012345678901234') + , array(true, base64_encode('UTF8allthngs+✓')) + , array(true, 'dGhlIHNhbXBsZSBub25jZQ==') ); } diff --git a/tests/Ratchet/Tests/WebSocket/Version/RFC6455Test.php b/tests/Ratchet/Tests/WebSocket/Version/RFC6455Test.php index 2e6dd0b..99ba1f6 100644 --- a/tests/Ratchet/Tests/WebSocket/Version/RFC6455Test.php +++ b/tests/Ratchet/Tests/WebSocket/Version/RFC6455Test.php @@ -3,33 +3,26 @@ namespace Ratchet\Tests\WebSocket\Version; use Ratchet\WebSocket\Version\RFC6455; use Ratchet\WebSocket\Version\RFC6455\Frame; use Guzzle\Http\Message\RequestFactory; +use Guzzle\Http\Message\EntityEnclosingRequest; /** * @covers Ratchet\WebSocket\Version\RFC6455 */ class RFC6455Test extends \PHPUnit_Framework_TestCase { - protected $_version; + protected $version; public function setUp() { - $this->_version = new RFC6455(); + $this->version = new RFC6455; } /** - * Is this useful? - */ - public function testClassImplementsVersionInterface() { - $constraint = $this->isInstanceOf('\\Ratchet\\WebSocket\\Version\\VersionInterface'); - $this->assertThat($this->_version, $constraint); - } - - /** - * @dataProvider HandshakeProvider + * @dataProvider handshakeProvider */ public function testKeySigningForHandshake($key, $accept) { - $this->assertEquals($accept, $this->_version->sign($key)); + $this->assertEquals($accept, $this->version->sign($key)); } - public static function HandshakeProvider() { + public static function handshakeProvider() { return array( array('x3JJHMbDL1EzLkh9GBhXDw==', 'HSmrc0sMlYUkAGmm5OPpG2HaGWk=') , array('dGhlIHNhbXBsZSBub25jZQ==', 's3pPLMBiTxaQ9kYGzzhZRbK+xOo=') @@ -56,8 +49,8 @@ class RFC6455Test extends \PHPUnit_Framework_TestCase { } public function testUnframeMatchesPreFraming() { - $string = 'Hello World!'; - $framed = $this->_version->frame($string); + $string = 'Hello World!'; + $framed = $this->version->frame($string); $frame = new Frame; $frame->addBuffer($framed); @@ -77,6 +70,26 @@ class RFC6455Test extends \PHPUnit_Framework_TestCase { , 'Sec-WebSocket-Version' => 13 ); + public function caseVariantProvider() { + return array( + array('Sec-Websocket-Version') + , array('sec-websocket-version') + , array('SEC-WEBSOCKET-VERSION') + , array('sEC-wEBsOCKET-vERSION') + ); + } + + /** + * @dataProvider caseVariantProvider + */ + public function testIsProtocolWithCaseInsensitivity($headerName) { + $header = static::$good_header; + unset($header['Sec-WebSocket-Version']); + $header[$headerName] = 13; + + $this->assertTrue($this->version->isProtocol(new EntityEnclosingRequest('get', '/', $header))); + } + /** * A helper function to try and quickly put together a valid WebSocket HTTP handshake * but optionally replace a piece to an invalid value for failure testing @@ -119,18 +132,18 @@ class RFC6455Test extends \PHPUnit_Framework_TestCase { $request = RequestFactory::getInstance()->fromMessage($header); if ($pass) { - $this->assertInstanceOf('\\Guzzle\\Http\\Message\\Response', $this->_version->handshake($request)); + $this->assertInstanceOf('\\Guzzle\\Http\\Message\\Response', $this->version->handshake($request)); } else { $this->setExpectedException('InvalidArgumentException'); - $this->_version->handshake($request); + $this->version->handshake($request); } } public function testNewMessage() { - $this->assertInstanceOf('\\Ratchet\\WebSocket\\Version\\RFC6455\\Message', $this->_version->newMessage()); + $this->assertInstanceOf('\\Ratchet\\WebSocket\\Version\\RFC6455\\Message', $this->version->newMessage()); } public function testNewFrame() { - $this->assertInstanceOf('\\Ratchet\\WebSocket\\Version\\RFC6455\\Frame', $this->_version->newFrame()); + $this->assertInstanceOf('\\Ratchet\\WebSocket\\Version\\RFC6455\\Frame', $this->version->newFrame()); } } \ No newline at end of file