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\ResponseInterface; | ||||
| use Psr\Http\Message\UriInterface; | ||||
| use Psr\Http\Message\RequestFactoryInterface; | ||||
| use GuzzleHttp\Psr7\Request; | ||||
| 
 | ||||
| class ClientNegotiator { | ||||
|     private ResponseVerifier $verifier; | ||||
|     /** | ||||
|      * @var ResponseVerifier | ||||
|      */ | ||||
|     private $verifier; | ||||
| 
 | ||||
|     private RequestInterface $defaultHeader; | ||||
|     /** | ||||
|      * @var \Psr\Http\Message\RequestInterface | ||||
|      */ | ||||
|     private $defaultHeader; | ||||
| 
 | ||||
|     private RequestFactoryInterface $requestFactory; | ||||
| 
 | ||||
|     public function __construct( | ||||
|         RequestFactoryInterface $requestFactory, | ||||
|         ?PermessageDeflateOptions $perMessageDeflateOptions = null | ||||
|     ) { | ||||
|     function __construct(?PermessageDeflateOptions $perMessageDeflateOptions = null) { | ||||
|         $this->verifier = new ResponseVerifier; | ||||
|         $this->requestFactory = $requestFactory; | ||||
| 
 | ||||
|         $this->defaultHeader = $this->requestFactory | ||||
|             ->createRequest('GET', '') | ||||
|             ->withHeader('Connection'           , 'Upgrade') | ||||
|             ->withHeader('Upgrade'              , 'websocket') | ||||
|             ->withHeader('Sec-WebSocket-Version', $this->getVersion()) | ||||
|             ->withHeader('User-Agent'           , 'Ratchet'); | ||||
|         $this->defaultHeader = new Request('GET', '', [ | ||||
|             'Connection'            => 'Upgrade' | ||||
|           , 'Upgrade'               => 'websocket' | ||||
|           , 'Sec-WebSocket-Version' => $this->getVersion() | ||||
|           , '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=74240 - need >=7.1.4 or >=7.0.18
 | ||||
|         if ($perMessageDeflateOptions->isEnabled() && !PermessageDeflateOptions::permessageDeflateSupported()) { | ||||
|             trigger_error('permessage-deflate is being disabled because it is not supported by your PHP version.', E_USER_NOTICE); | ||||
|         if ($perMessageDeflateOptions->isEnabled() && | ||||
|             !PermessageDeflateOptions::permessageDeflateSupported()) { | ||||
|             trigger_error('permessage-deflate is being disabled because it is not support by your PHP version.', E_USER_NOTICE); | ||||
|             $perMessageDeflateOptions = PermessageDeflateOptions::createDisabled(); | ||||
|         } | ||||
|         if ($perMessageDeflateOptions->isEnabled() && !function_exists('deflate_add')) { | ||||
| @ -42,16 +45,16 @@ class ClientNegotiator { | ||||
|         $this->defaultHeader = $perMessageDeflateOptions->addHeaderToRequest($this->defaultHeader); | ||||
|     } | ||||
| 
 | ||||
|     public function generateRequest(UriInterface $uri): RequestInterface { | ||||
|     public function generateRequest(UriInterface $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); | ||||
|     } | ||||
| 
 | ||||
|     public function generateKey(): string { | ||||
|     public function generateKey() { | ||||
|         $chars     = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwzyz1234567890+/='; | ||||
|         $charRange = strlen($chars) - 1; | ||||
|         $key       = ''; | ||||
| @ -62,7 +65,7 @@ class ClientNegotiator { | ||||
|         return base64_encode($key); | ||||
|     } | ||||
| 
 | ||||
|     public function getVersion(): int { | ||||
|     public function getVersion() { | ||||
|         return 13; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,7 +1,6 @@ | ||||
| <?php | ||||
| namespace mfmdevsystem\RFC6455\Handshake; | ||||
| use Psr\Http\Message\RequestInterface; | ||||
| use Psr\Http\Message\ResponseInterface; | ||||
| 
 | ||||
| /** | ||||
|  * A standard interface for interacting with the various version of the WebSocket protocol | ||||
| @ -15,34 +14,34 @@ interface NegotiatorInterface { | ||||
|      * @param RequestInterface $request | ||||
|      * @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 | ||||
|      * @return int | ||||
|      */ | ||||
|     public function getVersionNumber(): int; | ||||
|     function getVersionNumber(); | ||||
| 
 | ||||
|     /** | ||||
|      * Perform the handshake and return the response headers | ||||
|      * @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 | ||||
|      * @param array $protocols | ||||
|      */ | ||||
|     public function setSupportedSubProtocols(array $protocols): void; | ||||
|     function setSupportedSubProtocols(array $protocols); | ||||
| 
 | ||||
|     /** | ||||
|      * If enabled and support for a subprotocol has been added handshake | ||||
|      *  will not upgrade if a match between request and supported subprotocols | ||||
|      * @param boolean $enable | ||||
|      * @todo Consider extending this interface and moving this there. | ||||
|      *       The spec does 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. | ||||
|      */ | ||||
|     public function setStrictSubProtocolCheck(bool $enable): void; | ||||
|     function setStrictSubProtocolCheck($enable); | ||||
| } | ||||
|  | ||||
| @ -8,21 +8,21 @@ use Psr\Http\Message\ResponseInterface; | ||||
| 
 | ||||
| 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 ?bool $server_no_context_takeover = null; | ||||
|     private ?bool $client_no_context_takeover = null; | ||||
|     private ?int $server_max_window_bits = null; | ||||
|     private ?int $client_max_window_bits = null; | ||||
|     private $server_no_context_takeover; | ||||
|     private $client_no_context_takeover; | ||||
|     private $server_max_window_bits; | ||||
|     private $client_max_window_bits; | ||||
| 
 | ||||
|     private function __construct() { } | ||||
| 
 | ||||
|     public static function createEnabled() { | ||||
|         $new                             = new self(); | ||||
|         $new                             = new static(); | ||||
|         $new->deflateEnabled             = true; | ||||
|         $new->client_max_window_bits     = self::MAX_WINDOW_BITS; | ||||
|         $new->client_no_context_takeover = false; | ||||
| @ -33,35 +33,35 @@ final class PermessageDeflateOptions | ||||
|     } | ||||
| 
 | ||||
|     public static function createDisabled() { | ||||
|         return new self(); | ||||
|         return new static(); | ||||
|     } | ||||
| 
 | ||||
|     public function withClientNoContextTakeover(): self { | ||||
|     public function withClientNoContextTakeover() { | ||||
|         $new = clone $this; | ||||
|         $new->client_no_context_takeover = true; | ||||
|         return $new; | ||||
|     } | ||||
| 
 | ||||
|     public function withoutClientNoContextTakeover(): self { | ||||
|     public function withoutClientNoContextTakeover() { | ||||
|         $new = clone $this; | ||||
|         $new->client_no_context_takeover = false; | ||||
|         return $new; | ||||
|     } | ||||
| 
 | ||||
|     public function withServerNoContextTakeover(): self { | ||||
|     public function withServerNoContextTakeover() { | ||||
|         $new = clone $this; | ||||
|         $new->server_no_context_takeover = true; | ||||
|         return $new; | ||||
|     } | ||||
| 
 | ||||
|     public function withoutServerNoContextTakeover(): self { | ||||
|     public function withoutServerNoContextTakeover() { | ||||
|         $new = clone $this; | ||||
|         $new->server_no_context_takeover = false; | ||||
|         return $new; | ||||
|     } | ||||
| 
 | ||||
|     public function withServerMaxWindowBits(int $bits = self::MAX_WINDOW_BITS): self { | ||||
|         if (!in_array($bits, self::VALID_BITS)) { | ||||
|     public function withServerMaxWindowBits($bits = self::MAX_WINDOW_BITS) { | ||||
|         if (!in_array($bits, self::$VALID_BITS)) { | ||||
|             throw new \Exception('server_max_window_bits must have a value between 8 and 15.'); | ||||
|         } | ||||
|         $new = clone $this; | ||||
| @ -69,8 +69,8 @@ final class PermessageDeflateOptions | ||||
|         return $new; | ||||
|     } | ||||
| 
 | ||||
|     public function withClientMaxWindowBits(int $bits = self::MAX_WINDOW_BITS): self { | ||||
|         if (!in_array($bits, self::VALID_BITS)) { | ||||
|     public function withClientMaxWindowBits($bits = self::MAX_WINDOW_BITS) { | ||||
|         if (!in_array($bits, self::$VALID_BITS)) { | ||||
|             throw new \Exception('client_max_window_bits must have a value between 8 and 15.'); | ||||
|         } | ||||
|         $new = clone $this; | ||||
| @ -86,7 +86,7 @@ final class PermessageDeflateOptions | ||||
|      * @return PermessageDeflateOptions[] | ||||
|      * @throws \Exception | ||||
|      */ | ||||
|     public static function fromRequestOrResponse(MessageInterface $requestOrResponse): array { | ||||
|     public static function fromRequestOrResponse(MessageInterface $requestOrResponse) { | ||||
|         $optionSets = []; | ||||
| 
 | ||||
|         $extHeader = preg_replace('/\s+/', '', join(', ', $requestOrResponse->getHeader('Sec-Websocket-Extensions'))); | ||||
| @ -103,7 +103,7 @@ final class PermessageDeflateOptions | ||||
|             } | ||||
| 
 | ||||
|             array_shift($parts); | ||||
|             $options                 = new self(); | ||||
|             $options                 = new static(); | ||||
|             $options->deflateEnabled = true; | ||||
|             foreach ($parts as $part) { | ||||
|                 $kv = explode('=', $part); | ||||
| @ -119,18 +119,15 @@ final class PermessageDeflateOptions | ||||
|                         $value = true; | ||||
|                         break; | ||||
|                     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.'); | ||||
|                         } | ||||
|                         break; | ||||
|                     case "client_max_window_bits": | ||||
|                         if ($value === null) { | ||||
|                             $value = 15; | ||||
|                         } else { | ||||
|                             $value = (int) $value; | ||||
|                             $value = '15'; | ||||
|                         } | ||||
|                         if (!in_array($value, self::VALID_BITS)) { | ||||
|                         if (!in_array($value, self::$VALID_BITS)) { | ||||
|                             throw new InvalidPermessageDeflateOptionsException($key . ' must have no value or a value between 8 and 15.'); | ||||
|                         } | ||||
|                         break; | ||||
| @ -157,39 +154,39 @@ final class PermessageDeflateOptions | ||||
|         } | ||||
| 
 | ||||
|         // always put a disabled on the end
 | ||||
|         $optionSets[] = new self(); | ||||
|         $optionSets[] = new static(); | ||||
| 
 | ||||
|         return $optionSets; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return bool|null | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function getServerNoContextTakeover(): ?bool | ||||
|     public function getServerNoContextTakeover() | ||||
|     { | ||||
|         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 int|null | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function getServerMaxWindowBits(): ?int | ||||
|     public function getServerMaxWindowBits() | ||||
|     { | ||||
|         return $this->server_max_window_bits; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return int|null | ||||
|      * @return mixed | ||||
|      */ | ||||
|     public function getClientMaxWindowBits(): ?int | ||||
|     public function getClientMaxWindowBits() | ||||
|     { | ||||
|         return $this->client_max_window_bits; | ||||
|     } | ||||
| @ -197,7 +194,7 @@ final class PermessageDeflateOptions | ||||
|     /** | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function isEnabled(): bool | ||||
|     public function isEnabled() | ||||
|     { | ||||
|         return $this->deflateEnabled; | ||||
|     } | ||||
| @ -206,7 +203,7 @@ final class PermessageDeflateOptions | ||||
|      * @param ResponseInterface $response | ||||
|      * @return ResponseInterface | ||||
|      */ | ||||
|     public function addHeaderToResponse(ResponseInterface $response): ResponseInterface | ||||
|     public function addHeaderToResponse(ResponseInterface $response) | ||||
|     { | ||||
|         if (!$this->deflateEnabled) { | ||||
|             return $response; | ||||
| @ -229,7 +226,7 @@ final class PermessageDeflateOptions | ||||
|         return $response->withAddedHeader('Sec-Websocket-Extensions', $header); | ||||
|     } | ||||
| 
 | ||||
|     public function addHeaderToRequest(RequestInterface $request): RequestInterface { | ||||
|     public function addHeaderToRequest(RequestInterface $request) { | ||||
|         if (!$this->deflateEnabled) { | ||||
|             return $request; | ||||
|         } | ||||
| @ -252,7 +249,7 @@ final class PermessageDeflateOptions | ||||
|         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')) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
| @ -8,14 +8,14 @@ use Psr\Http\Message\RequestInterface; | ||||
|  * @todo Currently just returning invalid - should consider returning appropriate HTTP status code error #s
 | ||||
|  */ | ||||
| class RequestVerifier { | ||||
|     public const VERSION = 13; | ||||
|     const VERSION = 13; | ||||
| 
 | ||||
|     /** | ||||
|      * Given an array of the headers this method will run through all verification methods | ||||
|      * @param RequestInterface $request | ||||
|      * @return bool TRUE if all headers are valid, FALSE if 1 or more were invalid | ||||
|      */ | ||||
|     public function verifyAll(RequestInterface $request): bool { | ||||
|     public function verifyAll(RequestInterface $request) { | ||||
|         $passes = 0; | ||||
| 
 | ||||
|         $passes += (int)$this->verifyMethod($request->getMethod()); | ||||
| @ -27,7 +27,7 @@ class RequestVerifier { | ||||
|         $passes += (int)$this->verifyKey($request->getHeader('Sec-WebSocket-Key')); | ||||
|         $passes += (int)$this->verifyVersion($request->getHeader('Sec-WebSocket-Version')); | ||||
| 
 | ||||
|         return 8 === $passes; | ||||
|         return (8 === $passes); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -35,8 +35,8 @@ class RequestVerifier { | ||||
|      * @param string | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function verifyMethod(string $val): bool { | ||||
|         return 'get' === strtolower($val); | ||||
|     public function verifyMethod($val) { | ||||
|         return ('get' === strtolower($val)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -44,15 +44,15 @@ class RequestVerifier { | ||||
|      * @param string|int | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function verifyHTTPVersion($val): bool { | ||||
|         return 1.1 <= (double)$val; | ||||
|     public function verifyHTTPVersion($val) { | ||||
|         return (1.1 <= (double)$val); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function verifyRequestURI(string $val): bool { | ||||
|     public function verifyRequestURI($val) { | ||||
|         if ($val[0] !== '/') { | ||||
|             return false; | ||||
|         } | ||||
| @ -73,8 +73,8 @@ class RequestVerifier { | ||||
|      * @return bool | ||||
|      * @todo Once I fix HTTP::getHeaders just verify this isn't NULL or empty...or maybe need to verify it's a valid domain??? Or should it equal $_SERVER['HOST'] ? | ||||
|      */ | ||||
|     public function verifyHost(array $hostHeader): bool { | ||||
|         return 1 === count($hostHeader); | ||||
|     public function verifyHost(array $hostHeader) { | ||||
|         return (1 === count($hostHeader)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -82,8 +82,8 @@ class RequestVerifier { | ||||
|      * @param  array $upgradeHeader MUST equal "websocket" | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function verifyUpgradeRequest(array $upgradeHeader): bool { | ||||
|         return 1 === count($upgradeHeader) && 'websocket' === strtolower($upgradeHeader[0]); | ||||
|     public function verifyUpgradeRequest(array $upgradeHeader) { | ||||
|         return (1 === count($upgradeHeader) && 'websocket' === strtolower($upgradeHeader[0])); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -91,11 +91,13 @@ class RequestVerifier { | ||||
|      * @param  array $connectionHeader MUST include "Upgrade" | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function verifyConnection(array $connectionHeader): bool { | ||||
|     public function verifyConnection(array $connectionHeader) { | ||||
|         foreach ($connectionHeader as $l) { | ||||
|             $upgrades = array_filter( | ||||
|                 array_map('trim', array_map('strtolower', explode(',', $l))), | ||||
|                 static fn (string $x) => 'upgrade' === $x | ||||
|                 function ($x) { | ||||
|                     return 'upgrade' === $x; | ||||
|                 } | ||||
|             ); | ||||
|             if (count($upgrades) > 0) { | ||||
|                 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 Check the spec to see what the encoding of the key could be | ||||
|      */ | ||||
|     public function verifyKey(array $keyHeader): bool { | ||||
|         return 1 === count($keyHeader) && 16 === strlen(base64_decode($keyHeader[0])); | ||||
|     public function verifyKey(array $keyHeader) { | ||||
|         return (1 === count($keyHeader) && 16 === strlen(base64_decode($keyHeader[0]))); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -120,33 +122,33 @@ class RequestVerifier { | ||||
|      * @param string[] $versionHeader MUST equal ["13"] | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function verifyVersion(array $versionHeader): bool { | ||||
|         return 1 === count($versionHeader) && static::VERSION === (int)$versionHeader[0]; | ||||
|     public function verifyVersion(array $versionHeader) { | ||||
|         return (1 === count($versionHeader) && static::VERSION === (int)$versionHeader[0]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @todo Write logic for this method.  See section 4.2.1.8 | ||||
|      */ | ||||
|     public function verifyProtocol($val): bool { | ||||
|         return true; | ||||
|     public function verifyProtocol($val) { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @todo Write logic for this method.  See section 4.2.1.9 | ||||
|      */ | ||||
|     public function verifyExtensions($val): bool { | ||||
|         return true; | ||||
|     public function verifyExtensions($val) { | ||||
|     } | ||||
| 
 | ||||
|     public function getPermessageDeflateOptions(array $requestHeader, array $responseHeader): array { | ||||
|         $headerChecker = static fn (string $val) => 'permessage-deflate' === substr($val, 0, strlen('permessage-deflate')); | ||||
| 
 | ||||
|     public function getPermessageDeflateOptions(array $requestHeader, array $responseHeader) { | ||||
|         $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; | ||||
|         } | ||||
| 
 | ||||
|         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; | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -4,7 +4,7 @@ use Psr\Http\Message\RequestInterface; | ||||
| use Psr\Http\Message\ResponseInterface; | ||||
| 
 | ||||
| class ResponseVerifier { | ||||
|     public function verifyAll(RequestInterface $request, ResponseInterface $response): bool { | ||||
|     public function verifyAll(RequestInterface $request, ResponseInterface $response) { | ||||
|         $passes = 0; | ||||
| 
 | ||||
|         $passes += (int)$this->verifyStatus($response->getStatusCode()); | ||||
| @ -26,31 +26,31 @@ class ResponseVerifier { | ||||
|         return (6 === $passes); | ||||
|     } | ||||
| 
 | ||||
|     public function verifyStatus(int $status): bool { | ||||
|         return $status === 101; | ||||
|     public function verifyStatus($status) { | ||||
|         return ((int)$status === 101); | ||||
|     } | ||||
| 
 | ||||
|     public function verifyUpgrade(array $upgrade): bool { | ||||
|         return in_array('websocket', array_map('strtolower', $upgrade)); | ||||
|     public function verifyUpgrade(array $upgrade) { | ||||
|         return (in_array('websocket', array_map('strtolower', $upgrade))); | ||||
|     } | ||||
| 
 | ||||
|     public function verifyConnection(array $connection): bool { | ||||
|         return in_array('upgrade', array_map('strtolower', $connection)); | ||||
|     public function verifyConnection(array $connection) { | ||||
|         return (in_array('upgrade', array_map('strtolower', $connection))); | ||||
|     } | ||||
| 
 | ||||
|     public function verifySecWebSocketAccept(array $swa, array $key): bool { | ||||
|         return | ||||
|     public function verifySecWebSocketAccept($swa, $key) { | ||||
|         return ( | ||||
|             1 === count($swa) && | ||||
|             1 === count($key) && | ||||
|             $swa[0] === $this->sign($key[0]) | ||||
|         ; | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     public function sign(string $key): string { | ||||
|     public function sign($key) { | ||||
|         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)) { | ||||
|             return true; | ||||
|         } | ||||
| @ -60,7 +60,7 @@ class ResponseVerifier { | ||||
|         return count($responseHeader) === 1 && count(array_intersect($responseHeader, $requestedProtocols)) === 1; | ||||
|     } | ||||
| 
 | ||||
|     public function verifyExtensions(array $requestHeader, array $responseHeader): int { | ||||
|     public function verifyExtensions(array $requestHeader, array $responseHeader) { | ||||
|         if (in_array('permessage-deflate', $responseHeader)) { | ||||
|             return strpos(implode(',', $requestHeader), 'permessage-deflate') !== false ? 1 : 0; | ||||
|         } | ||||
|  | ||||
| @ -1,33 +1,26 @@ | ||||
| <?php | ||||
| namespace mfmdevsystem\RFC6455\Handshake; | ||||
| use Psr\Http\Message\RequestInterface; | ||||
| use Psr\Http\Message\ResponseFactoryInterface; | ||||
| use GuzzleHttp\Psr7\Response; | ||||
| use Psr\Http\Message\ResponseInterface; | ||||
| 
 | ||||
| /** | ||||
|  * The latest version of the WebSocket protocol | ||||
|  * @todo Unicode: return mb_convert_encoding(pack("N",$u), mb_internal_encoding(), 'UCS-4BE'); | ||||
|  */ | ||||
| class ServerNegotiator implements NegotiatorInterface { | ||||
|     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, | ||||
|         ResponseFactoryInterface $responseFactory, | ||||
|         $enablePerMessageDeflate = null | ||||
|     ) { | ||||
|         if ($enablePerMessageDeflate == null) $enablePerMessageDeflate = false; | ||||
|     public function __construct(RequestVerifier $requestVerifier, $enablePerMessageDeflate = false) { | ||||
|         $this->verifier = $requestVerifier; | ||||
|         $this->responseFactory = $responseFactory; | ||||
| 
 | ||||
|         // https://bugs.php.net/bug.php?id=73373
 | ||||
|         // https://bugs.php.net/bug.php?id=74240 - need >=7.1.4 or >=7.0.18
 | ||||
| @ -45,85 +38,85 @@ class ServerNegotiator implements NegotiatorInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function isProtocol(RequestInterface $request): bool { | ||||
|     public function isProtocol(RequestInterface $request) { | ||||
|         return $this->verifier->verifyVersion($request->getHeader('Sec-WebSocket-Version')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getVersionNumber(): int { | ||||
|     public function getVersionNumber() { | ||||
|         return RequestVerifier::VERSION; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function handshake(RequestInterface $request): ResponseInterface { | ||||
|         $response = $this->responseFactory->createResponse(); | ||||
|     public function handshake(RequestInterface $request) { | ||||
|         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())) { | ||||
|             return $response->withStatus(505); | ||||
|             return new Response(505); | ||||
|         } | ||||
| 
 | ||||
|         if (true !== $this->verifier->verifyRequestURI($request->getUri()->getPath())) { | ||||
|             return $response->withStatus(400); | ||||
|             return new Response(400); | ||||
|         } | ||||
| 
 | ||||
|         if (true !== $this->verifier->verifyHost($request->getHeader('Host'))) { | ||||
|             return $response->withStatus(400); | ||||
|             return new Response(400); | ||||
|         } | ||||
| 
 | ||||
|         $upgradeResponse = $response | ||||
|             ->withHeader('Connection'           , 'Upgrade') | ||||
|             ->withHeader('Upgrade'              , 'websocket') | ||||
|             ->withHeader('Sec-WebSocket-Version', $this->getVersionNumber()); | ||||
| 
 | ||||
|         $upgradeSuggestion = [ | ||||
|             'Connection'             => 'Upgrade', | ||||
|             'Upgrade'                => 'websocket', | ||||
|             'Sec-WebSocket-Version'  => $this->getVersionNumber() | ||||
|         ]; | ||||
|         if (count($this->_supportedSubProtocols) > 0) { | ||||
|             $upgradeResponse = $upgradeResponse->withHeader( | ||||
|                 'Sec-WebSocket-Protocol', implode(', ', array_keys($this->_supportedSubProtocols)) | ||||
|             ); | ||||
|             $upgradeSuggestion['Sec-WebSocket-Protocol'] = implode(', ', array_keys($this->_supportedSubProtocols)); | ||||
|         } | ||||
|         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'))) { | ||||
|             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'))) { | ||||
|             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'))) { | ||||
|             return $upgradeResponse->withStatus(426); | ||||
|             return new Response(426, $upgradeSuggestion); | ||||
|         } | ||||
| 
 | ||||
|         $headers = []; | ||||
|         $subProtocols = $request->getHeader('Sec-WebSocket-Protocol'); | ||||
|         if (count($subProtocols) > 0 || (count($this->_supportedSubProtocols) > 0 && $this->_strictSubProtocols)) { | ||||
|             $subProtocols = array_map('trim', explode(',', implode(',', $subProtocols))); | ||||
| 
 | ||||
|             $match = array_reduce($subProtocols, 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) { | ||||
|                 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) { | ||||
|                 $response = $response->withHeader('Sec-WebSocket-Protocol', $match); | ||||
|                 $headers['Sec-WebSocket-Protocol'] = $match; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         $response = $response | ||||
|             ->withStatus(101) | ||||
|             ->withHeader('Upgrade'             , 'websocket') | ||||
|             ->withHeader('Connection'          , 'Upgrade') | ||||
|             ->withHeader('Sec-WebSocket-Accept', $this->sign((string)$request->getHeader('Sec-WebSocket-Key')[0])) | ||||
|             ->withHeader('X-Powered-By'        , 'Ratchet'); | ||||
|         $response = new Response(101, array_merge($headers, [ | ||||
|             'Upgrade'              => 'websocket' | ||||
|             , 'Connection'           => 'Upgrade' | ||||
|             , 'Sec-WebSocket-Accept' => $this->sign((string)$request->getHeader('Sec-WebSocket-Key')[0]) | ||||
|             , 'X-Powered-By'         => 'Ratchet' | ||||
|         ])); | ||||
| 
 | ||||
|         try { | ||||
|             $perMessageDeflateRequest = PermessageDeflateOptions::fromRequestOrResponse($request)[0]; | ||||
| @ -144,14 +137,14 @@ class ServerNegotiator implements NegotiatorInterface { | ||||
|      * @return string | ||||
|      * @internal | ||||
|      */ | ||||
|     public function sign(string $key): string { | ||||
|     public function sign($key) { | ||||
|         return base64_encode(sha1($key . static::GUID, true)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param array $protocols | ||||
|      */ | ||||
|     public function setSupportedSubProtocols(array $protocols): void { | ||||
|     function setSupportedSubProtocols(array $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 | ||||
|      * @param boolean $enable | ||||
|      * @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. | ||||
|      */ | ||||
|     public function setStrictSubProtocolCheck(bool $enable): void { | ||||
|         $this->_strictSubProtocols = $enable; | ||||
|     function setStrictSubProtocolCheck($enable) { | ||||
|         $this->_strictSubProtocols = (boolean)$enable; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2,7 +2,10 @@ | ||||
| namespace mfmdevsystem\RFC6455\Messaging; | ||||
| 
 | ||||
| class CloseFrameChecker { | ||||
|     private array $validCloseCodes = [ | ||||
|     private $validCloseCodes = []; | ||||
| 
 | ||||
|     public function __construct() { | ||||
|         $this->validCloseCodes = [ | ||||
|             Frame::CLOSE_NORMAL, | ||||
|             Frame::CLOSE_GOING_AWAY, | ||||
|             Frame::CLOSE_PROTOCOL, | ||||
| @ -13,8 +16,9 @@ class CloseFrameChecker { | ||||
|             Frame::CLOSE_MAND_EXT, | ||||
|             Frame::CLOSE_SRV_ERR, | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     public function __invoke(int $val): bool { | ||||
|     public function __invoke($val) { | ||||
|         return ($val >= 3000 && $val <= 4999) || in_array($val, $this->validCloseCodes); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,28 +1,34 @@ | ||||
| <?php | ||||
| namespace mfmdevsystem\RFC6455\Messaging; | ||||
| 
 | ||||
| interface DataInterface extends \Stringable { | ||||
| interface DataInterface { | ||||
|     /** | ||||
|      * Determine if the message is complete or still fragmented | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function isCoalesced(): bool; | ||||
|     function isCoalesced(); | ||||
| 
 | ||||
|     /** | ||||
|      * Get the number of bytes the payload is set to be | ||||
|      * @return int | ||||
|      */ | ||||
|     public function getPayloadLength(): int; | ||||
|     function getPayloadLength(); | ||||
| 
 | ||||
|     /** | ||||
|      * Get the payload (message) sent from peer | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getPayload(): string; | ||||
|     function getPayload(); | ||||
| 
 | ||||
|     /** | ||||
|      * Get raw contents of the message | ||||
|      * @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 | ||||
|      * @var string | ||||
|      */ | ||||
|     protected string $data = ''; | ||||
|     protected $data = ''; | ||||
| 
 | ||||
|     /** | ||||
|      * 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) | ||||
|      * @var int | ||||
|      */ | ||||
|     protected int $defPayLen = -1; | ||||
|     protected $defPayLen = -1; | ||||
| 
 | ||||
|     /** | ||||
|      * If the frame is coalesced this is true | ||||
|      * This is to prevent doing math every time ::isCoalesced is called | ||||
|      * @var boolean | ||||
|      */ | ||||
|     private bool $isCoalesced = false; | ||||
|     private $isCoalesced = false; | ||||
| 
 | ||||
|     /** | ||||
|      * The unpacked first byte of the frame | ||||
|      * @var int | ||||
|      */ | ||||
|     protected int $firstByte = -1; | ||||
|     protected $firstByte = -1; | ||||
| 
 | ||||
|     /** | ||||
|      * The unpacked second byte of the frame | ||||
|      * @var int | ||||
|      */ | ||||
|     protected int $secondByte = -1; | ||||
|     protected $secondByte = -1; | ||||
| 
 | ||||
|     /** | ||||
|      * @var callable | ||||
| @ -67,8 +73,10 @@ class Frame implements FrameInterface { | ||||
|      * @param int         $opcode | ||||
|      * @param callable<\UnderflowException> $ufExceptionFactory | ||||
|      */ | ||||
|     public function __construct(?string $payload = null, bool $final = true, int $opcode = 1, $ufExceptionFactory = null) { | ||||
|         $this->ufeg = $ufExceptionFactory ?: static fn (string $msg = '') => new \UnderflowException($msg); | ||||
|     public function __construct($payload = null, $final = true, $opcode = 1, ?callable $ufExceptionFactory = null) { | ||||
|         $this->ufeg = $ufExceptionFactory ?: static function($msg = '') { | ||||
|             return new \UnderflowException($msg); | ||||
|         }; | ||||
| 
 | ||||
|         if (null === $payload) { | ||||
|             return; | ||||
| @ -95,7 +103,7 @@ class Frame implements FrameInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function isCoalesced(): bool { | ||||
|     public function isCoalesced() { | ||||
|         if (true === $this->isCoalesced) { | ||||
|             return true; | ||||
|         } | ||||
| @ -115,7 +123,7 @@ class Frame implements FrameInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function addBuffer(string $buf): void { | ||||
|     public function addBuffer($buf) { | ||||
|         $len = strlen($buf); | ||||
| 
 | ||||
|         $this->data       .= $buf; | ||||
| @ -133,7 +141,7 @@ class Frame implements FrameInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function isFinal(): bool { | ||||
|     public function isFinal() { | ||||
|         if (-1 === $this->firstByte) { | ||||
|             throw call_user_func($this->ufeg, 'Not enough bytes received to determine if this is the final frame in message'); | ||||
|         } | ||||
| @ -141,7 +149,7 @@ class Frame implements FrameInterface { | ||||
|         return 128 === ($this->firstByte & 128); | ||||
|     } | ||||
| 
 | ||||
|     public function setRsv1(bool $value = true): self { | ||||
|     public function setRsv1($value = true) { | ||||
|         if (strlen($this->data) == 0) { | ||||
|             throw new \UnderflowException("Cannot set Rsv1 because there is no data."); | ||||
|         } | ||||
| @ -162,7 +170,7 @@ class Frame implements FrameInterface { | ||||
|      * @return boolean | ||||
|      * @throws \UnderflowException | ||||
|      */ | ||||
|     public function getRsv1(): bool { | ||||
|     public function getRsv1() { | ||||
|         if (-1 === $this->firstByte) { | ||||
|             throw call_user_func($this->ufeg, 'Not enough bytes received to determine reserved bit'); | ||||
|         } | ||||
| @ -174,7 +182,7 @@ class Frame implements FrameInterface { | ||||
|      * @return boolean | ||||
|      * @throws \UnderflowException | ||||
|      */ | ||||
|     public function getRsv2(): bool { | ||||
|     public function getRsv2() { | ||||
|         if (-1 === $this->firstByte) { | ||||
|             throw call_user_func($this->ufeg, 'Not enough bytes received to determine reserved bit'); | ||||
|         } | ||||
| @ -186,7 +194,7 @@ class Frame implements FrameInterface { | ||||
|      * @return boolean | ||||
|      * @throws \UnderflowException | ||||
|      */ | ||||
|     public function getRsv3(): bool { | ||||
|     public function getRsv3() { | ||||
|         if (-1 === $this->firstByte) { | ||||
|             throw call_user_func($this->ufeg, 'Not enough bytes received to determine reserved bit'); | ||||
|         } | ||||
| @ -197,7 +205,7 @@ class Frame implements FrameInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function isMasked(): bool { | ||||
|     public function isMasked() { | ||||
|         if (-1 === $this->secondByte) { | ||||
|             throw call_user_func($this->ufeg, "Not enough bytes received ({$this->bytesRecvd}) to determine if mask is set"); | ||||
|         } | ||||
| @ -208,7 +216,7 @@ class Frame implements FrameInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getMaskingKey(): string { | ||||
|     public function getMaskingKey() { | ||||
|         if (!$this->isMasked()) { | ||||
|             return ''; | ||||
|         } | ||||
| @ -226,7 +234,7 @@ class Frame implements FrameInterface { | ||||
|      * Create a 4 byte masking key | ||||
|      * @return string | ||||
|      */ | ||||
|     public function generateMaskingKey(): string { | ||||
|     public function generateMaskingKey() { | ||||
|         $mask = ''; | ||||
| 
 | ||||
|         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 | ||||
|      * @return Frame | ||||
|      */ | ||||
|     public function maskPayload(?string $maskingKey = null): self { | ||||
|     public function maskPayload($maskingKey = null) { | ||||
|         if (null === $maskingKey) { | ||||
|             $maskingKey = $this->generateMaskingKey(); | ||||
|         } | ||||
| @ -274,7 +282,7 @@ class Frame implements FrameInterface { | ||||
|      * @throws \UnderFlowException If the frame is not coalesced | ||||
|      * @return Frame | ||||
|      */ | ||||
|     public function unMaskPayload(): self { | ||||
|     public function unMaskPayload() { | ||||
|         if (!$this->isCoalesced()) { | ||||
|             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 | ||||
|      * @return string              The masked string | ||||
|      */ | ||||
|     public function applyMask(string $maskingKey, ?string $payload = null): string { | ||||
|     public function applyMask($maskingKey, $payload = null) { | ||||
|         if (null === $payload) { | ||||
|             if (!$this->isCoalesced()) { | ||||
|                 throw call_user_func($this->ufeg, 'Frame must be coalesced to apply a mask'); | ||||
| @ -324,7 +332,7 @@ class Frame implements FrameInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getOpcode(): int { | ||||
|     public function getOpcode() { | ||||
|         if (-1 === $this->firstByte) { | ||||
|             throw call_user_func($this->ufeg, 'Not enough bytes received to determine opcode'); | ||||
|         } | ||||
| @ -337,7 +345,7 @@ class Frame implements FrameInterface { | ||||
|      * @return int | ||||
|      * @throws \UnderflowException If the buffer doesn't have enough data to determine this | ||||
|      */ | ||||
|     protected function getFirstPayloadVal(): int { | ||||
|     protected function getFirstPayloadVal() { | ||||
|         if (-1 === $this->secondByte) { | ||||
|             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 | ||||
|      * @throws \UnderflowException | ||||
|      */ | ||||
|     protected function getNumPayloadBits(): int { | ||||
|     protected function getNumPayloadBits() { | ||||
|         if (-1 === $this->secondByte) { | ||||
|             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)
 | ||||
|      * @see getNumPayloadBits | ||||
|      */ | ||||
|     protected function getNumPayloadBytes(): int { | ||||
|     protected function getNumPayloadBytes() { | ||||
|         return (1 + $this->getNumPayloadBits()) / 8; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getPayloadLength(): int { | ||||
|     public function getPayloadLength() { | ||||
|         if ($this->defPayLen !== -1) { | ||||
|             return $this->defPayLen; | ||||
|         } | ||||
| @ -416,7 +424,7 @@ class Frame implements FrameInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getPayloadStartingByte(): int { | ||||
|     public function getPayloadStartingByte() { | ||||
|         return 1 + $this->getNumPayloadBytes() + ($this->isMasked() ? static::MASK_LENGTH : 0); | ||||
|     } | ||||
| 
 | ||||
| @ -424,7 +432,7 @@ class Frame implements FrameInterface { | ||||
|      * {@inheritdoc} | ||||
|      * @todo Consider not checking mask, always returning the payload, masked or not | ||||
|      */ | ||||
|     public function getPayload(): string { | ||||
|     public function getPayload() { | ||||
|         if (!$this->isCoalesced()) { | ||||
|             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 | ||||
|      * @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()); | ||||
|     } | ||||
| 
 | ||||
|     public function __toString(): string { | ||||
|     public function __toString() { | ||||
|         $payload = (string)substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength()); | ||||
| 
 | ||||
|         if ($this->isMasked()) { | ||||
| @ -455,7 +463,7 @@ class Frame implements FrameInterface { | ||||
|      * This method will take the extra bytes off the end and return them | ||||
|      * @return string | ||||
|      */ | ||||
|     public function extractOverflow(): string { | ||||
|     public function extractOverflow() { | ||||
|         if ($this->isCoalesced()) { | ||||
|             $endPoint  = $this->getPayloadLength(); | ||||
|             $endPoint += $this->getPayloadStartingByte(); | ||||
|  | ||||
| @ -6,33 +6,33 @@ interface FrameInterface extends DataInterface { | ||||
|      * Add incoming data to the frame from peer | ||||
|      * @param string | ||||
|      */ | ||||
|     public function addBuffer(string $buf): void; | ||||
|     function addBuffer($buf); | ||||
| 
 | ||||
|     /** | ||||
|      * Is this the final frame in a fragmented message? | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function isFinal(): bool; | ||||
|     function isFinal(); | ||||
| 
 | ||||
|     /** | ||||
|      * Is the payload masked? | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function isMasked(): bool; | ||||
|     function isMasked(); | ||||
| 
 | ||||
|     /** | ||||
|      * @return int | ||||
|      */ | ||||
|     public function getOpcode(): int; | ||||
|     function getOpcode(); | ||||
| 
 | ||||
|     /** | ||||
|      * @return int | ||||
|      */ | ||||
|     //public function getReceivedPayloadLength(): int;
 | ||||
|     //function getReceivedPayloadLength();
 | ||||
| 
 | ||||
|     /** | ||||
|      * 32-big string | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getMaskingKey(): string; | ||||
|     function getMaskingKey(); | ||||
| } | ||||
|  | ||||
| @ -2,43 +2,53 @@ | ||||
| namespace mfmdevsystem\RFC6455\Messaging; | ||||
| 
 | ||||
| class Message implements \IteratorAggregate, MessageInterface { | ||||
|     private \SplDoublyLinkedList $_frames; | ||||
|     /** | ||||
|      * @var \SplDoublyLinkedList | ||||
|      */ | ||||
|     private $_frames; | ||||
| 
 | ||||
|     private int $len; | ||||
|     /** | ||||
|      * @var int | ||||
|      */ | ||||
|     private $len; | ||||
| 
 | ||||
|     #[\ReturnTypeWillChange]
 | ||||
|     public function __construct() { | ||||
|         $this->_frames = new \SplDoublyLinkedList; | ||||
|         $this->len = 0; | ||||
|     } | ||||
| 
 | ||||
|     public function getIterator(): \Traversable { | ||||
|     #[\ReturnTypeWillChange]
 | ||||
|     public function getIterator() { | ||||
|         return $this->_frames; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function count(): int { | ||||
|     #[\ReturnTypeWillChange]
 | ||||
|     public function count() { | ||||
|         return count($this->_frames); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function isCoalesced(): bool { | ||||
|     #[\ReturnTypeWillChange]
 | ||||
|     public function isCoalesced() { | ||||
|         if (count($this->_frames) == 0) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         $last = $this->_frames->top(); | ||||
| 
 | ||||
|         return $last->isCoalesced() && $last->isFinal(); | ||||
|         return ($last->isCoalesced() && $last->isFinal()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function addFrame(FrameInterface $fragment): MessageInterface { | ||||
|     public function addFrame(FrameInterface $fragment) { | ||||
|         $this->len += $fragment->getPayloadLength(); | ||||
|         $this->_frames->push($fragment); | ||||
| 
 | ||||
| @ -48,7 +58,7 @@ class Message implements \IteratorAggregate, MessageInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getOpcode(): int { | ||||
|     public function getOpcode() { | ||||
|         if (count($this->_frames) == 0) { | ||||
|             throw new \UnderflowException('No frames have been added to this message'); | ||||
|         } | ||||
| @ -59,14 +69,14 @@ class Message implements \IteratorAggregate, MessageInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getPayloadLength(): int { | ||||
|     public function getPayloadLength() { | ||||
|         return $this->len; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getPayload(): string { | ||||
|     public function getPayload() { | ||||
|         if (!$this->isCoalesced()) { | ||||
|             throw new \UnderflowException('Message has not been put back together yet'); | ||||
|         } | ||||
| @ -77,7 +87,7 @@ class Message implements \IteratorAggregate, MessageInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getContents(): string { | ||||
|     public function getContents() { | ||||
|         if (!$this->isCoalesced()) { | ||||
|             throw new \UnderflowException("Message has not been put back together yet"); | ||||
|         } | ||||
| @ -91,7 +101,7 @@ class Message implements \IteratorAggregate, MessageInterface { | ||||
|         return $buffer; | ||||
|     } | ||||
| 
 | ||||
|     public function __toString(): string { | ||||
|     public function __toString() { | ||||
|         $buffer = ''; | ||||
| 
 | ||||
|         foreach ($this->_frames as $frame) { | ||||
| @ -104,7 +114,7 @@ class Message implements \IteratorAggregate, MessageInterface { | ||||
|     /** | ||||
|      * @return boolean | ||||
|      */ | ||||
|     public function isBinary(): bool { | ||||
|     public function isBinary() { | ||||
|         if ($this->_frames->isEmpty()) { | ||||
|             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 | ||||
|      */ | ||||
|     public function getRsv1(): bool { | ||||
|     public function getRsv1() { | ||||
|         if ($this->_frames->isEmpty()) { | ||||
|             return false; | ||||
|             //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; | ||||
| 
 | ||||
| class MessageBuffer { | ||||
|     private CloseFrameChecker $closeFrameChecker; | ||||
|     /** | ||||
|      * @var \Ratchet\RFC6455\Messaging\CloseFrameChecker | ||||
|      */ | ||||
|     private $closeFrameChecker; | ||||
| 
 | ||||
|     /** | ||||
|      * @var callable | ||||
|      */ | ||||
|     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 | ||||
| @ -25,55 +34,71 @@ class MessageBuffer { | ||||
|      */ | ||||
|     private $onControl; | ||||
| 
 | ||||
|     private bool $checkForMask; | ||||
|     /** | ||||
|      * @var bool | ||||
|      */ | ||||
|     private $checkForMask; | ||||
| 
 | ||||
|     /** | ||||
|      * @var callable | ||||
|      */ | ||||
|     private $sender; | ||||
| 
 | ||||
|     private string $leftovers = ''; | ||||
| 
 | ||||
|     private int $streamingMessageOpCode = -1; | ||||
| 
 | ||||
|     private PermessageDeflateOptions $permessageDeflateOptions; | ||||
| 
 | ||||
|     private bool $deflateEnabled; | ||||
| 
 | ||||
|     private int $maxMessagePayloadSize; | ||||
| 
 | ||||
|     private int $maxFramePayloadSize; | ||||
| 
 | ||||
|     private bool $compressedMessage = false; | ||||
|     /** | ||||
|      * @var string | ||||
|      */ | ||||
|     private $leftovers; | ||||
| 
 | ||||
|     /** | ||||
|      * @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, | ||||
|         callable $onMessage, | ||||
|         ?callable $onControl = null, | ||||
|         bool $expectMask = true, | ||||
|         ?callable $exceptionFactory = null, | ||||
|         ?int $maxMessagePayloadSize = null, // null for default - zero for no limit
 | ||||
|         ?int $maxFramePayloadSize = null,   // null for default - zero for no limit
 | ||||
|         ?callable $sender = null, | ||||
|         ?PermessageDeflateOptions $permessageDeflateOptions = null | ||||
|         callable $onControl = null, | ||||
|         $expectMask = true, | ||||
|         $exceptionFactory = null, | ||||
|         $maxMessagePayloadSize = null, // null for default - zero for no limit
 | ||||
|         $maxFramePayloadSize = null,   // null for default - zero for no limit
 | ||||
|         callable $sender = null, | ||||
|         PermessageDeflateOptions $permessageDeflateOptions = null | ||||
|     ) { | ||||
|         $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->onControl = $onControl ?: static function (): void {}; | ||||
|         $this->onControl = $onControl ?: function() {}; | ||||
| 
 | ||||
|         $this->sender = $sender; | ||||
| 
 | ||||
| @ -85,6 +110,10 @@ class MessageBuffer { | ||||
|             throw new \InvalidArgumentException('sender must be set when deflate is enabled'); | ||||
|         } | ||||
| 
 | ||||
|         $this->compressedMessage = false; | ||||
| 
 | ||||
|         $this->leftovers = ''; | ||||
| 
 | ||||
|         $memory_limit_bytes = static::getMemoryLimit(); | ||||
| 
 | ||||
|         if ($maxMessagePayloadSize === null) { | ||||
| @ -94,18 +123,18 @@ class MessageBuffer { | ||||
|             $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'); | ||||
|         } | ||||
|         $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'); | ||||
|         } | ||||
|         $this->maxMessagePayloadSize = $maxMessagePayloadSize; | ||||
|     } | ||||
| 
 | ||||
|     public function onData(string $data): void { | ||||
|     public function onData($data) { | ||||
|         $data = $this->leftovers . $data; | ||||
|         $dataLen = strlen($data); | ||||
| 
 | ||||
| @ -121,7 +150,6 @@ class MessageBuffer { | ||||
|             $payload_length = unpack('C', $data[$frameStart + 1] & "\x7f")[1]; | ||||
|             $isMasked       = ($data[$frameStart + 1] & "\x80") === "\x80"; | ||||
|             $headerSize     += $isMasked ? 4 : 0; | ||||
|             $payloadLenOver2GB = false; | ||||
|             if ($payload_length > 125 && ($dataLen - $frameStart < $headerSize + 125)) { | ||||
|                 // no point of checking - this frame is going to be bigger than the buffer is right now
 | ||||
|                 break; | ||||
| @ -130,18 +158,9 @@ class MessageBuffer { | ||||
|                 $payloadLenBytes = $payload_length === 126 ? 2 : 8; | ||||
|                 $headerSize      += $payloadLenBytes; | ||||
|                 $bytesToUpack    = substr($data, $frameStart + 2, $payloadLenBytes); | ||||
| 
 | ||||
|                 if ($payload_length === 126){ | ||||
|                     $payload_length = unpack('n', $bytesToUpack)[1]; | ||||
|                 } else { | ||||
|                     $payloadLenOver2GB = unpack('N', $bytesToUpack)[1] > 0; //Decode only the 4 first bytes
 | ||||
|                     if (PHP_INT_SIZE == 4) { // if 32bits PHP
 | ||||
|                         $bytesToUpack = substr($bytesToUpack, 4); //Keep only 4 last bytes
 | ||||
|                         $payload_length = unpack('N', $bytesToUpack)[1]; | ||||
|                     } else { | ||||
|                         $payload_length = unpack('J', $bytesToUpack)[1]; | ||||
|                     } | ||||
|                 } | ||||
|                 $payload_length  = $payload_length === 126 | ||||
|                     ? unpack('n', $bytesToUpack)[1] | ||||
|                     : unpack('J', $bytesToUpack)[1]; | ||||
|             } | ||||
| 
 | ||||
|             $closeFrame = null; | ||||
| @ -151,10 +170,6 @@ class MessageBuffer { | ||||
|                 $closeFrame = $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Invalid frame length'); | ||||
|             } | ||||
| 
 | ||||
|             if (!$closeFrame && PHP_INT_SIZE == 4 && $payloadLenOver2GB) { | ||||
|                 $closeFrame = $this->newCloseFrame(Frame::CLOSE_TOO_BIG, 'Frame over 2GB can\'t be handled on 32bits PHP'); | ||||
|             } | ||||
| 
 | ||||
|             if (!$closeFrame && $this->maxFramePayloadSize > 1 && $payload_length > $this->maxFramePayloadSize) { | ||||
|                 $closeFrame = $this->newCloseFrame(Frame::CLOSE_TOO_BIG, 'Maximum frame size exceeded'); | ||||
|             } | ||||
| @ -185,9 +200,9 @@ class MessageBuffer { | ||||
| 
 | ||||
|     /** | ||||
|      * @param string $data | ||||
|      * @return void | ||||
|      * @return null | ||||
|      */ | ||||
|     private function processData(string $data): void { | ||||
|     private function processData($data) { | ||||
|         $this->messageBuffer ?: $this->messageBuffer = $this->newMessage(); | ||||
|         $this->frameBuffer   ?: $this->frameBuffer   = $this->newFrame(); | ||||
| 
 | ||||
| @ -206,7 +221,7 @@ class MessageBuffer { | ||||
|             $onControl($this->frameBuffer, $this); | ||||
| 
 | ||||
|             if (Frame::OP_CLOSE === $opcode) { | ||||
|                 return; | ||||
|                 return ''; | ||||
|             } | ||||
|         } else { | ||||
|             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 | ||||
|      * @param FrameInterface $frame | ||||
|      * @return FrameInterface | ||||
|      * @param \Ratchet\RFC6455\Messaging\FrameInterface|FrameInterface $frame | ||||
|      * @return \Ratchet\RFC6455\Messaging\FrameInterface|FrameInterface | ||||
|      */ | ||||
|     public function frameCheck(FrameInterface $frame): FrameInterface { | ||||
|     public function frameCheck(FrameInterface $frame) { | ||||
|         if ((false !== $frame->getRsv1() && !$this->deflateEnabled) || | ||||
|             false !== $frame->getRsv2() || | ||||
|             false !== $frame->getRsv3() | ||||
| @ -294,11 +309,13 @@ class MessageBuffer { | ||||
|                     } | ||||
| 
 | ||||
|                     return $frame; | ||||
|                     break; | ||||
|                 case Frame::OP_PING: | ||||
|                 case Frame::OP_PONG: | ||||
|                     break; | ||||
|                 default: | ||||
|                     return $this->newCloseFrame(Frame::CLOSE_PROTOCOL, 'Ratchet detected an invalid OP code'); | ||||
|                     break; | ||||
|             } | ||||
| 
 | ||||
|             return $frame; | ||||
| @ -317,7 +334,7 @@ class MessageBuffer { | ||||
| 
 | ||||
|     /** | ||||
|      * 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 | ||||
|      */ | ||||
|     public function checkMessage(MessageInterface $message) { | ||||
| @ -330,7 +347,7 @@ class MessageBuffer { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     private function checkUtf8(string $string): bool { | ||||
|     private function checkUtf8($string) { | ||||
|         if (extension_loaded('mbstring')) { | ||||
|             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; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string|null $payload | ||||
|      * @param bool        $final | ||||
|      * @param int         $opcode | ||||
|      * @return FrameInterface | ||||
|      * @param bool|null   $final | ||||
|      * @param int|null    $opcode | ||||
|      * @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); | ||||
|     } | ||||
| 
 | ||||
|     public function newCloseFrame(int $code, string $reason = ''): FrameInterface { | ||||
|     public function newCloseFrame($code, $reason = '') { | ||||
|         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) { | ||||
|             throw new \Exception('To send frames using the MessageBuffer, sender must be set.'); | ||||
|         } | ||||
| @ -377,7 +394,7 @@ class MessageBuffer { | ||||
|         $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; | ||||
|         if ($this->streamingMessageOpCode === -1) { | ||||
|             $this->streamingMessageOpCode = $opCode; | ||||
| @ -400,27 +417,29 @@ class MessageBuffer { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function getDeflateNoContextTakeover(): ?bool { | ||||
|     private $inflator; | ||||
| 
 | ||||
|     private function getDeflateNoContextTakeover() { | ||||
|         return $this->checkForMask ? | ||||
|             $this->permessageDeflateOptions->getServerNoContextTakeover() : | ||||
|             $this->permessageDeflateOptions->getClientNoContextTakeover(); | ||||
|     } | ||||
| 
 | ||||
|     private function getDeflateWindowBits(): int { | ||||
|     private function getDeflateWindowBits() { | ||||
|         return $this->checkForMask ? $this->permessageDeflateOptions->getServerMaxWindowBits() : $this->permessageDeflateOptions->getClientMaxWindowBits(); | ||||
|     } | ||||
| 
 | ||||
|     private function getInflateNoContextTakeover(): ?bool { | ||||
|     private function getInflateNoContextTakeover() { | ||||
|         return $this->checkForMask ? | ||||
|             $this->permessageDeflateOptions->getClientNoContextTakeover() : | ||||
|             $this->permessageDeflateOptions->getServerNoContextTakeover(); | ||||
|     } | ||||
| 
 | ||||
|     private function getInflateWindowBits(): int { | ||||
|     private function getInflateWindowBits() { | ||||
|         return $this->checkForMask ? $this->permessageDeflateOptions->getClientMaxWindowBits() : $this->permessageDeflateOptions->getServerMaxWindowBits(); | ||||
|     } | ||||
| 
 | ||||
|     private function inflateFrame(FrameInterface $frame): Frame { | ||||
|     private function inflateFrame(Frame $frame) { | ||||
|         if ($this->inflator === null) { | ||||
|             $this->inflator = inflate_init( | ||||
|                 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()) { | ||||
|             return $frame; // frame is already deflated
 | ||||
|         } | ||||
| 
 | ||||
|         if ($this->deflator === null) { | ||||
|             $bits = $this->getDeflateWindowBits(); | ||||
|             $bits = (int)$this->getDeflateWindowBits(); | ||||
|             if ($bits === 8) { | ||||
|                 $bits = 9; | ||||
|             } | ||||
| @ -514,7 +535,7 @@ class MessageBuffer { | ||||
|      * @param null|string $memory_limit | ||||
|      * @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_bytes = 0; | ||||
|         if ($memory_limit !== '') { | ||||
|  | ||||
| @ -6,15 +6,15 @@ interface MessageInterface extends DataInterface, \Traversable, \Countable { | ||||
|      * @param FrameInterface $fragment | ||||
|      * @return MessageInterface | ||||
|      */ | ||||
|     public function addFrame(FrameInterface $fragment): self; | ||||
|     function addFrame(FrameInterface $fragment); | ||||
| 
 | ||||
|     /** | ||||
|      * @return int | ||||
|      */ | ||||
|     public function getOpcode(): int; | ||||
|     function getOpcode(); | ||||
| 
 | ||||
|     /** | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function isBinary(): bool; | ||||
|     function isBinary(); | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user