diff --git a/Makefile b/Makefile index 9bd90f0..4ec818f 100644 --- a/Makefile +++ b/Makefile @@ -13,16 +13,19 @@ abtests: ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver-noutf8.php 8003 StreamSelect & ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8004 LibEv & wstest -m testeeserver -w ws://localhost:8000 & + sleep 1 wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-all.json killall php wstest abtest: ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8000 StreamSelect & + sleep 1 wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-quick.json killall php profile: php -d 'xdebug.profiler_enable=1' tests/autobahn/bin/fuzzingserver.php 8000 LibEvent & + sleep 1 wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-profile.json killall php diff --git a/composer.json b/composer.json index 80a3aa5..401dec6 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "cboden/ratchet" , "type": "library" , "description": "PHP WebSocket library" - , "keywords": ["WebSockets", "Server", "Ratchet", "Sockets"] + , "keywords": ["WebSockets", "Server", "Ratchet", "Sockets", "WebSocket"] , "homepage": "http://socketo.me" , "license": "MIT" , "authors": [ @@ -22,10 +22,14 @@ "Ratchet\\": "src/Ratchet" } } + , "suggest": { + "ext-pecl_http": "^2.0" + } , "require": { - "php": ">=5.3.9" + "php": ">=5.4.2" + , "ratchet/rfc6455": "^0.2" , "react/socket": "^0.3 || ^0.4" - , "guzzle/http": "^3.6" + , "guzzlehttp/psr7": "^1.0" , "symfony/http-foundation": "^2.2|^3.0" , "symfony/routing": "^2.2|^3.0" } diff --git a/src/Ratchet/App.php b/src/Ratchet/App.php index b7d0e55..da144ec 100644 --- a/src/Ratchet/App.php +++ b/src/Ratchet/App.php @@ -113,6 +113,10 @@ class App { $decorated = $controller; } + if ($decorated instanceof WsServer) { + $decorated->enableKeepAlive($this->_server->loop, 30); + } + if ($httpHost === null) { $httpHost = $this->httpHost; } diff --git a/src/Ratchet/Http/CloseResponseTrait.php b/src/Ratchet/Http/CloseResponseTrait.php new file mode 100644 index 0000000..abdf5c4 --- /dev/null +++ b/src/Ratchet/Http/CloseResponseTrait.php @@ -0,0 +1,22 @@ + \Ratchet\VERSION + ], $additional_headers)); + + $conn->send(gPsr\str($response)); + $conn->close(); + } +} \ No newline at end of file diff --git a/src/Ratchet/Http/Guzzle/Http/Message/RequestFactory.php b/src/Ratchet/Http/Guzzle/Http/Message/RequestFactory.php deleted file mode 100644 index 8f68e5e..0000000 --- a/src/Ratchet/Http/Guzzle/Http/Message/RequestFactory.php +++ /dev/null @@ -1,34 +0,0 @@ -entityEnclosingRequestClass; - $request = new $c($method, $url, $headers); - $request->setBody(EntityBody::factory($body)); - - return $request; - } -} diff --git a/src/Ratchet/Http/HttpRequestParser.php b/src/Ratchet/Http/HttpRequestParser.php index cbb5bbd..25bf489 100644 --- a/src/Ratchet/Http/HttpRequestParser.php +++ b/src/Ratchet/Http/HttpRequestParser.php @@ -2,11 +2,11 @@ namespace Ratchet\Http; use Ratchet\MessageInterface; use Ratchet\ConnectionInterface; -use Ratchet\Http\Guzzle\Http\Message\RequestFactory; +use GuzzleHttp\Psr7 as gPsr; /** * This class receives streaming data from a client request - * and parses HTTP headers, returning a Guzzle Request object + * and parses HTTP headers, returning a PSR-7 Request object * once it's been buffered */ class HttpRequestParser implements MessageInterface { @@ -22,7 +22,7 @@ class HttpRequestParser implements MessageInterface { /** * @param \Ratchet\ConnectionInterface $context * @param string $data Data stream to buffer - * @return \Guzzle\Http\Message\RequestInterface|null + * @return \Psr\Http\Message\RequestInterface * @throws \OverflowException If the message buffer has become too large */ public function onMessage(ConnectionInterface $context, $data) { @@ -37,7 +37,7 @@ class HttpRequestParser implements MessageInterface { } if ($this->isEom($context->httpBuffer)) { - $request = RequestFactory::getInstance()->fromMessage($context->httpBuffer); + $request = $this->parse($context->httpBuffer); unset($context->httpBuffer); @@ -53,4 +53,24 @@ class HttpRequestParser implements MessageInterface { public function isEom($message) { return (boolean)strpos($message, static::EOM); } + + /** + * @param string $headers + * @return \Psr\Http\Message\RequestInterface + */ + public function parse($headers) { + if (function_exists('http_parse_message')) { + $parts = http_parse_message($headers); + + return new gPsr\Request( + $parts->requestMethod + , $parts->requestUrl + , $parts->headers + , null + , $parts->httpVersion + ); + } else { + return gPsr\parse_request($headers); + } + } } diff --git a/src/Ratchet/Http/HttpServer.php b/src/Ratchet/Http/HttpServer.php index f1b8f69..bbd8d53 100644 --- a/src/Ratchet/Http/HttpServer.php +++ b/src/Ratchet/Http/HttpServer.php @@ -2,9 +2,10 @@ namespace Ratchet\Http; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; -use Guzzle\Http\Message\Response; class HttpServer implements MessageComponentInterface { + use CloseResponseTrait; + /** * Buffers incoming HTTP requests returning a Guzzle Request when coalesced * @var HttpRequestParser @@ -72,19 +73,4 @@ class HttpServer implements MessageComponentInterface { $this->close($conn, 500); } } - - /** - * Close a connection with an HTTP response - * @param \Ratchet\ConnectionInterface $conn - * @param int $code HTTP status code - * @return null - */ - protected function close(ConnectionInterface $conn, $code = 400) { - $response = new Response($code, array( - 'X-Powered-By' => \Ratchet\VERSION - )); - - $conn->send((string)$response); - $conn->close(); - } } diff --git a/src/Ratchet/Http/HttpServerInterface.php b/src/Ratchet/Http/HttpServerInterface.php index 79b7d55..2c37c49 100644 --- a/src/Ratchet/Http/HttpServerInterface.php +++ b/src/Ratchet/Http/HttpServerInterface.php @@ -2,12 +2,12 @@ namespace Ratchet\Http; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; -use Guzzle\Http\Message\RequestInterface; +use Psr\Http\Message\RequestInterface; interface HttpServerInterface extends MessageComponentInterface { /** * @param \Ratchet\ConnectionInterface $conn - * @param \Guzzle\Http\Message\RequestInterface $request null is default because PHP won't let me overload; don't pass null!!! + * @param \Psr\Http\Message\RequestInterface $request null is default because PHP won't let me overload; don't pass null!!! * @throws \UnexpectedValueException if a RequestInterface is not passed */ public function onOpen(ConnectionInterface $conn, RequestInterface $request = null); diff --git a/src/Ratchet/Http/OriginCheck.php b/src/Ratchet/Http/OriginCheck.php index 640d3c7..2bdc0f7 100644 --- a/src/Ratchet/Http/OriginCheck.php +++ b/src/Ratchet/Http/OriginCheck.php @@ -1,9 +1,8 @@ _component = $component; $this->allowedOrigins += $allowed; } @@ -31,7 +32,7 @@ class OriginCheck implements HttpServerInterface { * {@inheritdoc} */ public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { - $header = (string)$request->getHeader('Origin'); + $header = (string)$request->getHeader('Origin')[0]; $origin = parse_url($header, PHP_URL_HOST) ?: $header; if (!in_array($origin, $this->allowedOrigins)) { @@ -61,19 +62,4 @@ class OriginCheck implements HttpServerInterface { function onError(ConnectionInterface $conn, \Exception $e) { return $this->_component->onError($conn, $e); } - - /** - * Close a connection with an HTTP response - * @param \Ratchet\ConnectionInterface $conn - * @param int $code HTTP status code - * @return null - */ - protected function close(ConnectionInterface $conn, $code = 400) { - $response = new Response($code, array( - 'X-Powered-By' => \Ratchet\VERSION - )); - - $conn->send((string)$response); - $conn->close(); - } -} +} \ No newline at end of file diff --git a/src/Ratchet/Http/Router.php b/src/Ratchet/Http/Router.php index bfc8193..8931b6e 100644 --- a/src/Ratchet/Http/Router.php +++ b/src/Ratchet/Http/Router.php @@ -1,14 +1,15 @@ getUri(); + $context = $this->_matcher->getContext(); $context->setMethod($request->getMethod()); - $context->setHost($request->getHost()); + $context->setHost($uri->getHost()); try { - $route = $this->_matcher->match($request->getPath()); + $route = $this->_matcher->match($uri->getPath()); } catch (MethodNotAllowedException $nae) { return $this->close($conn, 403); } catch (ResourceNotFoundException $nfe) { @@ -47,17 +50,15 @@ class Router implements HttpServerInterface { throw new \UnexpectedValueException('All routes must implement Ratchet\Http\HttpServerInterface'); } - $parameters = array(); + $parameters = []; foreach($route as $key => $value) { if ((is_string($key)) && ('_' !== substr($key, 0, 1))) { $parameters[$key] = $value; } } - $parameters = array_merge($parameters, $request->getQuery()->getAll()); + $parameters = array_merge($parameters, gPsr\parse_query($uri->getQuery() ?: '')); - $url = Url::factory($request->getPath()); - $url->setQuery($parameters); - $request->setUrl($url); + $request = $request->withUri($uri->withQuery(gPsr\build_query($parameters))); $conn->controller = $route['_controller']; $conn->controller->onOpen($conn, $request); @@ -87,19 +88,4 @@ class Router implements HttpServerInterface { $conn->controller->onError($conn, $e); } } - - /** - * Close a connection with an HTTP response - * @param \Ratchet\ConnectionInterface $conn - * @param int $code HTTP status code - * @return null - */ - protected function close(ConnectionInterface $conn, $code = 400) { - $response = new Response($code, array( - 'X-Powered-By' => \Ratchet\VERSION - )); - - $conn->send((string)$response); - $conn->close(); - } -} +} \ No newline at end of file diff --git a/src/Ratchet/Session/SessionProvider.php b/src/Ratchet/Session/SessionProvider.php index 043b3ab..44276c5 100644 --- a/src/Ratchet/Session/SessionProvider.php +++ b/src/Ratchet/Session/SessionProvider.php @@ -1,9 +1,8 @@ getCookie(ini_get('session.name')))) { + $sessionName = ini_get('session.name'); + + $id = array_reduce($request->getHeader('Cookie'), function($accumulator, $cookie) use ($sessionName) { + if ($accumulator) { + return $accumulator; + } + + $crumbs = $this->parseCookie($cookie); + + return isset($crumbs['cookies'][$sessionName]) ? $crumbs['cookies'][$sessionName] : false; + }, false); + + if (null === $request || false === $id) { $saveHandler = $this->_null; $id = ''; } else { @@ -148,4 +159,85 @@ class SessionProvider implements HttpServerInterface { protected function toClassCase($langDef) { return str_replace(' ', '', ucwords(str_replace('_', ' ', $langDef))); } + + /** + * Taken from Guzzle3 + */ + private static $cookieParts = array( + 'domain' => 'Domain', + 'path' => 'Path', + 'max_age' => 'Max-Age', + 'expires' => 'Expires', + 'version' => 'Version', + 'secure' => 'Secure', + 'port' => 'Port', + 'discard' => 'Discard', + 'comment' => 'Comment', + 'comment_url' => 'Comment-Url', + 'http_only' => 'HttpOnly' + ); + + /** + * Taken from Guzzle3 + */ + private function parseCookie($cookie, $host = null, $path = null, $decode = false) { + // Explode the cookie string using a series of semicolons + $pieces = array_filter(array_map('trim', explode(';', $cookie))); + + // The name of the cookie (first kvp) must include an equal sign. + if (empty($pieces) || !strpos($pieces[0], '=')) { + return false; + } + + // Create the default return array + $data = array_merge(array_fill_keys(array_keys(self::$cookieParts), null), array( + 'cookies' => array(), + 'data' => array(), + 'path' => $path ?: '/', + 'http_only' => false, + 'discard' => false, + 'domain' => $host + )); + $foundNonCookies = 0; + + // Add the cookie pieces into the parsed data array + foreach ($pieces as $part) { + + $cookieParts = explode('=', $part, 2); + $key = trim($cookieParts[0]); + + if (count($cookieParts) == 1) { + // Can be a single value (e.g. secure, httpOnly) + $value = true; + } else { + // Be sure to strip wrapping quotes + $value = trim($cookieParts[1], " \n\r\t\0\x0B\""); + if ($decode) { + $value = urldecode($value); + } + } + + // Only check for non-cookies when cookies have been found + if (!empty($data['cookies'])) { + foreach (self::$cookieParts as $mapValue => $search) { + if (!strcasecmp($search, $key)) { + $data[$mapValue] = $mapValue == 'port' ? array_map('trim', explode(',', $value)) : $value; + $foundNonCookies++; + continue 2; + } + } + } + + // If cookies have not yet been retrieved, or this value was not found in the pieces array, treat it as a + // cookie. IF non-cookies have been parsed, then this isn't a cookie, it's cookie data. Cookies then data. + $data[$foundNonCookies ? 'data' : 'cookies'][$key] = $value; + } + + // Calculate the expires date + if (!$data['expires'] && $data['max_age']) { + $data['expires'] = time() + (int) $data['max_age']; + } + + return $data; + } } diff --git a/src/Ratchet/WebSocket/Encoding/ToggleableValidator.php b/src/Ratchet/WebSocket/Encoding/ToggleableValidator.php deleted file mode 100644 index edf14bc..0000000 --- a/src/Ratchet/WebSocket/Encoding/ToggleableValidator.php +++ /dev/null @@ -1,31 +0,0 @@ -validator = new Validator; - $this->on = (boolean)$on; - } - - /** - * {@inheritdoc} - */ - public function checkEncoding($str, $encoding) { - if (!(boolean)$this->on) { - return true; - } - - return $this->validator->checkEncoding($str, $encoding); - } -} diff --git a/src/Ratchet/WebSocket/Encoding/Validator.php b/src/Ratchet/WebSocket/Encoding/Validator.php deleted file mode 100644 index 3b02230..0000000 --- a/src/Ratchet/WebSocket/Encoding/Validator.php +++ /dev/null @@ -1,93 +0,0 @@ -hasMbString = extension_loaded('mbstring'); - $this->hasIconv = extension_loaded('iconv'); - } - - /** - * @param string $str The value to check the encoding - * @param string $against The type of encoding to check against - * @return bool - */ - public function checkEncoding($str, $against) { - if ('UTF-8' == $against) { - return $this->isUtf8($str); - } - - if ($this->hasMbString) { - return mb_check_encoding($str, $against); - } elseif ($this->hasIconv) { - return ($str == iconv($against, "{$against}//IGNORE", $str)); - } - - return true; - } - - protected function isUtf8($str) { - if ($this->hasMbString) { - if (false === mb_check_encoding($str, 'UTF-8')) { - return false; - } - } elseif ($this->hasIconv) { - if ($str != iconv('UTF-8', 'UTF-8//IGNORE', $str)) { - return false; - } - } - - $state = static::UTF8_ACCEPT; - - for ($i = 0, $len = strlen($str); $i < $len; $i++) { - $state = static::$dfa[256 + ($state << 4) + static::$dfa[ord($str[$i])]]; - - if (static::UTF8_REJECT === $state) { - return false; - } - } - - return true; - } -} diff --git a/src/Ratchet/WebSocket/Encoding/ValidatorInterface.php b/src/Ratchet/WebSocket/Encoding/ValidatorInterface.php deleted file mode 100644 index 374f220..0000000 --- a/src/Ratchet/WebSocket/Encoding/ValidatorInterface.php +++ /dev/null @@ -1,12 +0,0 @@ -getHeader('Sec-WebSocket-Key2')); - } - - /** - * {@inheritdoc} - */ - public function getVersionNumber() { - return 0; - } - - /** - * @param \Guzzle\Http\Message\RequestInterface $request - * @return \Guzzle\Http\Message\Response - * @throws \UnderflowException If there hasn't been enough data received - */ - public function handshake(RequestInterface $request) { - $body = substr($request->getBody(), 0, 8); - if (8 !== strlen($body)) { - throw new \UnderflowException("Not enough data received to issue challenge response"); - } - - $challenge = $this->sign((string)$request->getHeader('Sec-WebSocket-Key1'), (string)$request->getHeader('Sec-WebSocket-Key2'), $body); - - $headers = array( - 'Upgrade' => 'WebSocket' - , 'Connection' => 'Upgrade' - , 'Sec-WebSocket-Origin' => (string)$request->getHeader('Origin') - , 'Sec-WebSocket-Location' => 'ws://' . (string)$request->getHeader('Host') . $request->getPath() - ); - - $response = new Response(101, $headers, $challenge); - $response->setStatus(101, 'WebSocket Protocol Handshake'); - - return $response; - } - - /** - * {@inheritdoc} - */ - public function upgradeConnection(ConnectionInterface $conn, MessageInterface $coalescedCallback) { - $upgraded = new Connection($conn); - - if (!isset($upgraded->WebSocket)) { - $upgraded->WebSocket = new \StdClass; - } - - $upgraded->WebSocket->coalescedCallback = $coalescedCallback; - - return $upgraded; - } - - public function onMessage(ConnectionInterface $from, $data) { - $overflow = ''; - - if (!isset($from->WebSocket->frame)) { - $from->WebSocket->frame = $this->newFrame(); - } - - $from->WebSocket->frame->addBuffer($data); - if ($from->WebSocket->frame->isCoalesced()) { - $overflow = $from->WebSocket->frame->extractOverflow(); - - $parsed = $from->WebSocket->frame->getPayload(); - unset($from->WebSocket->frame); - - $from->WebSocket->coalescedCallback->onMessage($from, $parsed); - - unset($from->WebSocket->frame); - } - - if (strlen($overflow) > 0) { - $this->onMessage($from, $overflow); - } - } - - public function newFrame() { - return new Frame; - } - - public function generateKeyNumber($key) { - if (0 === substr_count($key, ' ')) { - return 0; - } - - return preg_replace('[\D]', '', $key) / substr_count($key, ' '); - } - - protected function sign($key1, $key2, $code) { - return md5( - pack('N', $this->generateKeyNumber($key1)) - . pack('N', $this->generateKeyNumber($key2)) - . $code - , true); - } -} diff --git a/src/Ratchet/WebSocket/Version/Hixie76/Connection.php b/src/Ratchet/WebSocket/Version/Hixie76/Connection.php deleted file mode 100644 index e3d0834..0000000 --- a/src/Ratchet/WebSocket/Version/Hixie76/Connection.php +++ /dev/null @@ -1,26 +0,0 @@ -WebSocket->closing) { - $this->getConnection()->send(chr(0) . $msg . chr(255)); - } - - return $this; - } - - public function close() { - if (!$this->WebSocket->closing) { - $this->getConnection()->send(chr(255)); - $this->getConnection()->close(); - - $this->WebSocket->closing = true; - } - } -} diff --git a/src/Ratchet/WebSocket/Version/Hixie76/Frame.php b/src/Ratchet/WebSocket/Version/Hixie76/Frame.php deleted file mode 100644 index 28eb90e..0000000 --- a/src/Ratchet/WebSocket/Version/Hixie76/Frame.php +++ /dev/null @@ -1,86 +0,0 @@ -_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); - } - - public function getContents() { - return $this->_data; - } - - public function extractOverflow() { - return ''; - } -} diff --git a/src/Ratchet/WebSocket/Version/HyBi10.php b/src/Ratchet/WebSocket/Version/HyBi10.php deleted file mode 100644 index a53d338..0000000 --- a/src/Ratchet/WebSocket/Version/HyBi10.php +++ /dev/null @@ -1,15 +0,0 @@ -getHeader('Sec-WebSocket-Version'); - - return ($version >= 6 && $version < 13); - } - - public function getVersionNumber() { - return 6; - } -} diff --git a/src/Ratchet/WebSocket/Version/MessageInterface.php b/src/Ratchet/WebSocket/Version/MessageInterface.php deleted file mode 100644 index 476c091..0000000 --- a/src/Ratchet/WebSocket/Version/MessageInterface.php +++ /dev/null @@ -1,15 +0,0 @@ -_verifier = new HandshakeVerifier; - $this->setCloseCodes(); - - if (null === $validator) { - $validator = new Validator; - } - - $this->validator = $validator; - } - - /** - * {@inheritdoc} - */ - public function isProtocol(RequestInterface $request) { - $version = (int)(string)$request->getHeader('Sec-WebSocket-Version'); - - return ($this->getVersionNumber() === $version); - } - - /** - * {@inheritdoc} - */ - public function getVersionNumber() { - return 13; - } - - /** - * {@inheritdoc} - */ - public function handshake(RequestInterface $request) { - if (true !== $this->_verifier->verifyAll($request)) { - return new Response(400); - } - - return new Response(101, array( - 'Upgrade' => 'websocket' - , 'Connection' => 'Upgrade' - , 'Sec-WebSocket-Accept' => $this->sign((string)$request->getHeader('Sec-WebSocket-Key')) - )); - } - - /** - * @param \Ratchet\ConnectionInterface $conn - * @param \Ratchet\MessageInterface $coalescedCallback - * @return \Ratchet\WebSocket\Version\RFC6455\Connection - */ - public function upgradeConnection(ConnectionInterface $conn, MessageInterface $coalescedCallback) { - $upgraded = new Connection($conn); - - if (!isset($upgraded->WebSocket)) { - $upgraded->WebSocket = new \StdClass; - } - - $upgraded->WebSocket->coalescedCallback = $coalescedCallback; - - return $upgraded; - } - - /** - * @param \Ratchet\WebSocket\Version\RFC6455\Connection $from - * @param string $data - */ - public function onMessage(ConnectionInterface $from, $data) { - $overflow = ''; - - if (!isset($from->WebSocket->message)) { - $from->WebSocket->message = $this->newMessage(); - } - - // There is a frame fragment attached to the connection, add to it - if (!isset($from->WebSocket->frame)) { - $from->WebSocket->frame = $this->newFrame(); - } - - $from->WebSocket->frame->addBuffer($data); - if ($from->WebSocket->frame->isCoalesced()) { - $frame = $from->WebSocket->frame; - - if (false !== $frame->getRsv1() || - false !== $frame->getRsv2() || - false !== $frame->getRsv3() - ) { - return $from->close($frame::CLOSE_PROTOCOL); - } - - if (!$frame->isMasked()) { - return $from->close($frame::CLOSE_PROTOCOL); - } - - $opcode = $frame->getOpcode(); - - if ($opcode > 2) { - if ($frame->getPayloadLength() > 125 || !$frame->isFinal()) { - return $from->close($frame::CLOSE_PROTOCOL); - } - - switch ($opcode) { - case $frame::OP_CLOSE: - $closeCode = 0; - - $bin = $frame->getPayload(); - - if (empty($bin)) { - return $from->close(); - } - - if (strlen($bin) >= 2) { - list($closeCode) = array_merge(unpack('n*', substr($bin, 0, 2))); - } - - if (!$this->isValidCloseCode($closeCode)) { - return $from->close($frame::CLOSE_PROTOCOL); - } - - if (!$this->validator->checkEncoding(substr($bin, 2), 'UTF-8')) { - return $from->close($frame::CLOSE_BAD_PAYLOAD); - } - - return $from->close($frame); - break; - case $frame::OP_PING: - $from->send($this->newFrame($frame->getPayload(), true, $frame::OP_PONG)); - break; - case $frame::OP_PONG: - break; - default: - return $from->close($frame::CLOSE_PROTOCOL); - break; - } - - $overflow = $from->WebSocket->frame->extractOverflow(); - - unset($from->WebSocket->frame, $frame, $opcode); - - if (strlen($overflow) > 0) { - $this->onMessage($from, $overflow); - } - - return; - } - - $overflow = $from->WebSocket->frame->extractOverflow(); - - if ($frame::OP_CONTINUE == $frame->getOpcode() && 0 == count($from->WebSocket->message)) { - return $from->close($frame::CLOSE_PROTOCOL); - } - - if (count($from->WebSocket->message) > 0 && $frame::OP_CONTINUE != $frame->getOpcode()) { - return $from->close($frame::CLOSE_PROTOCOL); - } - - $from->WebSocket->message->addFrame($from->WebSocket->frame); - unset($from->WebSocket->frame); - } - - if ($from->WebSocket->message->isCoalesced()) { - $parsed = $from->WebSocket->message->getPayload(); - unset($from->WebSocket->message); - - if (!$this->validator->checkEncoding($parsed, 'UTF-8')) { - return $from->close(Frame::CLOSE_BAD_PAYLOAD); - } - - $from->WebSocket->coalescedCallback->onMessage($from, $parsed); - } - - if (strlen($overflow) > 0) { - $this->onMessage($from, $overflow); - } - } - - /** - * @return RFC6455\Message - */ - public function newMessage() { - return new Message; - } - - /** - * @param string|null $payload - * @param bool|null $final - * @param int|null $opcode - * @return RFC6455\Frame - */ - public function newFrame($payload = null, $final = null, $opcode = null) { - return new Frame($payload, $final, $opcode); - } - - /** - * Used when doing the handshake to encode the key, verifying client/server are speaking the same language - * @param string $key - * @return string - * @internal - */ - public function sign($key) { - return base64_encode(sha1($key . static::GUID, true)); - } - - /** - * Determine if a close code is valid - * @param int|string - * @return bool - */ - public function isValidCloseCode($val) { - if (array_key_exists($val, $this->closeCodes)) { - return true; - } - - if ($val >= 3000 && $val <= 4999) { - return true; - } - - return false; - } - - /** - * Creates a private lookup of valid, private close codes - */ - protected function setCloseCodes() { - $this->closeCodes[Frame::CLOSE_NORMAL] = true; - $this->closeCodes[Frame::CLOSE_GOING_AWAY] = true; - $this->closeCodes[Frame::CLOSE_PROTOCOL] = true; - $this->closeCodes[Frame::CLOSE_BAD_DATA] = true; - //$this->closeCodes[Frame::CLOSE_NO_STATUS] = true; - //$this->closeCodes[Frame::CLOSE_ABNORMAL] = true; - $this->closeCodes[Frame::CLOSE_BAD_PAYLOAD] = true; - $this->closeCodes[Frame::CLOSE_POLICY] = true; - $this->closeCodes[Frame::CLOSE_TOO_BIG] = true; - $this->closeCodes[Frame::CLOSE_MAND_EXT] = true; - $this->closeCodes[Frame::CLOSE_SRV_ERR] = true; - //$this->closeCodes[Frame::CLOSE_TLS] = true; - } -} diff --git a/src/Ratchet/WebSocket/Version/RFC6455/Frame.php b/src/Ratchet/WebSocket/Version/RFC6455/Frame.php deleted file mode 100644 index 77d1258..0000000 --- a/src/Ratchet/WebSocket/Version/RFC6455/Frame.php +++ /dev/null @@ -1,451 +0,0 @@ -defPayLen = strlen($payload); - $this->firstByte = ($final ? 128 : 0) + $opcode; - $this->secondByte = $this->defPayLen; - $this->isCoalesced = true; - - $ext = ''; - if ($this->defPayLen > 65535) { - $ext = pack('NN', 0, $this->defPayLen); - $this->secondByte = 127; - } elseif ($this->defPayLen > 125) { - $ext = pack('n', $this->defPayLen); - $this->secondByte = 126; - } - - $this->data = chr($this->firstByte) . chr($this->secondByte) . $ext . $payload; - $this->bytesRecvd = 2 + strlen($ext) + $this->defPayLen; - } - - /** - * {@inheritdoc} - */ - public function isCoalesced() { - if (true === $this->isCoalesced) { - return true; - } - - try { - $payload_length = $this->getPayloadLength(); - $payload_start = $this->getPayloadStartingByte(); - } catch (\UnderflowException $e) { - return false; - } - - $this->isCoalesced = $this->bytesRecvd >= $payload_length + $payload_start; - - return $this->isCoalesced; - } - - /** - * {@inheritdoc} - */ - public function addBuffer($buf) { - $len = strlen($buf); - - $this->data .= $buf; - $this->bytesRecvd += $len; - - if ($this->firstByte === -1 && $this->bytesRecvd !== 0) { - $this->firstByte = ord($this->data[0]); - } - - if ($this->secondByte === -1 && $this->bytesRecvd >= 2) { - $this->secondByte = ord($this->data[1]); - } - } - - /** - * {@inheritdoc} - */ - public function isFinal() { - if (-1 === $this->firstByte) { - throw new \UnderflowException('Not enough bytes received to determine if this is the final frame in message'); - } - - return 128 === ($this->firstByte & 128); - } - - /** - * @return boolean - * @throws \UnderflowException - */ - public function getRsv1() { - if (-1 === $this->firstByte) { - throw new \UnderflowException('Not enough bytes received to determine reserved bit'); - } - - return 64 === ($this->firstByte & 64); - } - - /** - * @return boolean - * @throws \UnderflowException - */ - public function getRsv2() { - if (-1 === $this->firstByte) { - throw new \UnderflowException('Not enough bytes received to determine reserved bit'); - } - - return 32 === ($this->firstByte & 32); - } - - /** - * @return boolean - * @throws \UnderflowException - */ - public function getRsv3() { - if (-1 === $this->firstByte) { - throw new \UnderflowException('Not enough bytes received to determine reserved bit'); - } - - return 16 == ($this->firstByte & 16); - } - - /** - * {@inheritdoc} - */ - public function isMasked() { - if (-1 === $this->secondByte) { - throw new \UnderflowException("Not enough bytes received ({$this->bytesRecvd}) to determine if mask is set"); - } - - return 128 === ($this->secondByte & 128); - } - - /** - * {@inheritdoc} - */ - public function getMaskingKey() { - if (!$this->isMasked()) { - return ''; - } - - $start = 1 + $this->getNumPayloadBytes(); - - if ($this->bytesRecvd < $start + static::MASK_LENGTH) { - throw new \UnderflowException('Not enough data buffered to calculate the masking key'); - } - - return substr($this->data, $start, static::MASK_LENGTH); - } - - /** - * Create a 4 byte masking key - * @return string - */ - public function generateMaskingKey() { - $mask = ''; - - for ($i = 1; $i <= static::MASK_LENGTH; $i++) { - $mask .= chr(rand(32, 126)); - } - - return $mask; - } - - /** - * Apply a mask to the payload - * @param string|null If NULL is passed a masking key will be generated - * @throws \OutOfBoundsException - * @throws \InvalidArgumentException If there is an issue with the given masking key - * @return Frame - */ - public function maskPayload($maskingKey = null) { - if (null === $maskingKey) { - $maskingKey = $this->generateMaskingKey(); - } - - if (static::MASK_LENGTH !== strlen($maskingKey)) { - throw new \InvalidArgumentException("Masking key must be " . static::MASK_LENGTH ." characters"); - } - - if (extension_loaded('mbstring') && true !== mb_check_encoding($maskingKey, 'US-ASCII')) { - throw new \OutOfBoundsException("Masking key MUST be ASCII"); - } - - $this->unMaskPayload(); - - $this->secondByte = $this->secondByte | 128; - $this->data[1] = chr($this->secondByte); - - $this->data = substr_replace($this->data, $maskingKey, $this->getNumPayloadBytes() + 1, 0); - - $this->bytesRecvd += static::MASK_LENGTH; - $this->data = substr_replace($this->data, $this->applyMask($maskingKey), $this->getPayloadStartingByte(), $this->getPayloadLength()); - - return $this; - } - - /** - * Remove a mask from the payload - * @throws \UnderFlowException If the frame is not coalesced - * @return Frame - */ - public function unMaskPayload() { - if (!$this->isCoalesced()) { - throw new \UnderflowException('Frame must be coalesced before applying mask'); - } - - if (!$this->isMasked()) { - return $this; - } - - $maskingKey = $this->getMaskingKey(); - - $this->secondByte = $this->secondByte & ~128; - $this->data[1] = chr($this->secondByte); - - $this->data = substr_replace($this->data, '', $this->getNumPayloadBytes() + 1, static::MASK_LENGTH); - - $this->bytesRecvd -= static::MASK_LENGTH; - $this->data = substr_replace($this->data, $this->applyMask($maskingKey), $this->getPayloadStartingByte(), $this->getPayloadLength()); - - return $this; - } - - /** - * Apply a mask to a string or the payload of the instance - * @param string $maskingKey The 4 character masking key to be applied - * @param string|null $payload A string to mask or null to use the payload - * @throws \UnderflowException If using the payload but enough hasn't been buffered - * @return string The masked string - */ - public function applyMask($maskingKey, $payload = null) { - if (null === $payload) { - if (!$this->isCoalesced()) { - throw new \UnderflowException('Frame must be coalesced to apply a mask'); - } - - $payload = substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength()); - } - - $applied = ''; - for ($i = 0, $len = strlen($payload); $i < $len; $i++) { - $applied .= $payload[$i] ^ $maskingKey[$i % static::MASK_LENGTH]; - } - - return $applied; - } - - /** - * {@inheritdoc} - */ - public function getOpcode() { - if (-1 === $this->firstByte) { - throw new \UnderflowException('Not enough bytes received to determine opcode'); - } - - return ($this->firstByte & ~240); - } - - /** - * 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 (-1 === $this->secondByte) { - throw new \UnderflowException('Not enough bytes received'); - } - - return $this->secondByte & 127; - } - - /** - * @return int (7|23|71) Number of bits defined for the payload length in the fame - * @throws \UnderflowException - */ - protected function getNumPayloadBits() { - if (-1 === $this->secondByte) { - 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 typo and is 64 (16+48) - if ($check === 127) { - $bits += 48; - } - - 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->defPayLen !== -1) { - return $this->defPayLen; - } - - $this->defPayLen = $this->getFirstPayloadVal(); - if ($this->defPayLen <= 125) { - return $this->getPayloadLength(); - } - - $byte_length = $this->getNumPayloadBytes(); - if ($this->bytesRecvd < 1 + $byte_length) { - $this->defPayLen = -1; - throw new \UnderflowException('Not enough data buffered to determine payload length'); - } - - $len = 0; - for ($i = 2; $i <= $byte_length; $i++) { - $len <<= 8; - $len += ord($this->data[$i]); - } - - $this->defPayLen = $len; - - return $this->getPayloadLength(); - } - - /** - * {@inheritdoc} - */ - public function getPayloadStartingByte() { - return 1 + $this->getNumPayloadBytes() + ($this->isMasked() ? static::MASK_LENGTH : 0); - } - - /** - * {@inheritdoc} - * @todo Consider not checking mask, always returning the payload, masked or not - */ - public function getPayload() { - if (!$this->isCoalesced()) { - throw new \UnderflowException('Can not return partial message'); - } - - $payload = substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength()); - - if ($this->isMasked()) { - $payload = $this->applyMask($this->getMaskingKey(), $payload); - } - - return $payload; - } - - /** - * Get the raw contents of the frame - * @todo This is untested, make sure the substr is right - trying to return the frame w/o the overflow - */ - public function getContents() { - return substr($this->data, 0, $this->getPayloadStartingByte() + $this->getPayloadLength()); - } - - /** - * Sometimes clients will concatenate more than one frame over the wire - * This method will take the extra bytes off the end and return them - * @todo Consider returning new Frame - * @return string - */ - public function extractOverflow() { - if ($this->isCoalesced()) { - $endPoint = $this->getPayloadLength(); - $endPoint += $this->getPayloadStartingByte(); - - if ($this->bytesRecvd > $endPoint) { - $overflow = substr($this->data, $endPoint); - $this->data = substr($this->data, 0, $endPoint); - - return $overflow; - } - } - - return ''; - } -} diff --git a/src/Ratchet/WebSocket/Version/RFC6455/HandshakeVerifier.php b/src/Ratchet/WebSocket/Version/RFC6455/HandshakeVerifier.php deleted file mode 100644 index fd783f6..0000000 --- a/src/Ratchet/WebSocket/Version/RFC6455/HandshakeVerifier.php +++ /dev/null @@ -1,137 +0,0 @@ -verifyMethod($request->getMethod()); - $passes += (int)$this->verifyHTTPVersion($request->getProtocolVersion()); - $passes += (int)$this->verifyRequestURI($request->getPath()); - $passes += (int)$this->verifyHost((string)$request->getHeader('Host')); - $passes += (int)$this->verifyUpgradeRequest((string)$request->getHeader('Upgrade')); - $passes += (int)$this->verifyConnection((string)$request->getHeader('Connection')); - $passes += (int)$this->verifyKey((string)$request->getHeader('Sec-WebSocket-Key')); - //$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 - */ - public function verifyMethod($val) { - return ('get' === strtolower($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 - */ - public function verifyRequestURI($val) { - if ($val[0] != '/') { - return false; - } - - if (false !== strstr($val, '#')) { - return false; - } - - if (!extension_loaded('mbstring')) { - return true; - } - - return mb_check_encoding($val, 'US-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 maybe need to verify it's a valid domain??? Or should it equal $_SERVER['HOST'] ? - */ - public function verifyHost($val) { - return (null !== $val); - } - - /** - * Verify the Upgrade request to WebSockets. - * @param string $val MUST equal "websocket" - * @return bool - */ - public function verifyUpgradeRequest($val) { - return ('websocket' === strtolower($val)); - } - - /** - * Verify the Connection header - * @param string $val MUST equal "Upgrade" - * @return bool - */ - public function verifyConnection($val) { - $val = strtolower($val); - - if ('upgrade' === $val) { - return true; - } - - $vals = explode(',', str_replace(', ', ',', $val)); - - return (false !== array_search('upgrade', $vals)); - } - - /** - * This function verifies 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? - * @todo Check the spec to see what the encoding of the key could be - */ - public function verifyKey($val) { - return (16 === strlen(base64_decode((string)$val))); - } - - /** - * 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) { - } -} diff --git a/src/Ratchet/WebSocket/Version/RFC6455/Message.php b/src/Ratchet/WebSocket/Version/RFC6455/Message.php deleted file mode 100644 index a839f2d..0000000 --- a/src/Ratchet/WebSocket/Version/RFC6455/Message.php +++ /dev/null @@ -1,107 +0,0 @@ -_frames = new \SplDoublyLinkedList; - } - - /** - * {@inheritdoc} - */ - public function count() { - return count($this->_frames); - } - - /** - * {@inheritdoc} - */ - public function isCoalesced() { - if (count($this->_frames) == 0) { - return false; - } - - $last = $this->_frames->top(); - - return ($last->isCoalesced() && $last->isFinal()); - } - - /** - * {@inheritdoc} - * @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); - - return $this; - } - - /** - * {@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) { - // Not an error, want the current amount buffered - } - } - - return $len; - } - - /** - * {@inheritdoc} - */ - public function getPayload() { - if (!$this->isCoalesced()) { - throw new \UnderflowException('Message has not been put back together yet'); - } - - $buffer = ''; - - foreach ($this->_frames as $frame) { - $buffer .= $frame->getPayload(); - } - - return $buffer; - } - - /** - * {@inheritdoc} - */ - public function getContents() { - if (!$this->isCoalesced()) { - throw new \UnderflowException("Message has not been put back together yet"); - } - - $buffer = ''; - - foreach ($this->_frames as $frame) { - $buffer .= $frame->getContents(); - } - - return $buffer; - } -} diff --git a/src/Ratchet/WebSocket/Version/VersionInterface.php b/src/Ratchet/WebSocket/Version/VersionInterface.php deleted file mode 100644 index 5bbe534..0000000 --- a/src/Ratchet/WebSocket/Version/VersionInterface.php +++ /dev/null @@ -1,57 +0,0 @@ -versions as $version) { - if ($version->isProtocol($request)) { - return $version; - } - } - - throw new \InvalidArgumentException("Version not found"); - } - - /** - * @param \Guzzle\Http\Message\RequestInterface - * @return bool - */ - public function isVersionEnabled(RequestInterface $request) { - foreach ($this->versions as $version) { - if ($version->isProtocol($request)) { - return true; - } - } - - return false; - } - - /** - * Enable support for a specific version of the WebSocket protocol - * @param \Ratchet\WebSocket\Version\VersionInterface $version - * @return VersionManager - */ - public function enableVersion(VersionInterface $version) { - $this->versions[$version->getVersionNumber()] = $version; - - if (empty($this->versionString)) { - $this->versionString = (string)$version->getVersionNumber(); - } else { - $this->versionString .= ", {$version->getVersionNumber()}"; - } - - return $this; - } - - /** - * Disable support for a specific WebSocket protocol version - * @param int $versionId The version ID to un-support - * @return VersionManager - */ - public function disableVersion($versionId) { - unset($this->versions[$versionId]); - - $this->versionString = implode(',', array_keys($this->versions)); - - return $this; - } - - /** - * Get a string of version numbers supported (comma delimited) - * @return string - */ - public function getSupportedVersionString() { - return $this->versionString; - } -} diff --git a/src/Ratchet/WebSocket/Version/RFC6455/Connection.php b/src/Ratchet/WebSocket/WsConnection.php similarity index 78% rename from src/Ratchet/WebSocket/Version/RFC6455/Connection.php rename to src/Ratchet/WebSocket/WsConnection.php index a17e382..d2d04ef 100644 --- a/src/Ratchet/WebSocket/Version/RFC6455/Connection.php +++ b/src/Ratchet/WebSocket/WsConnection.php @@ -1,13 +1,14 @@ WebSocket->closing) { diff --git a/src/Ratchet/WebSocket/WsServer.php b/src/Ratchet/WebSocket/WsServer.php index 5783789..61d42b6 100644 --- a/src/Ratchet/WebSocket/WsServer.php +++ b/src/Ratchet/WebSocket/WsServer.php @@ -3,10 +3,17 @@ namespace Ratchet\WebSocket; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; use Ratchet\Http\HttpServerInterface; -use Guzzle\Http\Message\RequestInterface; -use Guzzle\Http\Message\Response; -use Ratchet\WebSocket\Version; -use Ratchet\WebSocket\Encoding\ToggleableValidator; +use Ratchet\Http\CloseResponseTrait; +use Psr\Http\Message\RequestInterface; +use Ratchet\RFC6455\Messaging\MessageInterface; +use Ratchet\RFC6455\Messaging\FrameInterface; +use Ratchet\RFC6455\Messaging\Frame; +use Ratchet\RFC6455\Messaging\MessageBuffer; +use Ratchet\RFC6455\Messaging\CloseFrameChecker; +use Ratchet\RFC6455\Handshake\ServerNegotiator; +use Ratchet\RFC6455\Handshake\RequestVerifier; +use React\EventLoop\LoopInterface; +use GuzzleHttp\Psr7 as gPsr; /** * The adapter to handle WebSocket requests/responses @@ -15,18 +22,13 @@ use Ratchet\WebSocket\Encoding\ToggleableValidator; * @link http://dev.w3.org/html5/websockets/ */ class WsServer implements HttpServerInterface { - /** - * Manage the various WebSocket versions to support - * @var VersionManager - * @note May not expose this in the future, may do through facade methods - */ - public $versioner; + use CloseResponseTrait; /** * Decorated component * @var \Ratchet\MessageComponentInterface */ - public $component; + private $delegate; /** * @var \SplObjectStorage @@ -34,38 +36,47 @@ class WsServer implements HttpServerInterface { protected $connections; /** - * Holder of accepted protocols, implement through WampServerInterface + * @var \Ratchet\RFC6455\Messaging\CloseFrameChecker */ - protected $acceptedSubProtocols = array(); + private $closeFrameChecker; /** - * UTF-8 validator - * @var \Ratchet\WebSocket\Encoding\ValidatorInterface + * @var \Ratchet\RFC6455\Handshake\ServerNegotiator */ - protected $validator; + private $handshakeNegotiator; /** - * Flag if we have checked the decorated component for sub-protocols - * @var boolean + * @var \Closure */ - private $isSpGenerated = false; + private $ueFlowFactory; + + /** + * @var \Closure + */ + private $pongReceiver; /** * @param \Ratchet\MessageComponentInterface $component Your application to run with WebSockets - * If you want to enable sub-protocols have your component implement WsServerInterface as well + * If you want to enable sub-protocols have your component implement WsServerInterface as well */ public function __construct(MessageComponentInterface $component) { - $this->versioner = new VersionManager; - $this->validator = new ToggleableValidator; - - $this->versioner - ->enableVersion(new Version\RFC6455($this->validator)) - ->enableVersion(new Version\HyBi10($this->validator)) - ->enableVersion(new Version\Hixie76) - ; - - $this->component = $component; + $this->delegate = $component; $this->connections = new \SplObjectStorage; + + $this->closeFrameChecker = new CloseFrameChecker; + $this->handshakeNegotiator = new ServerNegotiator(new RequestVerifier); + $this->handshakeNegotiator->setStrictSubProtocolCheck(true); + + if ($component instanceof WsServerInterface) { + $this->handshakeNegotiator->setSupportedSubProtocols($component->getSubProtocols()); + } + + $this->pongReceiver = function() {}; + + $reusableUnderflowException = new \UnderflowException; + $this->ueFlowFactory = function() use ($reusableUnderflowException) { + return $reusableUnderflowException; + }; } /** @@ -76,12 +87,37 @@ class WsServer implements HttpServerInterface { throw new \UnexpectedValueException('$request can not be null'); } - $conn->WebSocket = new \StdClass; - $conn->WebSocket->request = $request; - $conn->WebSocket->established = false; - $conn->WebSocket->closing = false; + $conn->httpRequest = $request; // This will replace ->WebSocket->request - $this->attemptUpgrade($conn); + $conn->WebSocket = new \StdClass; + $conn->WebSocket->closing = false; + $conn->WebSocket->request = $request; // deprecated + + $response = $this->handshakeNegotiator->handshake($request)->withHeader('X-Powered-By', \Ratchet\VERSION); + + $conn->send(gPsr\str($response)); + + if (101 !== $response->getStatusCode()) { + return $conn->close(); + } + + $wsConn = new WsConnection($conn); + + $streamer = new MessageBuffer( + $this->closeFrameChecker, + function(MessageInterface $msg) use ($wsConn) { + $this->delegate->onMessage($wsConn, $msg); + }, + function(FrameInterface $frame) use ($wsConn) { + $this->onControlFrame($frame, $wsConn); + }, + true, + $this->ueFlowFactory + ); + + $this->connections->attach($conn, [$wsConn, $streamer]); + + return $this->delegate->onOpen($wsConn); } /** @@ -92,50 +128,8 @@ class WsServer implements HttpServerInterface { return; } - if (true === $from->WebSocket->established) { - return $from->WebSocket->version->onMessage($this->connections[$from], $msg); - } - - $this->attemptUpgrade($from, $msg); - } - - protected function attemptUpgrade(ConnectionInterface $conn, $data = '') { - if ('' !== $data) { - $conn->WebSocket->request->getBody()->write($data); - } - - if (!$this->versioner->isVersionEnabled($conn->WebSocket->request)) { - return $this->close($conn); - } - - $conn->WebSocket->version = $this->versioner->getVersion($conn->WebSocket->request); - - try { - $response = $conn->WebSocket->version->handshake($conn->WebSocket->request); - } catch (\UnderflowException $e) { - return; - } - - if (null !== ($subHeader = $conn->WebSocket->request->getHeader('Sec-WebSocket-Protocol'))) { - if ('' !== ($agreedSubProtocols = $this->getSubProtocolString($subHeader->normalize()))) { - $response->setHeader('Sec-WebSocket-Protocol', $agreedSubProtocols); - } - } - - $response->setHeader('X-Powered-By', \Ratchet\VERSION); - $conn->send((string)$response); - - if (101 != $response->getStatusCode()) { - return $conn->close(); - } - - $upgraded = $conn->WebSocket->version->upgradeConnection($conn, $this->component); - - $this->connections->attach($conn, $upgraded); - - $upgraded->WebSocket->established = true; - - return $this->component->onOpen($upgraded); + $context = $this->connections[$from]; + $context[1]->onData($msg); } /** @@ -143,10 +137,10 @@ class WsServer implements HttpServerInterface { */ public function onClose(ConnectionInterface $conn) { if ($this->connections->contains($conn)) { - $decor = $this->connections[$conn]; + $context = $this->connections[$conn]; $this->connections->detach($conn); - $this->component->onClose($decor); + $this->delegate->onClose($context[0]); } } @@ -154,79 +148,59 @@ class WsServer implements HttpServerInterface { * {@inheritdoc} */ public function onError(ConnectionInterface $conn, \Exception $e) { - if ($conn->WebSocket->established && $this->connections->contains($conn)) { - $this->component->onError($this->connections[$conn], $e); + if ($this->connections->contains($conn)) { + $context = $this->connections[$conn]; + $this->delegate->onError($context[0], $e); } else { $conn->close(); } } - /** - * Disable a specific version of the WebSocket protocol - * @param int $versionId Version ID to disable - * @return WsServer - */ - public function disableVersion($versionId) { - $this->versioner->disableVersion($versionId); - - return $this; - } - - /** - * Toggle weather to check encoding of incoming messages - * @param bool - * @return WsServer - */ - public function setEncodingChecks($opt) { - $this->validator->on = (boolean)$opt; - - return $this; - } - - /** - * @param string - * @return boolean - */ - public function isSubProtocolSupported($name) { - if (!$this->isSpGenerated) { - if ($this->component instanceof WsServerInterface) { - $this->acceptedSubProtocols = array_flip($this->component->getSubProtocols()); - } - - $this->isSpGenerated = true; + public function onControlFrame(FrameInterface $frame, WsConnection $conn) { + switch ($frame->getOpCode()) { + case Frame::OP_CLOSE: + $conn->close($frame); + break; + case Frame::OP_PING: + $conn->send(new Frame($frame->getPayload(), true, Frame::OP_PONG)); + break; + case Frame::OP_PONG: + $pongReceiver = $this->pongReceiver; + $pongReceiver($frame, $conn); + break; } - - return array_key_exists($name, $this->acceptedSubProtocols); } - /** - * @param \Traversable|null $requested - * @return string - */ - protected function getSubProtocolString(\Traversable $requested = null) { - if (null !== $requested) { - foreach ($requested as $sub) { - if ($this->isSubProtocolSupported($sub)) { - return $sub; - } + public function setStrictSubProtocolCheck($enable) { + $this->handshakeNegotiator->setStrictSubProtocolCheck($enable); + } + + public function enableKeepAlive(LoopInterface $loop, $interval = 30) { + $lastPing = null; + $pingedConnections = new \SplObjectStorage; + $splClearer = new \SplObjectStorage; + + $this->pongReceiver = function(FrameInterface $frame, $wsConn) use ($pingedConnections, &$lastPing) { + if ($frame->getPayload() === $lastPing->getPayload()) { + $pingedConnections->detach($wsConn); } - } + }; - return ''; - } + $loop->addPeriodicTimer((int)$interval, function() use ($pingedConnections, &$lastPing, $splClearer) { + foreach ($pingedConnections as $wsConn) { + $wsConn->close(); + } + $pingedConnections->removeAllExcept($splClearer); - /** - * Close a connection with an HTTP response - * @param \Ratchet\ConnectionInterface $conn - * @param int $code HTTP status code - */ - protected function close(ConnectionInterface $conn, $code = 400) { - $response = new Response($code, array( - 'Sec-WebSocket-Version' => $this->versioner->getSupportedVersionString() - , 'X-Powered-By' => \Ratchet\VERSION - )); + $lastPing = new Frame(uniqid(), true, Frame::OP_PING); - $conn->send((string)$response); - $conn->close(); - } + foreach ($this->connections as $key => $conn) { + $context = $this->connections[$conn]; + $wsConn = $context[0]; + + $wsConn->send($lastPing); + $pingedConnections->attach($wsConn); + } + }); + } } diff --git a/tests/integration/GuzzleTest.php b/tests/integration/GuzzleTest.php deleted file mode 100644 index 5e4d8aa..0000000 --- a/tests/integration/GuzzleTest.php +++ /dev/null @@ -1,53 +0,0 @@ - 'websocket' - , 'Connection' => 'Upgrade' - , 'Host' => 'localhost:8080' - , 'Origin' => 'chrome://newtab' - , 'Sec-WebSocket-Protocol' => 'one, two, three' - , 'Sec-WebSocket-Key' => '9bnXNp3ae6FbFFRtPdiPXA==' - , 'Sec-WebSocket-Version' => '13' - ); - - public function setUp() { - $this->_request = new Request('GET', 'http://localhost', $this->_headers); - } - - public function testGetHeaderString() { - $this->assertEquals('Upgrade', (string)$this->_request->getHeader('connection')); - $this->assertEquals('9bnXNp3ae6FbFFRtPdiPXA==', (string)$this->_request->getHeader('Sec-Websocket-Key')); - } - - public function testGetHeaderInteger() { - $this->assertSame('13', (string)$this->_request->getHeader('Sec-Websocket-Version')); - $this->assertSame(13, (int)(string)$this->_request->getHeader('Sec-WebSocket-Version')); - } - - public function testGetHeaderObject() { - $this->assertInstanceOf('Guzzle\Http\Message\Header', $this->_request->getHeader('Origin')); - $this->assertNull($this->_request->getHeader('Non-existant-header')); - } - - public function testHeaderObjectNormalizeValues() { - $expected = 1 + substr_count($this->_headers['Sec-WebSocket-Protocol'], ','); - $protocols = $this->_request->getHeader('Sec-WebSocket-Protocol')->normalize(); - $count = 0; - - foreach ($protocols as $protocol) { - $count++; - } - - $this->assertEquals($expected, $count); - $this->assertEquals($expected, count($protocols)); - } - - public function testRequestFactoryCreateSignature() { - $ref = new \ReflectionMethod('Guzzle\Http\Message\RequestFactory', 'create'); - $this->assertEquals(2, $ref->getNumberOfRequiredParameters()); - } -} diff --git a/tests/unit/Http/Guzzle/Http/Message/RequestFactoryTest.php b/tests/unit/Http/Guzzle/Http/Message/RequestFactoryTest.php deleted file mode 100644 index 7860f72..0000000 --- a/tests/unit/Http/Guzzle/Http/Message/RequestFactoryTest.php +++ /dev/null @@ -1,67 +0,0 @@ -factory = RequestFactory::getInstance(); - } - - public function testMessageProvider() { - return array( - 'status' => 'GET / HTTP/1.1' - , 'headers' => array( - 'Upgrade' => 'WebSocket' - , 'Connection' => 'Upgrade' - , 'Host' => 'localhost:8000' - , 'Sec-WebSocket-Key1' => '> b3lU Z0 fh f 3+83394 6 (zG4' - , 'Sec-WebSocket-Key2' => ',3Z0X0677 dV-d [159 Z*4' - ) - , 'body' => "123456\r\n\r\n" - ); - } - - public function combineMessage($status, array $headers, $body = '') { - $message = $status . "\r\n"; - - foreach ($headers as $key => $val) { - $message .= "{$key}: {$val}\r\n"; - } - - $message .= "\r\n{$body}"; - - return $message; - } - - public function testExpectedDataFromGuzzleHeaders() { - $parts = $this->testMessageProvider(); - $message = $this->combineMessage($parts['status'], $parts['headers'], $parts['body']); - $object = $this->factory->fromMessage($message); - - foreach ($parts['headers'] as $key => $val) { - $this->assertEquals($val, $object->getHeader($key, true)); - } - } - - public function testExpectedDataFromNonGuzzleHeaders() { - $parts = $this->testMessageProvider(); - $message = $this->combineMessage($parts['status'], $parts['headers'], $parts['body']); - $object = $this->factory->fromMessage($message); - - $this->assertNull($object->getHeader('Nope', true)); - $this->assertNull($object->getHeader('Nope')); - } - - public function testExpectedDataFromNonGuzzleBody() { - $parts = $this->testMessageProvider(); - $message = $this->combineMessage($parts['status'], $parts['headers'], $parts['body']); - $object = $this->factory->fromMessage($message); - - $this->assertEquals($parts['body'], (string)$object->getBody()); - } -} diff --git a/tests/unit/Http/HttpRequestParserTest.php b/tests/unit/Http/HttpRequestParserTest.php index 4df7d8d..6af8402 100644 --- a/tests/unit/Http/HttpRequestParserTest.php +++ b/tests/unit/Http/HttpRequestParserTest.php @@ -1,6 +1,5 @@ getMock('\Ratchet\ConnectionInterface'); $return = $this->parser->onMessage($conn, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n"); - $this->assertInstanceOf('\Guzzle\Http\Message\RequestInterface', $return); + $this->assertInstanceOf('\Psr\Http\Message\RequestInterface', $return); } } diff --git a/tests/unit/Http/OriginCheckTest.php b/tests/unit/Http/OriginCheckTest.php index 34db439..c1c4012 100644 --- a/tests/unit/Http/OriginCheckTest.php +++ b/tests/unit/Http/OriginCheckTest.php @@ -9,8 +9,8 @@ class OriginCheckTest extends AbstractMessageComponentTestCase { protected $_reqStub; public function setUp() { - $this->_reqStub = $this->getMock('Guzzle\Http\Message\RequestInterface'); - $this->_reqStub->expects($this->any())->method('getHeader')->will($this->returnValue('localhost')); + $this->_reqStub = $this->getMock('Psr\Http\Message\RequestInterface'); + $this->_reqStub->expects($this->any())->method('getHeader')->will($this->returnValue(['localhost'])); parent::setUp(); @@ -34,7 +34,7 @@ class OriginCheckTest extends AbstractMessageComponentTestCase { } public function testCloseOnNonMatchingOrigin() { - $this->_serv->allowedOrigins = array('socketo.me'); + $this->_serv->allowedOrigins = ['socketo.me']; $this->_conn->expects($this->once())->method('close'); $this->_serv->onOpen($this->_conn, $this->_reqStub); diff --git a/tests/unit/Http/RouterTest.php b/tests/unit/Http/RouterTest.php index 5a1128e..0799519 100644 --- a/tests/unit/Http/RouterTest.php +++ b/tests/unit/Http/RouterTest.php @@ -1,6 +1,5 @@ getMock('Guzzle\Http\QueryString'); - $queryMock - ->expects($this->any()) - ->method('getAll') - ->will($this->returnValue(array())); - - $this->_conn = $this->getMock('\Ratchet\ConnectionInterface'); - $this->_req = $this->getMock('\Guzzle\Http\Message\RequestInterface'); + $this->_conn = $this->getMock('\Ratchet\ConnectionInterface'); + $this->_uri = $this->getMock('Psr\Http\Message\UriInterface'); + $this->_req = $this->getMock('\Psr\Http\Message\RequestInterface'); $this->_req ->expects($this->any()) - ->method('getQuery') - ->will($this->returnValue($queryMock)); + ->method('getUri') + ->will($this->returnValue($this->_uri)); $this->_matcher = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface'); $this->_matcher ->expects($this->any()) @@ -34,7 +29,14 @@ class RouterTest extends \PHPUnit_Framework_TestCase { ->will($this->returnValue($this->getMock('Symfony\Component\Routing\RequestContext'))); $this->_router = new Router($this->_matcher); - $this->_req->expects($this->any())->method('getPath')->will($this->returnValue('/whatever')); + $this->_uri->expects($this->any())->method('getPath')->will($this->returnValue('ws://doesnt.matter/')); + $this->_uri->expects($this->any())->method('withQuery')->with($this->callback(function($val) { + $this->setResult($val); + + return true; + }))->will($this->returnSelf()); + $this->_uri->expects($this->any())->method('getQuery')->will($this->returnCallback([$this, 'getResult'])); + $this->_req->expects($this->any())->method('withUri')->will($this->returnSelf()); } public function testFourOhFour() { @@ -103,38 +105,39 @@ class RouterTest extends \PHPUnit_Framework_TestCase { $controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); /** @var $matcher UrlMatcherInterface */ $this->_matcher->expects($this->any())->method('match')->will( - $this->returnValue(array('_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux')) + $this->returnValue(['_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux']) ); $conn = $this->getMock('Ratchet\Mock\Connection'); - $request = $this->getMock('Guzzle\Http\Message\Request', array('getPath'), array('GET', 'ws://random.url'), '', false); - $request->expects($this->any())->method('getPath')->will($this->returnValue('ws://doesnt.matter/')); - - $request->setHeaderFactory($this->getMock('Guzzle\Http\Message\Header\HeaderFactoryInterface')); - $request->setUrl('ws://doesnt.matter/'); - $router = new Router($this->_matcher); - $router->onOpen($conn, $request); + $router->onOpen($conn, $this->_req); - $this->assertEquals(array('foo' => 'bar', 'baz' => 'qux'), $request->getQuery()->getAll()); + $this->assertEquals('foo=bar&baz=qux', $this->_req->getUri()->getQuery()); } public function testQueryParams() { $controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); $this->_matcher->expects($this->any())->method('match')->will( - $this->returnValue(array('_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux')) + $this->returnValue(['_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux']) ); $conn = $this->getMock('Ratchet\Mock\Connection'); - $request = $this->getMock('Guzzle\Http\Message\Request', array('getPath'), array('GET', ''), '', false); + $request = $this->getMock('Psr\Http\Message\RequestInterface'); + $uri = new \GuzzleHttp\Psr7\Uri('ws://doesnt.matter/endpoint?hello=world&foo=nope'); - $request->setHeaderFactory($this->getMock('Guzzle\Http\Message\Header\HeaderFactoryInterface')); - $request->setUrl('ws://doesnt.matter?hello=world&foo=nope'); + $request->expects($this->any())->method('getUri')->will($this->returnCallback(function() use (&$uri) { + return $uri; + })); + $request->expects($this->any())->method('withUri')->with($this->callback(function($url) use (&$uri) { + $uri = $url; + + return true; + }))->will($this->returnSelf()); $router = new Router($this->_matcher); $router->onOpen($conn, $request); - $this->assertEquals(array('foo' => 'nope', 'baz' => 'qux', 'hello' => 'world'), $request->getQuery()->getAll()); + $this->assertEquals('foo=nope&baz=qux&hello=world', $request->getUri()->getQuery()); } } diff --git a/tests/unit/Session/SessionComponentTest.php b/tests/unit/Session/SessionComponentTest.php index e889637..ebfdde4 100644 --- a/tests/unit/Session/SessionComponentTest.php +++ b/tests/unit/Session/SessionComponentTest.php @@ -1,7 +1,6 @@ getMock($this->getComponentClassString()), $pdoHandler, array('auto_start' => 1)); $connection = $this->getMock('Ratchet\\ConnectionInterface'); - $headers = $this->getMock('Guzzle\\Http\\Message\\Request', array('getCookie'), array('POST', '/', array())); - $headers->expects($this->once())->method('getCookie', array(ini_get('session.name')))->will($this->returnValue($sessionId)); + $headers = $this->getMock('Psr\Http\Message\RequestInterface'); + $headers->expects($this->once())->method('getHeader')->will($this->returnValue([ini_get('session.name') . "={$sessionId};"])); $component->onOpen($connection, $headers); @@ -94,7 +93,7 @@ class SessionProviderTest extends AbstractMessageComponentTestCase { protected function newConn() { $conn = $this->getMock('Ratchet\ConnectionInterface'); - $headers = $this->getMock('Guzzle\Http\Message\Request', array('getCookie'), array('POST', '/', array())); + $headers = $this->getMock('Psr\Http\Message\Request', array('getCookie'), array('POST', '/', array())); $headers->expects($this->once())->method('getCookie', array(ini_get('session.name')))->will($this->returnValue(null)); return $conn; @@ -115,4 +114,11 @@ class SessionProviderTest extends AbstractMessageComponentTestCase { $this->setExpectedException('\RuntimeException'); new SessionProvider($this->getMock($this->getComponentClassString()), $this->getMock('\SessionHandlerInterface')); } + + protected function doOpen($conn) { + $request = $this->getMock('Psr\Http\Message\RequestInterface'); + $request->expects($this->any())->method('getHeader')->will($this->returnValue([])); + + $this->_serv->onOpen($conn, $request); + } } diff --git a/tests/unit/WebSocket/Version/Hixie76Test.php b/tests/unit/WebSocket/Version/Hixie76Test.php deleted file mode 100644 index 75998aa..0000000 --- a/tests/unit/WebSocket/Version/Hixie76Test.php +++ /dev/null @@ -1,103 +0,0 @@ -_version = new Hixie76; - } - - public function testClassImplementsVersionInterface() { - $constraint = $this->isInstanceOf('\\Ratchet\\WebSocket\\Version\\VersionInterface'); - $this->assertThat($this->_version, $constraint); - } - - /** - * @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(906585445, '3e6b263 4 17 80') - , array(0, '3e6b26341780') - ); - } - - public function headerProvider() { - $key1 = base64_decode('QTN+ICszNiA2IDJvICBWOG4gNyAgc08yODhZ'); - $key2 = base64_decode('TzEyICAgeVsgIFFSNDUgM1IgLiAyOFggNC00dn4z'); - - $headers = "GET / HTTP/1.1"; - $headers .= "Upgrade: WebSocket{$this->_crlf}"; - $headers .= "Connection: Upgrade{$this->_crlf}"; - $headers .= "Host: socketo.me{$this->_crlf}"; - $headers .= "Origin: http://fiddle.jshell.net{$this->_crlf}"; - $headers .= "Sec-WebSocket-Key1:17 Z4< F94 N3 7P41 7{$this->_crlf}"; - $headers .= "Sec-WebSocket-Key2:1 23C3:,2% 1-29 4 f0{$this->_crlf}"; - $headers .= "(Key3):70:00:EE:6E:33:20:90:69{$this->_crlf}"; - $headers .= $this->_crlf; - - return $headers; - } - - public function testNoUpgradeBeforeBody() { - $headers = $this->headerProvider(); - - $mockConn = $this->getMock('\Ratchet\ConnectionInterface'); - $mockApp = $this->getMock('\Ratchet\MessageComponentInterface'); - - $server = new HttpServer(new WsServer($mockApp)); - $server->onOpen($mockConn); - $mockApp->expects($this->exactly(0))->method('onOpen'); - $server->onMessage($mockConn, $headers); - } - - public function testTcpFragmentedUpgrade() { - $headers = $this->headerProvider(); - $body = base64_decode($this->_body); - - $mockConn = $this->getMock('\Ratchet\ConnectionInterface'); - $mockApp = $this->getMock('\Ratchet\MessageComponentInterface'); - - $server = new HttpServer(new WsServer($mockApp)); - $server->onOpen($mockConn); - $server->onMessage($mockConn, $headers); - - $mockApp->expects($this->once())->method('onOpen'); - $server->onMessage($mockConn, $body . $this->_crlf . $this->_crlf); - } - - public function testTcpFragmentedBodyUpgrade() { - $headers = $this->headerProvider(); - $body = base64_decode($this->_body); - $body1 = substr($body, 0, 4); - $body2 = substr($body, 4); - - $mockConn = $this->getMock('\Ratchet\ConnectionInterface'); - $mockApp = $this->getMock('\Ratchet\MessageComponentInterface'); - - $server = new HttpServer(new WsServer($mockApp)); - $server->onOpen($mockConn); - $server->onMessage($mockConn, $headers); - - $mockApp->expects($this->once())->method('onOpen'); - - $server->onMessage($mockConn, $body1); - $server->onMessage($mockConn, $body2); - $server->onMessage($mockConn, $this->_crlf . $this->_crlf); - } -} diff --git a/tests/unit/WebSocket/Version/HyBi10Test.php b/tests/unit/WebSocket/Version/HyBi10Test.php deleted file mode 100644 index 1d9e8a9..0000000 --- a/tests/unit/WebSocket/Version/HyBi10Test.php +++ /dev/null @@ -1,67 +0,0 @@ -_version = new HyBi10(); - } - - /** - * Is this useful? - */ - public function testClassImplementsVersionInterface() { - $constraint = $this->isInstanceOf('\\Ratchet\\WebSocket\\Version\\VersionInterface'); - $this->assertThat($this->_version, $constraint); - } - - /** - * @dataProvider HandshakeProvider - */ - public function testKeySigningForHandshake($key, $accept) { - $this->assertEquals($accept, $this->_version->sign($key)); - } - - public static function HandshakeProvider() { - return array( - array('x3JJHMbDL1EzLkh9GBhXDw==', 'HSmrc0sMlYUkAGmm5OPpG2HaGWk=') - , array('dGhlIHNhbXBsZSBub25jZQ==', 's3pPLMBiTxaQ9kYGzzhZRbK+xOo=') - ); - } - - /** - * @dataProvider UnframeMessageProvider - */ - public function testUnframeMessage($message, $framed) { -// $decoded = $this->_version->unframe(base64_decode($framed)); - $frame = new Frame; - $frame->addBuffer(base64_decode($framed)); - - $this->assertEquals($message, $frame->getPayload()); - } - - public static function UnframeMessageProvider() { - return array( - array('Hello World!', 'gYydAIfa1WXrtvIg0LXvbOP7') - , array('!@#$%^&*()-=_+[]{}\|/.,<>`~', 'gZv+h96r38f9j9vZ+IHWrvOWoayF9oX6gtfRqfKXwOeg') - , array('ಠ_ಠ', 'gYfnSpu5B/g75gf4Ow==') - , array("The quick brown fox jumps over the lazy dog. All work and no play makes Chris a dull boy. I'm trying to get past 128 characters for a unit test here...", 'gf4Amahb14P8M7Kj2S6+4MN7tfHHLLmjzjSvo8IuuvPbe7j1zSn398A+9+/JIa6jzDSwrYh7lu/Ee6Ds2jD34sY/9+3He6fvySL37skwsvCIGL/xwSj34og/ou/Ee7Xs0XX3o+F8uqPcKa7qxjz398d7sObce6fi2y/3sppj9+DAOqXiyy+y8dt7sezae7aj3TW+94gvsvDce7/m2j75rYY=') - ); - } - - public function testUnframeMatchesPreFraming() { - $string = 'Hello World!'; - $framed = $this->_version->newFrame($string)->getContents(); - - $frame = new Frame; - $frame->addBuffer($framed); - - $this->assertEquals($string, $frame->getPayload()); - } -} diff --git a/tests/unit/WebSocket/Version/RFC6455/FrameTest.php b/tests/unit/WebSocket/Version/RFC6455/FrameTest.php deleted file mode 100644 index eff9513..0000000 --- a/tests/unit/WebSocket/Version/RFC6455/FrameTest.php +++ /dev/null @@ -1,543 +0,0 @@ -_frame = new Frame; - } - - /** - * Encode the fake binary string to send over the wire - * @param string of 1's and 0's - * @return string - */ - public static function encode($in) { - if (strlen($in) > 8) { - $out = ''; - - while (strlen($in) >= 8) { - $out .= static::encode(substr($in, 0, 8)); - $in = substr($in, 8); - } - - return $out; - } - - return chr(bindec($in)); - } - - /** - * This is a data provider - * @param string The UTF8 message - * @param string The WebSocket framed message, then base64_encoded - */ - public static function UnframeMessageProvider() { - return array( - array('Hello World!', 'gYydAIfa1WXrtvIg0LXvbOP7') - , array('!@#$%^&*()-=_+[]{}\|/.,<>`~', 'gZv+h96r38f9j9vZ+IHWrvOWoayF9oX6gtfRqfKXwOeg') - , array('ಠ_ಠ', 'gYfnSpu5B/g75gf4Ow==') - , array("The quick brown fox jumps over the lazy dog. All work and no play makes Chris a dull boy. I'm trying to get past 128 characters for a unit test here...", 'gf4Amahb14P8M7Kj2S6+4MN7tfHHLLmjzjSvo8IuuvPbe7j1zSn398A+9+/JIa6jzDSwrYh7lu/Ee6Ds2jD34sY/9+3He6fvySL37skwsvCIGL/xwSj34og/ou/Ee7Xs0XX3o+F8uqPcKa7qxjz398d7sObce6fi2y/3sppj9+DAOqXiyy+y8dt7sezae7aj3TW+94gvsvDce7/m2j75rYY=') - ); - } - - public static function underflowProvider() { - return array( - array('isFinal', '') - , array('getRsv1', '') - , array('getRsv2', '') - , array('getRsv3', '') - , array('getOpcode', '') - , array('isMasked', '10000001') - , array('getPayloadLength', '10000001') - , array('getPayloadLength', '1000000111111110') - , array('getMaskingKey', '1000000110000111') - , array('getPayload', '100000011000000100011100101010101001100111110100') - ); - } - - /** - * @dataProvider underflowProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::isFinal - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv1 - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv2 - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv3 - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getOpcode - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::isMasked - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getMaskingKey - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayload - */ - public function testUnderflowExceptionFromAllTheMethodsMimickingBuffering($method, $bin) { - $this->setExpectedException('\UnderflowException'); - - if (!empty($bin)) { - $this->_frame->addBuffer(static::encode($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 - * @param int Given, what is the expected opcode - * @param string of 0|1 Each character represents a bit in the byte - */ - public static function firstByteProvider() { - return array( - array(false, false, false, true, 8, '00011000') - , array(true, false, true, false, 10, '10101010') - , array(false, false, false, false, 15, '00001111') - , array(true, false, false, false, 1, '10000001') - , array(true, true, true, true, 15, '11111111') - , array(true, true, false, false, 7, '11000111') - ); - } - - /** - * @dataProvider firstByteProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::isFinal - */ - public function testFinCodeFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) { - $this->_frame->addBuffer(static::encode($bin)); - $this->assertEquals($fin, $this->_frame->isFinal()); - } - - /** - * @dataProvider firstByteProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv1 - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv2 - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv3 - */ - public function testGetRsvFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) { - $this->_frame->addBuffer(static::encode($bin)); - - $this->assertEquals($rsv1, $this->_frame->getRsv1()); - $this->assertEquals($rsv2, $this->_frame->getRsv2()); - $this->assertEquals($rsv3, $this->_frame->getRsv3()); - } - - /** - * @dataProvider firstByteProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getOpcode - */ - public function testOpcodeFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) { - $this->_frame->addBuffer(static::encode($bin)); - $this->assertEquals($opcode, $this->_frame->getOpcode()); - } - - /** - * @dataProvider UnframeMessageProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::isFinal - */ - public function testFinCodeFromFullMessage($msg, $encoded) { - $this->_frame->addBuffer(base64_decode($encoded)); - $this->assertTrue($this->_frame->isFinal()); - } - - /** - * @dataProvider UnframeMessageProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getOpcode - */ - public function testOpcodeFromFullMessage($msg, $encoded) { - $this->_frame->addBuffer(base64_decode($encoded)); - $this->assertEquals(1, $this->_frame->getOpcode()); - } - - public static function payloadLengthDescriptionProvider() { - return array( - array(7, '01110101') - , array(7, '01111101') - , array(23, '01111110') - , array(71, '01111111') - , array(7, '00000000') // Should this throw an exception? Can a payload be empty? - , array(7, '00000001') - ); - } - - /** - * @dataProvider payloadLengthDescriptionProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::addBuffer - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getFirstPayloadVal - */ - public function testFirstPayloadDesignationValue($bits, $bin) { - $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); - $this->_frame->addBuffer(static::encode($bin)); - - $ref = new \ReflectionClass($this->_frame); - $cb = $ref->getMethod('getFirstPayloadVal'); - $cb->setAccessible(true); - - $this->assertEquals(bindec($bin), $cb->invoke($this->_frame)); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getFirstPayloadVal - */ - public function testFirstPayloadValUnderflow() { - $ref = new \ReflectionClass($this->_frame); - $cb = $ref->getMethod('getFirstPayloadVal'); - $cb->setAccessible(true); - - $this->setExpectedException('UnderflowException'); - $cb->invoke($this->_frame); - } - - /** - * @dataProvider payloadLengthDescriptionProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getNumPayloadBits - */ - public function testDetermineHowManyBitsAreUsedToDescribePayload($expected_bits, $bin) { - $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); - $this->_frame->addBuffer(static::encode($bin)); - - $ref = new \ReflectionClass($this->_frame); - $cb = $ref->getMethod('getNumPayloadBits'); - $cb->setAccessible(true); - - $this->assertEquals($expected_bits, $cb->invoke($this->_frame)); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getNumPayloadBits - */ - public function testgetNumPayloadBitsUnderflow() { - $ref = new \ReflectionClass($this->_frame); - $cb = $ref->getMethod('getNumPayloadBits'); - $cb->setAccessible(true); - - $this->setExpectedException('UnderflowException'); - $cb->invoke($this->_frame); - } - - public function secondByteProvider() { - return array( - array(true, 1, '10000001') - , array(false, 1, '00000001') - , array(true, 125, $this->_secondByteMaskedSPL) - ); - } - - /** - * @dataProvider secondByteProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::isMasked - */ - public function testIsMaskedReturnsExpectedValue($masked, $payload_length, $bin) { - $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); - $this->_frame->addBuffer(static::encode($bin)); - - $this->assertEquals($masked, $this->_frame->isMasked()); - } - - /** - * @dataProvider UnframeMessageProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::isMasked - */ - public function testIsMaskedFromFullMessage($msg, $encoded) { - $this->_frame->addBuffer(base64_decode($encoded)); - $this->assertTrue($this->_frame->isMasked()); - } - - /** - * @dataProvider secondByteProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength - */ - public function testGetPayloadLengthWhenOnlyFirstFrameIsUsed($masked, $payload_length, $bin) { - $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); - $this->_frame->addBuffer(static::encode($bin)); - - $this->assertEquals($payload_length, $this->_frame->getPayloadLength()); - } - - /** - * @dataProvider UnframeMessageProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength - * @todo Not yet testing when second additional payload length descriptor - */ - public function testGetPayloadLengthFromFullMessage($msg, $encoded) { - $this->_frame->addBuffer(base64_decode($encoded)); - $this->assertEquals(strlen($msg), $this->_frame->getPayloadLength()); - } - - public function maskingKeyProvider() { - $frame = new Frame; - - return array( - array($frame->generateMaskingKey()) - , array($frame->generateMaskingKey()) - , array($frame->generateMaskingKey()) - ); - } - - /** - * @dataProvider maskingKeyProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getMaskingKey - * @todo I I wrote the dataProvider incorrectly, skipping for now - */ - public function testGetMaskingKey($mask) { - $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); - $this->_frame->addBuffer(static::encode($this->_secondByteMaskedSPL)); - $this->_frame->addBuffer($mask); - - $this->assertEquals($mask, $this->_frame->getMaskingKey()); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getMaskingKey - */ - public function testGetMaskingKeyOnUnmaskedPayload() { - $frame = new Frame('Hello World!'); - - $this->assertEquals('', $frame->getMaskingKey()); - } - - /** - * @dataProvider UnframeMessageProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayload - * @todo Move this test to bottom as it requires all methods of the class - */ - public function testUnframeFullMessage($unframed, $base_framed) { - $this->_frame->addBuffer(base64_decode($base_framed)); - $this->assertEquals($unframed, $this->_frame->getPayload()); - } - - public static function messageFragmentProvider() { - return array( - array(false, '', '', '', '', '') - ); - } - - /** - * @dataProvider UnframeMessageProvider - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayload - */ - public function testCheckPiecingTogetherMessage($msg, $encoded) { - $framed = base64_decode($encoded); - for ($i = 0, $len = strlen($framed);$i < $len; $i++) { - $this->_frame->addBuffer(substr($framed, $i, 1)); - } - - $this->assertEquals($msg, $this->_frame->getPayload()); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::__construct - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayload - */ - public function testLongCreate() { - $len = 65525; - $pl = $this->generateRandomString($len); - - $frame = new Frame($pl, true, Frame::OP_PING); - - $this->assertTrue($frame->isFinal()); - $this->assertEquals(Frame::OP_PING, $frame->getOpcode()); - $this->assertFalse($frame->isMasked()); - $this->assertEquals($len, $frame->getPayloadLength()); - $this->assertEquals($pl, $frame->getPayload()); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::__construct - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength - */ - public function testReallyLongCreate() { - $len = 65575; - - $frame = new Frame($this->generateRandomString($len)); - - $this->assertEquals($len, $frame->getPayloadLength()); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::__construct - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::extractOverflow - */ - public function testExtractOverflow() { - $string1 = $this->generateRandomString(); - $frame1 = new Frame($string1); - - $string2 = $this->generateRandomString(); - $frame2 = new Frame($string2); - - $cat = new Frame; - $cat->addBuffer($frame1->getContents() . $frame2->getContents()); - - $this->assertEquals($frame1->getContents(), $cat->getContents()); - $this->assertEquals($string1, $cat->getPayload()); - - $uncat = new Frame; - $uncat->addBuffer($cat->extractOverflow()); - - $this->assertEquals($string1, $cat->getPayload()); - $this->assertEquals($string2, $uncat->getPayload()); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::extractOverflow - */ - public function testEmptyExtractOverflow() { - $string = $this->generateRandomString(); - $frame = new Frame($string); - - $this->assertEquals($string, $frame->getPayload()); - $this->assertEquals('', $frame->extractOverflow()); - $this->assertEquals($string, $frame->getPayload()); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getContents - */ - public function testGetContents() { - $msg = 'The quick brown fox jumps over the lazy dog.'; - - $frame1 = new Frame($msg); - $frame2 = new Frame($msg); - $frame2->maskPayload(); - - $this->assertNotEquals($frame1->getContents(), $frame2->getContents()); - $this->assertEquals(strlen($frame1->getContents()) + 4, strlen($frame2->getContents())); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::maskPayload - */ - public function testMasking() { - $msg = 'The quick brown fox jumps over the lazy dog.'; - $frame = new Frame($msg); - $frame->maskPayload(); - - $this->assertTrue($frame->isMasked()); - $this->assertEquals($msg, $frame->getPayload()); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::unMaskPayload - */ - public function testUnMaskPayload() { - $string = $this->generateRandomString(); - $frame = new Frame($string); - $frame->maskPayload()->unMaskPayload(); - - $this->assertFalse($frame->isMasked()); - $this->assertEquals($string, $frame->getPayload()); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::generateMaskingKey - */ - public function testGenerateMaskingKey() { - $dupe = false; - $done = array(); - - for ($i = 0; $i < 10; $i++) { - $new = $this->_frame->generateMaskingKey(); - - if (in_array($new, $done)) { - $dupe = true; - } - - $done[] = $new; - } - - $this->assertEquals(4, strlen($new)); - $this->assertFalse($dupe); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::maskPayload - */ - public function testGivenMaskIsValid() { - $this->setExpectedException('InvalidArgumentException'); - $this->_frame->maskPayload('hello world'); - } - - /** - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::maskPayload - */ - public function testGivenMaskIsValidAscii() { - if (!extension_loaded('mbstring')) { - return $this->markTestSkipped("mbstring required for this test"); - } - - $this->setExpectedException('OutOfBoundsException'); - $this->_frame->maskPayload('x✖'); - } - - protected function generateRandomString($length = 10, $addSpaces = true, $addNumbers = true) { - $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$%&/()=[]{}'; // ยง - - $useChars = array(); - for($i = 0; $i < $length; $i++) { - $useChars[] = $characters[mt_rand(0, strlen($characters) - 1)]; - } - - if($addSpaces === true) { - array_push($useChars, ' ', ' ', ' ', ' ', ' ', ' '); - } - - if($addNumbers === true) { - array_push($useChars, rand(0, 9), rand(0, 9), rand(0, 9)); - } - - shuffle($useChars); - - $randomString = trim(implode('', $useChars)); - $randomString = substr($randomString, 0, $length); - - return $randomString; - } - - /** - * There was a frame boundary issue when the first 3 bytes of a frame with a payload greater than - * 126 was added to the frame buffer and then Frame::getPayloadLength was called. It would cause the frame - * to set the payload length to 126 and then not recalculate it once the full length information was available. - * - * This is fixed by setting the defPayLen back to -1 before the underflow exception is thrown. - * - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength - * @covers Ratchet\WebSocket\Version\RFC6455\Frame::extractOverflow - */ - public function testFrameDeliveredOneByteAtATime() { - $startHeader = "\x01\x7e\x01\x00"; // header for a text frame of 256 - non-final - $framePayload = str_repeat("*", 256); - $rawOverflow = "xyz"; - $rawFrame = $startHeader . $framePayload . $rawOverflow; - - $frame = new Frame(); - $payloadLen = 256; - - for ($i = 0; $i < strlen($rawFrame); $i++) { - $frame->addBuffer($rawFrame[$i]); - - try { - // payloadLen will - $payloadLen = $frame->getPayloadLength(); - } catch (\UnderflowException $e) { - if ($i > 2) { // we should get an underflow on 0,1,2 - $this->fail("Underflow exception when the frame length should be available"); - } - } - - if ($payloadLen !== 256) { - $this->fail("Payload length of " . $payloadLen . " should have been 256."); - } - } - - // make sure the overflow is good - $this->assertEquals($rawOverflow, $frame->extractOverflow()); - } -} diff --git a/tests/unit/WebSocket/Version/RFC6455/HandshakeVerifierTest.php b/tests/unit/WebSocket/Version/RFC6455/HandshakeVerifierTest.php deleted file mode 100644 index 6761c32..0000000 --- a/tests/unit/WebSocket/Version/RFC6455/HandshakeVerifierTest.php +++ /dev/null @@ -1,170 +0,0 @@ -_v = new HandshakeVerifier; - } - - public static function methodProvider() { - return array( - array(true, 'GET') - , array(true, 'get') - , array(true, 'Get') - , array(false, 'POST') - , array(false, 'DELETE') - , array(false, 'PUT') - , array(false, 'PATCH') - ); - } - - /** - * @dataProvider methodProvider - */ - public function testMethodMustBeGet($result, $in) { - $this->assertEquals($result, $this->_v->verifyMethod($in)); - } - - public static function httpVersionProvider() { - return array( - array(true, 1.1) - , array(true, '1.1') - , array(true, 1.2) - , array(true, '1.2') - , array(true, 2) - , array(true, '2') - , array(true, '2.0') - , array(false, '1.0') - , array(false, 1) - , array(false, '0.9') - , array(false, '') - , array(false, 'hello') - ); - } - - /** - * @dataProvider httpVersionProvider - */ - public function testHttpVersionIsAtLeast1Point1($expected, $in) { - $this->assertEquals($expected, $this->_v->verifyHTTPVersion($in)); - } - - public static function uRIProvider() { - return array( - array(true, '/chat') - , array(true, '/hello/world?key=val') - , array(false, '/chat#bad') - , array(false, 'nope') - , array(false, '/ ಠ_ಠ ') - , array(false, '/✖') - ); - } - - /** - * @dataProvider URIProvider - */ - public function testRequestUri($expected, $in) { - $this->assertEquals($expected, $this->_v->verifyRequestURI($in)); - } - - public static function hostProvider() { - return array( - array(true, 'server.example.com') - , array(false, null) - ); - } - - /** - * @dataProvider HostProvider - */ - public function testVerifyHostIsSet($expected, $in) { - $this->assertEquals($expected, $this->_v->verifyHost($in)); - } - - public static function upgradeProvider() { - return array( - array(true, 'websocket') - , array(true, 'Websocket') - , array(true, 'webSocket') - , array(false, null) - , array(false, '') - ); - } - - /** - * @dataProvider upgradeProvider - */ - public function testVerifyUpgradeIsWebSocket($expected, $val) { - $this->assertEquals($expected, $this->_v->verifyUpgradeRequest($val)); - } - - public static function connectionProvider() { - return array( - array(true, 'Upgrade') - , array(true, 'upgrade') - , array(true, 'keep-alive, Upgrade') - , array(true, 'Upgrade, keep-alive') - , array(true, 'keep-alive, Upgrade, something') - , array(false, '') - , array(false, null) - ); - } - - /** - * @dataProvider connectionProvider - */ - public function testConnectionHeaderVerification($expected, $val) { - $this->assertEquals($expected, $this->_v->verifyConnection($val)); - } - - public static function keyProvider() { - return array( - array(true, 'hkfa1L7uwN6DCo4IS3iWAw==') - , array(true, '765vVoQpKSGJwPzJIMM2GA==') - , array(true, 'AQIDBAUGBwgJCgsMDQ4PEC==') - , array(true, 'axa2B/Yz2CdpfQAY2Q5P7w==') - , array(false, 0) - , array(false, 'Hello World') - , array(false, '1234567890123456') - , array(false, '123456789012345678901234') - , array(true, base64_encode('UTF8allthngs+✓')) - , array(true, 'dGhlIHNhbXBsZSBub25jZQ==') - ); - } - - /** - * @dataProvider keyProvider - */ - public function testKeyIsBase64Encoded16BitNonce($expected, $val) { - $this->assertEquals($expected, $this->_v->verifyKey($val)); - } - - public static function versionProvider() { - return array( - array(true, 13) - , array(true, '13') - , array(false, 12) - , array(false, 14) - , array(false, '14') - , array(false, 'hi') - , array(false, '') - , array(false, null) - ); - } - - /** - * @dataProvider versionProvider - */ - public function testVersionEquals13($expected, $in) { - $this->assertEquals($expected, $this->_v->verifyVersion($in)); - } -} diff --git a/tests/unit/WebSocket/Version/RFC6455/MessageTest.php b/tests/unit/WebSocket/Version/RFC6455/MessageTest.php deleted file mode 100644 index b2d21d2..0000000 --- a/tests/unit/WebSocket/Version/RFC6455/MessageTest.php +++ /dev/null @@ -1,63 +0,0 @@ -message = new Message; - } - - public function testNoFrames() { - $this->assertFalse($this->message->isCoalesced()); - } - - public function testNoFramesOpCode() { - $this->setExpectedException('UnderflowException'); - $this->message->getOpCode(); - } - - public function testFragmentationPayload() { - $a = 'Hello '; - $b = 'World!'; - - $f1 = new Frame($a, false); - $f2 = new Frame($b, true, Frame::OP_CONTINUE); - - $this->message->addFrame($f1)->addFrame($f2); - - $this->assertEquals(strlen($a . $b), $this->message->getPayloadLength()); - $this->assertEquals($a . $b, $this->message->getPayload()); - } - - public function testUnbufferedFragment() { - $this->message->addFrame(new Frame('The quick brow', false)); - - $this->setExpectedException('UnderflowException'); - $this->message->getPayload(); - } - - public function testGetOpCode() { - $this->message - ->addFrame(new Frame('The quick brow', false, Frame::OP_TEXT)) - ->addFrame(new Frame('n fox jumps ov', false, Frame::OP_CONTINUE)) - ->addFrame(new Frame('er the lazy dog', true, Frame::OP_CONTINUE)) - ; - - $this->assertEquals(Frame::OP_TEXT, $this->message->getOpCode()); - } - - public function testGetUnBufferedPayloadLength() { - $this->message - ->addFrame(new Frame('The quick brow', false, Frame::OP_TEXT)) - ->addFrame(new Frame('n fox jumps ov', false, Frame::OP_CONTINUE)) - ; - - $this->assertEquals(28, $this->message->getPayloadLength()); - } -} diff --git a/tests/unit/WebSocket/Version/RFC6455Test.php b/tests/unit/WebSocket/Version/RFC6455Test.php deleted file mode 100644 index 86e5631..0000000 --- a/tests/unit/WebSocket/Version/RFC6455Test.php +++ /dev/null @@ -1,151 +0,0 @@ -version = new RFC6455; - } - - /** - * @dataProvider handshakeProvider - */ - public function testKeySigningForHandshake($key, $accept) { - $this->assertEquals($accept, $this->version->sign($key)); - } - - public static function handshakeProvider() { - return array( - array('x3JJHMbDL1EzLkh9GBhXDw==', 'HSmrc0sMlYUkAGmm5OPpG2HaGWk=') - , array('dGhlIHNhbXBsZSBub25jZQ==', 's3pPLMBiTxaQ9kYGzzhZRbK+xOo=') - ); - } - - /** - * @dataProvider UnframeMessageProvider - */ - public function testUnframeMessage($message, $framed) { - $frame = new Frame; - $frame->addBuffer(base64_decode($framed)); - - $this->assertEquals($message, $frame->getPayload()); - } - - public static function UnframeMessageProvider() { - return array( - array('Hello World!', 'gYydAIfa1WXrtvIg0LXvbOP7') - , array('!@#$%^&*()-=_+[]{}\|/.,<>`~', 'gZv+h96r38f9j9vZ+IHWrvOWoayF9oX6gtfRqfKXwOeg') - , array('ಠ_ಠ', 'gYfnSpu5B/g75gf4Ow==') - , array("The quick brown fox jumps over the lazy dog. All work and no play makes Chris a dull boy. I'm trying to get past 128 characters for a unit test here...", 'gf4Amahb14P8M7Kj2S6+4MN7tfHHLLmjzjSvo8IuuvPbe7j1zSn398A+9+/JIa6jzDSwrYh7lu/Ee6Ds2jD34sY/9+3He6fvySL37skwsvCIGL/xwSj34og/ou/Ee7Xs0XX3o+F8uqPcKa7qxjz398d7sObce6fi2y/3sppj9+DAOqXiyy+y8dt7sezae7aj3TW+94gvsvDce7/m2j75rYY=') - ); - } - - public function testUnframeMatchesPreFraming() { - $string = 'Hello World!'; - $framed = $this->version->newFrame($string)->getContents(); - - $frame = new Frame; - $frame->addBuffer($framed); - - $this->assertEquals($string, $frame->getPayload()); - } - - public static $good_rest = 'GET /chat HTTP/1.1'; - - public static $good_header = array( - '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 - ); - - 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 - */ - public static function getAndSpliceHeader($key = null, $val = null) { - $headers = static::$good_header; - - if (null !== $key && null !== $val) { - $headers[$key] = $val; - } - - $header = ''; - foreach ($headers as $key => $val) { - if (!empty($key)) { - $header .= "{$key}: "; - } - - $header .= "{$val}\r\n"; - } - $header .= "\r\n"; - - return $header; - } - - 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')) - ); - } - - /** - * @dataProvider headerHandshakeProvider - */ - public function testVariousHeadersToCheckHandshakeTolerance($pass, $header) { - $request = RequestFactory::getInstance()->fromMessage($header); - $response = $this->version->handshake($request); - - $this->assertInstanceOf('\\Guzzle\\Http\\Message\\Response', $response); - - if ($pass) { - $this->assertEquals(101, $response->getStatusCode()); - } else { - $this->assertGreaterThanOrEqual(400, $response->getStatusCode()); - } - } - - public function testNewMessage() { - $this->assertInstanceOf('\\Ratchet\\WebSocket\\Version\\RFC6455\\Message', $this->version->newMessage()); - } - - public function testNewFrame() { - $this->assertInstanceOf('\\Ratchet\\WebSocket\\Version\\RFC6455\\Frame', $this->version->newFrame()); - } -} diff --git a/tests/unit/WebSocket/VersionManagerTest.php b/tests/unit/WebSocket/VersionManagerTest.php deleted file mode 100644 index d9c55fe..0000000 --- a/tests/unit/WebSocket/VersionManagerTest.php +++ /dev/null @@ -1,91 +0,0 @@ -vm = new VersionManager; - } - - public function testFluentInterface() { - $rfc = new RFC6455; - - $this->assertSame($this->vm, $this->vm->enableVersion($rfc)); - $this->assertSame($this->vm, $this->vm->disableVersion(13)); - } - - public function testGetVersion() { - $rfc = new RFC6455; - $this->vm->enableVersion($rfc); - - $req = new EntityEnclosingRequest('get', '/', array( - 'Host' => 'socketo.me' - , 'Sec-WebSocket-Version' => 13 - )); - - $this->assertSame($rfc, $this->vm->getVersion($req)); - } - - public function testGetNopeVersionAndDisable() { - $req = new EntityEnclosingRequest('get', '/', array( - 'Host' => 'socketo.me' - , 'Sec-WebSocket-Version' => 13 - )); - - $this->setExpectedException('InvalidArgumentException'); - - $this->vm->getVersion($req); - } - - public function testYesIsVersionEnabled() { - $this->vm->enableVersion(new RFC6455); - - $this->assertTrue($this->vm->isVersionEnabled(new EntityEnclosingRequest('get', '/', array( - 'Host' => 'socketo.me' - , 'Sec-WebSocket-Version' => 13 - )))); - } - - public function testNoIsVersionEnabled() { - $this->assertFalse($this->vm->isVersionEnabled(new EntityEnclosingRequest('get', '/', array( - 'Host' => 'socketo.me' - , 'Sec-WebSocket-Version' => 9000 - )))); - } - - public function testGetSupportedVersionString() { - $v1 = new RFC6455; - $v2 = new HyBi10; - - $this->vm->enableVersion($v1); - $this->vm->enableVersion($v2); - - $string = $this->vm->getSupportedVersionString(); - $values = explode(',', $string); - - $this->assertContains($v1->getVersionNumber(), $values); - $this->assertContains($v2->getVersionNumber(), $values); - } - - public function testGetSupportedVersionAfterRemoval() { - $this->vm->enableVersion(new RFC6455); - $this->vm->enableVersion(new HyBi10); - $this->vm->enableVersion(new Hixie76); - - $this->vm->disableVersion(0); - - $values = explode(',', $this->vm->getSupportedVersionString()); - - $this->assertEquals(2, count($values)); - $this->assertFalse(array_search(0, $values)); - } -} diff --git a/tests/unit/WebSocket/WsServerTest.php b/tests/unit/WebSocket/WsServerTest.php deleted file mode 100644 index 3f4aa43..0000000 --- a/tests/unit/WebSocket/WsServerTest.php +++ /dev/null @@ -1,51 +0,0 @@ -comp = new MockComponent; - $this->serv = new WsServer($this->comp); - } - - public function testIsSubProtocolSupported() { - $this->comp->protocols = array('hello', 'world'); - - $this->assertTrue($this->serv->isSubProtocolSupported('hello')); - $this->assertFalse($this->serv->isSubProtocolSupported('nope')); - } - - public function protocolProvider() { - return array( - array('hello', array('hello', 'world'), array('hello', 'world')) - , array('', array('hello', 'world'), array('wamp')) - , array('', array(), null) - , array('wamp', array('hello', 'wamp', 'world'), array('herp', 'derp', 'wamp')) - , array('wamp', array('wamp'), array('wamp')) - ); - } - - /** - * @dataProvider protocolProvider - */ - public function testGetSubProtocolString($expected, $supported, $requested) { - $this->comp->protocols = $supported; - $req = (null === $requested ? $requested : new \ArrayIterator($requested)); - - $class = new \ReflectionClass('Ratchet\\WebSocket\\WsServer'); - $method = $class->getMethod('getSubProtocolString'); - $method->setAccessible(true); - - $this->assertSame($expected, $method->invokeArgs($this->serv, array($req))); - } -}