Replace slow validator with preg_match UTF8 check

This commit is contained in:
Chris Boden 2016-01-12 20:44:07 -05:00
parent 4f15d6558e
commit 59a30c3b72
8 changed files with 6 additions and 182 deletions

View File

@ -1,14 +0,0 @@
<?php
namespace Ratchet\RFC6455\Encoding;
class NullValidator implements ValidatorInterface {
/**
* What value to return when checkEncoding is valid
* @var boolean
*/
public $validationResponse = true;
public function checkEncoding($str, $encoding) {
return (boolean)$this->validationResponse;
}
}

View File

@ -1,34 +0,0 @@
<?php
namespace Ratchet\RFC6455\Encoding;
/**
* @deprecated - Use NullValidator
*/
class ToggleableValidator implements ValidatorInterface {
/**
* Toggle if checkEncoding checks the encoding or not
* @var bool
*/
public $on;
/**
* @var Validator
*/
private $validator;
public function __construct($on = true) {
$this->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);
}
}

View File

@ -1,93 +0,0 @@
<?php
namespace Ratchet\RFC6455\Encoding;
/**
* This class handled encoding validation
*/
class Validator implements ValidatorInterface {
const UTF8_ACCEPT = 0;
const UTF8_REJECT = 1;
/**
* Incremental UTF-8 validator with constant memory consumption (minimal state).
*
* Implements the algorithm "Flexible and Economical UTF-8 Decoder" by
* Bjoern Hoehrmann (http://bjoern.hoehrmann.de/utf-8/decoder/dfa/).
*/
protected static $dfa = array(
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 00..1f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 20..3f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 40..5f
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 60..7f
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, # 80..9f
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, # a0..bf
8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, # c0..df
0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, # e0..ef
0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, # f0..ff
0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, # s0..s0
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, # s1..s2
1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, # s3..s4
1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, # s5..s6
1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, # s7..s8
);
/**
* Lookup if mbstring is available
* @var bool
*/
private $hasMbString = false;
/**
* Lookup if iconv is available
* @var bool
*/
private $hasIconv = false;
public function __construct() {
$this->hasMbString = extension_loaded('mbstring');
$this->hasIconv = extension_loaded('iconv');
}
/**
* @param string $str The value to check the encoding
* @param string $against 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;
}
}

View File

@ -1,15 +0,0 @@
<?php
namespace Ratchet\RFC6455\Encoding;
/**
* @todo Probably move this into Messaging\Validation
*/
interface ValidatorInterface {
/**
* Verify a string matches the encoding type
* @param string $str The string to check
* @param string $encoding The encoding type to check against
* @return bool
*/
function checkEncoding($str, $encoding);
}

View File

@ -2,7 +2,6 @@
namespace Ratchet\RFC6455\Handshake;
use Psr\Http\Message\RequestInterface;
use GuzzleHttp\Psr7\Response;
use Ratchet\RFC6455\Encoding\ValidatorInterface;
/**
* The latest version of the WebSocket protocol
@ -14,19 +13,12 @@ class Negotiator implements NegotiatorInterface {
*/
private $verifier;
/**
* @var \Ratchet\RFC6455\Encoding\ValidatorInterface
*/
private $validator;
private $_supportedSubProtocols = [];
private $_strictSubProtocols = true;
public function __construct(ValidatorInterface $validator) {
public function __construct() {
$this->verifier = new RequestVerifier;
$this->validator = $validator;
}
/**

View File

@ -1,6 +1,5 @@
<?php
namespace Ratchet\RFC6455\Messaging\Streaming;
use Ratchet\RFC6455\Encoding\ValidatorInterface;
use Ratchet\RFC6455\Messaging\Protocol\CloseFrameChecker;
use Ratchet\RFC6455\Messaging\Protocol\MessageInterface;
use Ratchet\RFC6455\Messaging\Protocol\FrameInterface;
@ -8,11 +7,6 @@ use Ratchet\RFC6455\Messaging\Protocol\Message;
use Ratchet\RFC6455\Messaging\Protocol\Frame;
class MessageStreamer {
/**
* @var \Ratchet\RFC6455\Encoding\ValidatorInterface
*/
private $validator;
/**
* @var \Ratchet\RFC6455\Messaging\Protocol\CloseFrameChecker
*/
@ -49,14 +43,12 @@ class MessageStreamer {
private $checkForMask;
function __construct(
ValidatorInterface $encodingValidator,
CloseFrameChecker $frameChecker,
callable $onMessage,
callable $onControl = null,
$expectMask = true,
$exceptionFactory = null
) {
$this->validator = $encodingValidator;
$this->closeFrameChecker = $frameChecker;
$this->checkForMask = (bool)$expectMask;
@ -166,7 +158,7 @@ class MessageStreamer {
return $this->newCloseFrame(Frame::CLOSE_PROTOCOL);
}
if (!$this->validator->checkEncoding(substr($bin, 2), 'UTF-8')) {
if (!preg_match('//u', substr($bin, 2))) {
return $this->newCloseFrame(Frame::CLOSE_BAD_PAYLOAD);
}
@ -201,7 +193,7 @@ class MessageStreamer {
*/
public function checkMessage(MessageInterface $message) {
if (!$message->isBinary()) {
if (!$this->validator->checkEncoding($message->getPayload(), 'UTF-8')) {
if (!preg_match('//u', $message->getPayload())) {
return Frame::CLOSE_BAD_PAYLOAD;
}
}

View File

@ -19,7 +19,6 @@ $factory = new \React\SocketClient\Connector($loop, $dnsResolver);
function echoStreamerFactory($conn)
{
return new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer(
new \Ratchet\RFC6455\Encoding\Validator,
new \Ratchet\RFC6455\Messaging\Protocol\CloseFrameChecker,
function (\Ratchet\RFC6455\Messaging\Protocol\MessageInterface $msg) use ($conn) {
/** @var Frame $frame */
@ -72,7 +71,6 @@ function getTestCases() {
$deferred->reject();
} else {
$ms = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer(
new \Ratchet\RFC6455\Encoding\Validator,
new \Ratchet\RFC6455\Messaging\Protocol\CloseFrameChecker,
function (\Ratchet\RFC6455\Messaging\Protocol\MessageInterface $msg) use ($deferred, $stream) {
$deferred->resolve($msg->getPayload());
@ -180,7 +178,6 @@ function createReport() {
$deferred->reject();
} else {
$ms = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer(
new \Ratchet\RFC6455\Encoding\Validator,
new \Ratchet\RFC6455\Messaging\Protocol\CloseFrameChecker,
function (\Ratchet\RFC6455\Messaging\Protocol\MessageInterface $msg) use ($deferred, $stream) {
$deferred->resolve($msg->getPayload());

View File

@ -10,13 +10,12 @@ $loop = \React\EventLoop\Factory::create();
$socket = new \React\Socket\Server($loop);
$server = new \React\Http\Server($socket);
$encodingValidator = new \Ratchet\RFC6455\Encoding\Validator;
$closeFrameChecker = new \Ratchet\RFC6455\Messaging\Protocol\CloseFrameChecker;
$negotiator = new \Ratchet\RFC6455\Handshake\Negotiator($encodingValidator);
$negotiator = new \Ratchet\RFC6455\Handshake\Negotiator;
$uException = new \UnderflowException;
$server->on('request', function (\React\Http\Request $request, \React\Http\Response $response) use ($negotiator, $encodingValidator, $closeFrameChecker, $uException) {
$server->on('request', function (\React\Http\Request $request, \React\Http\Response $response) use ($negotiator, $closeFrameChecker, $uException) {
$psrRequest = new \GuzzleHttp\Psr7\Request($request->getMethod(), $request->getPath(), $request->getHeaders());
$negotiatorResponse = $negotiator->handshake($psrRequest);
@ -34,7 +33,7 @@ $server->on('request', function (\React\Http\Request $request, \React\Http\Respo
return;
}
$parser = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer($encodingValidator, $closeFrameChecker, function(MessageInterface $message) use ($response) {
$parser = new \Ratchet\RFC6455\Messaging\Streaming\MessageStreamer($closeFrameChecker, function(MessageInterface $message) use ($response) {
$response->write($message->getContents());
}, function(FrameInterface $frame) use ($response, &$parser) {
switch ($frame->getOpCode()) {