This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | namespace PHPDaemon\Clients\WebSocket; |
||
3 | |||
4 | use PHPDaemon\Core\Daemon; |
||
5 | use PHPDaemon\Network\ClientConnection; |
||
6 | use PHPDaemon\Utils\Binary; |
||
7 | use PHPDaemon\Utils\Crypt; |
||
8 | |||
9 | /** |
||
10 | * Class Connection |
||
11 | * @package Clients |
||
12 | * @subpackage WebSocket |
||
13 | * @author Kozin Denis <[email protected]> |
||
14 | * @author Vasily Zorin <[email protected]> |
||
15 | */ |
||
16 | class Connection extends ClientConnection |
||
17 | { |
||
18 | |||
19 | /** |
||
20 | * Globally Unique Identifier |
||
21 | * @see http://tools.ietf.org/html/rfc6455 |
||
22 | */ |
||
23 | const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; |
||
24 | |||
25 | const STATE_HEADER = 1; |
||
26 | const STATE_DATA = 2; |
||
27 | |||
28 | /** |
||
29 | * @var string |
||
30 | */ |
||
31 | protected $key; |
||
32 | |||
33 | /** |
||
34 | * @var array |
||
35 | */ |
||
36 | public $headers = []; |
||
37 | |||
38 | /** |
||
39 | * @var int |
||
40 | */ |
||
41 | protected $state = self::STATE_STANDBY; |
||
42 | |||
43 | /** |
||
44 | * @var array |
||
45 | */ |
||
46 | protected $opCodes = [ |
||
47 | 1 => Pool::TYPE_TEXT, |
||
48 | 2 => Pool::TYPE_BINARY, |
||
49 | 8 => Pool::TYPE_CLOSE, |
||
50 | 9 => Pool::TYPE_PING, |
||
51 | 10 => Pool::TYPE_PONG |
||
52 | ]; |
||
53 | |||
54 | /** |
||
55 | * @var string |
||
56 | */ |
||
57 | public $type; |
||
58 | |||
59 | /** |
||
60 | * @var int |
||
61 | */ |
||
62 | protected $pctLength = 0; |
||
63 | |||
64 | /** |
||
65 | * @var string |
||
66 | */ |
||
67 | protected $isMasked = false; |
||
68 | |||
69 | /** |
||
70 | * Called when the connection is handshaked (at low-level), and peer is ready to recv. data |
||
71 | * @return void |
||
72 | */ |
||
73 | public function onReady() |
||
74 | { |
||
75 | $this->setWatermark(2, $this->pool->maxAllowedPacket); |
||
76 | Crypt::randomString(16, null, function ($string) { |
||
77 | $this->key = base64_encode($string); |
||
78 | $this->write('GET /' . $this->path . " HTTP/1.1\r\nHost: " . $this->host . ($this->port != 80 ? ':' . $this->port : '') . "\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Key: " . $this->key . "\r\nSec-WebSocket-Version: 13\r\n\r\n"); |
||
79 | }); |
||
80 | } |
||
81 | |||
82 | /** |
||
83 | * Called when new data received |
||
84 | * @return void |
||
85 | */ |
||
86 | public function onRead() |
||
87 | { |
||
88 | start: |
||
89 | if ($this->state === static::STATE_HEADER) { |
||
90 | $l = $this->getInputLength(); |
||
91 | if ($l < 2) { |
||
92 | return; |
||
93 | } |
||
94 | $hdr = $this->look(2); |
||
95 | $fb = Binary::getbitmap(ord($hdr)); |
||
96 | $fin = (bool)$fb[0]; |
||
0 ignored issues
–
show
|
|||
97 | $opCode = bindec(substr($fb, 4, 4)); |
||
98 | |||
99 | if (isset($this->opCodes[$opCode])) { |
||
100 | $this->type = $this->opCodes[$opCode]; |
||
101 | } else { |
||
102 | $this->log('opCode: ' . $opCode . ': unknown frame type'); |
||
103 | $this->finish(); |
||
104 | return; |
||
105 | } |
||
106 | $sb = ord(mb_orig_substr($hdr, 1)); |
||
107 | $sbm = Binary::getbitmap($sb); |
||
108 | $this->isMasked = (bool)$sbm[0]; |
||
0 ignored issues
–
show
The property
$isMasked was declared of type string , but (bool) $sbm[0] is of type boolean . Maybe add a type cast?
This check looks for assignments to scalar types that may be of the wrong type. To ensure the code behaves as expected, it may be a good idea to add an explicit type cast. $answer = 42;
$correct = false;
$correct = (bool) $answer;
![]() |
|||
109 | $payloadLength = $sb & 127; |
||
110 | |||
111 | if ($payloadLength <= 125) { |
||
112 | $this->drain(2); |
||
113 | $this->pctLength = $payloadLength; |
||
114 | } elseif ($payloadLength === 126) { |
||
115 | if ($l < 4) { |
||
116 | return; |
||
117 | } |
||
118 | $this->drain(2); |
||
119 | $this->pctLength = Binary::b2i($this->read(2)); |
||
0 ignored issues
–
show
$this->read(2) is of type string|false , but the function expects a integer .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
![]() It seems like
\PHPDaemon\Utils\Binary::b2i($this->read(2)) can also be of type double . However, the property $pctLength is declared as type integer . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
120 | } elseif ($payloadLength === 127) { |
||
121 | if ($l < 10) { |
||
122 | return; |
||
123 | } |
||
124 | $this->drain(2); |
||
125 | $this->pctLength = Binary::b2i($this->read(8)); |
||
0 ignored issues
–
show
$this->read(8) is of type string|false , but the function expects a integer .
It seems like the type of the argument is not accepted by the function/method which you are calling. In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug. We suggest to add an explicit type cast like in the following example: function acceptsInteger($int) { }
$x = '123'; // string "123"
// Instead of
acceptsInteger($x);
// we recommend to use
acceptsInteger((integer) $x);
![]() It seems like
\PHPDaemon\Utils\Binary::b2i($this->read(8)) can also be of type double . However, the property $pctLength is declared as type integer . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
126 | } |
||
127 | |||
128 | View Code Duplication | if ($this->pool->maxAllowedPacket < $this->pctLength) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
129 | Daemon::$process->log('max-allowed-packet (' . $this->pool->config->maxallowedpacket->getHumanValue() . ') exceed, aborting connection'); |
||
130 | $this->finish(); |
||
131 | return; |
||
132 | } |
||
133 | $this->setWatermark($this->pctLength + ($this->isMasked ? 4 : 0)); |
||
134 | $this->state = static::STATE_DATA; |
||
135 | } |
||
136 | |||
137 | if ($this->state === static::STATE_DATA) { |
||
138 | if ($this->getInputLength() < $this->pctLength + ($this->isMasked ? 4 : 0)) { |
||
139 | return; |
||
140 | } |
||
141 | $this->state = static::STATE_HEADER; |
||
142 | $this->setWatermark(2); |
||
143 | if ($this->isMasked) { |
||
144 | $this->trigger('frame', static::mask($this->read(4), $this->read($this->pctLength))); |
||
0 ignored issues
–
show
|
|||
145 | } else { |
||
146 | $this->trigger('frame', $this->read($this->pctLength)); |
||
147 | } |
||
148 | } |
||
149 | if ($this->state === static::STATE_STANDBY) { |
||
150 | while (($line = $this->readLine()) !== null) { |
||
151 | $line = trim($line); |
||
152 | if ($line === '') { |
||
153 | $expectedKey = base64_encode(pack('H*', sha1($this->key . static::GUID))); |
||
154 | if (isset($this->headers['HTTP_SEC_WEBSOCKET_ACCEPT']) && $expectedKey === $this->headers['HTTP_SEC_WEBSOCKET_ACCEPT']) { |
||
155 | $this->state = static::STATE_HEADER; |
||
156 | if ($this->onConnected) { |
||
157 | $this->connected = true; |
||
158 | $this->onConnected->executeAll($this); |
||
159 | $this->onConnected = null; |
||
160 | } |
||
161 | $this->trigger('connected'); |
||
162 | goto start; |
||
163 | } else { |
||
164 | Daemon::$process->log(__METHOD__ . ': Handshake failed. Connection to ' . $this->url . ' failed.'); |
||
165 | $this->finish(); |
||
166 | } |
||
167 | } else { |
||
168 | $e = explode(': ', $line); |
||
169 | if (isset($e[1])) { |
||
170 | $this->headers['HTTP_' . strtoupper(strtr($e[0], ['-' => '_']))] = $e[1]; |
||
171 | } |
||
172 | } |
||
173 | } |
||
174 | return; |
||
175 | } |
||
176 | goto start; |
||
177 | } |
||
178 | |||
179 | /** |
||
180 | * Send frame to WebSocket server |
||
181 | * @param string $payload |
||
182 | * @param string $type |
||
183 | * @param boolean $isMasked |
||
184 | */ |
||
185 | public function sendFrame($payload, $type = Pool::TYPE_TEXT, $isMasked = true) |
||
186 | { |
||
187 | $payloadLength = mb_orig_strlen($payload); |
||
188 | View Code Duplication | if ($payloadLength > $this->pool->maxAllowedPacket) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
189 | Daemon::$process->log('max-allowed-packet (' . $this->pool->config->maxallowedpacket->getHumanValue() . ') exceed, aborting connection'); |
||
190 | return; |
||
191 | } |
||
192 | |||
193 | $firstByte = ''; |
||
194 | switch ($type) { |
||
195 | case Pool::TYPE_TEXT: |
||
196 | $firstByte = 129; |
||
197 | break; |
||
198 | case Pool::TYPE_CLOSE: |
||
199 | $firstByte = 136; |
||
200 | break; |
||
201 | case Pool::TYPE_PING: |
||
202 | $firstByte = 137; |
||
203 | break; |
||
204 | case Pool::TYPE_PONG: |
||
205 | $firstByte = 138; |
||
206 | break; |
||
207 | } |
||
208 | |||
209 | $hdrPacket = chr($firstByte); |
||
210 | |||
211 | $isMaskedInt = $isMasked ? 128 : 0; |
||
212 | if ($payloadLength <= 125) { |
||
213 | $hdrPacket .= chr($payloadLength + $isMaskedInt); |
||
214 | } elseif ($payloadLength <= 65535) { |
||
215 | $hdrPacket .= chr(126 + $isMaskedInt) . // 126 + 128 |
||
216 | chr($payloadLength >> 8) . |
||
217 | chr($payloadLength & 0xFF); |
||
218 | View Code Duplication | } else { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
219 | $hdrPacket .= chr(127 + $isMaskedInt) . // 127 + 128 |
||
220 | chr($payloadLength >> 56) . |
||
221 | chr($payloadLength >> 48) . |
||
222 | chr($payloadLength >> 40) . |
||
223 | chr($payloadLength >> 32) . |
||
224 | chr($payloadLength >> 24) . |
||
225 | chr($payloadLength >> 16) . |
||
226 | chr($payloadLength >> 8) . |
||
227 | chr($payloadLength & 0xFF); |
||
228 | } |
||
229 | $this->write($hdrPacket); |
||
230 | View Code Duplication | if ($isMasked) { |
|
0 ignored issues
–
show
This code seems to be duplicated across your project.
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation. You can also find more detailed suggestions in the “Code” section of your repository. ![]() |
|||
231 | $this->write($mask = chr(mt_rand(0, 0xFF)) . |
||
232 | chr(mt_rand(0, 0xFF)) . |
||
233 | chr(mt_rand(0, 0xFF)) . |
||
234 | chr(mt_rand(0, 0xFF))); |
||
235 | $this->write(static::mask($mask, $payload)); |
||
236 | } else { |
||
237 | $this->write($payload); |
||
238 | } |
||
239 | } |
||
240 | |||
241 | /** |
||
242 | * @TODO |
||
243 | * @return void |
||
244 | */ |
||
245 | public function onFinish() |
||
246 | { |
||
247 | parent::onFinish(); |
||
248 | $this->trigger('disconnected'); |
||
249 | } |
||
250 | |||
251 | /** |
||
252 | * @TODO |
||
253 | * @param string $mask |
||
254 | * @param string $str |
||
255 | * @return string |
||
256 | */ |
||
257 | protected static function mask($mask, $str) |
||
258 | { |
||
259 | $out = ''; |
||
260 | $l = mb_orig_strlen($str); |
||
261 | $ml = mb_orig_strlen($mask); |
||
262 | while (($o = mb_orig_strlen($out)) < $l) { |
||
263 | $out .= mb_orig_substr($str, $o, $ml) ^ $mask; |
||
264 | } |
||
265 | return $out; |
||
266 | } |
||
267 | } |
||
268 |
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.
Both the
$myVar
assignment in line 1 and the$higher
assignment in line 2 are dead. The first because$myVar
is never used and the second because$higher
is always overwritten for every possible time line.