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