Compare commits
	
		
			19 Commits
		
	
	
		
			5d67378e09
			...
			859d95f85d
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 859d95f85d | ||
|   | 3a6e857170 | ||
|   | 9e2c1c9bea | ||
|   | 2c2ace1397 | ||
|   | 772e465650 | ||
|   | 43043372a9 | ||
|   | 2aedfd27a7 | ||
|   | 58b29fb4a3 | ||
|   | 345fab10e6 | ||
|   | 191926dd3f | ||
|   | 82760a7616 | ||
|   | fe3db0a5d8 | ||
|   | 382a3ed941 | ||
|   | 93050e65a0 | ||
|   | 0050245eb4 | ||
|   | c018f30d0c | ||
|   | 42a6fd89e0 | ||
|   | 6df0bd1b68 | ||
|   | f9a5094726 | 
							
								
								
									
										11
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @ -6,10 +6,12 @@ on: | ||||
| 
 | ||||
| jobs: | ||||
|   PHPUnit: | ||||
|     name: PHPUnit (PHP ${{ matrix.php }})(${{ matrix.env }}) | ||||
|     runs-on: ubuntu-20.04 | ||||
|     name: PHPUnit (PHP ${{ matrix.php }})(${{ matrix.env }}) on ${{ matrix.os }} | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     strategy: | ||||
|       matrix: | ||||
|         os: | ||||
|           - ubuntu-22.04 | ||||
|         env: | ||||
|           - client | ||||
|           - server | ||||
| @ -17,8 +19,11 @@ jobs: | ||||
|           - 7.4 | ||||
|           - 8.0 | ||||
|           - 8.1 | ||||
|           - 8.2 | ||||
|           - 8.3 | ||||
|           - 8.4 | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|       - uses: actions/checkout@v4 | ||||
|       - name: Setup PHP | ||||
|         uses: shivammathur/setup-php@v2 | ||||
|         with: | ||||
|  | ||||
| @ -27,11 +27,13 @@ | ||||
|     }, | ||||
|     "require": { | ||||
|         "php": ">=7.4", | ||||
|         "guzzlehttp/psr7": "^2 || ^1.7" | ||||
|         "psr/http-factory-implementation": "^1.0", | ||||
|         "symfony/polyfill-php80": "^1.15" | ||||
|     }, | ||||
|     "require-dev": { | ||||
|         "phpunit/phpunit": "^9.5", | ||||
|         "react/socket": "^1.3" | ||||
|         "react/socket": "^1.3", | ||||
|         "guzzlehttp/psr7": "^2.7" | ||||
|     }, | ||||
|     "scripts": { | ||||
|         "abtest-client": "ABTEST=client && sh tests/ab/run_ab_tests.sh", | ||||
|  | ||||
| @ -3,38 +3,35 @@ namespace Ratchet\RFC6455\Handshake; | ||||
| use Psr\Http\Message\RequestInterface; | ||||
| use Psr\Http\Message\ResponseInterface; | ||||
| use Psr\Http\Message\UriInterface; | ||||
| use GuzzleHttp\Psr7\Request; | ||||
| use Psr\Http\Message\RequestFactoryInterface; | ||||
| 
 | ||||
| class ClientNegotiator { | ||||
|     /** | ||||
|      * @var ResponseVerifier | ||||
|      */ | ||||
|     private $verifier; | ||||
|     private ResponseVerifier $verifier; | ||||
| 
 | ||||
|     /** | ||||
|      * @var \Psr\Http\Message\RequestInterface | ||||
|      */ | ||||
|     private $defaultHeader; | ||||
|     private RequestInterface $defaultHeader; | ||||
| 
 | ||||
|     function __construct(PermessageDeflateOptions $perMessageDeflateOptions = null) { | ||||
|     private RequestFactoryInterface $requestFactory; | ||||
| 
 | ||||
|     public function __construct( | ||||
|         RequestFactoryInterface $requestFactory, | ||||
|         ?PermessageDeflateOptions $perMessageDeflateOptions = null | ||||
|     ) { | ||||
|         $this->verifier = new ResponseVerifier; | ||||
|         $this->requestFactory = $requestFactory; | ||||
| 
 | ||||
|         $this->defaultHeader = new Request('GET', '', [ | ||||
|             'Connection'            => 'Upgrade' | ||||
|           , 'Upgrade'               => 'websocket' | ||||
|           , 'Sec-WebSocket-Version' => $this->getVersion() | ||||
|           , 'User-Agent'            => "Ratchet" | ||||
|         ]); | ||||
|         $this->defaultHeader = $this->requestFactory | ||||
|             ->createRequest('GET', '') | ||||
|             ->withHeader('Connection'           , 'Upgrade') | ||||
|             ->withHeader('Upgrade'              , 'websocket') | ||||
|             ->withHeader('Sec-WebSocket-Version', $this->getVersion()) | ||||
|             ->withHeader('User-Agent'           , 'Ratchet'); | ||||
| 
 | ||||
|         if ($perMessageDeflateOptions === null) { | ||||
|             $perMessageDeflateOptions = PermessageDeflateOptions::createDisabled(); | ||||
|         } | ||||
|         $perMessageDeflateOptions ??= PermessageDeflateOptions::createDisabled(); | ||||
| 
 | ||||
|         // https://bugs.php.net/bug.php?id=73373
 | ||||
|         // https://bugs.php.net/bug.php?id=74240 - need >=7.1.4 or >=7.0.18
 | ||||
|         if ($perMessageDeflateOptions->isEnabled() && | ||||
|             !PermessageDeflateOptions::permessageDeflateSupported()) { | ||||
|             trigger_error('permessage-deflate is being disabled because it is not support by your PHP version.', E_USER_NOTICE); | ||||
|         if ($perMessageDeflateOptions->isEnabled() && !PermessageDeflateOptions::permessageDeflateSupported()) { | ||||
|             trigger_error('permessage-deflate is being disabled because it is not supported by your PHP version.', E_USER_NOTICE); | ||||
|             $perMessageDeflateOptions = PermessageDeflateOptions::createDisabled(); | ||||
|         } | ||||
|         if ($perMessageDeflateOptions->isEnabled() && !function_exists('deflate_add')) { | ||||
| @ -45,16 +42,16 @@ class ClientNegotiator { | ||||
|         $this->defaultHeader = $perMessageDeflateOptions->addHeaderToRequest($this->defaultHeader); | ||||
|     } | ||||
| 
 | ||||
|     public function generateRequest(UriInterface $uri) { | ||||
|     public function generateRequest(UriInterface $uri): RequestInterface { | ||||
|         return $this->defaultHeader->withUri($uri) | ||||
|             ->withHeader("Sec-WebSocket-Key", $this->generateKey()); | ||||
|             ->withHeader('Sec-WebSocket-Key', $this->generateKey()); | ||||
|     } | ||||
| 
 | ||||
|     public function validateResponse(RequestInterface $request, ResponseInterface $response) { | ||||
|     public function validateResponse(RequestInterface $request, ResponseInterface $response): bool { | ||||
|         return $this->verifier->verifyAll($request, $response); | ||||
|     } | ||||
| 
 | ||||
|     public function generateKey() { | ||||
|     public function generateKey(): string { | ||||
|         $chars     = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwzyz1234567890+/='; | ||||
|         $charRange = strlen($chars) - 1; | ||||
|         $key       = ''; | ||||
| @ -65,7 +62,7 @@ class ClientNegotiator { | ||||
|         return base64_encode($key); | ||||
|     } | ||||
| 
 | ||||
|     public function getVersion() { | ||||
|     public function getVersion(): int { | ||||
|         return 13; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| <?php | ||||
| namespace Ratchet\RFC6455\Handshake; | ||||
| use Psr\Http\Message\RequestInterface; | ||||
| use Psr\Http\Message\ResponseInterface; | ||||
| 
 | ||||
| /** | ||||
|  * A standard interface for interacting with the various version of the WebSocket protocol | ||||
| @ -14,34 +15,34 @@ interface NegotiatorInterface { | ||||
|      * @param RequestInterface $request | ||||
|      * @return bool | ||||
|      */ | ||||
|     function isProtocol(RequestInterface $request); | ||||
|     public function isProtocol(RequestInterface $request): bool; | ||||
| 
 | ||||
|     /** | ||||
|      * Although the version has a name associated with it the integer returned is the proper identification | ||||
|      * @return int | ||||
|      */ | ||||
|     function getVersionNumber(); | ||||
|     public function getVersionNumber(): int; | ||||
| 
 | ||||
|     /** | ||||
|      * Perform the handshake and return the response headers | ||||
|      * @param RequestInterface $request | ||||
|      * @return \Psr\Http\Message\ResponseInterface | ||||
|      * @return ResponseInterface | ||||
|      */ | ||||
|     function handshake(RequestInterface $request); | ||||
|     public function handshake(RequestInterface $request): ResponseInterface; | ||||
| 
 | ||||
|     /** | ||||
|      * Add supported protocols. If the request has any matching the response will include one | ||||
|      * @param array $protocols | ||||
|      */ | ||||
|     function setSupportedSubProtocols(array $protocols); | ||||
|     public function setSupportedSubProtocols(array $protocols): void; | ||||
| 
 | ||||
|     /** | ||||
|      * 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 | ||||
|      *       The spec does say the server can fail for this reason, but | ||||
|      *       it is not a requirement. This is an implementation detail. | ||||
|      */ | ||||
|     function setStrictSubProtocolCheck($enable); | ||||
|     public function setStrictSubProtocolCheck(bool $enable): void; | ||||
| } | ||||
|  | ||||
| @ -8,21 +8,21 @@ use Psr\Http\Message\ResponseInterface; | ||||
| 
 | ||||
| final class PermessageDeflateOptions | ||||
| { | ||||
|     const MAX_WINDOW_BITS = 15; | ||||
|     /* this is a private instead of const for 5.4 compatibility */ | ||||
|     private static $VALID_BITS = ['8', '9', '10', '11', '12', '13', '14', '15']; | ||||
|     public const MAX_WINDOW_BITS = 15; | ||||
| 
 | ||||
|     private $deflateEnabled = false; | ||||
|     private const VALID_BITS = [8, 9, 10, 11, 12, 13, 14, 15]; | ||||
| 
 | ||||
|     private $server_no_context_takeover; | ||||
|     private $client_no_context_takeover; | ||||
|     private $server_max_window_bits; | ||||
|     private $client_max_window_bits; | ||||
|     private bool $deflateEnabled = false; | ||||
| 
 | ||||
|     private ?bool $server_no_context_takeover = null; | ||||
|     private ?bool $client_no_context_takeover = null; | ||||
|     private ?int $server_max_window_bits = null; | ||||
|     private ?int $client_max_window_bits = null; | ||||
| 
 | ||||
|     private function __construct() { } | ||||
| 
 | ||||
|     public static function createEnabled() { | ||||
|         $new                             = new static(); | ||||
|         $new                             = new self(); | ||||
|         $new->deflateEnabled             = true; | ||||
|         $new->client_max_window_bits     = self::MAX_WINDOW_BITS; | ||||
|         $new->client_no_context_takeover = false; | ||||
| @ -33,35 +33,35 @@ final class PermessageDeflateOptions | ||||
|     } | ||||
| 
 | ||||
|     public static function createDisabled() { | ||||
|         return new static(); | ||||
|         return new self(); | ||||
|     } | ||||
| 
 | ||||
|     public function withClientNoContextTakeover() { | ||||
|     public function withClientNoContextTakeover(): self { | ||||
|         $new = clone $this; | ||||
|         $new->client_no_context_takeover = true; | ||||
|         return $new; | ||||
|     } | ||||
| 
 | ||||
|     public function withoutClientNoContextTakeover() { | ||||
|     public function withoutClientNoContextTakeover(): self { | ||||
|         $new = clone $this; | ||||
|         $new->client_no_context_takeover = false; | ||||
|         return $new; | ||||
|     } | ||||
| 
 | ||||
|     public function withServerNoContextTakeover() { | ||||
|     public function withServerNoContextTakeover(): self { | ||||
|         $new = clone $this; | ||||
|         $new->server_no_context_takeover = true; | ||||
|         return $new; | ||||
|     } | ||||
| 
 | ||||
|     public function withoutServerNoContextTakeover() { | ||||
|     public function withoutServerNoContextTakeover(): self { | ||||
|         $new = clone $this; | ||||
|         $new->server_no_context_takeover = false; | ||||
|         return $new; | ||||
|     } | ||||
| 
 | ||||
|     public function withServerMaxWindowBits($bits = self::MAX_WINDOW_BITS) { | ||||
|         if (!in_array($bits, self::$VALID_BITS)) { | ||||
|     public function withServerMaxWindowBits(int $bits = self::MAX_WINDOW_BITS): self { | ||||
|         if (!in_array($bits, self::VALID_BITS)) { | ||||
|             throw new \Exception('server_max_window_bits must have a value between 8 and 15.'); | ||||
|         } | ||||
|         $new = clone $this; | ||||
| @ -69,8 +69,8 @@ final class PermessageDeflateOptions | ||||
|         return $new; | ||||
|     } | ||||
| 
 | ||||
|     public function withClientMaxWindowBits($bits = self::MAX_WINDOW_BITS) { | ||||
|         if (!in_array($bits, self::$VALID_BITS)) { | ||||
|     public function withClientMaxWindowBits(int $bits = self::MAX_WINDOW_BITS): self { | ||||
|         if (!in_array($bits, self::VALID_BITS)) { | ||||
|             throw new \Exception('client_max_window_bits must have a value between 8 and 15.'); | ||||
|         } | ||||
|         $new = clone $this; | ||||
| @ -86,7 +86,7 @@ final class PermessageDeflateOptions | ||||
|      * @return PermessageDeflateOptions[] | ||||
|      * @throws \Exception | ||||
|      */ | ||||
|     public static function fromRequestOrResponse(MessageInterface $requestOrResponse) { | ||||
|     public static function fromRequestOrResponse(MessageInterface $requestOrResponse): array { | ||||
|         $optionSets = []; | ||||
| 
 | ||||
|         $extHeader = preg_replace('/\s+/', '', join(', ', $requestOrResponse->getHeader('Sec-Websocket-Extensions'))); | ||||
| @ -103,7 +103,7 @@ final class PermessageDeflateOptions | ||||
|             } | ||||
| 
 | ||||
|             array_shift($parts); | ||||
|             $options                 = new static(); | ||||
|             $options                 = new self(); | ||||
|             $options->deflateEnabled = true; | ||||
|             foreach ($parts as $part) { | ||||
|                 $kv = explode('=', $part); | ||||
| @ -119,15 +119,18 @@ final class PermessageDeflateOptions | ||||
|                         $value = true; | ||||
|                         break; | ||||
|                     case "server_max_window_bits": | ||||
|                         if (!in_array($value, self::$VALID_BITS)) { | ||||
|                         $value = (int) $value; | ||||
|                         if (!in_array($value, self::VALID_BITS)) { | ||||
|                             throw new InvalidPermessageDeflateOptionsException($key . ' must have a value between 8 and 15.'); | ||||
|                         } | ||||
|                         break; | ||||
|                     case "client_max_window_bits": | ||||
|                         if ($value === null) { | ||||
|                             $value = '15'; | ||||
|                             $value = 15; | ||||
|                         } else { | ||||
|                             $value = (int) $value; | ||||
|                         } | ||||
|                         if (!in_array($value, self::$VALID_BITS)) { | ||||
|                         if (!in_array($value, self::VALID_BITS)) { | ||||
|                             throw new InvalidPermessageDeflateOptionsException($key . ' must have no value or a value between 8 and 15.'); | ||||
|                         } | ||||
|                         break; | ||||
| @ -154,39 +157,39 @@ final class PermessageDeflateOptions | ||||
|         } | ||||
| 
 | ||||
|         // always put a disabled on the end
 | ||||
|         $optionSets[] = new static(); | ||||
|         $optionSets[] = new self(); | ||||
| 
 | ||||
|         return $optionSets; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      * @return bool|null | ||||
|      */ | ||||
|     public function getServerNoContextTakeover() | ||||
|     public function getServerNoContextTakeover(): ?bool | ||||
|     { | ||||
|         return $this->server_no_context_takeover; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      * @return bool|null | ||||
|      */ | ||||
|     public function getClientNoContextTakeover() | ||||
|     public function getClientNoContextTakeover(): ?bool | ||||
|     { | ||||
|         return $this->client_no_context_takeover; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      * @return int|null | ||||
|      */ | ||||
|     public function getServerMaxWindowBits() | ||||
|     public function getServerMaxWindowBits(): ?int | ||||
|     { | ||||
|         return $this->server_max_window_bits; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return mixed | ||||
|      * @return int|null | ||||
|      */ | ||||
|     public function getClientMaxWindowBits() | ||||
|     public function getClientMaxWindowBits(): ?int | ||||
|     { | ||||
|         return $this->client_max_window_bits; | ||||
|     } | ||||
| @ -194,7 +197,7 @@ final class PermessageDeflateOptions | ||||
|     /** | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function isEnabled() | ||||
|     public function isEnabled(): bool | ||||
|     { | ||||
|         return $this->deflateEnabled; | ||||
|     } | ||||
| @ -203,7 +206,7 @@ final class PermessageDeflateOptions | ||||
|      * @param ResponseInterface $response | ||||
|      * @return ResponseInterface | ||||
|      */ | ||||
|     public function addHeaderToResponse(ResponseInterface $response) | ||||
|     public function addHeaderToResponse(ResponseInterface $response): ResponseInterface | ||||
|     { | ||||
|         if (!$this->deflateEnabled) { | ||||
|             return $response; | ||||
| @ -226,7 +229,7 @@ final class PermessageDeflateOptions | ||||
|         return $response->withAddedHeader('Sec-Websocket-Extensions', $header); | ||||
|     } | ||||
| 
 | ||||
|     public function addHeaderToRequest(RequestInterface $request) { | ||||
|     public function addHeaderToRequest(RequestInterface $request): RequestInterface { | ||||
|         if (!$this->deflateEnabled) { | ||||
|             return $request; | ||||
|         } | ||||
| @ -249,7 +252,7 @@ final class PermessageDeflateOptions | ||||
|         return $request->withAddedHeader('Sec-Websocket-Extensions', $header); | ||||
|     } | ||||
| 
 | ||||
|     public static function permessageDeflateSupported($version = PHP_VERSION) { | ||||
|     public static function permessageDeflateSupported(string $version = PHP_VERSION): bool { | ||||
|         if (!function_exists('deflate_init')) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
| @ -8,14 +8,14 @@ use Psr\Http\Message\RequestInterface; | ||||
|  * @todo Currently just returning invalid - should consider returning appropriate HTTP status code error #s
 | ||||
|  */ | ||||
| class RequestVerifier { | ||||
|     const VERSION = 13; | ||||
|     public 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) { | ||||
|     public function verifyAll(RequestInterface $request): bool { | ||||
|         $passes = 0; | ||||
| 
 | ||||
|         $passes += (int)$this->verifyMethod($request->getMethod()); | ||||
| @ -27,7 +27,7 @@ class RequestVerifier { | ||||
|         $passes += (int)$this->verifyKey($request->getHeader('Sec-WebSocket-Key')); | ||||
|         $passes += (int)$this->verifyVersion($request->getHeader('Sec-WebSocket-Version')); | ||||
| 
 | ||||
|         return (8 === $passes); | ||||
|         return 8 === $passes; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -35,8 +35,8 @@ class RequestVerifier { | ||||
|      * @param string | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function verifyMethod($val) { | ||||
|         return ('get' === strtolower($val)); | ||||
|     public function verifyMethod(string $val): bool { | ||||
|         return 'get' === strtolower($val); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -44,15 +44,15 @@ class RequestVerifier { | ||||
|      * @param string|int | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function verifyHTTPVersion($val) { | ||||
|         return (1.1 <= (double)$val); | ||||
|     public function verifyHTTPVersion($val): bool { | ||||
|         return 1.1 <= (double)$val; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function verifyRequestURI($val) { | ||||
|     public function verifyRequestURI(string $val): bool { | ||||
|         if ($val[0] !== '/') { | ||||
|             return false; | ||||
|         } | ||||
| @ -73,8 +73,8 @@ class RequestVerifier { | ||||
|      * @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)); | ||||
|     public function verifyHost(array $hostHeader): bool { | ||||
|         return 1 === count($hostHeader); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -82,8 +82,8 @@ class RequestVerifier { | ||||
|      * @param  array $upgradeHeader MUST equal "websocket" | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function verifyUpgradeRequest(array $upgradeHeader) { | ||||
|         return (1 === count($upgradeHeader) && 'websocket' === strtolower($upgradeHeader[0])); | ||||
|     public function verifyUpgradeRequest(array $upgradeHeader): bool { | ||||
|         return 1 === count($upgradeHeader) && 'websocket' === strtolower($upgradeHeader[0]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -91,13 +91,11 @@ class RequestVerifier { | ||||
|      * @param  array $connectionHeader MUST include "Upgrade" | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function verifyConnection(array $connectionHeader) { | ||||
|     public function verifyConnection(array $connectionHeader): bool { | ||||
|         foreach ($connectionHeader as $l) { | ||||
|             $upgrades = array_filter( | ||||
|                 array_map('trim', array_map('strtolower', explode(',', $l))), | ||||
|                 function ($x) { | ||||
|                     return 'upgrade' === $x; | ||||
|                 } | ||||
|                 static fn (string $x) => 'upgrade' === $x | ||||
|             ); | ||||
|             if (count($upgrades) > 0) { | ||||
|                 return true; | ||||
| @ -113,8 +111,8 @@ class RequestVerifier { | ||||
|      * @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]))); | ||||
|     public function verifyKey(array $keyHeader): bool { | ||||
|         return 1 === count($keyHeader) && 16 === strlen(base64_decode($keyHeader[0])); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -122,33 +120,33 @@ class RequestVerifier { | ||||
|      * @param string[] $versionHeader MUST equal ["13"] | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function verifyVersion(array $versionHeader) { | ||||
|         return (1 === count($versionHeader) && static::VERSION === (int)$versionHeader[0]); | ||||
|     public function verifyVersion(array $versionHeader): bool { | ||||
|         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) { | ||||
|     public function verifyProtocol($val): bool { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @todo Write logic for this method.  See section 4.2.1.9 | ||||
|      */ | ||||
|     public function verifyExtensions($val) { | ||||
|     public function verifyExtensions($val): bool { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     public function getPermessageDeflateOptions(array $requestHeader, array $responseHeader) { | ||||
|     public function getPermessageDeflateOptions(array $requestHeader, array $responseHeader): array { | ||||
|         $headerChecker = static fn (string $val) => 'permessage-deflate' === substr($val, 0, strlen('permessage-deflate')); | ||||
| 
 | ||||
|         $deflate = true; | ||||
|         if (!isset($requestHeader['Sec-WebSocket-Extensions']) || count(array_filter($requestHeader['Sec-WebSocket-Extensions'], function ($val) { | ||||
|             return 'permessage-deflate' === substr($val, 0, strlen('permessage-deflate')); | ||||
|         })) === 0) { | ||||
|         if (!isset($requestHeader['Sec-WebSocket-Extensions']) || count(array_filter($requestHeader['Sec-WebSocket-Extensions'], $headerChecker)) === 0) { | ||||
|              $deflate = false; | ||||
|         } | ||||
| 
 | ||||
|         if (!isset($responseHeader['Sec-WebSocket-Extensions']) || count(array_filter($responseHeader['Sec-WebSocket-Extensions'], function ($val) { | ||||
|                 return 'permessage-deflate' === substr($val, 0, strlen('permessage-deflate')); | ||||
|             })) === 0) { | ||||
|         if (!isset($responseHeader['Sec-WebSocket-Extensions']) || count(array_filter($responseHeader['Sec-WebSocket-Extensions'], $headerChecker)) === 0) { | ||||
|             $deflate = false; | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -4,7 +4,7 @@ use Psr\Http\Message\RequestInterface; | ||||
| use Psr\Http\Message\ResponseInterface; | ||||
| 
 | ||||
| class ResponseVerifier { | ||||
|     public function verifyAll(RequestInterface $request, ResponseInterface $response) { | ||||
|     public function verifyAll(RequestInterface $request, ResponseInterface $response): bool { | ||||
|         $passes = 0; | ||||
| 
 | ||||
|         $passes += (int)$this->verifyStatus($response->getStatusCode()); | ||||
| @ -26,31 +26,31 @@ class ResponseVerifier { | ||||
|         return (6 === $passes); | ||||
|     } | ||||
| 
 | ||||
|     public function verifyStatus($status) { | ||||
|         return ((int)$status === 101); | ||||
|     public function verifyStatus(int $status): bool { | ||||
|         return $status === 101; | ||||
|     } | ||||
| 
 | ||||
|     public function verifyUpgrade(array $upgrade) { | ||||
|         return (in_array('websocket', array_map('strtolower', $upgrade))); | ||||
|     public function verifyUpgrade(array $upgrade): bool { | ||||
|         return in_array('websocket', array_map('strtolower', $upgrade)); | ||||
|     } | ||||
| 
 | ||||
|     public function verifyConnection(array $connection) { | ||||
|         return (in_array('upgrade', array_map('strtolower', $connection))); | ||||
|     public function verifyConnection(array $connection): bool { | ||||
|         return in_array('upgrade', array_map('strtolower', $connection)); | ||||
|     } | ||||
| 
 | ||||
|     public function verifySecWebSocketAccept($swa, $key) { | ||||
|         return ( | ||||
|     public function verifySecWebSocketAccept(array $swa, array $key): bool { | ||||
|         return | ||||
|             1 === count($swa) && | ||||
|             1 === count($key) && | ||||
|             $swa[0] === $this->sign($key[0]) | ||||
|         ); | ||||
|         ; | ||||
|     } | ||||
| 
 | ||||
|     public function sign($key) { | ||||
|     public function sign(string $key): string { | ||||
|         return base64_encode(sha1($key . NegotiatorInterface::GUID, true)); | ||||
|     } | ||||
| 
 | ||||
|     public function verifySubProtocol(array $requestHeader, array $responseHeader) { | ||||
|     public function verifySubProtocol(array $requestHeader, array $responseHeader): bool { | ||||
|         if (0 === count($responseHeader)) { | ||||
|             return true; | ||||
|         } | ||||
| @ -60,7 +60,7 @@ class ResponseVerifier { | ||||
|         return count($responseHeader) === 1 && count(array_intersect($responseHeader, $requestedProtocols)) === 1; | ||||
|     } | ||||
| 
 | ||||
|     public function verifyExtensions(array $requestHeader, array $responseHeader) { | ||||
|     public function verifyExtensions(array $requestHeader, array $responseHeader): int { | ||||
|         if (in_array('permessage-deflate', $responseHeader)) { | ||||
|             return strpos(implode(',', $requestHeader), 'permessage-deflate') !== false ? 1 : 0; | ||||
|         } | ||||
|  | ||||
| @ -1,26 +1,32 @@ | ||||
| <?php | ||||
| namespace Ratchet\RFC6455\Handshake; | ||||
| use Psr\Http\Message\RequestInterface; | ||||
| use Psr\Http\Message\ResponseFactoryInterface; | ||||
| use GuzzleHttp\Psr7\Response; | ||||
| use Psr\Http\Message\ResponseInterface; | ||||
| 
 | ||||
| /** | ||||
|  * 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 RequestVerifier $verifier; | ||||
| 
 | ||||
|     private $_supportedSubProtocols = []; | ||||
|     private ResponseFactoryInterface $responseFactory; | ||||
| 
 | ||||
|     private $_strictSubProtocols = false; | ||||
|     private array $_supportedSubProtocols = []; | ||||
| 
 | ||||
|     private $enablePerMessageDeflate = false; | ||||
|     private bool $_strictSubProtocols = false; | ||||
| 
 | ||||
|     public function __construct(RequestVerifier $requestVerifier, $enablePerMessageDeflate = false) { | ||||
|     private bool $enablePerMessageDeflate = false; | ||||
| 
 | ||||
|     public function __construct( | ||||
|         RequestVerifier $requestVerifier, | ||||
|         ResponseFactoryInterface $responseFactory, | ||||
|         $enablePerMessageDeflate = false | ||||
|     ) { | ||||
|         $this->verifier = $requestVerifier; | ||||
|         $this->responseFactory = $responseFactory; | ||||
| 
 | ||||
|         // https://bugs.php.net/bug.php?id=73373
 | ||||
|         // https://bugs.php.net/bug.php?id=74240 - need >=7.1.4 or >=7.0.18
 | ||||
| @ -38,85 +44,85 @@ class ServerNegotiator implements NegotiatorInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function isProtocol(RequestInterface $request) { | ||||
|     public function isProtocol(RequestInterface $request): bool { | ||||
|         return $this->verifier->verifyVersion($request->getHeader('Sec-WebSocket-Version')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getVersionNumber() { | ||||
|     public function getVersionNumber(): int { | ||||
|         return RequestVerifier::VERSION; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function handshake(RequestInterface $request) { | ||||
|     public function handshake(RequestInterface $request): ResponseInterface { | ||||
|         $response = $this->responseFactory->createResponse(); | ||||
|         if (true !== $this->verifier->verifyMethod($request->getMethod())) { | ||||
|             return new Response(405, ['Allow' => 'GET']); | ||||
|             return $response->withHeader('Allow', 'GET')->withStatus(405); | ||||
|         } | ||||
| 
 | ||||
|         if (true !== $this->verifier->verifyHTTPVersion($request->getProtocolVersion())) { | ||||
|             return new Response(505); | ||||
|             return $response->withStatus(505); | ||||
|         } | ||||
| 
 | ||||
|         if (true !== $this->verifier->verifyRequestURI($request->getUri()->getPath())) { | ||||
|             return new Response(400); | ||||
|             return $response->withStatus(400); | ||||
|         } | ||||
| 
 | ||||
|         if (true !== $this->verifier->verifyHost($request->getHeader('Host'))) { | ||||
|             return new Response(400); | ||||
|             return $response->withStatus(400); | ||||
|         } | ||||
| 
 | ||||
|         $upgradeSuggestion = [ | ||||
|             'Connection'             => 'Upgrade', | ||||
|             'Upgrade'                => 'websocket', | ||||
|             'Sec-WebSocket-Version'  => $this->getVersionNumber() | ||||
|         ]; | ||||
|         $upgradeResponse = $response | ||||
|             ->withHeader('Connection'           , 'Upgrade') | ||||
|             ->withHeader('Upgrade'              , 'websocket') | ||||
|             ->withHeader('Sec-WebSocket-Version', $this->getVersionNumber()); | ||||
| 
 | ||||
|         if (count($this->_supportedSubProtocols) > 0) { | ||||
|             $upgradeSuggestion['Sec-WebSocket-Protocol'] = implode(', ', array_keys($this->_supportedSubProtocols)); | ||||
|             $upgradeResponse = $upgradeResponse->withHeader( | ||||
|                 'Sec-WebSocket-Protocol', implode(', ', array_keys($this->_supportedSubProtocols)) | ||||
|             ); | ||||
|         } | ||||
|         if (true !== $this->verifier->verifyUpgradeRequest($request->getHeader('Upgrade'))) { | ||||
|             return new Response(426, $upgradeSuggestion, null, '1.1', 'Upgrade header MUST be provided'); | ||||
|             return $upgradeResponse->withStatus(426, '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'); | ||||
|             return $response->withStatus(400, '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'); | ||||
|             return $response->withStatus(400, 'Invalid Sec-WebSocket-Key'); | ||||
|         } | ||||
| 
 | ||||
|         if (true !== $this->verifier->verifyVersion($request->getHeader('Sec-WebSocket-Version'))) { | ||||
|             return new Response(426, $upgradeSuggestion); | ||||
|             return $upgradeResponse->withStatus(426); | ||||
|         } | ||||
| 
 | ||||
|         $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); | ||||
|             $match = array_reduce($subProtocols, fn ($accumulator, $protocol) => $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'); | ||||
|                 return $upgradeResponse->withStatus(426, 'No Sec-WebSocket-Protocols requested supported'); | ||||
|             } | ||||
| 
 | ||||
|             if (null !== $match) { | ||||
|                 $headers['Sec-WebSocket-Protocol'] = $match; | ||||
|                 $response = $response->withHeader('Sec-WebSocket-Protocol', $match); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         $response = 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' | ||||
|         ])); | ||||
|         $response = $response | ||||
|             ->withStatus(101) | ||||
|             ->withHeader('Upgrade'             , 'websocket') | ||||
|             ->withHeader('Connection'          , 'Upgrade') | ||||
|             ->withHeader('Sec-WebSocket-Accept', $this->sign((string)$request->getHeader('Sec-WebSocket-Key')[0])) | ||||
|             ->withHeader('X-Powered-By'        , 'Ratchet'); | ||||
| 
 | ||||
|         try { | ||||
|             $perMessageDeflateRequest = PermessageDeflateOptions::fromRequestOrResponse($request)[0]; | ||||
| @ -137,14 +143,14 @@ class ServerNegotiator implements NegotiatorInterface { | ||||
|      * @return string | ||||
|      * @internal | ||||
|      */ | ||||
|     public function sign($key) { | ||||
|     public function sign(string $key): string { | ||||
|         return base64_encode(sha1($key . static::GUID, true)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $protocols | ||||
|      */ | ||||
|     function setSupportedSubProtocols(array $protocols) { | ||||
|     public function setSupportedSubProtocols(array $protocols): void { | ||||
|         $this->_supportedSubProtocols = array_flip($protocols); | ||||
|     } | ||||
| 
 | ||||
| @ -153,10 +159,10 @@ class ServerNegotiator implements NegotiatorInterface { | ||||
|      *  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 | ||||
|      *       The spec does say 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; | ||||
|     public function setStrictSubProtocolCheck(bool $enable): void { | ||||
|         $this->_strictSubProtocols = $enable; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2,10 +2,7 @@ | ||||
| namespace Ratchet\RFC6455\Messaging; | ||||
| 
 | ||||
| class CloseFrameChecker { | ||||
|     private $validCloseCodes = []; | ||||
| 
 | ||||
|     public function __construct() { | ||||
|         $this->validCloseCodes = [ | ||||
|     private array $validCloseCodes = [ | ||||
|         Frame::CLOSE_NORMAL, | ||||
|         Frame::CLOSE_GOING_AWAY, | ||||
|         Frame::CLOSE_PROTOCOL, | ||||
| @ -16,9 +13,8 @@ class CloseFrameChecker { | ||||
|         Frame::CLOSE_MAND_EXT, | ||||
|         Frame::CLOSE_SRV_ERR, | ||||
|     ]; | ||||
|     } | ||||
| 
 | ||||
|     public function __invoke($val) { | ||||
|     public function __invoke(int $val): bool { | ||||
|         return ($val >= 3000 && $val <= 4999) || in_array($val, $this->validCloseCodes); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,34 +1,28 @@ | ||||
| <?php | ||||
| namespace Ratchet\RFC6455\Messaging; | ||||
| 
 | ||||
| interface DataInterface { | ||||
| interface DataInterface extends \Stringable { | ||||
|     /** | ||||
|      * Determine if the message is complete or still fragmented | ||||
|      * @return bool | ||||
|      */ | ||||
|     function isCoalesced(); | ||||
|     public function isCoalesced(): bool; | ||||
| 
 | ||||
|     /** | ||||
|      * Get the number of bytes the payload is set to be | ||||
|      * @return int | ||||
|      */ | ||||
|     function getPayloadLength(); | ||||
|     public function getPayloadLength(): int; | ||||
| 
 | ||||
|     /** | ||||
|      * Get the payload (message) sent from peer | ||||
|      * @return string | ||||
|      */ | ||||
|     function getPayload(); | ||||
|     public function getPayload(): string; | ||||
| 
 | ||||
|     /** | ||||
|      * Get raw contents of the message | ||||
|      * @return string | ||||
|      */ | ||||
|     function getContents(); | ||||
| 
 | ||||
|     /** | ||||
|      * Should return the unmasked payload received from peer | ||||
|      * @return string | ||||
|      */ | ||||
|     function __toString(); | ||||
|     public function getContents(): string; | ||||
| } | ||||
|  | ||||
| @ -26,40 +26,34 @@ class Frame implements FrameInterface { | ||||
| 
 | ||||
|     /** | ||||
|      * The contents of the frame | ||||
|      * @var string | ||||
|      */ | ||||
|     protected $data = ''; | ||||
|     protected string $data = ''; | ||||
| 
 | ||||
|     /** | ||||
|      * Number of bytes received from the frame | ||||
|      * @var int | ||||
|      */ | ||||
|     public $bytesRecvd = 0; | ||||
|     public int $bytesRecvd = 0; | ||||
| 
 | ||||
|     /** | ||||
|      * Number of bytes in the payload (as per framing protocol) | ||||
|      * @var int | ||||
|      */ | ||||
|     protected $defPayLen = -1; | ||||
|     protected int $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; | ||||
|     private bool $isCoalesced = false; | ||||
| 
 | ||||
|     /** | ||||
|      * The unpacked first byte of the frame | ||||
|      * @var int | ||||
|      */ | ||||
|     protected $firstByte = -1; | ||||
|     protected int $firstByte = -1; | ||||
| 
 | ||||
|     /** | ||||
|      * The unpacked second byte of the frame | ||||
|      * @var int | ||||
|      */ | ||||
|     protected $secondByte = -1; | ||||
|     protected int $secondByte = -1; | ||||
| 
 | ||||
|     /** | ||||
|      * @var callable | ||||
| @ -73,10 +67,8 @@ class Frame implements FrameInterface { | ||||
|      * @param int         $opcode | ||||
|      * @param callable<\UnderflowException> $ufExceptionFactory | ||||
|      */ | ||||
|     public function __construct($payload = null, $final = true, $opcode = 1, callable $ufExceptionFactory = null) { | ||||
|         $this->ufeg = $ufExceptionFactory ?: static function($msg = '') { | ||||
|             return new \UnderflowException($msg); | ||||
|         }; | ||||
|     public function __construct(?string $payload = null, bool $final = true, int $opcode = 1, ?callable $ufExceptionFactory = null) { | ||||
|         $this->ufeg = $ufExceptionFactory ?: static fn (string $msg = '') => new \UnderflowException($msg); | ||||
| 
 | ||||
|         if (null === $payload) { | ||||
|             return; | ||||
| @ -103,7 +95,7 @@ class Frame implements FrameInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function isCoalesced() { | ||||
|     public function isCoalesced(): bool { | ||||
|         if (true === $this->isCoalesced) { | ||||
|             return true; | ||||
|         } | ||||
| @ -123,7 +115,7 @@ class Frame implements FrameInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function addBuffer($buf) { | ||||
|     public function addBuffer(string $buf): void { | ||||
|         $len = strlen($buf); | ||||
| 
 | ||||
|         $this->data       .= $buf; | ||||
| @ -141,7 +133,7 @@ class Frame implements FrameInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function isFinal() { | ||||
|     public function isFinal(): bool { | ||||
|         if (-1 === $this->firstByte) { | ||||
|             throw call_user_func($this->ufeg, 'Not enough bytes received to determine if this is the final frame in message'); | ||||
|         } | ||||
| @ -149,7 +141,7 @@ class Frame implements FrameInterface { | ||||
|         return 128 === ($this->firstByte & 128); | ||||
|     } | ||||
| 
 | ||||
|     public function setRsv1($value = true) { | ||||
|     public function setRsv1(bool $value = true): self { | ||||
|         if (strlen($this->data) == 0) { | ||||
|             throw new \UnderflowException("Cannot set Rsv1 because there is no data."); | ||||
|         } | ||||
| @ -170,7 +162,7 @@ class Frame implements FrameInterface { | ||||
|      * @return boolean | ||||
|      * @throws \UnderflowException | ||||
|      */ | ||||
|     public function getRsv1() { | ||||
|     public function getRsv1(): bool { | ||||
|         if (-1 === $this->firstByte) { | ||||
|             throw call_user_func($this->ufeg, 'Not enough bytes received to determine reserved bit'); | ||||
|         } | ||||
| @ -182,7 +174,7 @@ class Frame implements FrameInterface { | ||||
|      * @return boolean | ||||
|      * @throws \UnderflowException | ||||
|      */ | ||||
|     public function getRsv2() { | ||||
|     public function getRsv2(): bool { | ||||
|         if (-1 === $this->firstByte) { | ||||
|             throw call_user_func($this->ufeg, 'Not enough bytes received to determine reserved bit'); | ||||
|         } | ||||
| @ -194,7 +186,7 @@ class Frame implements FrameInterface { | ||||
|      * @return boolean | ||||
|      * @throws \UnderflowException | ||||
|      */ | ||||
|     public function getRsv3() { | ||||
|     public function getRsv3(): bool { | ||||
|         if (-1 === $this->firstByte) { | ||||
|             throw call_user_func($this->ufeg, 'Not enough bytes received to determine reserved bit'); | ||||
|         } | ||||
| @ -205,7 +197,7 @@ class Frame implements FrameInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function isMasked() { | ||||
|     public function isMasked(): bool { | ||||
|         if (-1 === $this->secondByte) { | ||||
|             throw call_user_func($this->ufeg, "Not enough bytes received ({$this->bytesRecvd}) to determine if mask is set"); | ||||
|         } | ||||
| @ -216,7 +208,7 @@ class Frame implements FrameInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getMaskingKey() { | ||||
|     public function getMaskingKey(): string { | ||||
|         if (!$this->isMasked()) { | ||||
|             return ''; | ||||
|         } | ||||
| @ -234,7 +226,7 @@ class Frame implements FrameInterface { | ||||
|      * Create a 4 byte masking key | ||||
|      * @return string | ||||
|      */ | ||||
|     public function generateMaskingKey() { | ||||
|     public function generateMaskingKey(): string { | ||||
|         $mask = ''; | ||||
| 
 | ||||
|         for ($i = 1; $i <= static::MASK_LENGTH; $i++) { | ||||
| @ -251,7 +243,7 @@ class Frame implements FrameInterface { | ||||
|      * @throws \InvalidArgumentException If there is an issue with the given masking key | ||||
|      * @return Frame | ||||
|      */ | ||||
|     public function maskPayload($maskingKey = null) { | ||||
|     public function maskPayload(?string $maskingKey = null): self { | ||||
|         if (null === $maskingKey) { | ||||
|             $maskingKey = $this->generateMaskingKey(); | ||||
|         } | ||||
| @ -282,7 +274,7 @@ class Frame implements FrameInterface { | ||||
|      * @throws \UnderFlowException If the frame is not coalesced | ||||
|      * @return Frame | ||||
|      */ | ||||
|     public function unMaskPayload() { | ||||
|     public function unMaskPayload(): self { | ||||
|         if (!$this->isCoalesced()) { | ||||
|             throw call_user_func($this->ufeg, 'Frame must be coalesced before applying mask'); | ||||
|         } | ||||
| @ -311,7 +303,7 @@ class Frame implements FrameInterface { | ||||
|      * @throws \UnderflowException If using the payload but enough hasn't been buffered | ||||
|      * @return string              The masked string | ||||
|      */ | ||||
|     public function applyMask($maskingKey, $payload = null) { | ||||
|     public function applyMask(string $maskingKey, ?string $payload = null): string { | ||||
|         if (null === $payload) { | ||||
|             if (!$this->isCoalesced()) { | ||||
|                 throw call_user_func($this->ufeg, 'Frame must be coalesced to apply a mask'); | ||||
| @ -332,7 +324,7 @@ class Frame implements FrameInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getOpcode() { | ||||
|     public function getOpcode(): int { | ||||
|         if (-1 === $this->firstByte) { | ||||
|             throw call_user_func($this->ufeg, 'Not enough bytes received to determine opcode'); | ||||
|         } | ||||
| @ -345,7 +337,7 @@ class Frame implements FrameInterface { | ||||
|      * @return int | ||||
|      * @throws \UnderflowException If the buffer doesn't have enough data to determine this | ||||
|      */ | ||||
|     protected function getFirstPayloadVal() { | ||||
|     protected function getFirstPayloadVal(): int { | ||||
|         if (-1 === $this->secondByte) { | ||||
|             throw call_user_func($this->ufeg, 'Not enough bytes received'); | ||||
|         } | ||||
| @ -357,7 +349,7 @@ class Frame implements FrameInterface { | ||||
|      * @return int (7|23|71) Number of bits defined for the payload length in the fame | ||||
|      * @throws \UnderflowException | ||||
|      */ | ||||
|     protected function getNumPayloadBits() { | ||||
|     protected function getNumPayloadBits(): int { | ||||
|         if (-1 === $this->secondByte) { | ||||
|             throw call_user_func($this->ufeg, 'Not enough bytes received'); | ||||
|         } | ||||
| @ -387,14 +379,14 @@ class Frame implements FrameInterface { | ||||
|      * 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() { | ||||
|     protected function getNumPayloadBytes(): int { | ||||
|         return (1 + $this->getNumPayloadBits()) / 8; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getPayloadLength() { | ||||
|     public function getPayloadLength(): int { | ||||
|         if ($this->defPayLen !== -1) { | ||||
|             return $this->defPayLen; | ||||
|         } | ||||
| @ -424,7 +416,7 @@ class Frame implements FrameInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getPayloadStartingByte() { | ||||
|     public function getPayloadStartingByte(): int { | ||||
|         return 1 + $this->getNumPayloadBytes() + ($this->isMasked() ? static::MASK_LENGTH : 0); | ||||
|     } | ||||
| 
 | ||||
| @ -432,7 +424,7 @@ class Frame implements FrameInterface { | ||||
|      * {@inheritdoc} | ||||
|      * @todo Consider not checking mask, always returning the payload, masked or not | ||||
|      */ | ||||
|     public function getPayload() { | ||||
|     public function getPayload(): string { | ||||
|         if (!$this->isCoalesced()) { | ||||
|             throw call_user_func($this->ufeg, 'Can not return partial message'); | ||||
|         } | ||||
| @ -444,11 +436,11 @@ class Frame implements FrameInterface { | ||||
|      * 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() { | ||||
|     public function getContents(): string { | ||||
|         return substr($this->data, 0, $this->getPayloadStartingByte() + $this->getPayloadLength()); | ||||
|     } | ||||
| 
 | ||||
|     public function __toString() { | ||||
|     public function __toString(): string { | ||||
|         $payload = (string)substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength()); | ||||
| 
 | ||||
|         if ($this->isMasked()) { | ||||
| @ -463,7 +455,7 @@ class Frame implements FrameInterface { | ||||
|      * This method will take the extra bytes off the end and return them | ||||
|      * @return string | ||||
|      */ | ||||
|     public function extractOverflow() { | ||||
|     public function extractOverflow(): string { | ||||
|         if ($this->isCoalesced()) { | ||||
|             $endPoint  = $this->getPayloadLength(); | ||||
|             $endPoint += $this->getPayloadStartingByte(); | ||||
|  | ||||
| @ -6,33 +6,33 @@ interface FrameInterface extends DataInterface { | ||||
|      * Add incoming data to the frame from peer | ||||
|      * @param string | ||||
|      */ | ||||
|     function addBuffer($buf); | ||||
|     public function addBuffer(string $buf): void; | ||||
| 
 | ||||
|     /** | ||||
|      * Is this the final frame in a fragmented message? | ||||
|      * @return bool | ||||
|      */ | ||||
|     function isFinal(); | ||||
|     public function isFinal(): bool; | ||||
| 
 | ||||
|     /** | ||||
|      * Is the payload masked? | ||||
|      * @return bool | ||||
|      */ | ||||
|     function isMasked(); | ||||
|     public function isMasked(): bool; | ||||
| 
 | ||||
|     /** | ||||
|      * @return int | ||||
|      */ | ||||
|     function getOpcode(); | ||||
|     public function getOpcode(): int; | ||||
| 
 | ||||
|     /** | ||||
|      * @return int | ||||
|      */ | ||||
|     //function getReceivedPayloadLength();
 | ||||
|     //public function getReceivedPayloadLength(): int;
 | ||||
| 
 | ||||
|     /** | ||||
|      * 32-big string | ||||
|      * @return string | ||||
|      */ | ||||
|     function getMaskingKey(); | ||||
|     public function getMaskingKey(): string; | ||||
| } | ||||
|  | ||||
| @ -2,53 +2,43 @@ | ||||
| namespace Ratchet\RFC6455\Messaging; | ||||
| 
 | ||||
| class Message implements \IteratorAggregate, MessageInterface { | ||||
|     /** | ||||
|      * @var \SplDoublyLinkedList | ||||
|      */ | ||||
|     private $_frames; | ||||
|     private \SplDoublyLinkedList $_frames; | ||||
| 
 | ||||
|     /** | ||||
|      * @var int | ||||
|      */ | ||||
|     private $len; | ||||
|     private int $len; | ||||
| 
 | ||||
|     #[\ReturnTypeWillChange]
 | ||||
|     public function __construct() { | ||||
|         $this->_frames = new \SplDoublyLinkedList; | ||||
|         $this->len = 0; | ||||
|     } | ||||
| 
 | ||||
|     #[\ReturnTypeWillChange]
 | ||||
|     public function getIterator() { | ||||
|     public function getIterator(): \Traversable { | ||||
|         return $this->_frames; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     #[\ReturnTypeWillChange]
 | ||||
|     public function count() { | ||||
|     public function count(): int { | ||||
|         return count($this->_frames); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     #[\ReturnTypeWillChange]
 | ||||
|     public function isCoalesced() { | ||||
|     public function isCoalesced(): bool { | ||||
|         if (count($this->_frames) == 0) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         $last = $this->_frames->top(); | ||||
| 
 | ||||
|         return ($last->isCoalesced() && $last->isFinal()); | ||||
|         return $last->isCoalesced() && $last->isFinal(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function addFrame(FrameInterface $fragment) { | ||||
|     public function addFrame(FrameInterface $fragment): MessageInterface { | ||||
|         $this->len += $fragment->getPayloadLength(); | ||||
|         $this->_frames->push($fragment); | ||||
| 
 | ||||
| @ -58,7 +48,7 @@ class Message implements \IteratorAggregate, MessageInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getOpcode() { | ||||
|     public function getOpcode(): int { | ||||
|         if (count($this->_frames) == 0) { | ||||
|             throw new \UnderflowException('No frames have been added to this message'); | ||||
|         } | ||||
| @ -69,14 +59,14 @@ class Message implements \IteratorAggregate, MessageInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getPayloadLength() { | ||||
|     public function getPayloadLength(): int { | ||||
|         return $this->len; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getPayload() { | ||||
|     public function getPayload(): string { | ||||
|         if (!$this->isCoalesced()) { | ||||
|             throw new \UnderflowException('Message has not been put back together yet'); | ||||
|         } | ||||
| @ -87,7 +77,7 @@ class Message implements \IteratorAggregate, MessageInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getContents() { | ||||
|     public function getContents(): string { | ||||
|         if (!$this->isCoalesced()) { | ||||
|             throw new \UnderflowException("Message has not been put back together yet"); | ||||
|         } | ||||
| @ -101,7 +91,7 @@ class Message implements \IteratorAggregate, MessageInterface { | ||||
|         return $buffer; | ||||
|     } | ||||
| 
 | ||||
|     public function __toString() { | ||||
|     public function __toString(): string { | ||||
|         $buffer = ''; | ||||
| 
 | ||||
|         foreach ($this->_frames as $frame) { | ||||
| @ -114,7 +104,7 @@ class Message implements \IteratorAggregate, MessageInterface { | ||||
|     /** | ||||
|      * @return boolean | ||||
|      */ | ||||
|     public function isBinary() { | ||||
|     public function isBinary(): bool { | ||||
|         if ($this->_frames->isEmpty()) { | ||||
|             throw new \UnderflowException('Not enough data has been received to determine if message is binary'); | ||||
|         } | ||||
| @ -125,7 +115,7 @@ class Message implements \IteratorAggregate, MessageInterface { | ||||
|     /** | ||||
|      * @return boolean | ||||
|      */ | ||||
|     public function getRsv1() { | ||||
|     public function getRsv1(): bool { | ||||
|         if ($this->_frames->isEmpty()) { | ||||
|             return false; | ||||
|             //throw new \UnderflowException('Not enough data has been received to determine if message is binary');
 | ||||
|  | ||||
| @ -4,25 +4,16 @@ namespace Ratchet\RFC6455\Messaging; | ||||
| use Ratchet\RFC6455\Handshake\PermessageDeflateOptions; | ||||
| 
 | ||||
| class MessageBuffer { | ||||
|     /** | ||||
|      * @var \Ratchet\RFC6455\Messaging\CloseFrameChecker | ||||
|      */ | ||||
|     private $closeFrameChecker; | ||||
|     private CloseFrameChecker $closeFrameChecker; | ||||
| 
 | ||||
|     /** | ||||
|      * @var callable | ||||
|      */ | ||||
|     private $exceptionFactory; | ||||
| 
 | ||||
|     /** | ||||
|      * @var \Ratchet\RFC6455\Messaging\Message | ||||
|      */ | ||||
|     private $messageBuffer; | ||||
|     private ?MessageInterface $messageBuffer = null; | ||||
| 
 | ||||
|     /** | ||||
|      * @var \Ratchet\RFC6455\Messaging\Frame | ||||
|      */ | ||||
|     private $frameBuffer; | ||||
|     private ?FrameInterface $frameBuffer = null; | ||||
| 
 | ||||
|     /** | ||||
|      * @var callable | ||||
| @ -34,71 +25,55 @@ class MessageBuffer { | ||||
|      */ | ||||
|     private $onControl; | ||||
| 
 | ||||
|     /** | ||||
|      * @var bool | ||||
|      */ | ||||
|     private $checkForMask; | ||||
|     private bool $checkForMask; | ||||
| 
 | ||||
|     /** | ||||
|      * @var callable | ||||
|      */ | ||||
|     private $sender; | ||||
| 
 | ||||
|     /** | ||||
|      * @var string | ||||
|      */ | ||||
|     private $leftovers; | ||||
|     private string $leftovers = ''; | ||||
| 
 | ||||
|     private int $streamingMessageOpCode = -1; | ||||
| 
 | ||||
|     private PermessageDeflateOptions $permessageDeflateOptions; | ||||
| 
 | ||||
|     private bool $deflateEnabled; | ||||
| 
 | ||||
|     private int $maxMessagePayloadSize; | ||||
| 
 | ||||
|     private int $maxFramePayloadSize; | ||||
| 
 | ||||
|     private bool $compressedMessage = false; | ||||
| 
 | ||||
|     /** | ||||
|      * @var int | ||||
|      * @var resource|bool|null | ||||
|      */ | ||||
|     private $streamingMessageOpCode = -1; | ||||
|     private $inflator = null; | ||||
| 
 | ||||
|     /** | ||||
|      * @var PermessageDeflateOptions | ||||
|      * @var resource|bool|null | ||||
|      */ | ||||
|     private $permessageDeflateOptions; | ||||
|     private $deflator = null; | ||||
| 
 | ||||
|     /** | ||||
|      * @var bool | ||||
|      */ | ||||
|     private $deflateEnabled = false; | ||||
| 
 | ||||
|     /** | ||||
|      * @var int | ||||
|      */ | ||||
|     private $maxMessagePayloadSize; | ||||
| 
 | ||||
|     /** | ||||
|      * @var int | ||||
|      */ | ||||
|     private $maxFramePayloadSize; | ||||
| 
 | ||||
|     /** | ||||
|      * @var bool | ||||
|      */ | ||||
|     private $compressedMessage; | ||||
| 
 | ||||
|     function __construct( | ||||
|     public function __construct( | ||||
|         CloseFrameChecker $frameChecker, | ||||
|         callable $onMessage, | ||||
|         callable $onControl = null, | ||||
|         $expectMask = true, | ||||
|         $exceptionFactory = null, | ||||
|         $maxMessagePayloadSize = null, // null for default - zero for no limit
 | ||||
|         $maxFramePayloadSize = null,   // null for default - zero for no limit
 | ||||
|         callable $sender = null, | ||||
|         PermessageDeflateOptions $permessageDeflateOptions = null | ||||
|         ?callable $onControl = null, | ||||
|         bool $expectMask = true, | ||||
|         ?callable $exceptionFactory = null, | ||||
|         ?int $maxMessagePayloadSize = null, // null for default - zero for no limit
 | ||||
|         ?int $maxFramePayloadSize = null,   // null for default - zero for no limit
 | ||||
|         ?callable $sender = null, | ||||
|         ?PermessageDeflateOptions $permessageDeflateOptions = null | ||||
|     ) { | ||||
|         $this->closeFrameChecker = $frameChecker; | ||||
|         $this->checkForMask = (bool)$expectMask; | ||||
|         $this->checkForMask = $expectMask; | ||||
| 
 | ||||
|         $this->exceptionFactory ?: $exceptionFactory = function($msg) { | ||||
|             return new \UnderflowException($msg); | ||||
|         }; | ||||
|         $this->exceptionFactory = $exceptionFactory ?: static fn (string $msg) => new \UnderflowException($msg); | ||||
| 
 | ||||
|         $this->onMessage = $onMessage; | ||||
|         $this->onControl = $onControl ?: function() {}; | ||||
|         $this->onControl = $onControl ?: static function (): void {}; | ||||
| 
 | ||||
|         $this->sender = $sender; | ||||
| 
 | ||||
| @ -110,10 +85,6 @@ class MessageBuffer { | ||||
|             throw new \InvalidArgumentException('sender must be set when deflate is enabled'); | ||||
|         } | ||||
| 
 | ||||
|         $this->compressedMessage = false; | ||||
| 
 | ||||
|         $this->leftovers = ''; | ||||
| 
 | ||||
|         $memory_limit_bytes = static::getMemoryLimit(); | ||||
| 
 | ||||
|         if ($maxMessagePayloadSize === null) { | ||||
| @ -123,18 +94,18 @@ class MessageBuffer { | ||||
|             $maxFramePayloadSize = (int)($memory_limit_bytes / 4); | ||||
|         } | ||||
| 
 | ||||
|         if (!is_int($maxFramePayloadSize) || $maxFramePayloadSize > 0x7FFFFFFFFFFFFFFF || $maxFramePayloadSize < 0) { // this should be interesting on non-64 bit systems
 | ||||
|         if ($maxFramePayloadSize > 0x7FFFFFFFFFFFFFFF || $maxFramePayloadSize < 0) { // this should be interesting on non-64 bit systems
 | ||||
|             throw new \InvalidArgumentException($maxFramePayloadSize . ' is not a valid maxFramePayloadSize'); | ||||
|         } | ||||
|         $this->maxFramePayloadSize = $maxFramePayloadSize; | ||||
| 
 | ||||
|         if (!is_int($maxMessagePayloadSize) || $maxMessagePayloadSize > 0x7FFFFFFFFFFFFFFF || $maxMessagePayloadSize < 0) { | ||||
|         if ($maxMessagePayloadSize > 0x7FFFFFFFFFFFFFFF || $maxMessagePayloadSize < 0) { | ||||
|             throw new \InvalidArgumentException($maxMessagePayloadSize . 'is not a valid maxMessagePayloadSize'); | ||||
|         } | ||||
|         $this->maxMessagePayloadSize = $maxMessagePayloadSize; | ||||
|     } | ||||
| 
 | ||||
|     public function onData($data) { | ||||
|     public function onData(string $data): void { | ||||
|         $data = $this->leftovers . $data; | ||||
|         $dataLen = strlen($data); | ||||
| 
 | ||||
| @ -150,6 +121,7 @@ class MessageBuffer { | ||||
|             $payload_length = unpack('C', $data[$frameStart + 1] & "\x7f")[1]; | ||||
|             $isMasked       = ($data[$frameStart + 1] & "\x80") === "\x80"; | ||||
|             $headerSize     += $isMasked ? 4 : 0; | ||||
|             $payloadLenOver2GB = false; | ||||
|             if ($payload_length > 125 && ($dataLen - $frameStart < $headerSize + 125)) { | ||||
|                 // no point of checking - this frame is going to be bigger than the buffer is right now
 | ||||
|                 break; | ||||
| @ -158,9 +130,18 @@ class MessageBuffer { | ||||
|                 $payloadLenBytes = $payload_length === 126 ? 2 : 8; | ||||
|                 $headerSize      += $payloadLenBytes; | ||||
|                 $bytesToUpack    = substr($data, $frameStart + 2, $payloadLenBytes); | ||||
|                 $payload_length  = $payload_length === 126 | ||||
|                     ? unpack('n', $bytesToUpack)[1] | ||||
|                     : unpack('J', $bytesToUpack)[1]; | ||||
| 
 | ||||
|                 if ($payload_length === 126){ | ||||
|                     $payload_length = unpack('n', $bytesToUpack)[1]; | ||||
|                 } else { | ||||
|                     $payloadLenOver2GB = unpack('N', $bytesToUpack)[1] > 0; //Decode only the 4 first bytes
 | ||||
|                     if (PHP_INT_SIZE == 4) { // if 32bits PHP
 | ||||
|                         $bytesToUpack = substr($bytesToUpack, 4); //Keep only 4 last bytes
 | ||||
|                         $payload_length = unpack('N', $bytesToUpack)[1]; | ||||
|                     } else { | ||||
|                         $payload_length = unpack('J', $bytesToUpack)[1]; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             $closeFrame = null; | ||||
| @ -170,6 +151,10 @@ class MessageBuffer { | ||||
|                 $closeFrame = $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Invalid frame length'); | ||||
|             } | ||||
| 
 | ||||
|             if (!$closeFrame && PHP_INT_SIZE == 4 && $payloadLenOver2GB) { | ||||
|                 $closeFrame = $this->newCloseFrame(Frame::CLOSE_TOO_BIG, 'Frame over 2GB can\'t be handled on 32bits PHP'); | ||||
|             } | ||||
| 
 | ||||
|             if (!$closeFrame && $this->maxFramePayloadSize > 1 && $payload_length > $this->maxFramePayloadSize) { | ||||
|                 $closeFrame = $this->newCloseFrame(Frame::CLOSE_TOO_BIG, 'Maximum frame size exceeded'); | ||||
|             } | ||||
| @ -200,9 +185,9 @@ class MessageBuffer { | ||||
| 
 | ||||
|     /** | ||||
|      * @param string $data | ||||
|      * @return null | ||||
|      * @return void | ||||
|      */ | ||||
|     private function processData($data) { | ||||
|     private function processData(string $data): void { | ||||
|         $this->messageBuffer ?: $this->messageBuffer = $this->newMessage(); | ||||
|         $this->frameBuffer   ?: $this->frameBuffer   = $this->newFrame(); | ||||
| 
 | ||||
| @ -221,7 +206,7 @@ class MessageBuffer { | ||||
|             $onControl($this->frameBuffer, $this); | ||||
| 
 | ||||
|             if (Frame::OP_CLOSE === $opcode) { | ||||
|                 return ''; | ||||
|                 return; | ||||
|             } | ||||
|         } else { | ||||
|             if ($this->messageBuffer->count() === 0 && $this->frameBuffer->getRsv1()) { | ||||
| @ -259,10 +244,10 @@ class MessageBuffer { | ||||
| 
 | ||||
|     /** | ||||
|      * Check a frame to be added to the current message buffer | ||||
|      * @param \Ratchet\RFC6455\Messaging\FrameInterface|FrameInterface $frame | ||||
|      * @return \Ratchet\RFC6455\Messaging\FrameInterface|FrameInterface | ||||
|      * @param FrameInterface $frame | ||||
|      * @return FrameInterface | ||||
|      */ | ||||
|     public function frameCheck(FrameInterface $frame) { | ||||
|     public function frameCheck(FrameInterface $frame): FrameInterface { | ||||
|         if ((false !== $frame->getRsv1() && !$this->deflateEnabled) || | ||||
|             false !== $frame->getRsv2() || | ||||
|             false !== $frame->getRsv3() | ||||
| @ -309,13 +294,11 @@ class MessageBuffer { | ||||
|                     } | ||||
| 
 | ||||
|                     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; | ||||
| @ -334,7 +317,7 @@ class MessageBuffer { | ||||
| 
 | ||||
|     /** | ||||
|      * Determine if a message is valid | ||||
|      * @param \Ratchet\RFC6455\Messaging\MessageInterface | ||||
|      * @param MessageInterface | ||||
|      * @return bool|int true if valid - false if incomplete - int of recommended close code | ||||
|      */ | ||||
|     public function checkMessage(MessageInterface $message) { | ||||
| @ -347,7 +330,7 @@ class MessageBuffer { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     private function checkUtf8($string) { | ||||
|     private function checkUtf8(string $string): bool { | ||||
|         if (extension_loaded('mbstring')) { | ||||
|             return mb_check_encoding($string, 'UTF-8'); | ||||
|         } | ||||
| @ -356,27 +339,27 @@ class MessageBuffer { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return \Ratchet\RFC6455\Messaging\MessageInterface | ||||
|      * @return MessageInterface | ||||
|      */ | ||||
|     public function newMessage() { | ||||
|     public function newMessage(): MessageInterface { | ||||
|         return new Message; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string|null $payload | ||||
|      * @param bool|null   $final | ||||
|      * @param int|null    $opcode | ||||
|      * @return \Ratchet\RFC6455\Messaging\FrameInterface | ||||
|      * @param bool        $final | ||||
|      * @param int         $opcode | ||||
|      * @return FrameInterface | ||||
|      */ | ||||
|     public function newFrame($payload = null, $final = null, $opcode = null) { | ||||
|     public function newFrame(?string $payload = null, bool $final = true, int $opcode = Frame::OP_TEXT): FrameInterface { | ||||
|         return new Frame($payload, $final, $opcode, $this->exceptionFactory); | ||||
|     } | ||||
| 
 | ||||
|     public function newCloseFrame($code, $reason = '') { | ||||
|     public function newCloseFrame(int $code, string $reason = ''): FrameInterface { | ||||
|         return $this->newFrame(pack('n', $code) . $reason, true, Frame::OP_CLOSE); | ||||
|     } | ||||
| 
 | ||||
|     public function sendFrame(Frame $frame) { | ||||
|     public function sendFrame(FrameInterface $frame): void { | ||||
|         if ($this->sender === null) { | ||||
|             throw new \Exception('To send frames using the MessageBuffer, sender must be set.'); | ||||
|         } | ||||
| @ -394,7 +377,7 @@ class MessageBuffer { | ||||
|         $sender($frame->getContents()); | ||||
|     } | ||||
| 
 | ||||
|     public function sendMessage($messagePayload, $final = true, $isBinary = false) { | ||||
|     public function sendMessage(string $messagePayload, bool $final = true, bool $isBinary = false): void { | ||||
|         $opCode = $isBinary ? Frame::OP_BINARY : Frame::OP_TEXT; | ||||
|         if ($this->streamingMessageOpCode === -1) { | ||||
|             $this->streamingMessageOpCode = $opCode; | ||||
| @ -417,29 +400,27 @@ class MessageBuffer { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private $inflator; | ||||
| 
 | ||||
|     private function getDeflateNoContextTakeover() { | ||||
|     private function getDeflateNoContextTakeover(): ?bool { | ||||
|         return $this->checkForMask ? | ||||
|             $this->permessageDeflateOptions->getServerNoContextTakeover() : | ||||
|             $this->permessageDeflateOptions->getClientNoContextTakeover(); | ||||
|     } | ||||
| 
 | ||||
|     private function getDeflateWindowBits() { | ||||
|     private function getDeflateWindowBits(): int { | ||||
|         return $this->checkForMask ? $this->permessageDeflateOptions->getServerMaxWindowBits() : $this->permessageDeflateOptions->getClientMaxWindowBits(); | ||||
|     } | ||||
| 
 | ||||
|     private function getInflateNoContextTakeover() { | ||||
|     private function getInflateNoContextTakeover(): ?bool { | ||||
|         return $this->checkForMask ? | ||||
|             $this->permessageDeflateOptions->getClientNoContextTakeover() : | ||||
|             $this->permessageDeflateOptions->getServerNoContextTakeover(); | ||||
|     } | ||||
| 
 | ||||
|     private function getInflateWindowBits() { | ||||
|     private function getInflateWindowBits(): int { | ||||
|         return $this->checkForMask ? $this->permessageDeflateOptions->getClientMaxWindowBits() : $this->permessageDeflateOptions->getServerMaxWindowBits(); | ||||
|     } | ||||
| 
 | ||||
|     private function inflateFrame(Frame $frame) { | ||||
|     private function inflateFrame(FrameInterface $frame): Frame { | ||||
|         if ($this->inflator === null) { | ||||
|             $this->inflator = inflate_init( | ||||
|                 ZLIB_ENCODING_RAW, | ||||
| @ -466,16 +447,14 @@ class MessageBuffer { | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     private $deflator; | ||||
| 
 | ||||
|     private function deflateFrame(Frame $frame) | ||||
|     private function deflateFrame(FrameInterface $frame): FrameInterface | ||||
|     { | ||||
|         if ($frame->getRsv1()) { | ||||
|             return $frame; // frame is already deflated
 | ||||
|         } | ||||
| 
 | ||||
|         if ($this->deflator === null) { | ||||
|             $bits = (int)$this->getDeflateWindowBits(); | ||||
|             $bits = $this->getDeflateWindowBits(); | ||||
|             if ($bits === 8) { | ||||
|                 $bits = 9; | ||||
|             } | ||||
| @ -535,7 +514,7 @@ class MessageBuffer { | ||||
|      * @param null|string $memory_limit | ||||
|      * @return int | ||||
|      */ | ||||
|     private static function getMemoryLimit($memory_limit = null) { | ||||
|     private static function getMemoryLimit(?string $memory_limit = null): int { | ||||
|         $memory_limit = $memory_limit === null ? \trim(\ini_get('memory_limit')) : $memory_limit; | ||||
|         $memory_limit_bytes = 0; | ||||
|         if ($memory_limit !== '') { | ||||
|  | ||||
| @ -6,15 +6,15 @@ interface MessageInterface extends DataInterface, \Traversable, \Countable { | ||||
|      * @param FrameInterface $fragment | ||||
|      * @return MessageInterface | ||||
|      */ | ||||
|     function addFrame(FrameInterface $fragment); | ||||
|     public function addFrame(FrameInterface $fragment): self; | ||||
| 
 | ||||
|     /** | ||||
|      * @return int | ||||
|      */ | ||||
|     function getOpcode(); | ||||
|     public function getOpcode(): int; | ||||
| 
 | ||||
|     /** | ||||
|      * @return bool | ||||
|      */ | ||||
|     function isBinary(); | ||||
|     public function isBinary(): bool; | ||||
| } | ||||
|  | ||||
| @ -12,12 +12,13 @@ use Ratchet\RFC6455\Messaging\MessageInterface; | ||||
| use React\Promise\Deferred; | ||||
| use Ratchet\RFC6455\Messaging\Frame; | ||||
| use React\Promise\PromiseInterface; | ||||
| use GuzzleHttp\Psr7\HttpFactory; | ||||
| use React\Socket\ConnectionInterface; | ||||
| use React\Socket\Connector; | ||||
| 
 | ||||
| require __DIR__ . '/../bootstrap.php'; | ||||
| 
 | ||||
| define('AGENT', 'RatchetRFC/0.3'); | ||||
| define('AGENT', 'RatchetRFC/0.4'); | ||||
| 
 | ||||
| $testServer = $argc > 1 ? $argv[1] : "127.0.0.1"; | ||||
| 
 | ||||
| @ -58,7 +59,7 @@ function getTestCases(): PromiseInterface { | ||||
|     $deferred = new Deferred(); | ||||
| 
 | ||||
|     $connector->connect($testServer . ':9002')->then(static function (ConnectionInterface $connection) use ($deferred, $testServer): void { | ||||
|         $cn = new ClientNegotiator(); | ||||
|         $cn = new ClientNegotiator(new HttpFactory()); | ||||
|         $cnRequest = $cn->generateRequest(new Uri('ws://' . $testServer . ':9002/getCaseCount')); | ||||
| 
 | ||||
|         $rawResponse = ""; | ||||
| @ -110,6 +111,7 @@ function getTestCases(): PromiseInterface { | ||||
| } | ||||
| 
 | ||||
| $cn = new ClientNegotiator( | ||||
|     new HttpFactory(), | ||||
|     PermessageDeflateOptions::permessageDeflateSupported() ? PermessageDeflateOptions::createEnabled() : null); | ||||
| 
 | ||||
| function runTest(int $case) | ||||
| @ -124,6 +126,7 @@ function runTest(int $case) | ||||
| 
 | ||||
|     $connector->connect($testServer . ':9002')->then(static function (ConnectionInterface $connection) use ($deferred, $casePath, $case, $testServer): void { | ||||
|         $cn = new ClientNegotiator( | ||||
|             new HttpFactory(), | ||||
|             PermessageDeflateOptions::permessageDeflateSupported() ? PermessageDeflateOptions::createEnabled() : null); | ||||
|         $cnRequest = $cn->generateRequest(new Uri('ws://' . $testServer . ':9002' . $casePath)); | ||||
| 
 | ||||
| @ -166,7 +169,7 @@ function runTest(int $case) | ||||
|         }); | ||||
| 
 | ||||
|         $connection->on('close', static function () use ($deferred): void { | ||||
|             $deferred->resolve(); | ||||
|             $deferred->resolve(null); | ||||
|         }); | ||||
| 
 | ||||
|         $connection->write(Message::toString($cnRequest)); | ||||
| @ -185,7 +188,7 @@ function createReport(): PromiseInterface { | ||||
|         // $reportPath = "/updateReports?agent=" . AGENT . "&shutdownOnComplete=true";
 | ||||
|         // we will stop it using docker now instead of just shutting down
 | ||||
|         $reportPath = "/updateReports?agent=" . AGENT; | ||||
|         $cn = new ClientNegotiator(); | ||||
|         $cn = new ClientNegotiator(new HttpFactory()); | ||||
|         $cnRequest = $cn->generateRequest(new Uri('ws://' . $testServer . ':9002' . $reportPath)); | ||||
| 
 | ||||
|         $rawResponse = ""; | ||||
| @ -245,7 +248,7 @@ getTestCases()->then(function ($count) use ($loop) { | ||||
|     $runNextCase = static function () use (&$i, &$runNextCase, $count, $allDeferred): void { | ||||
|         $i++; | ||||
|         if ($i > $count) { | ||||
|             $allDeferred->resolve(); | ||||
|             $allDeferred->resolve(null); | ||||
|             return; | ||||
|         } | ||||
|         echo "Running test $i/$count..."; | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
|     } | ||||
|   , "outdir": "/reports/servers" | ||||
|   , "servers": [{ | ||||
|         "agent": "RatchetRFC/0.3" | ||||
|         "agent": "RatchetRFC/0.4" | ||||
|       , "url": "ws://host.ratchet.internal:9001" | ||||
|       , "options": {"version": 18} | ||||
|     }] | ||||
|  | ||||
| @ -4,7 +4,7 @@ | ||||
|     } | ||||
|   , "outdir": "/reports/servers" | ||||
|   , "servers": [{ | ||||
|         "agent": "RatchetRFC/0.3" | ||||
|         "agent": "RatchetRFC/0.4" | ||||
|       , "url": "ws://host.ratchet.internal:9001" | ||||
|       , "options": {"version": 18} | ||||
|     }] | ||||
|  | ||||
							
								
								
									
										0
									
								
								tests/ab/run_ab_tests.sh
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										0
									
								
								tests/ab/run_ab_tests.sh
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							| @ -10,6 +10,7 @@ use Ratchet\RFC6455\Messaging\MessageBuffer; | ||||
| use Ratchet\RFC6455\Messaging\MessageInterface; | ||||
| use Ratchet\RFC6455\Messaging\FrameInterface; | ||||
| use Ratchet\RFC6455\Messaging\Frame; | ||||
| use GuzzleHttp\Psr7\HttpFactory; | ||||
| 
 | ||||
| require_once __DIR__ . "/../bootstrap.php"; | ||||
| 
 | ||||
| @ -18,7 +19,12 @@ $loop   = \React\EventLoop\Factory::create(); | ||||
| $socket = new \React\Socket\Server('0.0.0.0:9001', $loop); | ||||
| 
 | ||||
| $closeFrameChecker = new CloseFrameChecker; | ||||
| $negotiator = new ServerNegotiator(new RequestVerifier, PermessageDeflateOptions::permessageDeflateSupported()); | ||||
| 
 | ||||
| $negotiator = new ServerNegotiator( | ||||
|     new RequestVerifier, | ||||
|     new HttpFactory(), | ||||
|     PermessageDeflateOptions::permessageDeflateSupported() | ||||
| ); | ||||
| 
 | ||||
| $uException = new \UnderflowException; | ||||
| 
 | ||||
|  | ||||
| @ -3,6 +3,7 @@ | ||||
| namespace Ratchet\RFC6455\Test\Unit\Handshake; | ||||
| 
 | ||||
| use GuzzleHttp\Psr7\Message; | ||||
| use GuzzleHttp\Psr7\HttpFactory; | ||||
| use Ratchet\RFC6455\Handshake\RequestVerifier; | ||||
| use Ratchet\RFC6455\Handshake\ServerNegotiator; | ||||
| use PHPUnit\Framework\TestCase; | ||||
| @ -13,7 +14,7 @@ use PHPUnit\Framework\TestCase; | ||||
| class ServerNegotiatorTest extends TestCase | ||||
| { | ||||
|     public function testNoUpgradeRequested(): void { | ||||
|         $negotiator = new ServerNegotiator(new RequestVerifier()); | ||||
|         $negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory()); | ||||
| 
 | ||||
|         $requestText = 'GET / HTTP/1.1 | ||||
| Host: 127.0.0.1:6789 | ||||
| @ -41,7 +42,7 @@ Accept-Language: en-US,en;q=0.8 | ||||
|     } | ||||
| 
 | ||||
|     public function testNoConnectionUpgradeRequested(): void { | ||||
|         $negotiator = new ServerNegotiator(new RequestVerifier()); | ||||
|         $negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory()); | ||||
| 
 | ||||
|         $requestText = 'GET / HTTP/1.1 | ||||
| Host: 127.0.0.1:6789 | ||||
| @ -67,7 +68,7 @@ Accept-Language: en-US,en;q=0.8 | ||||
|     } | ||||
| 
 | ||||
|     public function testInvalidSecWebsocketKey(): void { | ||||
|         $negotiator = new ServerNegotiator(new RequestVerifier()); | ||||
|         $negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory()); | ||||
| 
 | ||||
|         $requestText = 'GET / HTTP/1.1 | ||||
| Host: 127.0.0.1:6789 | ||||
| @ -94,7 +95,7 @@ Accept-Language: en-US,en;q=0.8 | ||||
|     } | ||||
| 
 | ||||
|     public function testInvalidSecWebsocketVersion(): void { | ||||
|         $negotiator = new ServerNegotiator(new RequestVerifier()); | ||||
|         $negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory()); | ||||
| 
 | ||||
|         $requestText = 'GET / HTTP/1.1 | ||||
| Host: 127.0.0.1:6789 | ||||
| @ -124,7 +125,7 @@ Accept-Language: en-US,en;q=0.8 | ||||
|     } | ||||
| 
 | ||||
|     public function testBadSubprotocolResponse(): void { | ||||
|         $negotiator = new ServerNegotiator(new RequestVerifier()); | ||||
|         $negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory()); | ||||
|         $negotiator->setStrictSubProtocolCheck(true); | ||||
|         $negotiator->setSupportedSubProtocols([]); | ||||
| 
 | ||||
| @ -158,7 +159,7 @@ Accept-Language: en-US,en;q=0.8 | ||||
|     } | ||||
| 
 | ||||
|     public function testNonStrictSubprotocolDoesNotIncludeHeaderWhenNoneAgreedOn(): void { | ||||
|         $negotiator = new ServerNegotiator(new RequestVerifier()); | ||||
|         $negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory()); | ||||
|         $negotiator->setStrictSubProtocolCheck(false); | ||||
|         $negotiator->setSupportedSubProtocols(['someproto']); | ||||
| 
 | ||||
| @ -192,7 +193,7 @@ Accept-Language: en-US,en;q=0.8 | ||||
| 
 | ||||
|     public function testSuggestsAppropriateSubprotocol(): void | ||||
|     { | ||||
|         $negotiator = new ServerNegotiator(new RequestVerifier()); | ||||
|         $negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory()); | ||||
|         $negotiator->setStrictSubProtocolCheck(true); | ||||
|         $negotiator->setSupportedSubProtocols(['someproto']); | ||||
| 
 | ||||
|  | ||||
| @ -294,7 +294,7 @@ class MessageBufferTest extends TestCase | ||||
|             false, | ||||
|             null, | ||||
|             0, | ||||
|             0x8000000000000000 | ||||
|             -1 | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
| @ -307,7 +307,7 @@ class MessageBufferTest extends TestCase | ||||
|             static function (Frame $frame): void {}, | ||||
|             false, | ||||
|             null, | ||||
|             0x8000000000000000, | ||||
|             -1, | ||||
|             0 | ||||
|         ); | ||||
|     } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user