Completed
Push — master ( c55d2b...07e932 )
by Vasily
03:55
created

V13::onRead()   C

Complexity

Conditions 18
Paths 163

Size

Total Lines 73
Code Lines 51

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 73
rs 5.0016
cc 18
eloc 51
nc 163
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace PHPDaemon\Servers\WebSocket\Protocols;
3
4
use PHPDaemon\Core\Daemon;
5
use PHPDaemon\Servers\WebSocket\Connection;
6
use PHPDaemon\Utils\Binary;
7
8
/**
9
 * Websocket protocol 13
10
 * @see    http://datatracker.ietf.org/doc/rfc6455/?include_text=1
11
 */
12
13
class V13 extends Connection {
14
	const CONTINUATION = 0;
15
	const STRING       = 0x1;
16
	const BINARY       = 0x2;
17
	const CONNCLOSE    = 0x8;
18
	const PING         = 0x9;
19
	const PONG         = 0xA;
20
	protected static $opcodes = [
21
		0   => 'CONTINUATION',
22
		0x1 => 'STRING',
23
		0x2 => 'BINARY',
24
		0x8 => 'CONNCLOSE',
25
		0x9 => 'PING',
26
		0xA => 'PONG',
27
	];
28
	protected $outgoingCompression = 0;
29
30
	protected $framebuf = '';
31
32
	/**
33
	 * Sends a handshake message reply
34
	 * @param string Received data (no use in this class)
35
	 * @return boolean OK?
36
	 */
37
	public function sendHandshakeReply($extraHeaders = '') {
38
		if (!isset($this->server['HTTP_SEC_WEBSOCKET_KEY']) || !isset($this->server['HTTP_SEC_WEBSOCKET_VERSION'])) {
39
			return false;
40
		}
41
		if ($this->server['HTTP_SEC_WEBSOCKET_VERSION'] !== '13' && $this->server['HTTP_SEC_WEBSOCKET_VERSION'] !== '8') {
42
			return false;
43
		}
44
45
		if (isset($this->server['HTTP_ORIGIN'])) {
46
			$this->server['HTTP_SEC_WEBSOCKET_ORIGIN'] = $this->server['HTTP_ORIGIN'];
47
		}
48
		if (!isset($this->server['HTTP_SEC_WEBSOCKET_ORIGIN'])) {
49
			$this->server['HTTP_SEC_WEBSOCKET_ORIGIN'] = '';
50
		}
51
		$this->write("HTTP/1.1 101 Switching Protocols\r\n"
52
				. "Upgrade: WebSocket\r\n"
53
				. "Connection: Upgrade\r\n"
54
				. "Date: " . date('r') . "\r\n"
55
				. "Sec-WebSocket-Origin: " . $this->server['HTTP_SEC_WEBSOCKET_ORIGIN'] . "\r\n"
56
				. "Sec-WebSocket-Location: ws://" . $this->server['HTTP_HOST'] . $this->server['REQUEST_URI'] . "\r\n"
57
				. "Sec-WebSocket-Accept: " . base64_encode(sha1(trim($this->server['HTTP_SEC_WEBSOCKET_KEY']) . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)) . "\r\n"
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 155 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
58
		);
59
		if (isset($this->server['HTTP_SEC_WEBSOCKET_PROTOCOL'])) {
60
			$this->writeln("Sec-WebSocket-Protocol: " . $this->server['HTTP_SEC_WEBSOCKET_PROTOCOL']);
61
		}
62
63
		if ($this->pool->config->expose->value) {
64
			$this->writeln('X-Powered-By: phpDaemon/' . Daemon::$version);
65
		}
66
67
		$this->writeln($extraHeaders);
68
69
		return true;
70
	}
71
72
73
	/**
74
	 * Sends a frame.
75
	 * @param  string   $data  Frame's data.
76
	 * @param  string   $type  Frame's type. ("STRING" OR "BINARY")
0 ignored issues
show
Documentation introduced by
Should the type for parameter $type not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
77
	 * @param  callable $cb    Optional. Callback called when the frame is received by client.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $cb not be callable|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
78
	 * @callback $cb ( )
79
	 * @return boolean         Success.
80
	 */
81
	public function sendFrame($data, $type = null, $cb = null) {
82
		if (!$this->handshaked) {
83
			return false;
84
		}
85
86
		if ($this->finished && $type !== 'CONNCLOSE') {
87
			return false;
88
		}
89
90
		/*if (in_array($type, ['STRING', 'BINARY']) && ($this->outgoingCompression > 0) && in_array('deflate-frame', $this->extensions)) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 132 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
91
			//$data = gzcompress($data, $this->outgoingCompression);
92
			//$rsv1 = 1;
93
		}*/
94
95
		$fin = 1;
96
		$rsv1 = 0;
97
		$rsv2 = 0;
98
		$rsv3 = 0;
99
		$this->write(chr(bindec($fin . $rsv1 . $rsv2 . $rsv3 . str_pad(decbin($this->getFrameType($type)), 4, '0', STR_PAD_LEFT))));
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
100
		$dataLength  = strlen($data);
101
		$isMasked    = false;
102
		$isMaskedInt = $isMasked ? 128 : 0;
103
		if ($dataLength <= 125) {
104
			$this->write(chr($dataLength + $isMaskedInt));
105
		}
106
		elseif ($dataLength <= 65535) {
107
			$this->write(chr(126 + $isMaskedInt) . // 126 + 128
108
					chr($dataLength >> 8) .
109
					chr($dataLength & 0xFF));
110
		}
111 View Code Duplication
		else {
0 ignored issues
show
Duplication introduced by
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.

Loading history...
112
			$this->write(chr(127 + $isMaskedInt) . // 127 + 128
113
					chr($dataLength >> 56) .
114
					chr($dataLength >> 48) .
115
					chr($dataLength >> 40) .
116
					chr($dataLength >> 32) .
117
					chr($dataLength >> 24) .
118
					chr($dataLength >> 16) .
119
					chr($dataLength >> 8) .
120
					chr($dataLength & 0xFF));
121
		}
122 View Code Duplication
		if ($isMasked) {
0 ignored issues
show
Duplication introduced by
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.

Loading history...
123
			$mask	= chr(mt_rand(0, 0xFF)) .
124
					chr(mt_rand(0, 0xFF)) .
125
					chr(mt_rand(0, 0xFF)) .
126
					chr(mt_rand(0, 0xFF));
127
			$this->write($mask . $this->mask($data, $mask));
128
		}
129
		else {
130
			$this->write($data);
131
		}
132
		if ($cb !== null) {
133
			$this->onWriteOnce($cb);
134
		}
135
		return true;
136
	}
137
138
	/**
139
	 * Apply mask
140
	 * @param $data
141
	 * @param string|false $mask
142
	 * @return mixed
143
	 */
144
	public function mask($data, $mask) {
145
		for ($i = 0, $l = strlen($data), $ml = strlen($mask); $i < $l; $i++) {
146
			$data[$i] = $data[$i] ^ $mask[$i % $ml];
147
		}
148
		return $data;
149
	}
150
151
	/**
152
	 * Called when new data received
153
	 * @see http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10#page-16
154
	 * @return void
155
	 */
156
	public function onRead() {
157
		if ($this->state === self::STATE_PREHANDSHAKE) {
158
			if (!$this->handshake()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->handshake() of type null|boolean is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
159
				return;
160
			}
161
		}
162
		if ($this->state === self::STATE_HANDSHAKED) {
163
164
			while (($buflen = $this->getInputLength()) >= 2) {
165
				$first = ord($this->look(1)); // first byte integer (fin, opcode)
166
				$firstBits = decbin($first);
167
				$opcode = (int)bindec(substr($firstBits, 4, 4));
168
				if ($opcode === 0x8) { // CLOSE
169
					$this->finish();
170
					return;
171
				}
172
				$opcodeName = isset(static::$opcodes[$opcode]) ? static::$opcodes[$opcode] : false;
173
				if (!$opcodeName) {
174
					Daemon::log(get_class($this) . ': Undefined opcode ' . $opcode);
175
					$this->finish();
176
					return;
177
				}
178
				$second = ord($this->look(1, 1)); // second byte integer (masked, payload length)
179
				$fin = (bool)($first >> 7);
180
				$isMasked = (bool)($second >> 7);
181
				$dataLength = $second & 0x7f;
182
				$p = 2;
183
				if ($dataLength === 0x7e) { // 2 bytes-length
184
					if ($buflen < $p + 2) {
185
						return; // not enough data yet
186
					}
187
					$dataLength = Binary::bytes2int($this->look(2, $p), false);
0 ignored issues
show
Security Bug introduced by
It seems like $this->look(2, $p) targeting PHPDaemon\Network\IOStream::look() can also be of type false; however, PHPDaemon\Utils\Binary::bytes2int() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
188
					$p += 2;
189
				} elseif ($dataLength === 0x7f) { // 8 bytes-length
190
					if ($buflen < $p + 8) {
191
						return; // not enough data yet
192
					}
193
					$dataLength = Binary::bytes2int($this->look(8, $p));
0 ignored issues
show
Security Bug introduced by
It seems like $this->look(8, $p) targeting PHPDaemon\Network\IOStream::look() can also be of type false; however, PHPDaemon\Utils\Binary::bytes2int() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
194
					$p += 8;
195
				}
196
				if ($this->pool->maxAllowedPacket <= $dataLength) {
197
					// Too big packet
198
					$this>finish();
199
					return;
200
				}
201
				if ($isMasked) {
202
					if ($buflen < $p + 4) {
203
						return; // not enough data yet
204
					}
205
					$mask = $this->look(4, $p);
206
					$p += 4;
207
				}
208
				if ($buflen < $p + $dataLength) {
209
					return; // not enough data yet
210
				}
211
				$this->drain($p);
212
				$data = $this->read($dataLength);
213
				if ($isMasked) {
214
					$data = $this->mask($data, $mask);
0 ignored issues
show
Bug introduced by
The variable $mask does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
215
				}
216
				//Daemon::log(Debug::dump(array('ext' => $this->extensions, 'rsv1' => $firstBits[1], 'data' => Debug::exportBytes($data))));
0 ignored issues
show
Unused Code Comprehensibility introduced by
66% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 128 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
217
				/*if ($firstBits[1] && in_array('deflate-frame', $this->extensions)) { // deflate frame
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
218
					$data = gzuncompress($data, $this->pool->maxAllowedPacket);
219
				}*/
220
				if (!$fin) {
221
					$this->framebuf .= $data;
222
				} else {
223
					$this->onFrame($this->framebuf . $data, $opcodeName);
224
					$this->framebuf = '';
225
				}
226
			}
227
		}
228
	}
229
}
230