Compare commits
	
		
			3 Commits
		
	
	
		
			dd679cc1c9
			...
			f79ed2f742
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | f79ed2f742 | ||
|   | d7084338b2 | ||
|   | 26dfb4a419 | 
| @ -3,35 +3,38 @@ namespace mfmdevsystem\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 Psr\Http\Message\RequestFactoryInterface; | use GuzzleHttp\Psr7\Request; | ||||||
| 
 | 
 | ||||||
| class ClientNegotiator { | class ClientNegotiator { | ||||||
|     private ResponseVerifier $verifier; |     /** | ||||||
|  |      * @var ResponseVerifier | ||||||
|  |      */ | ||||||
|  |     private $verifier; | ||||||
| 
 | 
 | ||||||
|     private RequestInterface $defaultHeader; |     /** | ||||||
|  |      * @var \Psr\Http\Message\RequestInterface | ||||||
|  |      */ | ||||||
|  |     private $defaultHeader; | ||||||
| 
 | 
 | ||||||
|     private RequestFactoryInterface $requestFactory; |     function __construct(?PermessageDeflateOptions $perMessageDeflateOptions = null) { | ||||||
| 
 |  | ||||||
|     public function __construct( |  | ||||||
|         RequestFactoryInterface $requestFactory, |  | ||||||
|         ?PermessageDeflateOptions $perMessageDeflateOptions = null |  | ||||||
|     ) { |  | ||||||
|         $this->verifier = new ResponseVerifier; |         $this->verifier = new ResponseVerifier; | ||||||
|         $this->requestFactory = $requestFactory; |  | ||||||
| 
 | 
 | ||||||
|         $this->defaultHeader = $this->requestFactory |         $this->defaultHeader = new Request('GET', '', [ | ||||||
|             ->createRequest('GET', '') |             'Connection'            => 'Upgrade' | ||||||
|             ->withHeader('Connection'           , 'Upgrade') |           , 'Upgrade'               => 'websocket' | ||||||
|             ->withHeader('Upgrade'              , 'websocket') |           , 'Sec-WebSocket-Version' => $this->getVersion() | ||||||
|             ->withHeader('Sec-WebSocket-Version', $this->getVersion()) |           , 'User-Agent'            => "Ratchet" | ||||||
|             ->withHeader('User-Agent'           , 'Ratchet'); |         ]); | ||||||
| 
 | 
 | ||||||
|         $perMessageDeflateOptions ??= PermessageDeflateOptions::createDisabled(); |         if ($perMessageDeflateOptions === null) { | ||||||
|  |             $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() && !PermessageDeflateOptions::permessageDeflateSupported()) { |         if ($perMessageDeflateOptions->isEnabled() && | ||||||
|             trigger_error('permessage-deflate is being disabled because it is not supported by your PHP version.', E_USER_NOTICE); |             !PermessageDeflateOptions::permessageDeflateSupported()) { | ||||||
|  |             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')) { | ||||||
| @ -42,16 +45,16 @@ class ClientNegotiator { | |||||||
|         $this->defaultHeader = $perMessageDeflateOptions->addHeaderToRequest($this->defaultHeader); |         $this->defaultHeader = $perMessageDeflateOptions->addHeaderToRequest($this->defaultHeader); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function generateRequest(UriInterface $uri): RequestInterface { |     public function generateRequest(UriInterface $uri) { | ||||||
|         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): bool { |     public function validateResponse(RequestInterface $request, ResponseInterface $response) { | ||||||
|         return $this->verifier->verifyAll($request, $response); |         return $this->verifier->verifyAll($request, $response); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function generateKey(): string { |     public function generateKey() { | ||||||
|         $chars     = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwzyz1234567890+/='; |         $chars     = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwzyz1234567890+/='; | ||||||
|         $charRange = strlen($chars) - 1; |         $charRange = strlen($chars) - 1; | ||||||
|         $key       = ''; |         $key       = ''; | ||||||
| @ -62,7 +65,7 @@ class ClientNegotiator { | |||||||
|         return base64_encode($key); |         return base64_encode($key); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function getVersion(): int { |     public function getVersion() { | ||||||
|         return 13; |         return 13; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| <?php | <?php | ||||||
| namespace mfmdevsystem\RFC6455\Handshake; | namespace mfmdevsystem\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 | ||||||
| @ -15,34 +14,34 @@ interface NegotiatorInterface { | |||||||
|      * @param RequestInterface $request |      * @param RequestInterface $request | ||||||
|      * @return bool |      * @return bool | ||||||
|      */ |      */ | ||||||
|     public function isProtocol(RequestInterface $request): bool; |     function isProtocol(RequestInterface $request); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * 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 | ||||||
|      */ |      */ | ||||||
|     public function getVersionNumber(): int; |     function getVersionNumber(); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Perform the handshake and return the response headers |      * Perform the handshake and return the response headers | ||||||
|      * @param RequestInterface $request |      * @param RequestInterface $request | ||||||
|      * @return ResponseInterface |      * @return \Psr\Http\Message\ResponseInterface | ||||||
|      */ |      */ | ||||||
|     public function handshake(RequestInterface $request): ResponseInterface; |     function handshake(RequestInterface $request); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * 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 | ||||||
|      */ |      */ | ||||||
|     public function setSupportedSubProtocols(array $protocols): void; |     function setSupportedSubProtocols(array $protocols); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * 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 say the server can fail for this reason, but |      *       The spec does says 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. | ||||||
|      */ |      */ | ||||||
|     public function setStrictSubProtocolCheck(bool $enable): void; |     function setStrictSubProtocolCheck($enable); | ||||||
| } | } | ||||||
|  | |||||||
| @ -8,21 +8,21 @@ use Psr\Http\Message\ResponseInterface; | |||||||
| 
 | 
 | ||||||
| final class PermessageDeflateOptions | final class PermessageDeflateOptions | ||||||
| { | { | ||||||
|     public const MAX_WINDOW_BITS = 15; |     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 const VALID_BITS = [8, 9, 10, 11, 12, 13, 14, 15]; |     private $deflateEnabled = false; | ||||||
| 
 | 
 | ||||||
|     private bool $deflateEnabled = false; |     private $server_no_context_takeover; | ||||||
| 
 |     private $client_no_context_takeover; | ||||||
|     private ?bool $server_no_context_takeover = null; |     private $server_max_window_bits; | ||||||
|     private ?bool $client_no_context_takeover = null; |     private $client_max_window_bits; | ||||||
|     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 self(); |         $new                             = new static(); | ||||||
|         $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 self(); |         return new static(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function withClientNoContextTakeover(): self { |     public function withClientNoContextTakeover() { | ||||||
|         $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(): self { |     public function withoutClientNoContextTakeover() { | ||||||
|         $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(): self { |     public function withServerNoContextTakeover() { | ||||||
|         $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(): self { |     public function withoutServerNoContextTakeover() { | ||||||
|         $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(int $bits = self::MAX_WINDOW_BITS): self { |     public function withServerMaxWindowBits($bits = self::MAX_WINDOW_BITS) { | ||||||
|         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(int $bits = self::MAX_WINDOW_BITS): self { |     public function withClientMaxWindowBits($bits = self::MAX_WINDOW_BITS) { | ||||||
|         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): array { |     public static function fromRequestOrResponse(MessageInterface $requestOrResponse) { | ||||||
|         $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 self(); |             $options                 = new static(); | ||||||
|             $options->deflateEnabled = true; |             $options->deflateEnabled = true; | ||||||
|             foreach ($parts as $part) { |             foreach ($parts as $part) { | ||||||
|                 $kv = explode('=', $part); |                 $kv = explode('=', $part); | ||||||
| @ -119,18 +119,15 @@ final class PermessageDeflateOptions | |||||||
|                         $value = true; |                         $value = true; | ||||||
|                         break; |                         break; | ||||||
|                     case "server_max_window_bits": |                     case "server_max_window_bits": | ||||||
|                         $value = (int) $value; |                         if (!in_array($value, self::$VALID_BITS)) { | ||||||
|                         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; | ||||||
| @ -157,39 +154,39 @@ final class PermessageDeflateOptions | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // always put a disabled on the end
 |         // always put a disabled on the end
 | ||||||
|         $optionSets[] = new self(); |         $optionSets[] = new static(); | ||||||
| 
 | 
 | ||||||
|         return $optionSets; |         return $optionSets; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return bool|null |      * @return mixed | ||||||
|      */ |      */ | ||||||
|     public function getServerNoContextTakeover(): ?bool |     public function getServerNoContextTakeover() | ||||||
|     { |     { | ||||||
|         return $this->server_no_context_takeover; |         return $this->server_no_context_takeover; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return bool|null |      * @return mixed | ||||||
|      */ |      */ | ||||||
|     public function getClientNoContextTakeover(): ?bool |     public function getClientNoContextTakeover() | ||||||
|     { |     { | ||||||
|         return $this->client_no_context_takeover; |         return $this->client_no_context_takeover; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return int|null |      * @return mixed | ||||||
|      */ |      */ | ||||||
|     public function getServerMaxWindowBits(): ?int |     public function getServerMaxWindowBits() | ||||||
|     { |     { | ||||||
|         return $this->server_max_window_bits; |         return $this->server_max_window_bits; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return int|null |      * @return mixed | ||||||
|      */ |      */ | ||||||
|     public function getClientMaxWindowBits(): ?int |     public function getClientMaxWindowBits() | ||||||
|     { |     { | ||||||
|         return $this->client_max_window_bits; |         return $this->client_max_window_bits; | ||||||
|     } |     } | ||||||
| @ -197,7 +194,7 @@ final class PermessageDeflateOptions | |||||||
|     /** |     /** | ||||||
|      * @return bool |      * @return bool | ||||||
|      */ |      */ | ||||||
|     public function isEnabled(): bool |     public function isEnabled() | ||||||
|     { |     { | ||||||
|         return $this->deflateEnabled; |         return $this->deflateEnabled; | ||||||
|     } |     } | ||||||
| @ -206,7 +203,7 @@ final class PermessageDeflateOptions | |||||||
|      * @param ResponseInterface $response |      * @param ResponseInterface $response | ||||||
|      * @return ResponseInterface |      * @return ResponseInterface | ||||||
|      */ |      */ | ||||||
|     public function addHeaderToResponse(ResponseInterface $response): ResponseInterface |     public function addHeaderToResponse(ResponseInterface $response) | ||||||
|     { |     { | ||||||
|         if (!$this->deflateEnabled) { |         if (!$this->deflateEnabled) { | ||||||
|             return $response; |             return $response; | ||||||
| @ -229,7 +226,7 @@ final class PermessageDeflateOptions | |||||||
|         return $response->withAddedHeader('Sec-Websocket-Extensions', $header); |         return $response->withAddedHeader('Sec-Websocket-Extensions', $header); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function addHeaderToRequest(RequestInterface $request): RequestInterface { |     public function addHeaderToRequest(RequestInterface $request) { | ||||||
|         if (!$this->deflateEnabled) { |         if (!$this->deflateEnabled) { | ||||||
|             return $request; |             return $request; | ||||||
|         } |         } | ||||||
| @ -252,7 +249,7 @@ final class PermessageDeflateOptions | |||||||
|         return $request->withAddedHeader('Sec-Websocket-Extensions', $header); |         return $request->withAddedHeader('Sec-Websocket-Extensions', $header); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static function permessageDeflateSupported(string $version = PHP_VERSION): bool { |     public static function permessageDeflateSupported($version = PHP_VERSION) { | ||||||
|         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 { | ||||||
|     public const VERSION = 13; |     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): bool { |     public function verifyAll(RequestInterface $request) { | ||||||
|         $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(string $val): bool { |     public function verifyMethod($val) { | ||||||
|         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): bool { |     public function verifyHTTPVersion($val) { | ||||||
|         return 1.1 <= (double)$val; |         return (1.1 <= (double)$val); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param string |      * @param string | ||||||
|      * @return bool |      * @return bool | ||||||
|      */ |      */ | ||||||
|     public function verifyRequestURI(string $val): bool { |     public function verifyRequestURI($val) { | ||||||
|         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): bool { |     public function verifyHost(array $hostHeader) { | ||||||
|         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): bool { |     public function verifyUpgradeRequest(array $upgradeHeader) { | ||||||
|         return 1 === count($upgradeHeader) && 'websocket' === strtolower($upgradeHeader[0]); |         return (1 === count($upgradeHeader) && 'websocket' === strtolower($upgradeHeader[0])); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -91,11 +91,13 @@ class RequestVerifier { | |||||||
|      * @param  array $connectionHeader MUST include "Upgrade" |      * @param  array $connectionHeader MUST include "Upgrade" | ||||||
|      * @return bool |      * @return bool | ||||||
|      */ |      */ | ||||||
|     public function verifyConnection(array $connectionHeader): bool { |     public function verifyConnection(array $connectionHeader) { | ||||||
|         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))), | ||||||
|                 static fn (string $x) => 'upgrade' === $x |                 function ($x) { | ||||||
|  |                     return 'upgrade' === $x; | ||||||
|  |                 } | ||||||
|             ); |             ); | ||||||
|             if (count($upgrades) > 0) { |             if (count($upgrades) > 0) { | ||||||
|                 return true; |                 return true; | ||||||
| @ -111,8 +113,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): bool { |     public function verifyKey(array $keyHeader) { | ||||||
|         return 1 === count($keyHeader) && 16 === strlen(base64_decode($keyHeader[0])); |         return (1 === count($keyHeader) && 16 === strlen(base64_decode($keyHeader[0]))); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -120,33 +122,33 @@ class RequestVerifier { | |||||||
|      * @param string[] $versionHeader MUST equal ["13"] |      * @param string[] $versionHeader MUST equal ["13"] | ||||||
|      * @return bool |      * @return bool | ||||||
|      */ |      */ | ||||||
|     public function verifyVersion(array $versionHeader): bool { |     public function verifyVersion(array $versionHeader) { | ||||||
|         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): bool { |     public function verifyProtocol($val) { | ||||||
|         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): bool { |     public function verifyExtensions($val) { | ||||||
|         return true; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function getPermessageDeflateOptions(array $requestHeader, array $responseHeader): array { |     public function getPermessageDeflateOptions(array $requestHeader, array $responseHeader) { | ||||||
|         $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'], $headerChecker)) === 0) { |         if (!isset($requestHeader['Sec-WebSocket-Extensions']) || count(array_filter($requestHeader['Sec-WebSocket-Extensions'], function ($val) { | ||||||
|  |             return 'permessage-deflate' === substr($val, 0, strlen('permessage-deflate')); | ||||||
|  |         })) === 0) { | ||||||
|              $deflate = false; |              $deflate = false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (!isset($responseHeader['Sec-WebSocket-Extensions']) || count(array_filter($responseHeader['Sec-WebSocket-Extensions'], $headerChecker)) === 0) { |         if (!isset($responseHeader['Sec-WebSocket-Extensions']) || count(array_filter($responseHeader['Sec-WebSocket-Extensions'], function ($val) { | ||||||
|  |                 return 'permessage-deflate' === substr($val, 0, strlen('permessage-deflate')); | ||||||
|  |             })) === 0) { | ||||||
|             $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): bool { |     public function verifyAll(RequestInterface $request, ResponseInterface $response) { | ||||||
|         $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(int $status): bool { |     public function verifyStatus($status) { | ||||||
|         return $status === 101; |         return ((int)$status === 101); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function verifyUpgrade(array $upgrade): bool { |     public function verifyUpgrade(array $upgrade) { | ||||||
|         return in_array('websocket', array_map('strtolower', $upgrade)); |         return (in_array('websocket', array_map('strtolower', $upgrade))); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function verifyConnection(array $connection): bool { |     public function verifyConnection(array $connection) { | ||||||
|         return in_array('upgrade', array_map('strtolower', $connection)); |         return (in_array('upgrade', array_map('strtolower', $connection))); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function verifySecWebSocketAccept(array $swa, array $key): bool { |     public function verifySecWebSocketAccept($swa, $key) { | ||||||
|         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(string $key): string { |     public function sign($key) { | ||||||
|         return base64_encode(sha1($key . NegotiatorInterface::GUID, true)); |         return base64_encode(sha1($key . NegotiatorInterface::GUID, true)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function verifySubProtocol(array $requestHeader, array $responseHeader): bool { |     public function verifySubProtocol(array $requestHeader, array $responseHeader) { | ||||||
|         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): int { |     public function verifyExtensions(array $requestHeader, array $responseHeader) { | ||||||
|         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,33 +1,26 @@ | |||||||
| <?php | <?php | ||||||
| namespace mfmdevsystem\RFC6455\Handshake; | namespace mfmdevsystem\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 ResponseFactoryInterface $responseFactory; |     private $_supportedSubProtocols = []; | ||||||
| 
 | 
 | ||||||
|     private array $_supportedSubProtocols = []; |     private $_strictSubProtocols = false; | ||||||
| 
 | 
 | ||||||
|     private bool $_strictSubProtocols = false; |     private $enablePerMessageDeflate = false; | ||||||
| 
 | 
 | ||||||
|     private bool $enablePerMessageDeflate = false; |     public function __construct(RequestVerifier $requestVerifier, $enablePerMessageDeflate = false) { | ||||||
| 
 |  | ||||||
|     public function __construct( |  | ||||||
|         RequestVerifier $requestVerifier, |  | ||||||
|         ResponseFactoryInterface $responseFactory, |  | ||||||
|         $enablePerMessageDeflate = null |  | ||||||
|     ) { |  | ||||||
|         if ($enablePerMessageDeflate == null) $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
 | ||||||
| @ -45,85 +38,85 @@ class ServerNegotiator implements NegotiatorInterface { | |||||||
|     /** |     /** | ||||||
|      * {@inheritdoc} |      * {@inheritdoc} | ||||||
|      */ |      */ | ||||||
|     public function isProtocol(RequestInterface $request): bool { |     public function isProtocol(RequestInterface $request) { | ||||||
|         return $this->verifier->verifyVersion($request->getHeader('Sec-WebSocket-Version')); |         return $this->verifier->verifyVersion($request->getHeader('Sec-WebSocket-Version')); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * {@inheritdoc} |      * {@inheritdoc} | ||||||
|      */ |      */ | ||||||
|     public function getVersionNumber(): int { |     public function getVersionNumber() { | ||||||
|         return RequestVerifier::VERSION; |         return RequestVerifier::VERSION; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * {@inheritdoc} |      * {@inheritdoc} | ||||||
|      */ |      */ | ||||||
|     public function handshake(RequestInterface $request): ResponseInterface { |     public function handshake(RequestInterface $request) { | ||||||
|         $response = $this->responseFactory->createResponse(); |  | ||||||
|         if (true !== $this->verifier->verifyMethod($request->getMethod())) { |         if (true !== $this->verifier->verifyMethod($request->getMethod())) { | ||||||
|             return $response->withHeader('Allow', 'GET')->withStatus(405); |             return new Response(405, ['Allow' => 'GET']); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (true !== $this->verifier->verifyHTTPVersion($request->getProtocolVersion())) { |         if (true !== $this->verifier->verifyHTTPVersion($request->getProtocolVersion())) { | ||||||
|             return $response->withStatus(505); |             return new Response(505); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (true !== $this->verifier->verifyRequestURI($request->getUri()->getPath())) { |         if (true !== $this->verifier->verifyRequestURI($request->getUri()->getPath())) { | ||||||
|             return $response->withStatus(400); |             return new Response(400); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (true !== $this->verifier->verifyHost($request->getHeader('Host'))) { |         if (true !== $this->verifier->verifyHost($request->getHeader('Host'))) { | ||||||
|             return $response->withStatus(400); |             return new Response(400); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $upgradeResponse = $response |         $upgradeSuggestion = [ | ||||||
|             ->withHeader('Connection'           , 'Upgrade') |             'Connection'             => 'Upgrade', | ||||||
|             ->withHeader('Upgrade'              , 'websocket') |             'Upgrade'                => 'websocket', | ||||||
|             ->withHeader('Sec-WebSocket-Version', $this->getVersionNumber()); |             'Sec-WebSocket-Version'  => $this->getVersionNumber() | ||||||
| 
 |         ]; | ||||||
|         if (count($this->_supportedSubProtocols) > 0) { |         if (count($this->_supportedSubProtocols) > 0) { | ||||||
|             $upgradeResponse = $upgradeResponse->withHeader( |             $upgradeSuggestion['Sec-WebSocket-Protocol'] = implode(', ', array_keys($this->_supportedSubProtocols)); | ||||||
|                 '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 $upgradeResponse->withStatus(426, 'Upgrade header MUST be provided'); |             return new Response(426, $upgradeSuggestion, null, '1.1', 'Upgrade header MUST be provided'); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (true !== $this->verifier->verifyConnection($request->getHeader('Connection'))) { |         if (true !== $this->verifier->verifyConnection($request->getHeader('Connection'))) { | ||||||
|             return $response->withStatus(400, 'Connection Upgrade MUST be requested'); |             return new Response(400, [], null, '1.1', '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 $response->withStatus(400, 'Invalid Sec-WebSocket-Key'); |             return new Response(400, [], null, '1.1', '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 $upgradeResponse->withStatus(426); |             return new Response(426, $upgradeSuggestion); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         $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, fn ($accumulator, $protocol) => $accumulator ?: (isset($this->_supportedSubProtocols[$protocol]) ? $protocol : null), null); |             $match = array_reduce($subProtocols, function($accumulator, $protocol) { | ||||||
|  |                 return $accumulator ?: (isset($this->_supportedSubProtocols[$protocol]) ? $protocol : null); | ||||||
|  |             }, null); | ||||||
| 
 | 
 | ||||||
|             if ($this->_strictSubProtocols && null === $match) { |             if ($this->_strictSubProtocols && null === $match) { | ||||||
|                 return $upgradeResponse->withStatus(426, 'No Sec-WebSocket-Protocols requested supported'); |                 return new Response(426, $upgradeSuggestion, null, '1.1', 'No Sec-WebSocket-Protocols requested supported'); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (null !== $match) { |             if (null !== $match) { | ||||||
|                 $response = $response->withHeader('Sec-WebSocket-Protocol', $match); |                 $headers['Sec-WebSocket-Protocol'] = $match; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $response = $response |         $response = new Response(101, array_merge($headers, [ | ||||||
|             ->withStatus(101) |             'Upgrade'              => 'websocket' | ||||||
|             ->withHeader('Upgrade'             , 'websocket') |             , 'Connection'           => 'Upgrade' | ||||||
|             ->withHeader('Connection'          , 'Upgrade') |             , 'Sec-WebSocket-Accept' => $this->sign((string)$request->getHeader('Sec-WebSocket-Key')[0]) | ||||||
|             ->withHeader('Sec-WebSocket-Accept', $this->sign((string)$request->getHeader('Sec-WebSocket-Key')[0])) |             , 'X-Powered-By'         => 'Ratchet' | ||||||
|             ->withHeader('X-Powered-By'        , 'Ratchet'); |         ])); | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             $perMessageDeflateRequest = PermessageDeflateOptions::fromRequestOrResponse($request)[0]; |             $perMessageDeflateRequest = PermessageDeflateOptions::fromRequestOrResponse($request)[0]; | ||||||
| @ -144,14 +137,14 @@ class ServerNegotiator implements NegotiatorInterface { | |||||||
|      * @return string |      * @return string | ||||||
|      * @internal |      * @internal | ||||||
|      */ |      */ | ||||||
|     public function sign(string $key): string { |     public function sign($key) { | ||||||
|         return base64_encode(sha1($key . static::GUID, true)); |         return base64_encode(sha1($key . static::GUID, true)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param array $protocols |      * @param array $protocols | ||||||
|      */ |      */ | ||||||
|     public function setSupportedSubProtocols(array $protocols): void { |     function setSupportedSubProtocols(array $protocols) { | ||||||
|         $this->_supportedSubProtocols = array_flip($protocols); |         $this->_supportedSubProtocols = array_flip($protocols); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -160,10 +153,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 say the server can fail for this reason, but |      *       The spec does says 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. | ||||||
|      */ |      */ | ||||||
|     public function setStrictSubProtocolCheck(bool $enable): void { |     function setStrictSubProtocolCheck($enable) { | ||||||
|         $this->_strictSubProtocols = $enable; |         $this->_strictSubProtocols = (boolean)$enable; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,19 +2,23 @@ | |||||||
| namespace mfmdevsystem\RFC6455\Messaging; | namespace mfmdevsystem\RFC6455\Messaging; | ||||||
| 
 | 
 | ||||||
| class CloseFrameChecker { | class CloseFrameChecker { | ||||||
|     private array $validCloseCodes = [ |     private $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(int $val): bool { |     public function __construct() { | ||||||
|  |         $this->validCloseCodes = [ | ||||||
|  |             Frame::CLOSE_NORMAL, | ||||||
|  |             Frame::CLOSE_GOING_AWAY, | ||||||
|  |             Frame::CLOSE_PROTOCOL, | ||||||
|  |             Frame::CLOSE_BAD_DATA, | ||||||
|  |             Frame::CLOSE_BAD_PAYLOAD, | ||||||
|  |             Frame::CLOSE_POLICY, | ||||||
|  |             Frame::CLOSE_TOO_BIG, | ||||||
|  |             Frame::CLOSE_MAND_EXT, | ||||||
|  |             Frame::CLOSE_SRV_ERR, | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function __invoke($val) { | ||||||
|         return ($val >= 3000 && $val <= 4999) || in_array($val, $this->validCloseCodes); |         return ($val >= 3000 && $val <= 4999) || in_array($val, $this->validCloseCodes); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,28 +1,34 @@ | |||||||
| <?php | <?php | ||||||
| namespace mfmdevsystem\RFC6455\Messaging; | namespace mfmdevsystem\RFC6455\Messaging; | ||||||
| 
 | 
 | ||||||
| interface DataInterface extends \Stringable { | interface DataInterface { | ||||||
|     /** |     /** | ||||||
|      * Determine if the message is complete or still fragmented |      * Determine if the message is complete or still fragmented | ||||||
|      * @return bool |      * @return bool | ||||||
|      */ |      */ | ||||||
|     public function isCoalesced(): bool; |     function isCoalesced(); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * 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 | ||||||
|      */ |      */ | ||||||
|     public function getPayloadLength(): int; |     function getPayloadLength(); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Get the payload (message) sent from peer |      * Get the payload (message) sent from peer | ||||||
|      * @return string |      * @return string | ||||||
|      */ |      */ | ||||||
|     public function getPayload(): string; |     function getPayload(); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Get raw contents of the message |      * Get raw contents of the message | ||||||
|      * @return string |      * @return string | ||||||
|      */ |      */ | ||||||
|     public function getContents(): string; |     function getContents(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Should return the unmasked payload received from peer | ||||||
|  |      * @return string | ||||||
|  |      */ | ||||||
|  |     function __toString(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -26,34 +26,40 @@ class Frame implements FrameInterface { | |||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * The contents of the frame |      * The contents of the frame | ||||||
|  |      * @var string | ||||||
|      */ |      */ | ||||||
|     protected string $data = ''; |     protected $data = ''; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Number of bytes received from the frame |      * Number of bytes received from the frame | ||||||
|  |      * @var int | ||||||
|      */ |      */ | ||||||
|     public int $bytesRecvd = 0; |     public $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 int $defPayLen = -1; |     protected $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 bool $isCoalesced = false; |     private $isCoalesced = false; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * The unpacked first byte of the frame |      * The unpacked first byte of the frame | ||||||
|  |      * @var int | ||||||
|      */ |      */ | ||||||
|     protected int $firstByte = -1; |     protected $firstByte = -1; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * The unpacked second byte of the frame |      * The unpacked second byte of the frame | ||||||
|  |      * @var int | ||||||
|      */ |      */ | ||||||
|     protected int $secondByte = -1; |     protected $secondByte = -1; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @var callable |      * @var callable | ||||||
| @ -67,8 +73,10 @@ class Frame implements FrameInterface { | |||||||
|      * @param int         $opcode |      * @param int         $opcode | ||||||
|      * @param callable<\UnderflowException> $ufExceptionFactory |      * @param callable<\UnderflowException> $ufExceptionFactory | ||||||
|      */ |      */ | ||||||
|     public function __construct(?string $payload = null, bool $final = true, int $opcode = 1, $ufExceptionFactory = null) { |     public function __construct($payload = null, $final = true, $opcode = 1, ?callable $ufExceptionFactory = null) { | ||||||
|         $this->ufeg = $ufExceptionFactory ?: static fn (string $msg = '') => new \UnderflowException($msg); |         $this->ufeg = $ufExceptionFactory ?: static function($msg = '') { | ||||||
|  |             return new \UnderflowException($msg); | ||||||
|  |         }; | ||||||
| 
 | 
 | ||||||
|         if (null === $payload) { |         if (null === $payload) { | ||||||
|             return; |             return; | ||||||
| @ -95,7 +103,7 @@ class Frame implements FrameInterface { | |||||||
|     /** |     /** | ||||||
|      * {@inheritdoc} |      * {@inheritdoc} | ||||||
|      */ |      */ | ||||||
|     public function isCoalesced(): bool { |     public function isCoalesced() { | ||||||
|         if (true === $this->isCoalesced) { |         if (true === $this->isCoalesced) { | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
| @ -115,7 +123,7 @@ class Frame implements FrameInterface { | |||||||
|     /** |     /** | ||||||
|      * {@inheritdoc} |      * {@inheritdoc} | ||||||
|      */ |      */ | ||||||
|     public function addBuffer(string $buf): void { |     public function addBuffer($buf) { | ||||||
|         $len = strlen($buf); |         $len = strlen($buf); | ||||||
| 
 | 
 | ||||||
|         $this->data       .= $buf; |         $this->data       .= $buf; | ||||||
| @ -133,7 +141,7 @@ class Frame implements FrameInterface { | |||||||
|     /** |     /** | ||||||
|      * {@inheritdoc} |      * {@inheritdoc} | ||||||
|      */ |      */ | ||||||
|     public function isFinal(): bool { |     public function isFinal() { | ||||||
|         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'); | ||||||
|         } |         } | ||||||
| @ -141,7 +149,7 @@ class Frame implements FrameInterface { | |||||||
|         return 128 === ($this->firstByte & 128); |         return 128 === ($this->firstByte & 128); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function setRsv1(bool $value = true): self { |     public function setRsv1($value = true) { | ||||||
|         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."); | ||||||
|         } |         } | ||||||
| @ -162,7 +170,7 @@ class Frame implements FrameInterface { | |||||||
|      * @return boolean |      * @return boolean | ||||||
|      * @throws \UnderflowException |      * @throws \UnderflowException | ||||||
|      */ |      */ | ||||||
|     public function getRsv1(): bool { |     public function getRsv1() { | ||||||
|         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'); | ||||||
|         } |         } | ||||||
| @ -174,7 +182,7 @@ class Frame implements FrameInterface { | |||||||
|      * @return boolean |      * @return boolean | ||||||
|      * @throws \UnderflowException |      * @throws \UnderflowException | ||||||
|      */ |      */ | ||||||
|     public function getRsv2(): bool { |     public function getRsv2() { | ||||||
|         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'); | ||||||
|         } |         } | ||||||
| @ -186,7 +194,7 @@ class Frame implements FrameInterface { | |||||||
|      * @return boolean |      * @return boolean | ||||||
|      * @throws \UnderflowException |      * @throws \UnderflowException | ||||||
|      */ |      */ | ||||||
|     public function getRsv3(): bool { |     public function getRsv3() { | ||||||
|         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'); | ||||||
|         } |         } | ||||||
| @ -197,7 +205,7 @@ class Frame implements FrameInterface { | |||||||
|     /** |     /** | ||||||
|      * {@inheritdoc} |      * {@inheritdoc} | ||||||
|      */ |      */ | ||||||
|     public function isMasked(): bool { |     public function isMasked() { | ||||||
|         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"); | ||||||
|         } |         } | ||||||
| @ -208,7 +216,7 @@ class Frame implements FrameInterface { | |||||||
|     /** |     /** | ||||||
|      * {@inheritdoc} |      * {@inheritdoc} | ||||||
|      */ |      */ | ||||||
|     public function getMaskingKey(): string { |     public function getMaskingKey() { | ||||||
|         if (!$this->isMasked()) { |         if (!$this->isMasked()) { | ||||||
|             return ''; |             return ''; | ||||||
|         } |         } | ||||||
| @ -226,7 +234,7 @@ class Frame implements FrameInterface { | |||||||
|      * Create a 4 byte masking key |      * Create a 4 byte masking key | ||||||
|      * @return string |      * @return string | ||||||
|      */ |      */ | ||||||
|     public function generateMaskingKey(): string { |     public function generateMaskingKey() { | ||||||
|         $mask = ''; |         $mask = ''; | ||||||
| 
 | 
 | ||||||
|         for ($i = 1; $i <= static::MASK_LENGTH; $i++) { |         for ($i = 1; $i <= static::MASK_LENGTH; $i++) { | ||||||
| @ -243,7 +251,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(?string $maskingKey = null): self { |     public function maskPayload($maskingKey = null) { | ||||||
|         if (null === $maskingKey) { |         if (null === $maskingKey) { | ||||||
|             $maskingKey = $this->generateMaskingKey(); |             $maskingKey = $this->generateMaskingKey(); | ||||||
|         } |         } | ||||||
| @ -274,7 +282,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(): self { |     public function unMaskPayload() { | ||||||
|         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'); | ||||||
|         } |         } | ||||||
| @ -303,7 +311,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(string $maskingKey, ?string $payload = null): string { |     public function applyMask($maskingKey, $payload = null) { | ||||||
|         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'); | ||||||
| @ -324,7 +332,7 @@ class Frame implements FrameInterface { | |||||||
|     /** |     /** | ||||||
|      * {@inheritdoc} |      * {@inheritdoc} | ||||||
|      */ |      */ | ||||||
|     public function getOpcode(): int { |     public function getOpcode() { | ||||||
|         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'); | ||||||
|         } |         } | ||||||
| @ -337,7 +345,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(): int { |     protected function getFirstPayloadVal() { | ||||||
|         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'); | ||||||
|         } |         } | ||||||
| @ -349,7 +357,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(): int { |     protected function getNumPayloadBits() { | ||||||
|         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'); | ||||||
|         } |         } | ||||||
| @ -379,14 +387,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(): int { |     protected function getNumPayloadBytes() { | ||||||
|         return (1 + $this->getNumPayloadBits()) / 8; |         return (1 + $this->getNumPayloadBits()) / 8; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * {@inheritdoc} |      * {@inheritdoc} | ||||||
|      */ |      */ | ||||||
|     public function getPayloadLength(): int { |     public function getPayloadLength() { | ||||||
|         if ($this->defPayLen !== -1) { |         if ($this->defPayLen !== -1) { | ||||||
|             return $this->defPayLen; |             return $this->defPayLen; | ||||||
|         } |         } | ||||||
| @ -416,7 +424,7 @@ class Frame implements FrameInterface { | |||||||
|     /** |     /** | ||||||
|      * {@inheritdoc} |      * {@inheritdoc} | ||||||
|      */ |      */ | ||||||
|     public function getPayloadStartingByte(): int { |     public function getPayloadStartingByte() { | ||||||
|         return 1 + $this->getNumPayloadBytes() + ($this->isMasked() ? static::MASK_LENGTH : 0); |         return 1 + $this->getNumPayloadBytes() + ($this->isMasked() ? static::MASK_LENGTH : 0); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -424,7 +432,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(): string { |     public function getPayload() { | ||||||
|         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'); | ||||||
|         } |         } | ||||||
| @ -436,11 +444,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(): string { |     public function getContents() { | ||||||
|         return substr($this->data, 0, $this->getPayloadStartingByte() + $this->getPayloadLength()); |         return substr($this->data, 0, $this->getPayloadStartingByte() + $this->getPayloadLength()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function __toString(): string { |     public function __toString() { | ||||||
|         $payload = (string)substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength()); |         $payload = (string)substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength()); | ||||||
| 
 | 
 | ||||||
|         if ($this->isMasked()) { |         if ($this->isMasked()) { | ||||||
| @ -455,7 +463,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(): string { |     public function extractOverflow() { | ||||||
|         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 | ||||||
|      */ |      */ | ||||||
|     public function addBuffer(string $buf): void; |     function addBuffer($buf); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Is this the final frame in a fragmented message? |      * Is this the final frame in a fragmented message? | ||||||
|      * @return bool |      * @return bool | ||||||
|      */ |      */ | ||||||
|     public function isFinal(): bool; |     function isFinal(); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Is the payload masked? |      * Is the payload masked? | ||||||
|      * @return bool |      * @return bool | ||||||
|      */ |      */ | ||||||
|     public function isMasked(): bool; |     function isMasked(); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return int |      * @return int | ||||||
|      */ |      */ | ||||||
|     public function getOpcode(): int; |     function getOpcode(); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return int |      * @return int | ||||||
|      */ |      */ | ||||||
|     //public function getReceivedPayloadLength(): int;
 |     //function getReceivedPayloadLength();
 | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * 32-big string |      * 32-big string | ||||||
|      * @return string |      * @return string | ||||||
|      */ |      */ | ||||||
|     public function getMaskingKey(): string; |     function getMaskingKey(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,43 +2,53 @@ | |||||||
| namespace mfmdevsystem\RFC6455\Messaging; | namespace mfmdevsystem\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; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function getIterator(): \Traversable { |     #[\ReturnTypeWillChange]
 | ||||||
|  |     public function getIterator() { | ||||||
|         return $this->_frames; |         return $this->_frames; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * {@inheritdoc} |      * {@inheritdoc} | ||||||
|      */ |      */ | ||||||
|     public function count(): int { |     #[\ReturnTypeWillChange]
 | ||||||
|  |     public function count() { | ||||||
|         return count($this->_frames); |         return count($this->_frames); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * {@inheritdoc} |      * {@inheritdoc} | ||||||
|      */ |      */ | ||||||
|     public function isCoalesced(): bool { |     #[\ReturnTypeWillChange]
 | ||||||
|  |     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): MessageInterface { |     public function addFrame(FrameInterface $fragment) { | ||||||
|         $this->len += $fragment->getPayloadLength(); |         $this->len += $fragment->getPayloadLength(); | ||||||
|         $this->_frames->push($fragment); |         $this->_frames->push($fragment); | ||||||
| 
 | 
 | ||||||
| @ -48,7 +58,7 @@ class Message implements \IteratorAggregate, MessageInterface { | |||||||
|     /** |     /** | ||||||
|      * {@inheritdoc} |      * {@inheritdoc} | ||||||
|      */ |      */ | ||||||
|     public function getOpcode(): int { |     public function getOpcode() { | ||||||
|         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'); | ||||||
|         } |         } | ||||||
| @ -59,14 +69,14 @@ class Message implements \IteratorAggregate, MessageInterface { | |||||||
|     /** |     /** | ||||||
|      * {@inheritdoc} |      * {@inheritdoc} | ||||||
|      */ |      */ | ||||||
|     public function getPayloadLength(): int { |     public function getPayloadLength() { | ||||||
|         return $this->len; |         return $this->len; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * {@inheritdoc} |      * {@inheritdoc} | ||||||
|      */ |      */ | ||||||
|     public function getPayload(): string { |     public function getPayload() { | ||||||
|         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'); | ||||||
|         } |         } | ||||||
| @ -77,7 +87,7 @@ class Message implements \IteratorAggregate, MessageInterface { | |||||||
|     /** |     /** | ||||||
|      * {@inheritdoc} |      * {@inheritdoc} | ||||||
|      */ |      */ | ||||||
|     public function getContents(): string { |     public function getContents() { | ||||||
|         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"); | ||||||
|         } |         } | ||||||
| @ -91,7 +101,7 @@ class Message implements \IteratorAggregate, MessageInterface { | |||||||
|         return $buffer; |         return $buffer; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function __toString(): string { |     public function __toString() { | ||||||
|         $buffer = ''; |         $buffer = ''; | ||||||
| 
 | 
 | ||||||
|         foreach ($this->_frames as $frame) { |         foreach ($this->_frames as $frame) { | ||||||
| @ -104,7 +114,7 @@ class Message implements \IteratorAggregate, MessageInterface { | |||||||
|     /** |     /** | ||||||
|      * @return boolean |      * @return boolean | ||||||
|      */ |      */ | ||||||
|     public function isBinary(): bool { |     public function isBinary() { | ||||||
|         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'); | ||||||
|         } |         } | ||||||
| @ -115,7 +125,7 @@ class Message implements \IteratorAggregate, MessageInterface { | |||||||
|     /** |     /** | ||||||
|      * @return boolean |      * @return boolean | ||||||
|      */ |      */ | ||||||
|     public function getRsv1(): bool { |     public function getRsv1() { | ||||||
|         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,16 +4,25 @@ namespace mfmdevsystem\RFC6455\Messaging; | |||||||
| use mfmdevsystem\RFC6455\Handshake\PermessageDeflateOptions; | use mfmdevsystem\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 | ||||||
| @ -25,55 +34,71 @@ 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 resource|bool|null |      * @var int | ||||||
|      */ |      */ | ||||||
|     private $inflator = null; |     private $streamingMessageOpCode = -1; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @var resource|bool|null |      * @var PermessageDeflateOptions | ||||||
|      */ |      */ | ||||||
|     private $deflator = null; |     private $permessageDeflateOptions; | ||||||
| 
 | 
 | ||||||
|     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, | ||||||
|         bool $expectMask = true, |         $expectMask = true, | ||||||
|         ?callable $exceptionFactory = null, |         $exceptionFactory = null, | ||||||
|         ?int $maxMessagePayloadSize = null, // null for default - zero for no limit
 |         $maxMessagePayloadSize = null, // null for default - zero for no limit
 | ||||||
|         ?int $maxFramePayloadSize = null,   // null for default - zero for no limit
 |         $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 = $expectMask; |         $this->checkForMask = (bool)$expectMask; | ||||||
| 
 | 
 | ||||||
|         $this->exceptionFactory = $exceptionFactory ?: static fn (string $msg) => new \UnderflowException($msg); |         $this->exceptionFactory ?: $exceptionFactory = function($msg) { | ||||||
|  |             return new \UnderflowException($msg); | ||||||
|  |         }; | ||||||
| 
 | 
 | ||||||
|         $this->onMessage = $onMessage; |         $this->onMessage = $onMessage; | ||||||
|         $this->onControl = $onControl ?: static function (): void {}; |         $this->onControl = $onControl ?: function() {}; | ||||||
| 
 | 
 | ||||||
|         $this->sender = $sender; |         $this->sender = $sender; | ||||||
| 
 | 
 | ||||||
| @ -85,6 +110,10 @@ 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) { | ||||||
| @ -94,18 +123,18 @@ class MessageBuffer { | |||||||
|             $maxFramePayloadSize = (int)($memory_limit_bytes / 4); |             $maxFramePayloadSize = (int)($memory_limit_bytes / 4); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if ($maxFramePayloadSize > 0x7FFFFFFFFFFFFFFF || $maxFramePayloadSize < 0) { // this should be interesting on non-64 bit systems
 |         if (!is_int($maxFramePayloadSize) || $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 ($maxMessagePayloadSize > 0x7FFFFFFFFFFFFFFF || $maxMessagePayloadSize < 0) { |         if (!is_int($maxMessagePayloadSize) || $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(string $data): void { |     public function onData($data) { | ||||||
|         $data = $this->leftovers . $data; |         $data = $this->leftovers . $data; | ||||||
|         $dataLen = strlen($data); |         $dataLen = strlen($data); | ||||||
| 
 | 
 | ||||||
| @ -121,7 +150,6 @@ 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; | ||||||
| @ -130,18 +158,9 @@ 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 | ||||||
|                 if ($payload_length === 126){ |                     ? unpack('n', $bytesToUpack)[1] | ||||||
|                     $payload_length = unpack('n', $bytesToUpack)[1]; |                     : unpack('J', $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; | ||||||
| @ -151,10 +170,6 @@ 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'); | ||||||
|             } |             } | ||||||
| @ -185,9 +200,9 @@ class MessageBuffer { | |||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param string $data |      * @param string $data | ||||||
|      * @return void |      * @return null | ||||||
|      */ |      */ | ||||||
|     private function processData(string $data): void { |     private function processData($data) { | ||||||
|         $this->messageBuffer ?: $this->messageBuffer = $this->newMessage(); |         $this->messageBuffer ?: $this->messageBuffer = $this->newMessage(); | ||||||
|         $this->frameBuffer   ?: $this->frameBuffer   = $this->newFrame(); |         $this->frameBuffer   ?: $this->frameBuffer   = $this->newFrame(); | ||||||
| 
 | 
 | ||||||
| @ -206,7 +221,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()) { | ||||||
| @ -244,10 +259,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 FrameInterface $frame |      * @param \Ratchet\RFC6455\Messaging\FrameInterface|FrameInterface $frame | ||||||
|      * @return FrameInterface |      * @return \Ratchet\RFC6455\Messaging\FrameInterface|FrameInterface | ||||||
|      */ |      */ | ||||||
|     public function frameCheck(FrameInterface $frame): FrameInterface { |     public function frameCheck(FrameInterface $frame) { | ||||||
|         if ((false !== $frame->getRsv1() && !$this->deflateEnabled) || |         if ((false !== $frame->getRsv1() && !$this->deflateEnabled) || | ||||||
|             false !== $frame->getRsv2() || |             false !== $frame->getRsv2() || | ||||||
|             false !== $frame->getRsv3() |             false !== $frame->getRsv3() | ||||||
| @ -294,11 +309,13 @@ 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; | ||||||
| @ -317,7 +334,7 @@ class MessageBuffer { | |||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Determine if a message is valid |      * Determine if a message is valid | ||||||
|      * @param MessageInterface |      * @param \Ratchet\RFC6455\Messaging\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) { | ||||||
| @ -330,7 +347,7 @@ class MessageBuffer { | |||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function checkUtf8(string $string): bool { |     private function checkUtf8($string) { | ||||||
|         if (extension_loaded('mbstring')) { |         if (extension_loaded('mbstring')) { | ||||||
|             return mb_check_encoding($string, 'UTF-8'); |             return mb_check_encoding($string, 'UTF-8'); | ||||||
|         } |         } | ||||||
| @ -339,27 +356,27 @@ class MessageBuffer { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return MessageInterface |      * @return \Ratchet\RFC6455\Messaging\MessageInterface | ||||||
|      */ |      */ | ||||||
|     public function newMessage(): MessageInterface { |     public function newMessage() { | ||||||
|         return new Message; |         return new Message; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @param string|null $payload |      * @param string|null $payload | ||||||
|      * @param bool        $final |      * @param bool|null   $final | ||||||
|      * @param int         $opcode |      * @param int|null    $opcode | ||||||
|      * @return FrameInterface |      * @return \Ratchet\RFC6455\Messaging\FrameInterface | ||||||
|      */ |      */ | ||||||
|     public function newFrame(?string $payload = null, bool $final = true, int $opcode = Frame::OP_TEXT): FrameInterface { |     public function newFrame($payload = null, $final = null, $opcode = null) { | ||||||
|         return new Frame($payload, $final, $opcode, $this->exceptionFactory); |         return new Frame($payload, $final, $opcode, $this->exceptionFactory); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function newCloseFrame(int $code, string $reason = ''): FrameInterface { |     public function newCloseFrame($code, $reason = '') { | ||||||
|         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(FrameInterface $frame): void { |     public function sendFrame(Frame $frame) { | ||||||
|         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.'); | ||||||
|         } |         } | ||||||
| @ -377,7 +394,7 @@ class MessageBuffer { | |||||||
|         $sender($frame->getContents()); |         $sender($frame->getContents()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function sendMessage(string $messagePayload, bool $final = true, bool $isBinary = false): void { |     public function sendMessage($messagePayload, $final = true, $isBinary = false) { | ||||||
|         $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; | ||||||
| @ -400,27 +417,29 @@ class MessageBuffer { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function getDeflateNoContextTakeover(): ?bool { |     private $inflator; | ||||||
|  | 
 | ||||||
|  |     private function getDeflateNoContextTakeover() { | ||||||
|         return $this->checkForMask ? |         return $this->checkForMask ? | ||||||
|             $this->permessageDeflateOptions->getServerNoContextTakeover() : |             $this->permessageDeflateOptions->getServerNoContextTakeover() : | ||||||
|             $this->permessageDeflateOptions->getClientNoContextTakeover(); |             $this->permessageDeflateOptions->getClientNoContextTakeover(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function getDeflateWindowBits(): int { |     private function getDeflateWindowBits() { | ||||||
|         return $this->checkForMask ? $this->permessageDeflateOptions->getServerMaxWindowBits() : $this->permessageDeflateOptions->getClientMaxWindowBits(); |         return $this->checkForMask ? $this->permessageDeflateOptions->getServerMaxWindowBits() : $this->permessageDeflateOptions->getClientMaxWindowBits(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function getInflateNoContextTakeover(): ?bool { |     private function getInflateNoContextTakeover() { | ||||||
|         return $this->checkForMask ? |         return $this->checkForMask ? | ||||||
|             $this->permessageDeflateOptions->getClientNoContextTakeover() : |             $this->permessageDeflateOptions->getClientNoContextTakeover() : | ||||||
|             $this->permessageDeflateOptions->getServerNoContextTakeover(); |             $this->permessageDeflateOptions->getServerNoContextTakeover(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function getInflateWindowBits(): int { |     private function getInflateWindowBits() { | ||||||
|         return $this->checkForMask ? $this->permessageDeflateOptions->getClientMaxWindowBits() : $this->permessageDeflateOptions->getServerMaxWindowBits(); |         return $this->checkForMask ? $this->permessageDeflateOptions->getClientMaxWindowBits() : $this->permessageDeflateOptions->getServerMaxWindowBits(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function inflateFrame(FrameInterface $frame): Frame { |     private function inflateFrame(Frame $frame) { | ||||||
|         if ($this->inflator === null) { |         if ($this->inflator === null) { | ||||||
|             $this->inflator = inflate_init( |             $this->inflator = inflate_init( | ||||||
|                 ZLIB_ENCODING_RAW, |                 ZLIB_ENCODING_RAW, | ||||||
| @ -447,14 +466,16 @@ class MessageBuffer { | |||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function deflateFrame(FrameInterface $frame): FrameInterface |     private $deflator; | ||||||
|  | 
 | ||||||
|  |     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 = $this->getDeflateWindowBits(); |             $bits = (int)$this->getDeflateWindowBits(); | ||||||
|             if ($bits === 8) { |             if ($bits === 8) { | ||||||
|                 $bits = 9; |                 $bits = 9; | ||||||
|             } |             } | ||||||
| @ -514,7 +535,7 @@ class MessageBuffer { | |||||||
|      * @param null|string $memory_limit |      * @param null|string $memory_limit | ||||||
|      * @return int |      * @return int | ||||||
|      */ |      */ | ||||||
|     private static function getMemoryLimit(?string $memory_limit = null): int { |     private static function getMemoryLimit($memory_limit = null) { | ||||||
|         $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 | ||||||
|      */ |      */ | ||||||
|     public function addFrame(FrameInterface $fragment): self; |     function addFrame(FrameInterface $fragment); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return int |      * @return int | ||||||
|      */ |      */ | ||||||
|     public function getOpcode(): int; |     function getOpcode(); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @return bool |      * @return bool | ||||||
|      */ |      */ | ||||||
|     public function isBinary(): bool; |     function isBinary(); | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user