[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
This commit is contained in:
Chris Boden 2012-05-19 23:36:32 -04:00
parent 27716fef78
commit d075b99c26
7 changed files with 52 additions and 50 deletions

View File

@ -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.

View File

@ -21,6 +21,7 @@
}
, "require": {
"php": ">=5.3.2"
, "ext-mbstring": "*"
, "guzzle/guzzle": "2.5.*"
, "symfony/http-foundation": "2.1.*"
, "react/socket": "dev-master"

16
composer.lock generated
View File

@ -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,

View File

@ -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
*/

View File

@ -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));
}
/**

View File

@ -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==')
);
}

View File

@ -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());
}
}