├── README.md
├── client.html
├── license.txt
├── testwebsock.php
├── users.php
└── websockets.php
/README.md:
--------------------------------------------------------------------------------
1 | PHP WebSockets
2 | ==============
3 |
4 | A WebSockets server written in PHP.
5 | -----------------------------------
6 |
7 | This project provides the functionality of an RFC-6455 (or Version 13) WebSockets server. It can be used as a stand-alone server, or as the back-end of a normal HTTP server that is WebSockets aware.
8 |
9 | In order to use PHP WebSockets, you must have the ability to arbitrarilly execute scripts, which almost always means having shell access to your server, at a minimum. It is strongly encouraged that you have the ability to configure your machine's HTTP server. It is strongly discouraged to allow arbitrary execution of scripts from a web interface, as this is a major security hole.
10 |
11 | To use:
12 |
13 | Do not place the files in your web server's document root -- they are not intended to be ran through a web browser or otherwise directly accessible to the world. They are intended to be ran through PHP's Command Line Interface (CLI).
14 |
15 | The main class, `WebSocketServer`, is intended to be inherited by your class, and the methods `connected`, `closed`, and `process` should be overridden. In fact, they are abstract, so they _must_ be overridden.
16 |
17 | Future plans include allowing child processes forked from the controlling daemon to support broadcasts and to relay data from one socket in a child process to another socket in a separate child proccess.
18 |
19 | Browser Support
20 | ---------------
21 |
22 | Broswer Name Earliest Version
23 |
24 | Google Chrome 16
25 |
26 | Mozilla Firefox 11
27 |
28 | Internet Explorer 10
29 |
30 | Safari 6
31 |
32 | Opera 12.10
33 |
34 | Android Browser 4.4
35 |
36 | Note: Current browser support is available at http://en.wikipedia.org/wiki/WebSocket#Browser_support under the RFC-6455 row.
37 |
38 | For Support
39 | -----------
40 |
41 | Right now, the only support available is in the Github Issues ( https://github.com/ghedipunk/PHP-Websockets/issues ). Once I reach my $250/mo Patreon reward level, I'll be able to maintain support forums for non-core code issues. If you'd like to support the project, and bring these forums closer to reality, you can do so at https://www.patreon.com/ghedipunk .
42 |
--------------------------------------------------------------------------------
/client.html:
--------------------------------------------------------------------------------
1 |
WebSocket
2 |
16 |
75 |
76 |
77 |
78 |
WebSocket v2.00
79 |
80 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012, Adam Alexander
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 |
6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8 | * Neither the name of PHP WebSockets nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9 |
10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/testwebsock.php:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | userClass = 'MyUser';
11 | }
12 |
13 | //protected $maxBufferSize = 1048576; //1MB... overkill for an echo server, but potentially plausible for other applications.
14 |
15 | protected function process ($user, $message) {
16 | $this->send($user,$message);
17 | }
18 |
19 | protected function connected ($user) {
20 | // Do nothing: This is just an echo server, there's no need to track the user.
21 | // However, if we did care about the users, we would probably have a cookie to
22 | // parse at this step, would be looking them up in permanent storage, etc.
23 | }
24 |
25 | protected function closed ($user) {
26 | // Do nothing: This is where cleanup would go, in case the user had any sort of
27 | // open files or other objects associated with them. This runs after the socket
28 | // has been closed, so there is no need to clean up the socket itself here.
29 | }
30 | }
31 |
32 | $echo = new echoServer("0.0.0.0","9000");
33 |
34 | try {
35 | $echo->run();
36 | }
37 | catch (Exception $e) {
38 | $echo->stdout($e->getMessage());
39 | }
40 |
--------------------------------------------------------------------------------
/users.php:
--------------------------------------------------------------------------------
1 | id = $id;
20 | $this->socket = $socket;
21 | }
22 | }
23 |
24 | class MyUser extends WebSocketUser {
25 | public $myId;
26 |
27 | function __construct($id, $socket, $myId) {
28 | parent::__construct($id, $socket);
29 | $this->myId = $myId;
30 | }
31 | }
--------------------------------------------------------------------------------
/websockets.php:
--------------------------------------------------------------------------------
1 | maxBufferSize = $bufferLength;
21 | $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("Failed: socket_create()");
22 | socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1) or die("Failed: socket_option()");
23 | socket_bind($this->master, $addr, $port) or die("Failed: socket_bind()");
24 | socket_listen($this->master,20) or die("Failed: socket_listen()");
25 | $this->sockets['m'] = $this->master;
26 | $this->stdout("Server started\nListening on: $addr:$port\nMaster socket: ".$this->master);
27 |
28 |
29 | }
30 |
31 | abstract protected function process($user,$message); // Called immediately when the data is recieved.
32 | abstract protected function connected($user); // Called after the handshake response is sent to the client.
33 | abstract protected function closed($user); // Called after the connection is closed.
34 |
35 | protected function connecting($user) {
36 | // Override to handle a connecting user, after the instance of the User is created, but before
37 | // the handshake has completed.
38 | }
39 |
40 | protected function send($user, $message) {
41 | if ($user->handshake) {
42 | $message = $this->frame($message,$user);
43 | $result = @socket_write($user->socket, $message, strlen($message));
44 | }
45 | else {
46 | // User has not yet performed their handshake. Store for sending later.
47 | $holdingMessage = array('user' => $user, 'message' => $message);
48 | $this->heldMessages[] = $holdingMessage;
49 | }
50 | }
51 |
52 | protected function tick() {
53 | // Override this for any process that should happen periodically. Will happen at least once
54 | // per second, but possibly more often.
55 | }
56 |
57 | protected function _tick() {
58 | // Core maintenance processes, such as retrying failed messages.
59 | foreach ($this->heldMessages as $key => $hm) {
60 | $found = false;
61 | foreach ($this->users as $currentUser) {
62 | if ($hm['user']->socket == $currentUser->socket) {
63 | $found = true;
64 | if ($currentUser->handshake) {
65 | unset($this->heldMessages[$key]);
66 | $this->send($currentUser, $hm['message']);
67 | }
68 | }
69 | }
70 | if (!$found) {
71 | // If they're no longer in the list of connected users, drop the message.
72 | unset($this->heldMessages[$key]);
73 | }
74 | }
75 | }
76 |
77 | /**
78 | * Main processing loop
79 | */
80 | public function run() {
81 | while(true) {
82 | if (empty($this->sockets)) {
83 | $this->sockets['m'] = $this->master;
84 | }
85 | $read = $this->sockets;
86 | $write = $except = null;
87 | $this->_tick();
88 | $this->tick();
89 | @socket_select($read,$write,$except,1);
90 | foreach ($read as $socket) {
91 | if ($socket == $this->master) {
92 | $client = socket_accept($socket);
93 | if ($client < 0) {
94 | $this->stderr("Failed: socket_accept()");
95 | continue;
96 | }
97 | else {
98 | $this->connect($client);
99 | $this->stdout("Client connected. " . $client);
100 | }
101 | }
102 | else {
103 | $numBytes = @socket_recv($socket, $buffer, $this->maxBufferSize, 0);
104 | if ($numBytes === false) {
105 | $sockErrNo = socket_last_error($socket);
106 | switch ($sockErrNo)
107 | {
108 | case 102: // ENETRESET -- Network dropped connection because of reset
109 | case 103: // ECONNABORTED -- Software caused connection abort
110 | case 104: // ECONNRESET -- Connection reset by peer
111 | case 108: // ESHUTDOWN -- Cannot send after transport endpoint shutdown -- probably more of an error on our part, if we're trying to write after the socket is closed. Probably not a critical error, though.
112 | case 110: // ETIMEDOUT -- Connection timed out
113 | case 111: // ECONNREFUSED -- Connection refused -- We shouldn't see this one, since we're listening... Still not a critical error.
114 | case 112: // EHOSTDOWN -- Host is down -- Again, we shouldn't see this, and again, not critical because it's just one connection and we still want to listen to/for others.
115 | case 113: // EHOSTUNREACH -- No route to host
116 | case 121: // EREMOTEIO -- Rempte I/O error -- Their hard drive just blew up.
117 | case 125: // ECANCELED -- Operation canceled
118 |
119 | $this->stderr("Unusual disconnect on socket " . $socket);
120 | $this->disconnect($socket, true, $sockErrNo); // disconnect before clearing error, in case someone with their own implementation wants to check for error conditions on the socket.
121 | break;
122 | default:
123 |
124 | $this->stderr('Socket error: ' . socket_strerror($sockErrNo));
125 | }
126 |
127 | }
128 | elseif ($numBytes == 0) {
129 | $this->disconnect($socket);
130 | $this->stderr("Client disconnected. TCP connection lost: " . $socket);
131 | }
132 | else {
133 | $user = $this->getUserBySocket($socket);
134 | if (!$user->handshake) {
135 | $tmp = str_replace("\r", '', $buffer);
136 | if (strpos($tmp, "\n\n") === false ) {
137 | continue; // If the client has not finished sending the header, then wait before sending our upgrade response.
138 | }
139 | $this->doHandshake($user,$buffer);
140 | }
141 | else {
142 | //split packet into frame and send it to deframe
143 | $this->split_packet($numBytes,$buffer, $user);
144 | }
145 | }
146 | }
147 | }
148 | }
149 | }
150 |
151 | protected function connect($socket) {
152 | $user = new $this->userClass(uniqid('u'), $socket);
153 | $this->users[$user->id] = $user;
154 | $this->sockets[$user->id] = $socket;
155 | $this->connecting($user);
156 | }
157 |
158 | protected function disconnect($socket, $triggerClosed = true, $sockErrNo = null) {
159 | $disconnectedUser = $this->getUserBySocket($socket);
160 |
161 | if ($disconnectedUser !== null) {
162 | unset($this->users[$disconnectedUser->id]);
163 |
164 | if (array_key_exists($disconnectedUser->id, $this->sockets)) {
165 | unset($this->sockets[$disconnectedUser->id]);
166 | }
167 |
168 | if (!is_null($sockErrNo)) {
169 | socket_clear_error($socket);
170 | }
171 |
172 | if ($triggerClosed) {
173 | $this->stdout("Client disconnected. ".$disconnectedUser->socket);
174 | $this->closed($disconnectedUser);
175 | socket_close($disconnectedUser->socket);
176 | }
177 | else {
178 | $message = $this->frame('', $disconnectedUser, 'close');
179 | @socket_write($disconnectedUser->socket, $message, strlen($message));
180 | }
181 | }
182 | }
183 |
184 | protected function doHandshake($user, $buffer) {
185 | $magicGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
186 | $headers = array();
187 | $lines = explode("\n",$buffer);
188 | foreach ($lines as $line) {
189 | if (strpos($line,":") !== false) {
190 | $header = explode(":",$line,2);
191 | $headers[strtolower(trim($header[0]))] = trim($header[1]);
192 | }
193 | elseif (stripos($line,"get ") !== false) {
194 | preg_match("/GET (.*) HTTP/i", $buffer, $reqResource);
195 | $headers['get'] = trim($reqResource[1]);
196 | }
197 | }
198 | if (isset($headers['get'])) {
199 | $user->requestedResource = $headers['get'];
200 | }
201 | else {
202 | // todo: fail the connection
203 | $handshakeResponse = "HTTP/1.1 405 Method Not Allowed\r\n\r\n";
204 | }
205 | if (!isset($headers['host']) || !$this->checkHost($headers['host'])) {
206 | $handshakeResponse = "HTTP/1.1 400 Bad Request";
207 | }
208 | if (!isset($headers['upgrade']) || strtolower($headers['upgrade']) != 'websocket') {
209 | $handshakeResponse = "HTTP/1.1 400 Bad Request";
210 | }
211 | if (!isset($headers['connection']) || strpos(strtolower($headers['connection']), 'upgrade') === FALSE) {
212 | $handshakeResponse = "HTTP/1.1 400 Bad Request";
213 | }
214 | if (!isset($headers['sec-websocket-key'])) {
215 | $handshakeResponse = "HTTP/1.1 400 Bad Request";
216 | }
217 | else {
218 |
219 | }
220 | if (!isset($headers['sec-websocket-version']) || strtolower($headers['sec-websocket-version']) != 13) {
221 | $handshakeResponse = "HTTP/1.1 426 Upgrade Required\r\nSec-WebSocketVersion: 13";
222 | }
223 | if (($this->headerOriginRequired && !isset($headers['origin']) ) || ($this->headerOriginRequired && !$this->checkOrigin($headers['origin']))) {
224 | $handshakeResponse = "HTTP/1.1 403 Forbidden";
225 | }
226 | if (($this->headerSecWebSocketProtocolRequired && !isset($headers['sec-websocket-protocol'])) || ($this->headerSecWebSocketProtocolRequired && !$this->checkWebsocProtocol($headers['sec-websocket-protocol']))) {
227 | $handshakeResponse = "HTTP/1.1 400 Bad Request";
228 | }
229 | if (($this->headerSecWebSocketExtensionsRequired && !isset($headers['sec-websocket-extensions'])) || ($this->headerSecWebSocketExtensionsRequired && !$this->checkWebsocExtensions($headers['sec-websocket-extensions']))) {
230 | $handshakeResponse = "HTTP/1.1 400 Bad Request";
231 | }
232 |
233 | // Done verifying the _required_ headers and optionally required headers.
234 |
235 | if (isset($handshakeResponse)) {
236 | socket_write($user->socket,$handshakeResponse,strlen($handshakeResponse));
237 | $this->disconnect($user->socket);
238 | return;
239 | }
240 |
241 | $user->headers = $headers;
242 | $user->handshake = $buffer;
243 |
244 | $webSocketKeyHash = sha1($headers['sec-websocket-key'] . $magicGUID);
245 |
246 | $rawToken = "";
247 | for ($i = 0; $i < 20; $i++) {
248 | $rawToken .= chr(hexdec(substr($webSocketKeyHash,$i*2, 2)));
249 | }
250 | $handshakeToken = base64_encode($rawToken) . "\r\n";
251 |
252 | $subProtocol = (isset($headers['sec-websocket-protocol'])) ? $this->processProtocol($headers['sec-websocket-protocol']) : "";
253 | $extensions = (isset($headers['sec-websocket-extensions'])) ? $this->processExtensions($headers['sec-websocket-extensions']) : "";
254 |
255 | $handshakeResponse = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: $handshakeToken$subProtocol$extensions\r\n";
256 | socket_write($user->socket,$handshakeResponse,strlen($handshakeResponse));
257 | $this->connected($user);
258 | }
259 |
260 | protected function checkHost($hostName) {
261 | return true; // Override and return false if the host is not one that you would expect.
262 | // Ex: You only want to accept hosts from the my-domain.com domain,
263 | // but you receive a host from malicious-site.com instead.
264 | }
265 |
266 | protected function checkOrigin($origin) {
267 | return true; // Override and return false if the origin is not one that you would expect.
268 | }
269 |
270 | protected function checkWebsocProtocol($protocol) {
271 | return true; // Override and return false if a protocol is not found that you would expect.
272 | }
273 |
274 | protected function checkWebsocExtensions($extensions) {
275 | return true; // Override and return false if an extension is not found that you would expect.
276 | }
277 |
278 | protected function processProtocol($protocol) {
279 | return ""; // return either "Sec-WebSocket-Protocol: SelectedProtocolFromClientList\r\n" or return an empty string.
280 | // The carriage return/newline combo must appear at the end of a non-empty string, and must not
281 | // appear at the beginning of the string nor in an otherwise empty string, or it will be considered part of
282 | // the response body, which will trigger an error in the client as it will not be formatted correctly.
283 | }
284 |
285 | protected function processExtensions($extensions) {
286 | return ""; // return either "Sec-WebSocket-Extensions: SelectedExtensions\r\n" or return an empty string.
287 | }
288 |
289 | protected function getUserBySocket($socket) {
290 | foreach ($this->users as $user) {
291 | if ($user->socket == $socket) {
292 | return $user;
293 | }
294 | }
295 | return null;
296 | }
297 |
298 | public function stdout($message) {
299 | if ($this->interactive) {
300 | echo "$message\n";
301 | }
302 | }
303 |
304 | public function stderr($message) {
305 | if ($this->interactive) {
306 | echo "$message\n";
307 | }
308 | }
309 |
310 | protected function frame($message, $user, $messageType='text', $messageContinues=false) {
311 | switch ($messageType) {
312 | case 'continuous':
313 | $b1 = 0;
314 | break;
315 | case 'text':
316 | $b1 = ($user->sendingContinuous) ? 0 : 1;
317 | break;
318 | case 'binary':
319 | $b1 = ($user->sendingContinuous) ? 0 : 2;
320 | break;
321 | case 'close':
322 | $b1 = 8;
323 | break;
324 | case 'ping':
325 | $b1 = 9;
326 | break;
327 | case 'pong':
328 | $b1 = 10;
329 | break;
330 | }
331 | if ($messageContinues) {
332 | $user->sendingContinuous = true;
333 | }
334 | else {
335 | $b1 += 128;
336 | $user->sendingContinuous = false;
337 | }
338 |
339 | $length = strlen($message);
340 | $lengthField = "";
341 | if ($length < 126) {
342 | $b2 = $length;
343 | }
344 | elseif ($length < 65536) {
345 | $b2 = 126;
346 | $hexLength = dechex($length);
347 | //$this->stdout("Hex Length: $hexLength");
348 | if (strlen($hexLength)%2 == 1) {
349 | $hexLength = '0' . $hexLength;
350 | }
351 | $n = strlen($hexLength) - 2;
352 |
353 | for ($i = $n; $i >= 0; $i=$i-2) {
354 | $lengthField = chr(hexdec(substr($hexLength, $i, 2))) . $lengthField;
355 | }
356 | while (strlen($lengthField) < 2) {
357 | $lengthField = chr(0) . $lengthField;
358 | }
359 | }
360 | else {
361 | $b2 = 127;
362 | $hexLength = dechex($length);
363 | if (strlen($hexLength)%2 == 1) {
364 | $hexLength = '0' . $hexLength;
365 | }
366 | $n = strlen($hexLength) - 2;
367 |
368 | for ($i = $n; $i >= 0; $i=$i-2) {
369 | $lengthField = chr(hexdec(substr($hexLength, $i, 2))) . $lengthField;
370 | }
371 | while (strlen($lengthField) < 8) {
372 | $lengthField = chr(0) . $lengthField;
373 | }
374 | }
375 |
376 | return chr($b1) . chr($b2) . $lengthField . $message;
377 | }
378 |
379 | //check packet if he have more than one frame and process each frame individually
380 | protected function split_packet($length,$packet, $user) {
381 | //add PartialPacket and calculate the new $length
382 | if ($user->handlingPartialPacket) {
383 | $packet = $user->partialBuffer . $packet;
384 | $user->handlingPartialPacket = false;
385 | $length=strlen($packet);
386 | }
387 | $fullpacket=$packet;
388 | $frame_pos=0;
389 | $frame_id=1;
390 |
391 | while($frame_pos<$length) {
392 | $headers = $this->extractHeaders($packet);
393 | $headers_size = $this->calcoffset($headers);
394 | $framesize=$headers['length']+$headers_size;
395 |
396 | //split frame from packet and process it
397 | $frame=substr($fullpacket,$frame_pos,$framesize);
398 |
399 | if (($message = $this->deframe($frame, $user,$headers)) !== FALSE) {
400 | if ($user->hasSentClose) {
401 | $this->disconnect($user->socket);
402 | } else {
403 | if ((preg_match('//u', $message)) || ($headers['opcode']==2)) {
404 | //$this->stdout("Text msg encoded UTF-8 or Binary msg\n".$message);
405 | $this->process($user, $message);
406 | } else {
407 | $this->stderr("not UTF-8\n");
408 | }
409 | }
410 | }
411 | //get the new position also modify packet data
412 | $frame_pos+=$framesize;
413 | $packet=substr($fullpacket,$frame_pos);
414 | $frame_id++;
415 | }
416 | }
417 |
418 | protected function calcoffset($headers) {
419 | $offset = 2;
420 | if ($headers['hasmask']) {
421 | $offset += 4;
422 | }
423 | if ($headers['length'] > 65535) {
424 | $offset += 8;
425 | } elseif ($headers['length'] > 125) {
426 | $offset += 2;
427 | }
428 | return $offset;
429 | }
430 |
431 | protected function deframe($message, &$user) {
432 | //echo $this->strtohex($message);
433 | $headers = $this->extractHeaders($message);
434 | $pongReply = false;
435 | $willClose = false;
436 | switch($headers['opcode']) {
437 | case 0:
438 | case 1:
439 | case 2:
440 | break;
441 | case 8:
442 | // todo: close the connection
443 | $user->hasSentClose = true;
444 | return "";
445 | case 9:
446 | $pongReply = true;
447 | case 10:
448 | break;
449 | default:
450 | //$this->disconnect($user); // todo: fail connection
451 | $willClose = true;
452 | break;
453 | }
454 |
455 | /* Deal by split_packet() as now deframe() do only one frame at a time.
456 | if ($user->handlingPartialPacket) {
457 | $message = $user->partialBuffer . $message;
458 | $user->handlingPartialPacket = false;
459 | return $this->deframe($message, $user);
460 | }
461 | */
462 |
463 | if ($this->checkRSVBits($headers,$user)) {
464 | return false;
465 | }
466 |
467 | if ($willClose) {
468 | // todo: fail the connection
469 | return false;
470 | }
471 |
472 | $payload = $user->partialMessage . $this->extractPayload($message,$headers);
473 |
474 | if ($pongReply) {
475 | $reply = $this->frame($payload,$user,'pong');
476 | socket_write($user->socket,$reply,strlen($reply));
477 | return false;
478 | }
479 | if ($headers['length'] > strlen($this->applyMask($headers,$payload))) {
480 | $user->handlingPartialPacket = true;
481 | $user->partialBuffer = $message;
482 | return false;
483 | }
484 |
485 | $payload = $this->applyMask($headers,$payload);
486 |
487 | if ($headers['fin']) {
488 | $user->partialMessage = "";
489 | return $payload;
490 | }
491 | $user->partialMessage = $payload;
492 | return false;
493 | }
494 |
495 | protected function extractHeaders($message) {
496 | $header = array('fin' => $message[0] & chr(128),
497 | 'rsv1' => $message[0] & chr(64),
498 | 'rsv2' => $message[0] & chr(32),
499 | 'rsv3' => $message[0] & chr(16),
500 | 'opcode' => ord($message[0]) & 15,
501 | 'hasmask' => $message[1] & chr(128),
502 | 'length' => 0,
503 | 'mask' => "");
504 | $header['length'] = (ord($message[1]) >= 128) ? ord($message[1]) - 128 : ord($message[1]);
505 |
506 | if ($header['length'] == 126) {
507 | if ($header['hasmask']) {
508 | $header['mask'] = $message[4] . $message[5] . $message[6] . $message[7];
509 | }
510 | $header['length'] = ord($message[2]) * 256
511 | + ord($message[3]);
512 | }
513 | elseif ($header['length'] == 127) {
514 | if ($header['hasmask']) {
515 | $header['mask'] = $message[10] . $message[11] . $message[12] . $message[13];
516 | }
517 | $header['length'] = ord($message[2]) * 65536 * 65536 * 65536 * 256
518 | + ord($message[3]) * 65536 * 65536 * 65536
519 | + ord($message[4]) * 65536 * 65536 * 256
520 | + ord($message[5]) * 65536 * 65536
521 | + ord($message[6]) * 65536 * 256
522 | + ord($message[7]) * 65536
523 | + ord($message[8]) * 256
524 | + ord($message[9]);
525 | }
526 | elseif ($header['hasmask']) {
527 | $header['mask'] = $message[2] . $message[3] . $message[4] . $message[5];
528 | }
529 | //echo $this->strtohex($message);
530 | //$this->printHeaders($header);
531 | return $header;
532 | }
533 |
534 | protected function extractPayload($message,$headers) {
535 | $offset = 2;
536 | if ($headers['hasmask']) {
537 | $offset += 4;
538 | }
539 | if ($headers['length'] > 65535) {
540 | $offset += 8;
541 | }
542 | elseif ($headers['length'] > 125) {
543 | $offset += 2;
544 | }
545 | return substr($message,$offset);
546 | }
547 |
548 | protected function applyMask($headers,$payload) {
549 | $effectiveMask = "";
550 | if ($headers['hasmask']) {
551 | $mask = $headers['mask'];
552 | }
553 | else {
554 | return $payload;
555 | }
556 |
557 | while (strlen($effectiveMask) < strlen($payload)) {
558 | $effectiveMask .= $mask;
559 | }
560 | while (strlen($effectiveMask) > strlen($payload)) {
561 | $effectiveMask = substr($effectiveMask,0,-1);
562 | }
563 | return $effectiveMask ^ $payload;
564 | }
565 | protected function checkRSVBits($headers,$user) { // override this method if you are using an extension where the RSV bits are used.
566 | if (ord($headers['rsv1']) + ord($headers['rsv2']) + ord($headers['rsv3']) > 0) {
567 | //$this->disconnect($user); // todo: fail connection
568 | return true;
569 | }
570 | return false;
571 | }
572 |
573 | protected function strtohex($str) {
574 | $strout = "";
575 | for ($i = 0; $i < strlen($str); $i++) {
576 | $strout .= (ord($str[$i])<16) ? "0" . dechex(ord($str[$i])) : dechex(ord($str[$i]));
577 | $strout .= " ";
578 | if ($i%32 == 7) {
579 | $strout .= ": ";
580 | }
581 | if ($i%32 == 15) {
582 | $strout .= ": ";
583 | }
584 | if ($i%32 == 23) {
585 | $strout .= ": ";
586 | }
587 | if ($i%32 == 31) {
588 | $strout .= "\n";
589 | }
590 | }
591 | return $strout . "\n";
592 | }
593 |
594 | protected function printHeaders($headers) {
595 | echo "Array\n(\n";
596 | foreach ($headers as $key => $value) {
597 | if ($key == 'length' || $key == 'opcode') {
598 | echo "\t[$key] => $value\n\n";
599 | }
600 | else {
601 | echo "\t[$key] => ".$this->strtohex($value)."\n";
602 |
603 | }
604 |
605 | }
606 | echo ")\n";
607 | }
608 | }
609 |
--------------------------------------------------------------------------------