Skip to content

Commit

Permalink
Merge commit '6df0bd1b68df5b9774b79c036b9ba31d85ad0d01' into v0.4
Browse files Browse the repository at this point in the history
# Conflicts:
#	composer.json
#	src/Handshake/ClientNegotiator.php
#	src/Handshake/ServerNegotiator.php
#	tests/ab/clientRunner.php
#	tests/ab/startServer.php
#	tests/unit/Handshake/ServerNegotiatorTest.php
  • Loading branch information
mbonneau committed Dec 4, 2024
2 parents 4304337 + 6df0bd1 commit 772e465
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 44 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
},
"require": {
"php": ">=7.4",
"guzzlehttp/psr7": "^2 || ^1.7",
"psr/http-factory-implementation": "^1.0",
"symfony/polyfill-php80": "^1.15"
},
"require-dev": {
Expand Down
24 changes: 15 additions & 9 deletions src/Handshake/ClientNegotiator.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,28 @@
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UriInterface;
use GuzzleHttp\Psr7\Request;
use Psr\Http\Message\RequestFactoryInterface;

class ClientNegotiator {
private ResponseVerifier $verifier;

private RequestInterface $defaultHeader;

public function __construct(PermessageDeflateOptions $perMessageDeflateOptions = null) {
private RequestFactoryInterface $requestFactory;

public function __construct(
RequestFactoryInterface $requestFactory,
PermessageDeflateOptions $perMessageDeflateOptions = null
) {
$this->verifier = new ResponseVerifier;
$this->requestFactory = $requestFactory;

$this->defaultHeader = new Request('GET', '', [
'Connection' => 'Upgrade'
, 'Upgrade' => 'websocket'
, 'Sec-WebSocket-Version' => $this->getVersion()
, 'User-Agent' => "Ratchet"
]);
$this->defaultHeader = $this->requestFactory
->createRequest('GET', '')
->withHeader('Connection' , 'Upgrade')
->withHeader('Upgrade' , 'websocket')
->withHeader('Sec-WebSocket-Version', $this->getVersion())
->withHeader('User-Agent' , 'Ratchet');

$perMessageDeflateOptions ??= PermessageDeflateOptions::createDisabled();

Expand All @@ -38,7 +44,7 @@ public function __construct(PermessageDeflateOptions $perMessageDeflateOptions =

public function generateRequest(UriInterface $uri): RequestInterface {
return $this->defaultHeader->withUri($uri)
->withHeader("Sec-WebSocket-Key", $this->generateKey());
->withHeader('Sec-WebSocket-Key', $this->generateKey());
}

public function validateResponse(RequestInterface $request, ResponseInterface $response): bool {
Expand Down
58 changes: 34 additions & 24 deletions src/Handshake/ServerNegotiator.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php
namespace Ratchet\RFC6455\Handshake;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\ResponseInterface;

Expand All @@ -11,14 +12,21 @@
class ServerNegotiator implements NegotiatorInterface {
private RequestVerifier $verifier;

private ResponseFactoryInterface $responseFactory;

private array $_supportedSubProtocols = [];

private bool $_strictSubProtocols = false;

private bool $enablePerMessageDeflate = false;

public function __construct(RequestVerifier $requestVerifier, $enablePerMessageDeflate = false) {
public function __construct(
RequestVerifier $requestVerifier,
ResponseFactoryInterface $responseFactory,
$enablePerMessageDeflate = false
) {
$this->verifier = $requestVerifier;
$this->responseFactory = $responseFactory;

// https://bugs.php.net/bug.php?id=73373
// https://bugs.php.net/bug.php?id=74240 - need >=7.1.4 or >=7.0.18
Expand Down Expand Up @@ -51,68 +59,70 @@ public function getVersionNumber(): int {
* {@inheritdoc}
*/
public function handshake(RequestInterface $request): ResponseInterface {
$response = $this->responseFactory->createResponse();
if (true !== $this->verifier->verifyMethod($request->getMethod())) {
return new Response(405, ['Allow' => 'GET']);
return $response->withHeader('Allow', 'GET')->withStatus(405);
}

if (true !== $this->verifier->verifyHTTPVersion($request->getProtocolVersion())) {
return new Response(505);
return $response->withStatus(505);
}

if (true !== $this->verifier->verifyRequestURI($request->getUri()->getPath())) {
return new Response(400);
return $response->withStatus(400);
}

if (true !== $this->verifier->verifyHost($request->getHeader('Host'))) {
return new Response(400);
return $response->withStatus(400);
}

$upgradeSuggestion = [
'Connection' => 'Upgrade',
'Upgrade' => 'websocket',
'Sec-WebSocket-Version' => $this->getVersionNumber()
];
$upgradeResponse = $response
->withHeader('Connection' , 'Upgrade')
->withHeader('Upgrade' , 'websocket')
->withHeader('Sec-WebSocket-Version', $this->getVersionNumber());

if (count($this->_supportedSubProtocols) > 0) {
$upgradeSuggestion['Sec-WebSocket-Protocol'] = implode(', ', array_keys($this->_supportedSubProtocols));
$upgradeResponse = $upgradeResponse->withHeader(
'Sec-WebSocket-Protocol', implode(', ', array_keys($this->_supportedSubProtocols))
);
}
if (true !== $this->verifier->verifyUpgradeRequest($request->getHeader('Upgrade'))) {
return new Response(426, $upgradeSuggestion, null, '1.1', 'Upgrade header MUST be provided');
return $upgradeResponse->withStatus(426, 'Upgrade header MUST be provided');
}

if (true !== $this->verifier->verifyConnection($request->getHeader('Connection'))) {
return new Response(400, [], null, '1.1', 'Connection Upgrade MUST be requested');
return $response->withStatus(400, 'Connection Upgrade MUST be requested');
}

if (true !== $this->verifier->verifyKey($request->getHeader('Sec-WebSocket-Key'))) {
return new Response(400, [], null, '1.1', 'Invalid Sec-WebSocket-Key');
return $response->withStatus(400, 'Invalid Sec-WebSocket-Key');
}

if (true !== $this->verifier->verifyVersion($request->getHeader('Sec-WebSocket-Version'))) {
return new Response(426, $upgradeSuggestion);
return $upgradeResponse->withStatus(426);
}

$headers = [];
$subProtocols = $request->getHeader('Sec-WebSocket-Protocol');
if (count($subProtocols) > 0 || (count($this->_supportedSubProtocols) > 0 && $this->_strictSubProtocols)) {
$subProtocols = array_map('trim', explode(',', implode(',', $subProtocols)));

$match = array_reduce($subProtocols, fn ($accumulator, $protocol) => $accumulator ?: (isset($this->_supportedSubProtocols[$protocol]) ? $protocol : null), null);

if ($this->_strictSubProtocols && null === $match) {
return new Response(426, $upgradeSuggestion, null, '1.1', 'No Sec-WebSocket-Protocols requested supported');
return $upgradeResponse->withStatus(426, 'No Sec-WebSocket-Protocols requested supported');
}

if (null !== $match) {
$headers['Sec-WebSocket-Protocol'] = $match;
$response = $response->withHeader('Sec-WebSocket-Protocol', $match);
}
}

$response = new Response(101, array_merge($headers, [
'Upgrade' => 'websocket'
, 'Connection' => 'Upgrade'
, 'Sec-WebSocket-Accept' => $this->sign((string)$request->getHeader('Sec-WebSocket-Key')[0])
, 'X-Powered-By' => 'Ratchet'
]));
$response = $response
->withStatus(101)
->withHeader('Upgrade' , 'websocket')
->withHeader('Connection' , 'Upgrade')
->withHeader('Sec-WebSocket-Accept', $this->sign((string)$request->getHeader('Sec-WebSocket-Key')[0]))
->withHeader('X-Powered-By' , 'Ratchet');

try {
$perMessageDeflateRequest = PermessageDeflateOptions::fromRequestOrResponse($request)[0];
Expand Down
7 changes: 5 additions & 2 deletions tests/ab/clientRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use React\Promise\Deferred;
use Ratchet\RFC6455\Messaging\Frame;
use React\Promise\PromiseInterface;
use GuzzleHttp\Psr7\HttpFactory;
use React\Socket\ConnectionInterface;
use React\Socket\Connector;

Expand Down Expand Up @@ -58,7 +59,7 @@ function getTestCases(): PromiseInterface {
$deferred = new Deferred();

$connector->connect($testServer . ':9002')->then(static function (ConnectionInterface $connection) use ($deferred, $testServer): void {
$cn = new ClientNegotiator();
$cn = new ClientNegotiator(new HttpFactory());
$cnRequest = $cn->generateRequest(new Uri('ws://' . $testServer . ':9002/getCaseCount'));

$rawResponse = "";
Expand Down Expand Up @@ -110,6 +111,7 @@ static function (): void {}
}

$cn = new ClientNegotiator(
new HttpFactory(),
PermessageDeflateOptions::permessageDeflateSupported() ? PermessageDeflateOptions::createEnabled() : null);

function runTest(int $case)
Expand All @@ -124,6 +126,7 @@ function runTest(int $case)

$connector->connect($testServer . ':9002')->then(static function (ConnectionInterface $connection) use ($deferred, $casePath, $case, $testServer): void {
$cn = new ClientNegotiator(
new HttpFactory(),
PermessageDeflateOptions::permessageDeflateSupported() ? PermessageDeflateOptions::createEnabled() : null);
$cnRequest = $cn->generateRequest(new Uri('ws://' . $testServer . ':9002' . $casePath));

Expand Down Expand Up @@ -185,7 +188,7 @@ function createReport(): PromiseInterface {
// $reportPath = "/updateReports?agent=" . AGENT . "&shutdownOnComplete=true";
// we will stop it using docker now instead of just shutting down
$reportPath = "/updateReports?agent=" . AGENT;
$cn = new ClientNegotiator();
$cn = new ClientNegotiator(new HttpFactory());
$cnRequest = $cn->generateRequest(new Uri('ws://' . $testServer . ':9002' . $reportPath));

$rawResponse = "";
Expand Down
Empty file modified tests/ab/run_ab_tests.sh
100644 → 100755
Empty file.
8 changes: 7 additions & 1 deletion tests/ab/startServer.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Ratchet\RFC6455\Messaging\MessageInterface;
use Ratchet\RFC6455\Messaging\FrameInterface;
use Ratchet\RFC6455\Messaging\Frame;
use GuzzleHttp\Psr7\HttpFactory;

require_once __DIR__ . "/../bootstrap.php";

Expand All @@ -18,7 +19,12 @@
$socket = new \React\Socket\Server('0.0.0.0:9001', $loop);

$closeFrameChecker = new CloseFrameChecker;
$negotiator = new ServerNegotiator(new RequestVerifier, PermessageDeflateOptions::permessageDeflateSupported());

$negotiator = new ServerNegotiator(
new RequestVerifier,
new HttpFactory(),
PermessageDeflateOptions::permessageDeflateSupported()
);

$uException = new \UnderflowException;

Expand Down
15 changes: 8 additions & 7 deletions tests/unit/Handshake/ServerNegotiatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Ratchet\RFC6455\Test\Unit\Handshake;

use GuzzleHttp\Psr7\Message;
use GuzzleHttp\Psr7\HttpFactory;
use Ratchet\RFC6455\Handshake\RequestVerifier;
use Ratchet\RFC6455\Handshake\ServerNegotiator;
use PHPUnit\Framework\TestCase;
Expand All @@ -13,7 +14,7 @@
class ServerNegotiatorTest extends TestCase
{
public function testNoUpgradeRequested(): void {
$negotiator = new ServerNegotiator(new RequestVerifier());
$negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory());

$requestText = 'GET / HTTP/1.1
Host: 127.0.0.1:6789
Expand Down Expand Up @@ -41,7 +42,7 @@ public function testNoUpgradeRequested(): void {
}

public function testNoConnectionUpgradeRequested(): void {
$negotiator = new ServerNegotiator(new RequestVerifier());
$negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory());

$requestText = 'GET / HTTP/1.1
Host: 127.0.0.1:6789
Expand All @@ -67,7 +68,7 @@ public function testNoConnectionUpgradeRequested(): void {
}

public function testInvalidSecWebsocketKey(): void {
$negotiator = new ServerNegotiator(new RequestVerifier());
$negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory());

$requestText = 'GET / HTTP/1.1
Host: 127.0.0.1:6789
Expand All @@ -94,7 +95,7 @@ public function testInvalidSecWebsocketKey(): void {
}

public function testInvalidSecWebsocketVersion(): void {
$negotiator = new ServerNegotiator(new RequestVerifier());
$negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory());

$requestText = 'GET / HTTP/1.1
Host: 127.0.0.1:6789
Expand Down Expand Up @@ -124,7 +125,7 @@ public function testInvalidSecWebsocketVersion(): void {
}

public function testBadSubprotocolResponse(): void {
$negotiator = new ServerNegotiator(new RequestVerifier());
$negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory());
$negotiator->setStrictSubProtocolCheck(true);
$negotiator->setSupportedSubProtocols([]);

Expand Down Expand Up @@ -158,7 +159,7 @@ public function testBadSubprotocolResponse(): void {
}

public function testNonStrictSubprotocolDoesNotIncludeHeaderWhenNoneAgreedOn(): void {
$negotiator = new ServerNegotiator(new RequestVerifier());
$negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory());
$negotiator->setStrictSubProtocolCheck(false);
$negotiator->setSupportedSubProtocols(['someproto']);

Expand Down Expand Up @@ -192,7 +193,7 @@ public function testNonStrictSubprotocolDoesNotIncludeHeaderWhenNoneAgreedOn():

public function testSuggestsAppropriateSubprotocol(): void
{
$negotiator = new ServerNegotiator(new RequestVerifier());
$negotiator = new ServerNegotiator(new RequestVerifier(), new HttpFactory());
$negotiator->setStrictSubProtocolCheck(true);
$negotiator->setSupportedSubProtocols(['someproto']);

Expand Down

0 comments on commit 772e465

Please sign in to comment.