Merge pull request #10 from mikealmond/master

Update version handshakes to return a Guzzle object (addresses issue #6)
This commit is contained in:
Chris Boden 2012-02-08 13:49:43 -08:00
commit f3d443b364
5 changed files with 67 additions and 59 deletions

View File

@ -1,6 +1,7 @@
<?php
namespace Ratchet\Component\WebSocket\Version;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
/**
* FOR THE LOVE OF BEER, PLEASE PLEASE PLEASE DON'T allow the use of this in your application!
@ -22,29 +23,21 @@ 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) {
$buffer = $request->getRawHeaders() . "\r\n\r\n" . $request->getBody();
$resource = $host = $origin = $key1 = $key2 = $protocol = $code = $handshake = null;
$body = $this->sign($request->getHeader('Sec-WebSocket-Key1'), $request->getHeader('Sec-WebSocket-Key2'), $request->getBody());
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];
$headers = array(
'Upgrade' => 'WebSocket'
, 'Connection' => 'Upgrade'
, 'Sec-WebSocket-Origin' => $request->getHeader('Origin')
, 'Sec-WebSocket-Location' => 'ws://' . $request->getHeader('Host') . $request->getPath()
);
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)
;
$response = new Response('101', $headers, $body);
$response->setStatus('101', 'WebSocket Protocol Handshake');
return $response;
}
public function newMessage() {
@ -59,17 +52,22 @@ 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]) :
''
;
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 _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);
}

View File

@ -2,6 +2,8 @@
namespace Ratchet\Component\WebSocket\Version;
use Ratchet\Component\WebSocket\Version\RFC6455\HandshakeVerifier;
use Guzzle\Http\Message\RequestInterface;
use Guzzle\Http\Message\Response;
/**
* @link http://tools.ietf.org/html/rfc6455
@ -33,14 +35,14 @@ 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' => ''
);
}
return new Response('101', $headers);
}
/**
* @return RFC6455\Message
@ -75,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):
@ -94,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;
@ -125,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];
}

View File

@ -79,31 +79,20 @@ class WebSocketComponent implements MessageComponentInterface {
$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', ',');
// This block is to be moved/changed later
$agreed_protocols = array();
$requested_protocols = $from->WebSocket->headers->getTokenizedHeader('Sec-WebSocket-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['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;
}
if (count($agreed_protocols) > 0) {
$response->setHeader('Sec-WebSocket-Protocol', implode(',', $agreed_protocols));
}
$header = (string)$response;
$comp = $this->_factory->newComposite();
$comp->enqueue($this->_factory->newCommand('SendMessage', $from)->setMessage($header));
@ -181,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]);

View File

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

View File

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