Commit 86d6903a authored by Dominik George's avatar Dominik George

New upstream version 0.2.4

parents
composer.lock
vendor
tests/ab/reports
reports
language: php
php:
- 5.4
- 5.5
- 5.6
- 7.0
- 7.1
- 7.2
- hhvm
before_install:
- export PATH=$HOME/.local/bin:$PATH
- pip install --user autobahntestsuite
- pip list --user autobahntestsuite
before_script:
- composer install
- sh tests/ab/run_ab_tests.sh
script:
- vendor/bin/phpunit
Copyright (c) 2011-2016 Chris Boden
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
# RFC6455 - The WebSocket Protocol
[![Build Status](https://travis-ci.org/ratchetphp/RFC6455.svg?branch=master)](https://travis-ci.org/ratchetphp/RFC6455)
![Autobahn Testsuite](https://img.shields.io/badge/Autobahn-passing-brightgreen.svg)
This library a protocol handler for the RFC6455 specification.
It contains components for both server and client side handshake and messaging protocol negotation.
Aspects that are left open to interpertation in the specification are also left open in this library.
It is up to the implementation to determine how those interpertations are to be dealt with.
This library is independent, framework agnostic, and does not deal with any I/O.
HTTP upgrade negotiation integration points are handled with PSR-7 interfaces.
{
"name": "ratchet/rfc6455",
"type": "library",
"description": "RFC6455 WebSocket protocol handler",
"keywords": ["WebSockets", "websocket", "RFC6455"],
"homepage": "http://socketo.me",
"license": "MIT",
"authors": [{
"name": "Chris Boden"
, "email": "cboden@gmail.com"
, "role": "Developer"
}],
"support": {
"forum": "https://groups.google.com/forum/#!forum/ratchet-php"
, "issues": "https://github.com/ratchetphp/RFC6455/issues"
, "irc": "irc://irc.freenode.org/reactphp"
},
"autoload": {
"psr-4": {
"Ratchet\\RFC6455\\": "src"
}
},
"require": {
"php": ">=5.4.2",
"guzzlehttp/psr7": "^1.0"
},
"require-dev": {
"react/http": "^0.4.1",
"react/socket-client": "^0.4.3",
"phpunit/phpunit": "4.8.*"
}
}
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
forceCoversAnnotation="true"
mapTestClassNameToCoveredClassName="true"
bootstrap="tests/bootstrap.php"
colors="true"
backupGlobals="false"
backupStaticAttributes="false"
syntaxCheck="false"
stopOnError="false"
>
<testsuites>
<testsuite name="tests">
<directory>tests</directory>
<exclude>
<directory>test/ab</directory>
</exclude>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src/</directory>
</whitelist>
</filter>
</phpunit>
\ No newline at end of file
<?php
namespace Ratchet\RFC6455\Handshake;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;
use GuzzleHttp\Psr7\Request;
class ClientNegotiator {
/**
* @var ResponseVerifier
*/
private $verifier;
/**
* @var \Psr\Http\Message\RequestInterface
*/
private $defaultHeader;
function __construct() {
$this->verifier = new ResponseVerifier;
$this->defaultHeader = new Request('GET', '', [
'Connection' => 'Upgrade'
, 'Upgrade' => 'websocket'
, 'Sec-WebSocket-Version' => $this->getVersion()
, 'User-Agent' => "Ratchet"
]);
}
public function generateRequest(UriInterface $uri) {
return $this->defaultHeader->withUri($uri)
->withHeader("Sec-WebSocket-Key", $this->generateKey());
}
public function validateResponse(RequestInterface $request, ResponseInterface $response) {
return $this->verifier->verifyAll($request, $response);
}
public function generateKey() {
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwzyz1234567890+/=';
$charRange = strlen($chars) - 1;
$key = '';
for ($i = 0; $i < 16; $i++) {
$key .= $chars[mt_rand(0, $charRange)];
}
return base64_encode($key);
}
public function getVersion() {
return 13;
}
}
<?php
namespace Ratchet\RFC6455\Handshake;
use Psr\Http\Message\RequestInterface;
/**
* A standard interface for interacting with the various version of the WebSocket protocol
* @todo Look in to extension support
*/
interface NegotiatorInterface {
const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
/**
* Given an HTTP header, determine if this version should handle the protocol
* @param RequestInterface $request
* @return bool
*/
function isProtocol(RequestInterface $request);
/**
* Although the version has a name associated with it the integer returned is the proper identification
* @return int
*/
function getVersionNumber();
/**
* Perform the handshake and return the response headers
* @param RequestInterface $request
* @return \Psr\Http\Message\ResponseInterface
*/
function handshake(RequestInterface $request);
/**
* Add supported protocols. If the request has any matching the response will include one
* @param array $protocols
*/
function setSupportedSubProtocols(array $protocols);
/**
* If enabled and support for a subprotocol has been added handshake
* will not upgrade if a match between request and supported subprotocols
* @param boolean $enable
* @todo Consider extending this interface and moving this there.
* The spec does says the server can fail for this reason, but
* it is not a requirement. This is an implementation detail.
*/
function setStrictSubProtocolCheck($enable);
}
<?php
namespace Ratchet\RFC6455\Handshake;
use Psr\Http\Message\RequestInterface;
/**
* These are checks to ensure the client requested handshake are valid
* Verification rules come from section 4.2.1 of the RFC6455 document
* @todo Currently just returning invalid - should consider returning appropriate HTTP status code error #s
*/
class RequestVerifier {
const VERSION = 13;
/**
* Given an array of the headers this method will run through all verification methods
* @param RequestInterface $request
* @return bool TRUE if all headers are valid, FALSE if 1 or more were invalid
*/
public function verifyAll(RequestInterface $request) {
$passes = 0;
$passes += (int)$this->verifyMethod($request->getMethod());
$passes += (int)$this->verifyHTTPVersion($request->getProtocolVersion());
$passes += (int)$this->verifyRequestURI($request->getUri()->getPath());
$passes += (int)$this->verifyHost($request->getHeader('Host'));
$passes += (int)$this->verifyUpgradeRequest($request->getHeader('Upgrade'));
$passes += (int)$this->verifyConnection($request->getHeader('Connection'));
$passes += (int)$this->verifyKey($request->getHeader('Sec-WebSocket-Key'));
$passes += (int)$this->verifyVersion($request->getHeader('Sec-WebSocket-Version'));
return (8 === $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 array $hostHeader
* @return bool
* @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(array $hostHeader) {
return (1 === count($hostHeader));
}
/**
* Verify the Upgrade request to WebSockets.
* @param array $upgradeHeader MUST equal "websocket"
* @return bool
*/
public function verifyUpgradeRequest(array $upgradeHeader) {
return (1 === count($upgradeHeader) && 'websocket' === strtolower($upgradeHeader[0]));
}
/**
* Verify the Connection header
* @param array $connectionHeader MUST include "Upgrade"
* @return bool
*/
public function verifyConnection(array $connectionHeader) {
foreach ($connectionHeader as $l) {
$upgrades = array_filter(
array_map('trim', array_map('strtolower', explode(',', $l))),
function ($x) {
return 'upgrade' === $x;
}
);
if (count($upgrades) > 0) {
return true;
}
}
return false;
}
/**
* This function verifies the nonce is valid (64 big encoded, 16 bytes random string)
* @param array $keyHeader
* @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(array $keyHeader) {
return (1 === count($keyHeader) && 16 === strlen(base64_decode($keyHeader[0])));
}
/**
* Verify the version passed matches this RFC
* @param string|int $versionHeader MUST equal 13|"13"
* @return bool
*/
public function verifyVersion($versionHeader) {
return (1 === count($versionHeader) && static::VERSION === (int)$versionHeader[0]);
}
/**
* @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) {
}
}
<?php
namespace Ratchet\RFC6455\Handshake;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
class ResponseVerifier {
public function verifyAll(RequestInterface $request, ResponseInterface $response) {
$passes = 0;
$passes += (int)$this->verifyStatus($response->getStatusCode());
$passes += (int)$this->verifyUpgrade($response->getHeader('Upgrade'));
$passes += (int)$this->verifyConnection($response->getHeader('Connection'));
$passes += (int)$this->verifySecWebSocketAccept(
$response->getHeader('Sec-WebSocket-Accept')
, $request->getHeader('Sec-WebSocket-Key')
);
$passes += (int)$this->verifySubProtocol(
$request->getHeader('Sec-WebSocket-Protocol')
, $response->getHeader('Sec-WebSocket-Protocol')
);
return (5 === $passes);
}
public function verifyStatus($status) {
return ((int)$status === 101);
}
public function verifyUpgrade(array $upgrade) {
return (in_array('websocket', array_map('strtolower', $upgrade)));
}
public function verifyConnection(array $connection) {
return (in_array('upgrade', array_map('strtolower', $connection)));
}
public function verifySecWebSocketAccept($swa, $key) {
return (
1 === count($swa) &&
1 === count($key) &&
$swa[0] === $this->sign($key[0])
);
}
public function sign($key) {
return base64_encode(sha1($key . NegotiatorInterface::GUID, true));
}
public function verifySubProtocol(array $requestHeader, array $responseHeader) {
return 0 === count($responseHeader) || count(array_intersect($responseHeader, $requestHeader)) > 0;
}
}
\ No newline at end of file
<?php
namespace Ratchet\RFC6455\Handshake;
use Psr\Http\Message\RequestInterface;
use GuzzleHttp\Psr7\Response;
/**
* The latest version of the WebSocket protocol
* @todo Unicode: return mb_convert_encoding(pack("N",$u), mb_internal_encoding(), 'UCS-4BE');
*/
class ServerNegotiator implements NegotiatorInterface {
/**
* @var \Ratchet\RFC6455\Handshake\RequestVerifier
*/
private $verifier;
private $_supportedSubProtocols = [];
private $_strictSubProtocols = false;
public function __construct(RequestVerifier $requestVerifier) {
$this->verifier = $requestVerifier;
}
/**
* {@inheritdoc}
*/
public function isProtocol(RequestInterface $request) {
return $this->verifier->verifyVersion($request->getHeader('Sec-WebSocket-Version'));
}
/**
* {@inheritdoc}
*/
public function getVersionNumber() {
return RequestVerifier::VERSION;
}
/**
* {@inheritdoc}
*/
public function handshake(RequestInterface $request) {
if (true !== $this->verifier->verifyMethod($request->getMethod())) {
return new Response(405, ['Allow' => 'GET']);
}
if (true !== $this->verifier->verifyHTTPVersion($request->getProtocolVersion())) {
return new Response(505);
}
if (true !== $this->verifier->verifyRequestURI($request->getUri()->getPath())) {
return new Response(400);
}
if (true !== $this->verifier->verifyHost($request->getHeader('Host'))) {
return new Response(400);
}
$upgradeSuggestion = [
'Connection' => 'Upgrade',
'Upgrade' => 'websocket',
'Sec-WebSocket-Version' => $this->getVersionNumber()
];
if (count($this->_supportedSubProtocols) > 0) {
$upgradeSuggestion['Sec-WebSocket-Protocol'] = implode(', ', $this->_supportedSubProtocols);
}
if (true !== $this->verifier->verifyUpgradeRequest($request->getHeader('Upgrade'))) {
return new Response(426, $upgradeSuggestion, null, '1.1', 'Upgrade header MUST be provided');
}
if (true !== $this->verifier->verifyConnection($request->getHeader('Connection'))) {
return new Response(400, [], null, '1.1', 'Connection Upgrade MUST be requested');
}
if (true !== $this->verifier->verifyKey($request->getHeader('Sec-WebSocket-Key'))) {
return new Response(400, [], null, '1.1', 'Invalid Sec-WebSocket-Key');
}
if (true !== $this->verifier->verifyVersion($request->getHeader('Sec-WebSocket-Version'))) {
return new Response(426, $upgradeSuggestion);
}
$headers = [];
$subProtocols = $request->getHeader('Sec-WebSocket-Protocol');
if (count($subProtocols) > 0 || (count($this->_supportedSubProtocols) > 0 && $this->_strictSubProtocols)) {
$subProtocols = array_map('trim', explode(',', implode(',', $subProtocols)));
$match = array_reduce($subProtocols, function($accumulator, $protocol) {
return $accumulator ?: (isset($this->_supportedSubProtocols[$protocol]) ? $protocol : null);
}, null);
if ($this->_strictSubProtocols && null === $match) {
return new Response(426, $upgradeSuggestion, null, '1.1', 'No Sec-WebSocket-Protocols requested supported');
}
if (null !== $match) {
$headers['Sec-WebSocket-Protocol'] = $match;
}
}
return new Response(101, array_merge($headers, [
'Upgrade' => 'websocket'
, 'Connection' => 'Upgrade'
, 'Sec-WebSocket-Accept' => $this->sign((string)$request->getHeader('Sec-WebSocket-Key')[0])
, 'X-Powered-By' => 'Ratchet'
]));
}
/**
* 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));
}
/**
* @param array $protocols
*/
function setSupportedSubProtocols(array $protocols) {
$this->_supportedSubProtocols = array_flip($protocols);
}
/**
* If enabled and support for a subprotocol has been added handshake
* will not upgrade if a match between request and supported subprotocols
* @param boolean $enable
* @todo Consider extending this interface and moving this there.
* The spec does says the server can fail for this reason, but
* it is not a requirement. This is an implementation detail.
*/
function setStrictSubProtocolCheck($enable) {
$this->_strictSubProtocols = (boolean)$enable;
}
}
<?php
namespace Ratchet\RFC6455\Messaging;
class CloseFrameChecker {
private $validCloseCodes = [];
public function __construct() {
$this->validCloseCodes = [
Frame::CLOSE_NORMAL,
Frame::CLOSE_GOING_AWAY,
Frame::CLOSE_PROTOCOL,
Frame::CLOSE_BAD_DATA,
Frame::CLOSE_BAD_PAYLOAD,
Frame::CLOSE_POLICY,
Frame::CLOSE_TOO_BIG,
Frame::CLOSE_MAND_EXT,
Frame::CLOSE_SRV_ERR,
];
}
public function __invoke($val) {
return ($val >= 3000 && $val <= 4999) || in_array($val, $this->validCloseCodes);
}
}
<?php
namespace Ratchet\RFC6455\Messaging;
interface DataInterface {
/**
* Determine if the message is complete or still fragmented
* @return bool
*/
function isCoalesced();
/**
* Get the number of bytes the payload is set to be
* @return int
*/
function getPayloadLength();
/**
* Get the payload (message) sent from peer
* @return string
*/
function getPayload();
/**
* Get raw contents of the message
* @return string
*/
function getContents();
/**
* Should return the unmasked payload received from peer
* @return string
*/
function __toString();
}
This diff is collapsed.
<?php
namespace Ratchet\RFC6455\Messaging;
interface FrameInterface extends DataInterface {
/**
* Add incoming data to the frame from peer
* @param string
*/
function addBuffer($buf);
/**
* Is this the final frame in a fragmented message?
* @return bool
*/
function isFinal();
/**
* Is the payload masked?
* @return bool
*/
function isMasked();
/**
* @return int
*/
function getOpcode();
/**
* @return int
*/
//function getReceivedPayloadLength();
/**
* 32-big string
* @return string
*/
function getMaskingKey();
}
<?php
namespace Ratchet\RFC6455\Messaging;
class Message implements \IteratorAggregate, MessageInterface {
/**
* @var \SplDoublyLinkedList
*/
private $_frames;
public function __construct() {
$this->_frames = new \SplDoublyLinkedList;
}
public function getIterator() {
return $this->_frames;
}
/**
* {@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}
*/
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
}
}