Separate negotiation and validation

This commit is contained in:
Chris Boden 2014-11-29 13:08:04 -05:00
parent d1376d824a
commit 5e79598448
4 changed files with 111 additions and 37 deletions

View File

@ -54,7 +54,7 @@ class Negotiator implements NegotiatorInterface {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getVersionNumber() { public function getVersionNumber() {
return 13; return RequestVerifier::VERSION;
} }
/** /**
@ -111,25 +111,7 @@ class Negotiator implements NegotiatorInterface {
if ($from->WebSocket->frame->isCoalesced()) { if ($from->WebSocket->frame->isCoalesced()) {
$frame = $from->WebSocket->frame; $frame = $from->WebSocket->frame;
if (false !== $frame->getRsv1() ||
false !== $frame->getRsv2() ||
false !== $frame->getRsv3()
) {
return $from->close($frame::CLOSE_PROTOCOL);
}
// This is server-side specific logic
if (!$frame->isMasked()) {
return $from->close($frame::CLOSE_PROTOCOL);
}
$opcode = $frame->getOpcode();
if ($opcode > 2) { if ($opcode > 2) {
if ($frame->getPayloadLength() > 125 || !$frame->isFinal()) {
return $from->close($frame::CLOSE_PROTOCOL);
}
switch ($opcode) { switch ($opcode) {
case $frame::OP_CLOSE: case $frame::OP_CLOSE:
$closeCode = 0; $closeCode = 0;
@ -177,14 +159,6 @@ class Negotiator implements NegotiatorInterface {
$overflow = $from->WebSocket->frame->extractOverflow(); $overflow = $from->WebSocket->frame->extractOverflow();
if ($frame::OP_CONTINUE == $frame->getOpcode() && 0 == count($from->WebSocket->message)) {
return $from->close($frame::CLOSE_PROTOCOL);
}
if (count($from->WebSocket->message) > 0 && $frame::OP_CONTINUE != $frame->getOpcode()) {
return $from->close($frame::CLOSE_PROTOCOL);
}
$from->WebSocket->message->addFrame($from->WebSocket->frame); $from->WebSocket->message->addFrame($from->WebSocket->frame);
unset($from->WebSocket->frame); unset($from->WebSocket->frame);
} }
@ -193,10 +167,6 @@ class Negotiator implements NegotiatorInterface {
$parsed = $from->WebSocket->message->getPayload(); $parsed = $from->WebSocket->message->getPayload();
unset($from->WebSocket->message); unset($from->WebSocket->message);
if (!$this->validator->checkEncoding($parsed, 'UTF-8')) {
return $from->close(Frame::CLOSE_BAD_PAYLOAD);
}
$from->WebSocket->coalescedCallback->onMessage($from, $parsed); $from->WebSocket->coalescedCallback->onMessage($from, $parsed);
} }

View File

@ -15,7 +15,6 @@ interface NegotiatorInterface {
* Given an HTTP header, determine if this version should handle the protocol * Given an HTTP header, determine if this version should handle the protocol
* @param \Guzzle\Http\Message\RequestInterface $request * @param \Guzzle\Http\Message\RequestInterface $request
* @return bool * @return bool
* @throws \UnderflowException If the protocol thinks the headers are still fragmented
*/ */
function isProtocol(RequestInterface $request); function isProtocol(RequestInterface $request);
@ -29,7 +28,6 @@ interface NegotiatorInterface {
* Perform the handshake and return the response headers * Perform the handshake and return the response headers
* @param \Guzzle\Http\Message\RequestInterface $request * @param \Guzzle\Http\Message\RequestInterface $request
* @return \Guzzle\Http\Message\Response * @return \Guzzle\Http\Message\Response
* @throws \UnderflowException If the message hasn't finished buffering (not yet implemented, theoretically will only happen with Hixie version)
*/ */
function handshake(RequestInterface $request); function handshake(RequestInterface $request);

View File

@ -8,6 +8,8 @@ use Guzzle\Http\Message\RequestInterface;
* @todo Currently just returning invalid - should consider returning appropriate HTTP status code error #s * @todo Currently just returning invalid - should consider returning appropriate HTTP status code error #s
*/ */
class RequestVerifier { class RequestVerifier {
const VERSION = 13;
/** /**
* Given an array of the headers this method will run through all verification methods * Given an array of the headers this method will run through all verification methods
* @param \Guzzle\Http\Message\RequestInterface $request * @param \Guzzle\Http\Message\RequestInterface $request
@ -23,9 +25,9 @@ class RequestVerifier {
$passes += (int)$this->verifyUpgradeRequest((string)$request->getHeader('Upgrade')); $passes += (int)$this->verifyUpgradeRequest((string)$request->getHeader('Upgrade'));
$passes += (int)$this->verifyConnection((string)$request->getHeader('Connection')); $passes += (int)$this->verifyConnection((string)$request->getHeader('Connection'));
$passes += (int)$this->verifyKey((string)$request->getHeader('Sec-WebSocket-Key')); $passes += (int)$this->verifyKey((string)$request->getHeader('Sec-WebSocket-Key'));
//$passes += (int)$this->verifyVersion($headers['Sec-WebSocket-Version']); // Temporarily breaking functionality $passes += (int)$this->verifyVersion($headers['Sec-WebSocket-Version']);
return (7 === $passes); return (8 === $passes);
} }
/** /**
@ -117,10 +119,9 @@ class RequestVerifier {
* Verify the version passed matches this RFC * Verify the version passed matches this RFC
* @param string|int MUST equal 13|"13" * @param string|int MUST equal 13|"13"
* @return bool * @return bool
* @todo Ran in to a problem here...I'm having HyBi use the RFC files, this breaks it! oops
*/ */
public function verifyVersion($val) { public function verifyVersion($val) {
return (13 === (int)$val); return (static::VERSION === (int)$val);
} }
/** /**

View File

@ -0,0 +1,105 @@
<?php
namespace Ratchet\RFC6455\Messaging\Validation;
use Ratchet\RFC6455\Encoding\ValidatorInterface;
use Ratchet\RFC6455\Messaging\Protocol\MessageInterface;
class MessageValidator {
public $checkForMask = true;
private $validator;
public function __construct(ValidatorInterface $validator) {
$this->validator = $validator;
}
/**
* Determine if a message is valid
* @param \Ratchet\RFC6455\Messaging\Protocol\MessageInterface
* @return bool|int true if valid - false if incomplete - int of recomended close code
*/
public function checkMessage(MessageInterface $message) {
// Need a progressive and complete check...this is only satisfying complete
if (!$message->isCoalesced()) {
return false;
}
$frame = $message[0];
$frameCheck = $this->checkFrame($frame);
if (true !== $frameCheck) {
return $frameCheck;
}
// This seems incorrect - how could a frame exist with message count being 0?
if ($frame::OP_CONTINUE === $frame->getOpcode() && 0 === count($message)) {
return $frame::CLOSE_PROTOCOL;
}
if (count($message) > 0 && $frame::OP_CONTINUE !== $frame->getOpcode()) {
return $frame::CLOSE_PROTOCOL;
}
$parsed = $message->getPayload();
if (!$this->validator->checkEncoding($parsed, 'UTF-8')) {
return $frame::CLOSE_BAD_PAYLOAD;
}
return true;
}
public function validateFrame(FrameInterface $frame) {
if (false !== $frame->getRsv1() ||
false !== $frame->getRsv2() ||
false !== $frame->getRsv3()
) {
return $frame::CLOSE_PROTOCOL;
}
// Should be checking all frames
if ($this->checkForMask && !$frame->isMasked()) {
return $frame::CLOSE_PROTOCOL;
}
$opcode = $frame->getOpcode();
if ($opcode > 2) {
if ($frame->getPayloadLength() > 125 || !$frame->isFinal()) {
return $frame::CLOSE_PROTOCOL;
}
switch ($opcode) {
case $frame::OP_CLOSE:
$closeCode = 0;
$bin = $frame->getPayload();
if (empty($bin)) {
return $frame::CLOSE_NORMAL;
}
if (strlen($bin) >= 2) {
list($closeCode) = array_merge(unpack('n*', substr($bin, 0, 2)));
}
if (!$this->isValidCloseCode($closeCode)) {
return $frame::CLOSE_PROTOCOL;
}
if (!$this->validator->checkEncoding(substr($bin, 2), 'UTF-8')) {
return $frame::CLOSE_BAD_PAYLOAD;
}
return $frame::CLOSE_NORMAL;
break;
case $frame::OP_PING:
case $frame::OP_PONG:
break;
default:
return $frame::CLOSE_PROTOCOL;
break;
}
}
return true;
}
}