diff --git a/CHANGELOG.md b/CHANGELOG.md index c103e62..1c706a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,11 @@ CHANGELOG * 0.2 (2012-TBD) - * Ratchet passes every non-binary message AB test + * Ratchet passes every non-binary-frame test from the Autobahn Testsuite + * Major performance improvements * Ratchet now relies on all stable dependancies - * mbstring no longer required (but recommended if compliance is important to you) + * Option to turn off UTF-8 checks in order to increase performance + * mbstring no longer required * 0.1.5 (2012-07-12) diff --git a/src/Ratchet/WebSocket/Encoding/ToggleableValidator.php b/src/Ratchet/WebSocket/Encoding/ToggleableValidator.php new file mode 100644 index 0000000..1a3965c --- /dev/null +++ b/src/Ratchet/WebSocket/Encoding/ToggleableValidator.php @@ -0,0 +1,31 @@ +validator = new Validator; + $this->on = (boolean)$on; + } + + /** + * {@inheritdoc} + */ + public function checkEncoding($str, $encoding) { + if (!(boolean)$this->on) { + return true; + } + + return $this->validator->checkEncoding($str, $encoding); + } +} \ No newline at end of file diff --git a/src/Ratchet/WebSocket/Encoding/Validator.php b/src/Ratchet/WebSocket/Encoding/Validator.php new file mode 100644 index 0000000..b0d59a4 --- /dev/null +++ b/src/Ratchet/WebSocket/Encoding/Validator.php @@ -0,0 +1,93 @@ +hasMbString = extension_loaded('mbstring'); + $this->hasIconv = extension_loaded('iconv'); + } + + /** + * @param string The value to check the encoding + * @param string The type of encoding to check against + * @return bool + */ + public function checkEncoding($str, $against) { + if ('UTF-8' == $against) { + return $this->isUtf8($str); + } + + if ($this->hasMbString) { + return mb_check_encoding($str, $against); + } elseif ($this->hasIconv) { + return ($str == iconv($against, "{$against}//IGNORE", $str)); + } + + return true; + } + + protected function isUtf8($str) { + if ($this->hasMbString) { + if (false === mb_check_encoding($str, 'UTF-8')) { + return false; + } + } elseif ($this->hasIconv) { + if ($str != iconv('UTF-8', 'UTF-8//IGNORE', $str)) { + return false; + } + } + + $state = static::UTF8_ACCEPT; + + for ($i = 0, $len = strlen($str); $i < $len; $i++) { + $state = static::$dfa[256 + ($state << 4) + static::$dfa[ord($str[$i])]]; + + if (static::UTF8_REJECT === $state) { + return false; + } + } + + return true; + } +} \ No newline at end of file diff --git a/src/Ratchet/WebSocket/Encoding/ValidatorInterface.php b/src/Ratchet/WebSocket/Encoding/ValidatorInterface.php new file mode 100644 index 0000000..2231c05 --- /dev/null +++ b/src/Ratchet/WebSocket/Encoding/ValidatorInterface.php @@ -0,0 +1,12 @@ +_verifier = new HandshakeVerifier; $this->setCloseCodes(); - $this->hasMbString = extension_loaded('mbstring'); + if (null === $validator) { + $validator = new Validator; + } + + $this->validator = $validator; } /** @@ -169,7 +148,7 @@ class RFC6455 implements VersionInterface { return $from->close($frame::CLOSE_PROTOCOL); } - if (!$this->isUtf8(substr($bin, 2))) { + if (!$this->validator->checkEncoding(substr($bin, 2), 'UTF-8')) { return $from->close($frame::CLOSE_BAD_PAYLOAD); } @@ -214,7 +193,7 @@ class RFC6455 implements VersionInterface { $parsed = $from->WebSocket->message->getPayload(); unset($from->WebSocket->message); - if (!$this->isUtf8($parsed)) { + if (!$this->validator->checkEncoding($parsed, 'UTF-8')) { return $from->close(Frame::CLOSE_BAD_PAYLOAD); } @@ -236,7 +215,7 @@ class RFC6455 implements VersionInterface { /** * @return RFC6455\Frame */ - public function newFrame($payload = null, $final = true, $opcode = 1) { + public function newFrame($payload = null, $final = null, $opcode = null) { return new Frame($payload, $final, $opcode); } @@ -284,35 +263,4 @@ class RFC6455 implements VersionInterface { $this->closeCodes[Frame::CLOSE_SRV_ERR] = true; //$this->closeCodes[Frame::CLOSE_TLS] = true; } - - /** - * Determine if a string is a valid UTF-8 string - * @param string - * @return bool - */ - function isUtf8($str) { - if ($this->hasMbString && false === mb_check_encoding($str, 'UTF-8')) { - return false; - } - - $len = strlen($str); - - // The secondary method of checking is painfully slow - // If the message is more than 10kb, skip UTF-8 checks - if ($len > 10000) { - return true; - } - - $state = static::UTF8_ACCEPT; - - for ($i = 0; $i < $len; $i++) { - $state = static::$dfa[256 + ($state << 4) + static::$dfa[ord($str[$i])]]; - - if (static::UTF8_REJECT === $state) { - return false; - } - } - - return true; - } } \ No newline at end of file diff --git a/src/Ratchet/WebSocket/WsServer.php b/src/Ratchet/WebSocket/WsServer.php index df2d2dc..6084672 100644 --- a/src/Ratchet/WebSocket/WsServer.php +++ b/src/Ratchet/WebSocket/WsServer.php @@ -3,6 +3,7 @@ namespace Ratchet\WebSocket; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; use Ratchet\WebSocket\Version; +use Ratchet\WebSocket\Encoding\ToggleableValidator; use Guzzle\Http\Message\Response; /** @@ -44,6 +45,11 @@ class WsServer implements MessageComponentInterface { */ protected $acceptedSubProtocols = array(); + /** + * @var Ratchet\WebSocket\Encoding\ValidatorInterface + */ + protected $validator; + /** * Flag if we have checked the decorated component for sub-protocols * @var boolean @@ -56,10 +62,11 @@ class WsServer implements MessageComponentInterface { public function __construct(MessageComponentInterface $component) { $this->reqParser = new HttpRequestParser; $this->versioner = new VersionManager; + $this->validator = new ToggleableValidator; $this->versioner - ->enableVersion(new Version\RFC6455($component)) - ->enableVersion(new Version\HyBi10($component)) + ->enableVersion(new Version\RFC6455($this->validator)) + ->enableVersion(new Version\HyBi10($this->validator)) ->enableVersion(new Version\Hixie76) ; @@ -147,9 +154,23 @@ class WsServer implements MessageComponentInterface { /** * Disable a specific version of the WebSocket protocol * @param int Version ID to disable + * @return WsServer */ public function disableVersion($versionId) { $this->versioner->disableVersion($versionId); + + return $this; + } + + /** + * Toggle weather to check encoding of incoming messages + * @param bool + * @return WsServer + */ + public function setEncodingChecks($opt) { + $this->validator->on = (boolean)$opt; + + return $this; } /** diff --git a/tests/AutobahnTestSuite/fuzzingclient-all.json b/tests/AutobahnTestSuite/fuzzingclient-all.json index 494d1aa..5ee7194 100644 --- a/tests/AutobahnTestSuite/fuzzingclient-all.json +++ b/tests/AutobahnTestSuite/fuzzingclient-all.json @@ -5,7 +5,7 @@ , "servers": [ {"agent": "Ratchet-libevent/0.2b", "url": "ws://localhost:8000", "options": {"version": 18}} , {"agent": "Ratchet-stream/0.2b", "url": "ws://localhost:8001", "options": {"version": 18}} - , {"agent": "AutobahnTestSuite/0.5.1-0.5.2", "url": "ws://localhost:8002", "options": {"version": 18}} + , {"agent": "AutobahnTestSuite/0.5.2", "url": "ws://localhost:8002", "options": {"version": 18}} ] , "cases": ["*"]