[BCB] Namespace changes
Removed the `Component` namespace Removed the `Resource` namespace Renamed components: `IOServerComponent` => `IoServer` `WebSocketComponent` => `WsServer` `SessionComponent` => `SessionProvider` `WAMPServerComponent` => `WampServer` `IpBlackListComponent` => `IpBlackList` `FlashPolicyComponent` => `FlashPolicy`
This commit is contained in:
commit
2ffcc6b0a7
19
Guzzle/Http/Message/RequestFactory.php
Normal file
19
Guzzle/Http/Message/RequestFactory.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket\Guzzle\Http\Message;
|
||||||
|
use Guzzle\Http\Message\RequestFactory as GuzzleRequestFactory;
|
||||||
|
use Guzzle\Http\EntityBody;
|
||||||
|
|
||||||
|
class RequestFactory extends GuzzleRequestFactory {
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function create($method, $url, $headers = null, $body = null) {
|
||||||
|
$c = $this->entityEnclosingRequestClass;
|
||||||
|
$request = new $c($method, $url, $headers);
|
||||||
|
if ($body) {
|
||||||
|
$request->setBody(EntityBody::factory($body));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $request;
|
||||||
|
}
|
||||||
|
}
|
62
Version/FrameInterface.php
Normal file
62
Version/FrameInterface.php
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket\Version;
|
||||||
|
|
||||||
|
interface FrameInterface {
|
||||||
|
/**
|
||||||
|
* Dunno if I'll use this
|
||||||
|
* Thinking could be used if a control frame?
|
||||||
|
*/
|
||||||
|
// function __invoke();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function isCoalesced();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string
|
||||||
|
* @todo Theoretically, there won't be a buffer overflow (end of frame + start of new frame) - but test later, return a string with overflow here
|
||||||
|
*/
|
||||||
|
function addBuffer($buf);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
// function isFragment();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function isFinal();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function isMasked();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
function getOpcode();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
function getPayloadLength();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
// function getReceivedPayloadLength();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 32-big string
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function getMaskingKey();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string
|
||||||
|
*/
|
||||||
|
function getPayload();
|
||||||
|
}
|
84
Version/Hixie76.php
Normal file
84
Version/Hixie76.php
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\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!
|
||||||
|
* Hixie76 is bad for 2 (there's more) reasons:
|
||||||
|
* 1) The handshake is done in HTTP, which includes a key for signing in the body...
|
||||||
|
* BUT there is no Length defined in the header (as per HTTP spec) so the TCP buffer can't tell when the message is done!
|
||||||
|
* 2) By nature it's insecure. Google did a test study where they were able to do a
|
||||||
|
* man-in-the-middle attack on 10%-15% of the people who saw their ad who had a browser (currently only Safari) supporting the Hixie76 protocol.
|
||||||
|
* This was exploited by taking advantage of proxy servers in front of the user who ignored some HTTP headers in the handshake
|
||||||
|
* The Hixie76 is currently implemented by Safari
|
||||||
|
* Handshake from Andrea Giammarchi (http://webreflection.blogspot.com/2010/06/websocket-handshake-76-simplified.html)
|
||||||
|
* @link http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
|
||||||
|
*/
|
||||||
|
class Hixie76 implements VersionInterface {
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function isProtocol(RequestInterface $request) {
|
||||||
|
return !(null === $request->getHeader('Sec-WebSocket-Key2', true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Guzzle\Http\Message\RequestInterface
|
||||||
|
* @return Guzzle\Http\Message\Response
|
||||||
|
*/
|
||||||
|
public function handshake(RequestInterface $request) {
|
||||||
|
$body = $this->sign($request->getHeader('Sec-WebSocket-Key1', true), $request->getHeader('Sec-WebSocket-Key2', true), (string)$request->getBody());
|
||||||
|
|
||||||
|
$headers = array(
|
||||||
|
'Upgrade' => 'WebSocket'
|
||||||
|
, 'Connection' => 'Upgrade'
|
||||||
|
, 'Sec-WebSocket-Origin' => $request->getHeader('Origin', true)
|
||||||
|
, 'Sec-WebSocket-Location' => 'ws://' . $request->getHeader('Host', true) . $request->getPath()
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = new Response('101', $headers, $body);
|
||||||
|
$response->setStatus('101', 'WebSocket Protocol Handshake');
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Hixie76\Message
|
||||||
|
*/
|
||||||
|
public function newMessage() {
|
||||||
|
return new Hixie76\Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Hixie76\Frame
|
||||||
|
*/
|
||||||
|
public function newFrame() {
|
||||||
|
return new Hixie76\Frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function frame($message, $mask = true) {
|
||||||
|
return chr(0) . $message . chr(255);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateKeyNumber($key) {
|
||||||
|
if (0 === substr_count($key, ' ')) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$int = (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))
|
||||||
|
. $code
|
||||||
|
, true);
|
||||||
|
}
|
||||||
|
}
|
78
Version/Hixie76/Frame.php
Normal file
78
Version/Hixie76/Frame.php
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket\Version\Hixie76;
|
||||||
|
use Ratchet\WebSocket\Version\FrameInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This does not entirely follow the protocol to spec, but (mostly) works
|
||||||
|
* Hixie76 probably should not even be supported
|
||||||
|
*/
|
||||||
|
class Frame implements FrameInterface {
|
||||||
|
/**
|
||||||
|
* @type string
|
||||||
|
*/
|
||||||
|
protected $_data = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isCoalesced() {
|
||||||
|
return (boolean)($this->_data[0] == chr(0) && substr($this->_data, -1) == chr(255));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function addBuffer($buf) {
|
||||||
|
$this->_data .= (string)$buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isFinal() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isMasked() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getOpcode() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getPayloadLength() {
|
||||||
|
if (!$this->isCoalesced()) {
|
||||||
|
throw new \UnderflowException('Not enough of the message has been buffered to determine the length of the payload');
|
||||||
|
}
|
||||||
|
|
||||||
|
return strlen($this->_data) - 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getMaskingKey() {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getPayload() {
|
||||||
|
if (!$this->isCoalesced()) {
|
||||||
|
return new \UnderflowException('Not enough data buffered to read payload');
|
||||||
|
}
|
||||||
|
|
||||||
|
return substr($this->_data, 1, strlen($this->_data) - 2);
|
||||||
|
}
|
||||||
|
}
|
66
Version/Hixie76/Message.php
Normal file
66
Version/Hixie76/Message.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket\Version\Hixie76;
|
||||||
|
use Ratchet\WebSocket\Version\MessageInterface;
|
||||||
|
use Ratchet\WebSocket\Version\FrameInterface;
|
||||||
|
|
||||||
|
class Message implements MessageInterface {
|
||||||
|
/**
|
||||||
|
* @var Ratchet\WebSocket\Version\FrameInterface
|
||||||
|
*/
|
||||||
|
protected $_frame = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function __toString() {
|
||||||
|
return $this->getPayload();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isCoalesced() {
|
||||||
|
if (!($this->_frame instanceof FrameInterface)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_frame->isCoalesced();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function addFrame(FrameInterface $fragment) {
|
||||||
|
if (null !== $this->_frame) {
|
||||||
|
throw new \OverflowException('Hixie76 does not support multiple framing of messages');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_frame = $fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getOpcode() {
|
||||||
|
// Hixie76 only supported text messages
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getPayloadLength() {
|
||||||
|
throw new \DomainException('Please sir, may I have some code? (' . __FUNCTION__ . ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getPayload() {
|
||||||
|
if (!$this->isCoalesced()) {
|
||||||
|
throw new \UnderflowException('Message has not been fully buffered yet');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_frame->getPayload();
|
||||||
|
}
|
||||||
|
}
|
25
Version/HyBi10.php
Normal file
25
Version/HyBi10.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket\Version;
|
||||||
|
use Guzzle\Http\Message\RequestInterface;
|
||||||
|
|
||||||
|
class HyBi10 extends RFC6455 {
|
||||||
|
public static function isProtocol(RequestInterface $request) {
|
||||||
|
$version = (int)$request->getHeader('Sec-WebSocket-Version', -1);
|
||||||
|
return ($version >= 6 && $version < 13);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return HyBi10\Message
|
||||||
|
* /
|
||||||
|
public function newMessage() {
|
||||||
|
return new HyBi10\Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return HyBi10\Frame
|
||||||
|
* /
|
||||||
|
public function newFrame() {
|
||||||
|
return new HyBi10\Frame;
|
||||||
|
}
|
||||||
|
/**/
|
||||||
|
}
|
37
Version/MessageInterface.php
Normal file
37
Version/MessageInterface.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket\Version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo Consider making parent interface/composite for Message/Frame with (isCoalesced, getOpcdoe, getPayloadLength, getPayload)
|
||||||
|
*/
|
||||||
|
interface MessageInterface {
|
||||||
|
/**
|
||||||
|
* @alias getPayload
|
||||||
|
*/
|
||||||
|
function __toString();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function isCoalesced();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param FragmentInterface
|
||||||
|
*/
|
||||||
|
function addFrame(FrameInterface $fragment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
function getOpcode();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
function getPayloadLength();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function getPayload();
|
||||||
|
}
|
155
Version/RFC6455.php
Normal file
155
Version/RFC6455.php
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket\Version;
|
||||||
|
use Ratchet\WebSocket\Version\RFC6455\HandshakeVerifier;
|
||||||
|
use Guzzle\Http\Message\RequestInterface;
|
||||||
|
use Guzzle\Http\Message\Response;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @link http://tools.ietf.org/html/rfc6455
|
||||||
|
*/
|
||||||
|
class RFC6455 implements VersionInterface {
|
||||||
|
const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var RFC6455\HandshakeVerifier
|
||||||
|
*/
|
||||||
|
protected $_verifier;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->_verifier = new HandshakeVerifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function isProtocol(RequestInterface $request) {
|
||||||
|
$version = (int)$request->getHeader('Sec-WebSocket-Version', -1);
|
||||||
|
return (13 === $version);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
* @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(RequestInterface $request) {
|
||||||
|
if (true !== $this->_verifier->verifyAll($request)) {
|
||||||
|
throw new \InvalidArgumentException('Invalid HTTP header');
|
||||||
|
}
|
||||||
|
|
||||||
|
$headers = array(
|
||||||
|
'Upgrade' => 'websocket'
|
||||||
|
, 'Connection' => 'Upgrade'
|
||||||
|
, 'Sec-WebSocket-Accept' => $this->sign($request->getHeader('Sec-WebSocket-Key'))
|
||||||
|
);
|
||||||
|
|
||||||
|
return new Response('101', $headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return RFC6455\Message
|
||||||
|
*/
|
||||||
|
public function newMessage() {
|
||||||
|
return new RFC6455\Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return RFC6455\Frame
|
||||||
|
*/
|
||||||
|
public function newFrame() {
|
||||||
|
return new RFC6455\Frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thanks to @lemmingzshadow for the code on decoding a HyBi-10 frame
|
||||||
|
* @link https://github.com/lemmingzshadow/php-websocket
|
||||||
|
* @todo look into what happens when false is returned here
|
||||||
|
* @todo This is needed when a client is created - needs re-write as missing parts of protocol
|
||||||
|
* @param string
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function frame($message, $mask = true) {
|
||||||
|
$payload = $message;
|
||||||
|
$type = 'text';
|
||||||
|
$masked = $mask;
|
||||||
|
|
||||||
|
$frameHead = array();
|
||||||
|
$frame = '';
|
||||||
|
$payloadLength = strlen($payload);
|
||||||
|
|
||||||
|
switch($type) {
|
||||||
|
case 'text':
|
||||||
|
// first byte indicates FIN, Text-Frame (10000001):
|
||||||
|
$frameHead[0] = 129;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'close':
|
||||||
|
// first byte indicates FIN, Close Frame(10001000):
|
||||||
|
$frameHead[0] = 136;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ping':
|
||||||
|
// first byte indicates FIN, Ping frame (10001001):
|
||||||
|
$frameHead[0] = 137;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'pong':
|
||||||
|
// first byte indicates FIN, Pong frame (10001010):
|
||||||
|
$frameHead[0] = 138;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
for($i = 0; $i < 8; $i++) {
|
||||||
|
$frameHead[$i+2] = bindec($payloadLengthBin[$i]);
|
||||||
|
}
|
||||||
|
// most significant bit MUST be 0 (return false if to much data)
|
||||||
|
if($frameHead[2] > 127) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} elseif($payloadLength > 125) {
|
||||||
|
$payloadLengthBin = str_split(sprintf('%016b', $payloadLength), 8);
|
||||||
|
$frameHead[1] = ($masked === true) ? 254 : 126;
|
||||||
|
$frameHead[2] = bindec($payloadLengthBin[0]);
|
||||||
|
$frameHead[3] = bindec($payloadLengthBin[1]);
|
||||||
|
} else {
|
||||||
|
$frameHead[1] = ($masked === true) ? $payloadLength + 128 : $payloadLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert frame-head to string:
|
||||||
|
foreach(array_keys($frameHead) as $i) {
|
||||||
|
$frameHead[$i] = chr($frameHead[$i]);
|
||||||
|
} if($masked === true) {
|
||||||
|
// generate a random mask:
|
||||||
|
$mask = array();
|
||||||
|
for($i = 0; $i < 4; $i++)
|
||||||
|
{
|
||||||
|
$mask[$i] = chr(rand(0, 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
$frameHead = array_merge($frameHead, $mask);
|
||||||
|
}
|
||||||
|
$frame = implode('', $frameHead);
|
||||||
|
|
||||||
|
// append payload to frame:
|
||||||
|
$framePayload = array();
|
||||||
|
for($i = 0; $i < $payloadLength; $i++) {
|
||||||
|
$frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used when doing the handshake to encode the key, verifying client/server are speaking the same language
|
||||||
|
* @param string
|
||||||
|
* @return string
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public function sign($key) {
|
||||||
|
return base64_encode(sha1($key . static::GUID, 1));
|
||||||
|
}
|
||||||
|
}
|
226
Version/RFC6455/Frame.php
Normal file
226
Version/RFC6455/Frame.php
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket\Version\RFC6455;
|
||||||
|
use Ratchet\WebSocket\Version\FrameInterface;
|
||||||
|
|
||||||
|
class Frame implements FrameInterface {
|
||||||
|
/**
|
||||||
|
* The contents of the frame
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $_data = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of bytes received from the frame
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
public $_bytes_rec = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of bytes in the payload (as per framing protocol)
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $_pay_len_def = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bit 9-15
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $_pay_check = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isCoalesced() {
|
||||||
|
try {
|
||||||
|
$payload_length = $this->getPayloadLength();
|
||||||
|
$payload_start = $this->getPayloadStartingByte();
|
||||||
|
} catch (\UnderflowException $e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $payload_length + $payload_start === $this->_bytes_rec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function addBuffer($buf) {
|
||||||
|
$buf = (string)$buf;
|
||||||
|
|
||||||
|
$this->_data .= $buf;
|
||||||
|
$this->_bytes_rec += strlen($buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isFinal() {
|
||||||
|
if ($this->_bytes_rec < 1) {
|
||||||
|
throw new \UnderflowException('Not enough bytes received to determine if this is the final frame in message');
|
||||||
|
}
|
||||||
|
|
||||||
|
$fbb = sprintf('%08b', ord($this->_data[0]));
|
||||||
|
return (boolean)(int)$fbb[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isMasked() {
|
||||||
|
if ($this->_bytes_rec < 2) {
|
||||||
|
throw new \UnderflowException("Not enough bytes received ({$this->_bytes_rec}) to determine if mask is set");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (boolean)bindec(substr(sprintf('%08b', ord($this->_data[1])), 0, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getOpcode() {
|
||||||
|
if ($this->_bytes_rec < 1) {
|
||||||
|
throw new \UnderflowException('Not enough bytes received to determine opcode');
|
||||||
|
}
|
||||||
|
|
||||||
|
return bindec(substr(sprintf('%08b', ord($this->_data[0])), 4, 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the decimal value of bits 9 (10th) through 15 inclusive
|
||||||
|
* @return int
|
||||||
|
* @throws UnderflowException If the buffer doesn't have enough data to determine this
|
||||||
|
*/
|
||||||
|
protected function getFirstPayloadVal() {
|
||||||
|
if ($this->_bytes_rec < 2) {
|
||||||
|
throw new \UnderflowException('Not enough bytes received');
|
||||||
|
}
|
||||||
|
|
||||||
|
return ord($this->_data[1]) & 127;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int (7|23|71) Number of bits defined for the payload length in the fame
|
||||||
|
* @throws UnderflowException
|
||||||
|
*/
|
||||||
|
protected function getNumPayloadBits() {
|
||||||
|
if ($this->_bytes_rec < 2) {
|
||||||
|
throw new \UnderflowException('Not enough bytes received');
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default 7 bits are used to describe the payload length
|
||||||
|
// These are bits 9 (10th) through 15 inclusive
|
||||||
|
$bits = 7;
|
||||||
|
|
||||||
|
// Get the value of those bits
|
||||||
|
$check = $this->getFirstPayloadVal();
|
||||||
|
|
||||||
|
// If the value is 126 the 7 bits plus the next 16 are used to describe the payload length
|
||||||
|
if ($check >= 126) {
|
||||||
|
$bits += 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the value of the initial payload length are is 127 an additional 48 bits are used to describe length
|
||||||
|
// Note: The documentation specifies the length is to be 63 bits, but I think that's a type and is 64 (16+48)
|
||||||
|
if ($check === 127) {
|
||||||
|
$bits += 48;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_array($bits, array(7, 23, 71))) {
|
||||||
|
throw new \UnexpectedValueException("Malformed frame, invalid payload length provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This just returns the number of bytes used in the frame to describe the payload length (as opposed to # of bits)
|
||||||
|
* @see getNumPayloadBits
|
||||||
|
*/
|
||||||
|
protected function getNumPayloadBytes() {
|
||||||
|
return (1 + $this->getNumPayloadBits()) / 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getPayloadLength() {
|
||||||
|
if ($this->_pay_len_def !== -1) {
|
||||||
|
return $this->_pay_len_def;
|
||||||
|
}
|
||||||
|
|
||||||
|
$length_check = $this->getFirstPayloadVal();
|
||||||
|
|
||||||
|
if ($length_check <= 125) {
|
||||||
|
$this->_pay_len_def = $length_check;
|
||||||
|
return $this->getPayloadLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
$byte_length = $this->getNumPayloadBytes();
|
||||||
|
if ($this->_bytes_rec < 1 + $byte_length) {
|
||||||
|
throw new \UnderflowException('Not enough data buffered to determine payload length');
|
||||||
|
}
|
||||||
|
|
||||||
|
$strings = array();
|
||||||
|
for ($i = 2; $i < $byte_length + 1; $i++) {
|
||||||
|
$strings[] = ord($this->_data[$i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_pay_len_def = bindec(vsprintf(str_repeat('%08b', $byte_length - 1), $strings));
|
||||||
|
return $this->getPayloadLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getMaskingKey() {
|
||||||
|
if (!$this->isMasked()) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$length = 4;
|
||||||
|
$start = 1 + $this->getNumPayloadBytes();
|
||||||
|
|
||||||
|
if ($this->_bytes_rec < $start + $length) {
|
||||||
|
throw new \UnderflowException('Not enough data buffered to calculate the masking key');
|
||||||
|
}
|
||||||
|
|
||||||
|
return substr($this->_data, $start, $length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getPayloadStartingByte() {
|
||||||
|
return 1 + $this->getNumPayloadBytes() + strlen($this->getMaskingKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getPayload() {
|
||||||
|
if (!$this->isCoalesced()) {
|
||||||
|
throw new \UnderflowException('Can not return partial message');
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = '';
|
||||||
|
$length = $this->getPayloadLength();
|
||||||
|
|
||||||
|
if ($this->isMasked()) {
|
||||||
|
$mask = $this->getMaskingKey();
|
||||||
|
$start = $this->getPayloadStartingByte();
|
||||||
|
|
||||||
|
for ($i = 0; $i < $length; $i++) {
|
||||||
|
$payload .= $this->_data[$i + $start] ^ $mask[$i % 4];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$payload = substr($this->_data, $start, $this->getPayloadLength());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($payload) !== $length) {
|
||||||
|
// Is this possible? isCoalesced() math _should_ ensure if there is mal-formed data, it would return false
|
||||||
|
throw new \UnexpectedValueException('Payload length does not match expected length');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $payload;
|
||||||
|
}
|
||||||
|
}
|
147
Version/RFC6455/HandshakeVerifier.php
Normal file
147
Version/RFC6455/HandshakeVerifier.php
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket\Version\RFC6455;
|
||||||
|
use Guzzle\Http\Message\RequestInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These are checks to ensure the client requested handshake are valid
|
||||||
|
* Verification rules come from section 4.2.1 of the RFC6455 document
|
||||||
|
* @todo Currently just returning invalid - should consider returning appropriate HTTP status code error #s
|
||||||
|
*/
|
||||||
|
class HandshakeVerifier {
|
||||||
|
/**
|
||||||
|
* Given an array of the headers this method will run through all verification methods
|
||||||
|
* @param Guzzle\Http\Message\RequestInterface
|
||||||
|
* @return bool TRUE if all headers are valid, FALSE if 1 or more were invalid
|
||||||
|
*/
|
||||||
|
public function verifyAll(RequestInterface $request) {
|
||||||
|
$passes = 0;
|
||||||
|
|
||||||
|
$passes += (int)$this->verifyMethod($request->getMethod());
|
||||||
|
$passes += (int)$this->verifyHTTPVersion($request->getProtocolVersion());
|
||||||
|
$passes += (int)$this->verifyRequestURI($request->getPath());
|
||||||
|
$passes += (int)$this->verifyHost($request->getHeader('Host', true));
|
||||||
|
$passes += (int)$this->verifyUpgradeRequest($request->getHeader('Upgrade', true));
|
||||||
|
$passes += (int)$this->verifyConnection($request->getHeader('Connection', true));
|
||||||
|
$passes += (int)$this->verifyKey($request->getHeader('Sec-WebSocket-Key', true));
|
||||||
|
//$passes += (int)$this->verifyVersion($headers['Sec-WebSocket-Version']); // Temporarily breaking functionality
|
||||||
|
|
||||||
|
return (7 === $passes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the HTTP version passed. MUST be 1.1 or greater
|
||||||
|
* @param string|int
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function verifyHTTPVersion($val) {
|
||||||
|
return (1.1 <= (double)$val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string
|
||||||
|
* @return bool
|
||||||
|
* @todo Verify the logic here is correct
|
||||||
|
*/
|
||||||
|
public function verifyRequestURI($val) {
|
||||||
|
if ($val[0] != '/') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false !== strstr($val, '#')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mb_check_encoding($val, 'ASCII');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|null
|
||||||
|
* @return bool
|
||||||
|
* @todo Find out if I can find the master socket, ensure the port is attached to header if not 80 or 443 - not sure if this is possible, as I tried to hide it
|
||||||
|
* @todo Once I fix HTTP::getHeaders just verify this isn't NULL or empty...or manybe need to verify it's a valid domin??? Or should it equal $_SERVER['HOST'] ?
|
||||||
|
*/
|
||||||
|
public function verifyHost($val) {
|
||||||
|
return (null !== $val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the Upgrade request to WebSockets.
|
||||||
|
* @param string MUST equal "websocket"
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function verifyUpgradeRequest($val) {
|
||||||
|
return ('websocket' === $val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the Connection header
|
||||||
|
* @param string MUST equal "Upgrade"
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function verifyConnection($val) {
|
||||||
|
if ('Upgrade' === $val) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$vals = explode(',', str_replace(', ', ',', $val));
|
||||||
|
return (false !== array_search('Upgrade', $vals));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function verifyies the nonce is valid (64 big encoded, 16 bytes random string)
|
||||||
|
* @param string|null
|
||||||
|
* @return bool
|
||||||
|
* @todo The spec says we don't need to base64_decode - can I just check if the length is 24 and not decode?
|
||||||
|
*/
|
||||||
|
public function verifyKey($val) {
|
||||||
|
return (16 === strlen(base64_decode((string)$val)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify Origin matches RFC6454 IF it is set
|
||||||
|
* Origin is an optional field
|
||||||
|
* @param string|null
|
||||||
|
* @return bool
|
||||||
|
* @todo Implement verification functality - see section 4.2.1.7
|
||||||
|
*/
|
||||||
|
public function verifyOrigin($val) {
|
||||||
|
if (null === $val) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// logic here
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the version passed matches this RFC
|
||||||
|
* @param string|int MUST equal 13|"13"
|
||||||
|
* @return bool
|
||||||
|
* @todo Ran in to a problem here...I'm having HyBi use the RFC files, this breaks it! oops
|
||||||
|
*/
|
||||||
|
public function verifyVersion($val) {
|
||||||
|
return (13 === (int)$val);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo Write logic for this method. See section 4.2.1.8
|
||||||
|
*/
|
||||||
|
public function verifyProtocol($val) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo Write logic for this method. See section 4.2.1.9
|
||||||
|
*/
|
||||||
|
public function verifyExtensions($val) {
|
||||||
|
}
|
||||||
|
}
|
88
Version/RFC6455/Message.php
Normal file
88
Version/RFC6455/Message.php
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket\Version\RFC6455;
|
||||||
|
use Ratchet\WebSocket\Version\MessageInterface;
|
||||||
|
use Ratchet\WebSocket\Version\FrameInterface;
|
||||||
|
|
||||||
|
class Message implements MessageInterface {
|
||||||
|
/**
|
||||||
|
* @var SplDoublyLinkedList
|
||||||
|
*/
|
||||||
|
protected $_frames;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->_frames = new \SplDoublyLinkedList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function __toString() {
|
||||||
|
return $this->getPayload();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isCoalesced() {
|
||||||
|
if (count($this->_frames) == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$last = $this->_frames->top();
|
||||||
|
|
||||||
|
return ($last->isCoalesced() && $last->isFinal());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
* @todo Should I allow addFrame if the frame is not coalesced yet? I believe I'm assuming this class will only receive fully formed frame messages
|
||||||
|
* @todo Also, I should perhaps check the type...control frames (ping/pong/close) are not to be considered part of a message
|
||||||
|
*/
|
||||||
|
public function addFrame(FrameInterface $fragment) {
|
||||||
|
$this->_frames->push($fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getOpcode() {
|
||||||
|
if (count($this->_frames) == 0) {
|
||||||
|
throw new \UnderflowException('No frames have been added to this message');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_frames->bottom()->getOpcode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getPayloadLength() {
|
||||||
|
$len = 0;
|
||||||
|
|
||||||
|
foreach ($this->_frames as $frame) {
|
||||||
|
try {
|
||||||
|
$len += $frame->getPayloadLength();
|
||||||
|
} catch (\UnderflowException $e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getPayload() {
|
||||||
|
if (!$this->isCoalesced()) {
|
||||||
|
throw new \UnderflowMessage('Message has not been put back together yet');
|
||||||
|
}
|
||||||
|
|
||||||
|
$buffer = '';
|
||||||
|
|
||||||
|
foreach ($this->_frames as $frame) {
|
||||||
|
$buffer .= $frame->getPayload();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $buffer;
|
||||||
|
}
|
||||||
|
}
|
47
Version/VersionInterface.php
Normal file
47
Version/VersionInterface.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket\Version;
|
||||||
|
use Guzzle\Http\Message\RequestInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Despite the version iterations of WebInterface the actions they go through are similar
|
||||||
|
* This standardizes how the server handles communication with each protocol version
|
||||||
|
* @todo Need better naming conventions...newMessage and newFrame are for reading incoming framed messages (action is unframing)
|
||||||
|
* The current method names suggest you could create a new message/frame to send, which they can not do
|
||||||
|
*/
|
||||||
|
interface VersionInterface {
|
||||||
|
/**
|
||||||
|
* Given an HTTP header, determine if this version should handle the protocol
|
||||||
|
* @param Guzzle\Http\Message\RequestInterface
|
||||||
|
* @return bool
|
||||||
|
* @throws UnderflowException If the protocol thinks the headers are still fragmented
|
||||||
|
*/
|
||||||
|
static function isProtocol(RequestInterface $request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform the handshake and return the response headers
|
||||||
|
* @param Guzzle\Http\Message\RequestInterface
|
||||||
|
* @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(RequestInterface $request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return MessageInterface
|
||||||
|
*/
|
||||||
|
function newMessage();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return FrameInterface
|
||||||
|
*/
|
||||||
|
function newFrame();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string
|
||||||
|
* @param bool
|
||||||
|
* @return string
|
||||||
|
* @todo Change to use other classes, this will be removed eventually
|
||||||
|
*/
|
||||||
|
function frame($message, $mask = true);
|
||||||
|
}
|
32
WsConnection.php
Normal file
32
WsConnection.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket;
|
||||||
|
use Ratchet\AbstractConnectionDecorator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property stdClass $WebSocket
|
||||||
|
*/
|
||||||
|
class WsConnection extends AbstractConnectionDecorator {
|
||||||
|
public function send($data) {
|
||||||
|
// need frame caching
|
||||||
|
|
||||||
|
$data = $this->WebSocket->version->frame($data, false);
|
||||||
|
|
||||||
|
$this->getConnection()->send($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function close() {
|
||||||
|
// send close frame
|
||||||
|
|
||||||
|
// ???
|
||||||
|
|
||||||
|
// profit
|
||||||
|
|
||||||
|
$this->getConnection()->close(); // temporary
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ping() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pong() {
|
||||||
|
}
|
||||||
|
}
|
230
WsServer.php
Normal file
230
WsServer.php
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket;
|
||||||
|
use Ratchet\MessageComponentInterface;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
use Guzzle\Http\Message\RequestInterface;
|
||||||
|
use Ratchet\WebSocket\Guzzle\Http\Message\RequestFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The adapter to handle WebSocket requests/responses
|
||||||
|
* This is a mediator between the Server and your application to handle real-time messaging through a web browser
|
||||||
|
* @todo Separate this class into a two classes: Component and a protocol handler
|
||||||
|
* @link http://ca.php.net/manual/en/ref.http.php
|
||||||
|
* @link http://dev.w3.org/html5/websockets/
|
||||||
|
*/
|
||||||
|
class WsServer implements MessageComponentInterface {
|
||||||
|
/**
|
||||||
|
* Decorated component
|
||||||
|
* @var Ratchet\MessageComponentInterface
|
||||||
|
*/
|
||||||
|
protected $_decorating;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var SplObjectStorage
|
||||||
|
*/
|
||||||
|
protected $connections;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-entrant instances of protocol version classes
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
protected $_versions = array(
|
||||||
|
'HyBi10' => null
|
||||||
|
, 'Hixie76' => null
|
||||||
|
, 'RFC6455' => null
|
||||||
|
);
|
||||||
|
|
||||||
|
protected $_mask_payload = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For now, array_push accepted subprotocols to this array
|
||||||
|
* @deprecated
|
||||||
|
* @temporary
|
||||||
|
*/
|
||||||
|
public $accepted_subprotocols = array();
|
||||||
|
|
||||||
|
public function __construct(MessageComponentInterface $component) {
|
||||||
|
$this->_decorating = $component;
|
||||||
|
$this->connections = new \SplObjectStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onOpen(ConnectionInterface $conn) {
|
||||||
|
$conn->WebSocket = new \stdClass;
|
||||||
|
$conn->WebSocket->handshake = false;
|
||||||
|
$conn->WebSocket->headers = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do handshake, frame/unframe messages coming/going in stack
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onMessage(ConnectionInterface $from, $msg) {
|
||||||
|
if (true !== $from->WebSocket->handshake) {
|
||||||
|
if (!isset($from->WebSocket->version)) {
|
||||||
|
$from->WebSocket->headers .= $msg;
|
||||||
|
if (!$this->isMessageComplete($from->WebSocket->headers)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$headers = RequestFactory::getInstance()->fromMessage($from->WebSocket->headers);
|
||||||
|
$from->WebSocket->version = $this->getVersion($headers);
|
||||||
|
$from->WebSocket->headers = $headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
$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', ',');
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
$response->setHeader('X-Powered-By', \Ratchet\VERSION);
|
||||||
|
$header = (string)$response;
|
||||||
|
|
||||||
|
$from->send($header);
|
||||||
|
|
||||||
|
$conn = new WsConnection($from);
|
||||||
|
$this->connections->attach($from, $conn);
|
||||||
|
|
||||||
|
return $this->_decorating->onOpen($conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($from->WebSocket->message)) {
|
||||||
|
$from->WebSocket->message = $from->WebSocket->version->newMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is a frame fragment attatched to the connection, add to it
|
||||||
|
if (!isset($from->WebSocket->frame)) {
|
||||||
|
$from->WebSocket->frame = $from->WebSocket->version->newFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
$from->WebSocket->frame->addBuffer($msg);
|
||||||
|
if ($from->WebSocket->frame->isCoalesced()) {
|
||||||
|
if ($from->WebSocket->frame->getOpcode() > 2) {
|
||||||
|
$from->end();
|
||||||
|
throw new \UnexpectedValueException('Control frame support coming soon!');
|
||||||
|
}
|
||||||
|
// Check frame
|
||||||
|
// If is control frame, do your thing
|
||||||
|
// Else, add to message
|
||||||
|
// Control frames (ping, pong, close) can be sent in between a fragmented message
|
||||||
|
|
||||||
|
$from->WebSocket->message->addFrame($from->WebSocket->frame);
|
||||||
|
unset($from->WebSocket->frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($from->WebSocket->message->isCoalesced()) {
|
||||||
|
$this->_decorating->onMessage($this->connections[$from], (string)$from->WebSocket->message);
|
||||||
|
unset($from->WebSocket->message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onClose(ConnectionInterface $conn) {
|
||||||
|
// WS::onOpen is not called when the socket connects, it's call when the handshake is done
|
||||||
|
// The socket could close before WS calls onOpen, so we need to check if we've "opened" it for the developer yet
|
||||||
|
if ($this->connections->contains($conn)) {
|
||||||
|
$decor = $this->connections[$conn];
|
||||||
|
$this->connections->detach($conn);
|
||||||
|
|
||||||
|
$this->_decorating->onClose($decor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function onError(ConnectionInterface $conn, \Exception $e) {
|
||||||
|
if ($this->connections->contains($conn)) {
|
||||||
|
$this->_decorating->onError($this->connections[$conn], $e);
|
||||||
|
} else {
|
||||||
|
$conn->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect the WebSocket protocol version a client is using based on the HTTP header request
|
||||||
|
* @param string HTTP handshake request
|
||||||
|
* @return Version\VersionInterface
|
||||||
|
* @throws UnderFlowException If we think the entire header message hasn't been buffered yet
|
||||||
|
* @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(RequestInterface $request) {
|
||||||
|
foreach ($this->_versions as $name => $instance) {
|
||||||
|
if (null !== $instance) {
|
||||||
|
if ($instance::isProtocol($request)) {
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$ns = __NAMESPACE__ . "\\Version\\{$name}";
|
||||||
|
if ($ns::isProtocol($request)) {
|
||||||
|
$this->_versions[$name] = new $ns;
|
||||||
|
return $this->_versions[$name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \InvalidArgumentException('Could not identify WebSocket protocol');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string
|
||||||
|
* @return bool
|
||||||
|
* @todo Abstract, some hard coding done for (stupid) Hixie protocol
|
||||||
|
*/
|
||||||
|
protected function isMessageComplete($message) {
|
||||||
|
static $crlf = "\r\n\r\n";
|
||||||
|
|
||||||
|
$headers = (boolean)strstr($message, $crlf);
|
||||||
|
if (!$headers) {
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr($message, 'Sec-WebSocket-Key2')) {
|
||||||
|
if (8 !== strlen(substr($message, strpos($message, $crlf) + strlen($crlf)))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable a version of the WebSocket protocol *cough*Hixie76*cough*
|
||||||
|
* @param string The name of the version to disable
|
||||||
|
* @throws InvalidArgumentException If the given version does not exist
|
||||||
|
*/
|
||||||
|
public function disableVersion($name) {
|
||||||
|
if (!array_key_exists($name, $this->_versions)) {
|
||||||
|
throw new \InvalidArgumentException("Version {$name} not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($this->_versions[$name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the option to mask the payload upon sending to client
|
||||||
|
* If WebSocket is used as server, this should be false, client to true
|
||||||
|
* @param bool
|
||||||
|
* @todo User shouldn't have to know/set this, need to figure out how to do this automatically
|
||||||
|
*/
|
||||||
|
public function setMaskPayload($opt) {
|
||||||
|
$this->_mask_payload = (boolean)$opt;
|
||||||
|
}
|
||||||
|
}
|
16
WsServerInterface.php
Normal file
16
WsServerInterface.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
namespace Ratchet\WebSocket;
|
||||||
|
use Ratchet\MessageComponentInterface;
|
||||||
|
|
||||||
|
interface WsServerInterface extends MessageComponentInterface {
|
||||||
|
/**
|
||||||
|
* Currently instead of this, I'm setting header in the Connection object passed around...not sure which I like more
|
||||||
|
* @param string
|
||||||
|
*/
|
||||||
|
//function setHeaders($headers);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function getSubProtocol();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user