261 lines
8.1 KiB
PHP
261 lines
8.1 KiB
PHP
<?php
|
|
|
|
namespace Ratchet\RFC6455\Handshake;
|
|
|
|
use Psr\Http\Message\MessageInterface;
|
|
use Psr\Http\Message\RequestInterface;
|
|
use Psr\Http\Message\ResponseInterface;
|
|
|
|
final class PermessageDeflateOptions
|
|
{
|
|
const MAX_WINDOW_BITS = 15;
|
|
/* this is a private instead of const for 5.4 compatibility */
|
|
private static $VALID_BITS = ['8', '9', '10', '11', '12', '13', '14', '15'];
|
|
|
|
private $deflateEnabled = false;
|
|
|
|
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 static();
|
|
$new->deflateEnabled = true;
|
|
$new->client_max_window_bits = self::MAX_WINDOW_BITS;
|
|
$new->client_no_context_takeover = false;
|
|
$new->server_max_window_bits = self::MAX_WINDOW_BITS;
|
|
$new->server_no_context_takeover = false;
|
|
|
|
return $new;
|
|
}
|
|
|
|
public static function createDisabled() {
|
|
return new static();
|
|
}
|
|
|
|
public function withClientNoContextTakeover() {
|
|
$new = clone $this;
|
|
$new->client_no_context_takeover = true;
|
|
}
|
|
|
|
public function withoutClientNoContextTakeover() {
|
|
$new = clone $this;
|
|
$new->client_no_context_takeover = false;
|
|
}
|
|
|
|
public function withServerNoContextTakeover() {
|
|
$new = clone $this;
|
|
$new->server_no_context_takeover = true;
|
|
}
|
|
|
|
public function withoutServerNoContextTakeover() {
|
|
$new = clone $this;
|
|
$new->server_no_context_takeover = false;
|
|
}
|
|
|
|
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;
|
|
$new->server_max_window_bits = $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;
|
|
$new->client_max_window_bits = $bits;
|
|
}
|
|
|
|
/**
|
|
* https://tools.ietf.org/html/rfc6455#section-9.1
|
|
* https://tools.ietf.org/html/rfc7692#section-7
|
|
*
|
|
* @param MessageInterface $requestOrResponse
|
|
* @return PermessageDeflateOptions[]
|
|
* @throws \Exception
|
|
*/
|
|
public static function fromRequestOrResponse(MessageInterface $requestOrResponse) {
|
|
$optionSets = [];
|
|
|
|
$extHeader = preg_replace('/\s+/', '', join(', ', $requestOrResponse->getHeader('Sec-Websocket-Extensions')));
|
|
|
|
$configurationRequests = explode(',', $extHeader);
|
|
foreach ($configurationRequests as $configurationRequest) {
|
|
$parts = explode(';', $configurationRequest);
|
|
if (count($parts) == 0) {
|
|
continue;
|
|
}
|
|
|
|
if ($parts[0] !== 'permessage-deflate') {
|
|
continue;
|
|
}
|
|
|
|
array_shift($parts);
|
|
$options = new static();
|
|
$options->deflateEnabled = true;
|
|
foreach ($parts as $part) {
|
|
$kv = explode('=', $part);
|
|
$key = $kv[0];
|
|
$value = count($kv) > 1 ? $kv[1] : null;
|
|
|
|
switch ($key) {
|
|
case "server_no_context_takeover":
|
|
case "client_no_context_takeover":
|
|
if ($value !== null) {
|
|
throw new InvalidPermessageDeflateOptionsException($key . ' must not have a value.');
|
|
}
|
|
$value = true;
|
|
break;
|
|
case "server_max_window_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';
|
|
}
|
|
if (!in_array($value, self::$VALID_BITS)) {
|
|
throw new InvalidPermessageDeflateOptionsException($key . ' must have no value or a value between 8 and 15.');
|
|
}
|
|
break;
|
|
default:
|
|
throw new InvalidPermessageDeflateOptionsException('Option "' . $key . '"is not valid for permessage deflate');
|
|
}
|
|
|
|
if ($options->$key !== null) {
|
|
throw new InvalidPermessageDeflateOptionsException($key . ' specified more than once. Connection must be declined.');
|
|
}
|
|
|
|
$options->$key = $value;
|
|
}
|
|
|
|
if ($options->getClientMaxWindowBits() === null) {
|
|
$options->client_max_window_bits = 15;
|
|
}
|
|
|
|
if ($options->getServerMaxWindowBits() === null) {
|
|
$options->server_max_window_bits = 15;
|
|
}
|
|
|
|
$optionSets[] = $options;
|
|
}
|
|
|
|
// always put a disabled on the end
|
|
$optionSets[] = new static();
|
|
|
|
return $optionSets;
|
|
}
|
|
|
|
/**
|
|
* @return mixed
|
|
*/
|
|
public function getServerNoContextTakeover()
|
|
{
|
|
return $this->server_no_context_takeover;
|
|
}
|
|
|
|
/**
|
|
* @return mixed
|
|
*/
|
|
public function getClientNoContextTakeover()
|
|
{
|
|
return $this->client_no_context_takeover;
|
|
}
|
|
|
|
/**
|
|
* @return mixed
|
|
*/
|
|
public function getServerMaxWindowBits()
|
|
{
|
|
return $this->server_max_window_bits;
|
|
}
|
|
|
|
/**
|
|
* @return mixed
|
|
*/
|
|
public function getClientMaxWindowBits()
|
|
{
|
|
return $this->client_max_window_bits;
|
|
}
|
|
|
|
/**
|
|
* @return bool
|
|
*/
|
|
public function isEnabled()
|
|
{
|
|
return $this->deflateEnabled;
|
|
}
|
|
|
|
/**
|
|
* @param ResponseInterface $response
|
|
* @return ResponseInterface
|
|
*/
|
|
public function addHeaderToResponse(ResponseInterface $response)
|
|
{
|
|
if (!$this->deflateEnabled) {
|
|
return $response;
|
|
}
|
|
|
|
$header = 'permessage-deflate';
|
|
if ($this->client_max_window_bits != 15) {
|
|
$header .= '; client_max_window_bits='. $this->client_max_window_bits;
|
|
}
|
|
if ($this->client_no_context_takeover) {
|
|
$header .= '; client_no_context_takeover';
|
|
}
|
|
if ($this->server_max_window_bits != 15) {
|
|
$header .= '; server_max_window_bits=' . $this->server_max_window_bits;
|
|
}
|
|
if ($this->server_no_context_takeover) {
|
|
$header .= '; server_no_context_takeover';
|
|
}
|
|
|
|
return $response->withAddedHeader('Sec-Websocket-Extensions', $header);
|
|
}
|
|
|
|
public function addHeaderToRequest(RequestInterface $request) {
|
|
if (!$this->deflateEnabled) {
|
|
return $request;
|
|
}
|
|
|
|
$header = 'permessage-deflate';
|
|
if ($this->server_no_context_takeover) {
|
|
$header .= '; server_no_context_takeover';
|
|
}
|
|
if ($this->client_no_context_takeover) {
|
|
$header .= '; client_no_context_takeover';
|
|
}
|
|
if ($this->server_max_window_bits != 15) {
|
|
$header .= '; server_max_window_bits=' . $this->server_max_window_bits;
|
|
}
|
|
$header .= '; client_max_window_bits';
|
|
if ($this->client_max_window_bits != 15) {
|
|
$header .= '='. $this->client_max_window_bits;
|
|
}
|
|
|
|
return $request->withAddedHeader('Sec-Websocket-Extensions', $header);
|
|
}
|
|
|
|
public static function permessageDeflateSupported($version = PHP_VERSION) {
|
|
if (!function_exists('deflate_init')) {
|
|
return false;
|
|
}
|
|
if (version_compare($version, '7.1.3', '>')) {
|
|
return true;
|
|
}
|
|
if (version_compare($version, '7.0.18', '>=')
|
|
&& version_compare($version, '7.1.0', '<')) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|