diff options
Diffstat (limited to 'assets/php/vendor/ratchet/rfc6455/src')
12 files changed, 1371 insertions, 0 deletions
diff --git a/assets/php/vendor/ratchet/rfc6455/src/Handshake/ClientNegotiator.php b/assets/php/vendor/ratchet/rfc6455/src/Handshake/ClientNegotiator.php new file mode 100644 index 0000000..70856df --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Handshake/ClientNegotiator.php @@ -0,0 +1,53 @@ +<?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; + } +} diff --git a/assets/php/vendor/ratchet/rfc6455/src/Handshake/NegotiatorInterface.php b/assets/php/vendor/ratchet/rfc6455/src/Handshake/NegotiatorInterface.php new file mode 100644 index 0000000..c152eca --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Handshake/NegotiatorInterface.php @@ -0,0 +1,47 @@ +<?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); +} diff --git a/assets/php/vendor/ratchet/rfc6455/src/Handshake/RequestVerifier.php b/assets/php/vendor/ratchet/rfc6455/src/Handshake/RequestVerifier.php new file mode 100644 index 0000000..1ace489 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Handshake/RequestVerifier.php @@ -0,0 +1,140 @@ +<?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) { + } +} diff --git a/assets/php/vendor/ratchet/rfc6455/src/Handshake/ResponseVerifier.php b/assets/php/vendor/ratchet/rfc6455/src/Handshake/ResponseVerifier.php new file mode 100644 index 0000000..de03f53 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Handshake/ResponseVerifier.php @@ -0,0 +1,52 @@ +<?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 diff --git a/assets/php/vendor/ratchet/rfc6455/src/Handshake/ServerNegotiator.php b/assets/php/vendor/ratchet/rfc6455/src/Handshake/ServerNegotiator.php new file mode 100644 index 0000000..5a0073b --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Handshake/ServerNegotiator.php @@ -0,0 +1,136 @@ +<?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; + } +} diff --git a/assets/php/vendor/ratchet/rfc6455/src/Messaging/CloseFrameChecker.php b/assets/php/vendor/ratchet/rfc6455/src/Messaging/CloseFrameChecker.php new file mode 100644 index 0000000..3d800e5 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Messaging/CloseFrameChecker.php @@ -0,0 +1,24 @@ +<?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); + } +} diff --git a/assets/php/vendor/ratchet/rfc6455/src/Messaging/DataInterface.php b/assets/php/vendor/ratchet/rfc6455/src/Messaging/DataInterface.php new file mode 100644 index 0000000..18aa2e3 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Messaging/DataInterface.php @@ -0,0 +1,34 @@ +<?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(); +} diff --git a/assets/php/vendor/ratchet/rfc6455/src/Messaging/Frame.php b/assets/php/vendor/ratchet/rfc6455/src/Messaging/Frame.php new file mode 100644 index 0000000..40f9eb2 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Messaging/Frame.php @@ -0,0 +1,473 @@ +<?php +namespace Ratchet\RFC6455\Messaging; + +class Frame implements FrameInterface { + const OP_CONTINUE = 0; + const OP_TEXT = 1; + const OP_BINARY = 2; + const OP_CLOSE = 8; + const OP_PING = 9; + const OP_PONG = 10; + + const CLOSE_NORMAL = 1000; + const CLOSE_GOING_AWAY = 1001; + const CLOSE_PROTOCOL = 1002; + const CLOSE_BAD_DATA = 1003; + const CLOSE_NO_STATUS = 1005; + const CLOSE_ABNORMAL = 1006; + const CLOSE_BAD_PAYLOAD = 1007; + const CLOSE_POLICY = 1008; + const CLOSE_TOO_BIG = 1009; + const CLOSE_MAND_EXT = 1010; + const CLOSE_SRV_ERR = 1011; + const CLOSE_TLS = 1015; + + const MASK_LENGTH = 4; + + /** + * The contents of the frame + * @var string + */ + protected $data = ''; + + /** + * Number of bytes received from the frame + * @var int + */ + public $bytesRecvd = 0; + + /** + * Number of bytes in the payload (as per framing protocol) + * @var int + */ + protected $defPayLen = -1; + + /** + * If the frame is coalesced this is true + * This is to prevent doing math every time ::isCoalesced is called + * @var boolean + */ + private $isCoalesced = false; + + /** + * The unpacked first byte of the frame + * @var int + */ + protected $firstByte = -1; + + /** + * The unpacked second byte of the frame + * @var int + */ + protected $secondByte = -1; + + /** + * @var callable + * @returns \UnderflowException + */ + private $ufeg; + + /** + * @param string|null $payload + * @param bool $final + * @param int $opcode + * @param callable<\UnderflowException> $ufExceptionFactory + */ + public function __construct($payload = null, $final = true, $opcode = 1, callable $ufExceptionFactory = null) { + $this->ufeg = $ufExceptionFactory ?: function($msg = '') { + return new \UnderflowException($msg); + }; + + if (null === $payload) { + return; + } + + $this->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 call_user_func($this->ufeg, '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 call_user_func($this->ufeg, '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 call_user_func($this->ufeg, '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 call_user_func($this->ufeg, 'Not enough bytes received to determine reserved bit'); + } + + return 16 === ($this->firstByte & 16); + } + + /** + * {@inheritdoc} + */ + public function isMasked() { + if (-1 === $this->secondByte) { + throw call_user_func($this->ufeg, "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 call_user_func($this->ufeg, '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 call_user_func($this->ufeg, '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 call_user_func($this->ufeg, 'Frame must be coalesced to apply a mask'); + } + + $payload = substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength()); + } + + $len = strlen($payload); + + if (0 === $len) { + return ''; + } + + return $payload ^ str_pad('', $len, $maskingKey, STR_PAD_RIGHT); + + // TODO: Remove this before publish - keeping methods here to compare performance (above is faster but need control against v0.3.3) + + $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 call_user_func($this->ufeg, '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 call_user_func($this->ufeg, '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 call_user_func($this->ufeg, '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 call_user_func($this->ufeg, '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 call_user_func($this->ufeg, 'Can not return partial message'); + } + + return $this->__toString(); + } + + /** + * 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()); + } + + public function __toString() { + $payload = (string)substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength()); + + if ($this->isMasked()) { + $payload = $this->applyMask($this->getMaskingKey(), $payload); + } + + return $payload; + } + + /** + * Sometimes clients will concatenate more than one frame over the wire + * This method will take the extra bytes off the end and return them + * @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/assets/php/vendor/ratchet/rfc6455/src/Messaging/FrameInterface.php b/assets/php/vendor/ratchet/rfc6455/src/Messaging/FrameInterface.php new file mode 100644 index 0000000..dc24091 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Messaging/FrameInterface.php @@ -0,0 +1,38 @@ +<?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(); +} diff --git a/assets/php/vendor/ratchet/rfc6455/src/Messaging/Message.php b/assets/php/vendor/ratchet/rfc6455/src/Messaging/Message.php new file mode 100644 index 0000000..4f3b014 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Messaging/Message.php @@ -0,0 +1,123 @@ +<?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 + } + } + + return $len; + } + + /** + * {@inheritdoc} + */ + public function getPayload() { + if (!$this->isCoalesced()) { + throw new \UnderflowException('Message has not been put back together yet'); + } + + return $this->__toString(); + } + + /** + * {@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; + } + + public function __toString() { + $buffer = ''; + + foreach ($this->_frames as $frame) { + $buffer .= $frame->getPayload(); + } + + return $buffer; + } + + /** + * @return boolean + */ + public function isBinary() { + if ($this->_frames->isEmpty()) { + throw new \UnderflowException('Not enough data has been received to determine if message is binary'); + } + + return Frame::OP_BINARY === $this->_frames->bottom()->getOpcode(); + } +} diff --git a/assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageBuffer.php b/assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageBuffer.php new file mode 100644 index 0000000..07ff4f1 --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageBuffer.php @@ -0,0 +1,231 @@ +<?php +namespace Ratchet\RFC6455\Messaging; + +class MessageBuffer { + /** + * @var \Ratchet\RFC6455\Messaging\CloseFrameChecker + */ + private $closeFrameChecker; + + /** + * @var callable + */ + private $exceptionFactory; + + /** + * @var \Ratchet\RFC6455\Messaging\Message + */ + private $messageBuffer; + + /** + * @var \Ratchet\RFC6455\Messaging\Frame + */ + private $frameBuffer; + + /** + * @var callable + */ + private $onMessage; + + /** + * @var callable + */ + private $onControl; + + /** + * @var bool + */ + private $checkForMask; + + function __construct( + CloseFrameChecker $frameChecker, + callable $onMessage, + callable $onControl = null, + $expectMask = true, + $exceptionFactory = null + ) { + $this->closeFrameChecker = $frameChecker; + $this->checkForMask = (bool)$expectMask; + + $this->exceptionFactory ?: $this->exceptionFactory = function($msg) { + return new \UnderflowException($msg); + }; + + $this->onMessage = $onMessage; + $this->onControl = $onControl ?: function() {}; + } + + public function onData($data) { + while (strlen($data) > 0) { + $data = $this->processData($data); + } + } + + /** + * @param string $data + * @return null + */ + private function processData($data) { + $this->messageBuffer ?: $this->messageBuffer = $this->newMessage(); + $this->frameBuffer ?: $this->frameBuffer = $this->newFrame(); + + $this->frameBuffer->addBuffer($data); + if (!$this->frameBuffer->isCoalesced()) { + return ''; + } + + $onMessage = $this->onMessage; + $onControl = $this->onControl; + + $this->frameBuffer = $this->frameCheck($this->frameBuffer); + + $overflow = $this->frameBuffer->extractOverflow(); + $this->frameBuffer->unMaskPayload(); + + $opcode = $this->frameBuffer->getOpcode(); + + if ($opcode > 2) { + $onControl($this->frameBuffer); + + if (Frame::OP_CLOSE === $opcode) { + return ''; + } + } else { + $this->messageBuffer->addFrame($this->frameBuffer); + } + + $this->frameBuffer = null; + + if ($this->messageBuffer->isCoalesced()) { + $msgCheck = $this->checkMessage($this->messageBuffer); + if (true !== $msgCheck) { + $onControl($this->newCloseFrame($msgCheck, 'Ratchet detected an invalid UTF-8 payload')); + } else { + $onMessage($this->messageBuffer); + } + + $this->messageBuffer = null; + } + + return $overflow; + } + + /** + * Check a frame to be added to the current message buffer + * @param \Ratchet\RFC6455\Messaging\FrameInterface|FrameInterface $frame + * @return \Ratchet\RFC6455\Messaging\FrameInterface|FrameInterface + */ + public function frameCheck(FrameInterface $frame) { + if (false !== $frame->getRsv1() || + false !== $frame->getRsv2() || + false !== $frame->getRsv3() + ) { + return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected an invalid reserve code'); + } + + if ($this->checkForMask && !$frame->isMasked()) { + return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected an incorrect frame mask'); + } + + $opcode = $frame->getOpcode(); + + if ($opcode > 2) { + if ($frame->getPayloadLength() > 125 || !$frame->isFinal()) { + return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected a mismatch between final bit and indicated payload length'); + } + + switch ($opcode) { + case Frame::OP_CLOSE: + $closeCode = 0; + + $bin = $frame->getPayload(); + + if (empty($bin)) { + return $this->newCloseFrame(Frame::CLOSE_NORMAL); + } + + if (strlen($bin) === 1) { + return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected an invalid close code'); + } + + if (strlen($bin) >= 2) { + list($closeCode) = array_merge(unpack('n*', substr($bin, 0, 2))); + } + + $checker = $this->closeFrameChecker; + if (!$checker($closeCode)) { + return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected an invalid close code'); + } + + if (!$this->checkUtf8(substr($bin, 2))) { + return $this->newCloseFrame(Frame::CLOSE_BAD_PAYLOAD, 'Ratchet detected an invalid UTF-8 payload in the close reason'); + } + + return $frame; + break; + case Frame::OP_PING: + case Frame::OP_PONG: + break; + default: + return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected an invalid OP code'); + break; + } + + return $frame; + } + + if (Frame::OP_CONTINUE === $frame->getOpcode() && 0 === count($this->messageBuffer)) { + return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected the first frame of a message was a continue'); + } + + if (count($this->messageBuffer) > 0 && Frame::OP_CONTINUE !== $frame->getOpcode()) { + return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected invalid OP code when expecting continue frame'); + } + + return $frame; + } + + /** + * Determine if a message is valid + * @param \Ratchet\RFC6455\Messaging\MessageInterface + * @return bool|int true if valid - false if incomplete - int of recommended close code + */ + public function checkMessage(MessageInterface $message) { + if (!$message->isBinary()) { + if (!$this->checkUtf8($message->getPayload())) { + return Frame::CLOSE_BAD_PAYLOAD; + } + } + + return true; + } + + private function checkUtf8($string) { + if (extension_loaded('mbstring')) { + return mb_check_encoding($string, 'UTF-8'); + } + + return preg_match('//u', $string); + } + + /** + * @return \Ratchet\RFC6455\Messaging\MessageInterface + */ + public function newMessage() { + return new Message; + } + + /** + * @param string|null $payload + * @param bool|null $final + * @param int|null $opcode + * @return \Ratchet\RFC6455\Messaging\FrameInterface + */ + public function newFrame($payload = null, $final = null, $opcode = null) { + return new Frame($payload, $final, $opcode, $this->exceptionFactory); + } + + public function newCloseFrame($code, $reason = '') { + return $this->newFrame(pack('n', $code) . $reason, true, Frame::OP_CLOSE); + } +} diff --git a/assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageInterface.php b/assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageInterface.php new file mode 100644 index 0000000..fd7212e --- /dev/null +++ b/assets/php/vendor/ratchet/rfc6455/src/Messaging/MessageInterface.php @@ -0,0 +1,20 @@ +<?php +namespace Ratchet\RFC6455\Messaging; + +interface MessageInterface extends DataInterface, \Traversable, \Countable { + /** + * @param FrameInterface $fragment + * @return MessageInterface + */ + function addFrame(FrameInterface $fragment); + + /** + * @return int + */ + function getOpcode(); + + /** + * @return bool + */ + function isBinary(); +} |