[WebSocket] Frame masking
This commit is contained in:
parent
54479da9d5
commit
9f0e29fe7f
@ -14,6 +14,9 @@ class MessageParser {
|
|||||||
|
|
||||||
$from->WebSocket->frame->addBuffer($data);
|
$from->WebSocket->frame->addBuffer($data);
|
||||||
if ($from->WebSocket->frame->isCoalesced()) {
|
if ($from->WebSocket->frame->isCoalesced()) {
|
||||||
|
// check if masked
|
||||||
|
// close if not
|
||||||
|
|
||||||
if ($from->WebSocket->frame->getOpcode() > 2) {
|
if ($from->WebSocket->frame->getOpcode() > 2) {
|
||||||
// take action on the control frame
|
// take action on the control frame
|
||||||
|
|
||||||
|
@ -10,6 +10,8 @@ class Frame implements FrameInterface {
|
|||||||
const OP_PING = 9;
|
const OP_PING = 9;
|
||||||
const OP_PONG = 10;
|
const OP_PONG = 10;
|
||||||
|
|
||||||
|
const MASK_LENGTH = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The contents of the frame
|
* The contents of the frame
|
||||||
* @var string
|
* @var string
|
||||||
@ -37,7 +39,7 @@ class Frame implements FrameInterface {
|
|||||||
* @throws InvalidArgumentException If the payload is not a valid UTF-8 string
|
* @throws InvalidArgumentException If the payload is not a valid UTF-8 string
|
||||||
* @throws LengthException If the payload is too big
|
* @throws LengthException If the payload is too big
|
||||||
*/
|
*/
|
||||||
public static function create($payload, $final = true, $opcode = 1, $mask = false) {
|
public static function create($payload, $final = true, $opcode = 1) {
|
||||||
$frame = new static();
|
$frame = new static();
|
||||||
|
|
||||||
if (!mb_check_encoding($payload, 'UTF-8')) {
|
if (!mb_check_encoding($payload, 'UTF-8')) {
|
||||||
@ -57,12 +59,6 @@ class Frame implements FrameInterface {
|
|||||||
|
|
||||||
$frame->addBuffer(static::encode($raw) . $payload);
|
$frame->addBuffer(static::encode($raw) . $payload);
|
||||||
|
|
||||||
if ($mask) {
|
|
||||||
// create masking key
|
|
||||||
// insert it
|
|
||||||
// mask the payload
|
|
||||||
}
|
|
||||||
|
|
||||||
return $frame;
|
return $frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,6 +130,107 @@ class Frame implements FrameInterface {
|
|||||||
return (boolean)bindec(substr(sprintf('%08b', ord(substr($this->data, 1, 1))), 0, 1));
|
return (boolean)bindec(substr(sprintf('%08b', ord(substr($this->data, 1, 1))), 0, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getMaskingKey() {
|
||||||
|
if (!$this->isMasked()) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$start = 1 + $this->getNumPayloadBytes();
|
||||||
|
|
||||||
|
if ($this->_bytes_rec < $start + static::MASK_LENGTH) {
|
||||||
|
throw new \UnderflowException('Not enough data buffered to calculate the masking key');
|
||||||
|
}
|
||||||
|
|
||||||
|
return substr($this->data, $start, static::MASK_LENGTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function generateMaskingKey() {
|
||||||
|
$mask = '';
|
||||||
|
|
||||||
|
for ($i = 1; $i <= static::MASK_LENGTH; $i++) {
|
||||||
|
$mask .= sprintf("%c", rand(32, 126));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a mask to the payload
|
||||||
|
* @param string|null
|
||||||
|
* @throws InvalidArgumentException If there is an issue with the given masking key
|
||||||
|
* @throws UnderflowException If the frame is not coalesced
|
||||||
|
*/
|
||||||
|
public function maskPayload($maskingKey = null) {
|
||||||
|
if (null === $maskingKey) {
|
||||||
|
$maskingKey = $this->generateMaskingKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (static::MASK_LENGTH !== strlen($maskingKey)) {
|
||||||
|
throw new \InvalidArgumentException("Masking key must be " . static::MASK_LENGTH ." characters");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mb_check_encoding($maskingKey, 'US-ASCII')) {
|
||||||
|
throw new \InvalidArgumentException("Masking key MUST be ASCII");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->isCoalesced()) {
|
||||||
|
throw new \UnderflowException('Frame must be coalesced to apply a mask');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isMasked()) {
|
||||||
|
$this->unMaskPayload();
|
||||||
|
}
|
||||||
|
|
||||||
|
$byte = sprintf('%08b', ord(substr($this->data, 1, 1)));
|
||||||
|
|
||||||
|
$this->data = substr_replace($this->data, static::encode(substr_replace($byte, '1', 0, 1)), 1, 1);
|
||||||
|
$this->data = substr_replace($this->data, $maskingKey, $this->getNumPayloadBytes() + 1, 0);
|
||||||
|
|
||||||
|
$this->_bytes_rec += static::MASK_LENGTH;
|
||||||
|
|
||||||
|
$plLen = $this->getPayloadLength();
|
||||||
|
$start = $this->getPayloadStartingByte();
|
||||||
|
$maskedPl = '';
|
||||||
|
|
||||||
|
for ($i = 0; $i < $plLen; $i++) {
|
||||||
|
$maskedPl .= substr($this->data, $i + $start, 1) ^ substr($maskingKey, $i % static::MASK_LENGTH, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->data = substr_replace($this->data, $maskedPl, $start, $plLen);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a mask from the payload
|
||||||
|
* @throws UnderFlowException If the frame is not coalesced
|
||||||
|
* @return Frame
|
||||||
|
*/
|
||||||
|
public function unMaskPayload() {
|
||||||
|
if (!$this->isMasked()) {
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->isCoalesced()) {
|
||||||
|
throw new \UnderflowException('Frame must be coalesced to apply a mask');
|
||||||
|
}
|
||||||
|
|
||||||
|
$maskingKey = $this->getMaskingKey();
|
||||||
|
|
||||||
|
// set the indicator bit to 0
|
||||||
|
// remove the masking key
|
||||||
|
// get the masking key
|
||||||
|
// unmask the payload
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
@ -231,24 +328,6 @@ class Frame implements FrameInterface {
|
|||||||
return $this->getPayloadLength();
|
return $this->getPayloadLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
public function getMaskingKey() {
|
|
||||||
if (!$this->isMasked()) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
$length = 4;
|
|
||||||
$start = 1 + $this->getNumPayloadBytes();
|
|
||||||
|
|
||||||
if ($this->_bytes_rec < $start + $length) {
|
|
||||||
throw new \UnderflowException('Not enough data buffered to calculate the masking key');
|
|
||||||
}
|
|
||||||
|
|
||||||
return substr($this->data, $start, $length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
@ -273,7 +352,7 @@ class Frame implements FrameInterface {
|
|||||||
|
|
||||||
for ($i = 0; $i < $length; $i++) {
|
for ($i = 0; $i < $length; $i++) {
|
||||||
// Double check the RFC - is the masking byte level or character level?
|
// Double check the RFC - is the masking byte level or character level?
|
||||||
$payload .= substr($this->data, $i + $start, 1) ^ substr($mask, $i % 4, 1);
|
$payload .= substr($this->data, $i + $start, 1) ^ substr($mask, $i % static::MASK_LENGTH, 1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$payload = substr($this->data, $start, $this->getPayloadLength());
|
$payload = substr($this->data, $start, $this->getPayloadLength());
|
||||||
@ -286,6 +365,7 @@ class Frame implements FrameInterface {
|
|||||||
|
|
||||||
return $payload;
|
return $payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sometimes clients will concatinate more than one frame over the wire
|
* Sometimes clients will concatinate more than one frame over the wire
|
||||||
* This method will take the extra bytes off the end and return them
|
* This method will take the extra bytes off the end and return them
|
||||||
|
@ -298,6 +298,14 @@ class FrameTest extends \PHPUnit_Framework_TestCase {
|
|||||||
$this->assertEquals($string, $frame->getPayload());
|
$this->assertEquals($string, $frame->getPayload());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testMasking() {
|
||||||
|
$msg = 'The quick brown fox jumps over the lazy dog.';
|
||||||
|
$frame = Frame::create($msg)->maskPayload();
|
||||||
|
|
||||||
|
$this->assertTrue($frame->isMasked());
|
||||||
|
$this->assertEquals($msg, $frame->getPayload());
|
||||||
|
}
|
||||||
|
|
||||||
protected function generateRandomString($length = 10, $addSpaces = true, $addNumbers = true) {
|
protected function generateRandomString($length = 10, $addSpaces = true, $addNumbers = true) {
|
||||||
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$%&/()=[]{}'; // ยง
|
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$%&/()=[]{}'; // ยง
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user