GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Failed Conditions
Push — master ( f2da15...888a68 )
by Charlotte
05:28
created

src/ProtocolParser.php (1 issue)

1
<?php
2
/**
3
 * Plasma Driver MySQL component
4
 * Copyright 2018 PlasmaPHP, All Rights Reserved
5
 *
6
 * Website: https://github.com/PlasmaPHP
7
 * License: https://github.com/PlasmaPHP/driver-mysql/blob/master/LICENSE
8
*/
9
10
namespace Plasma\Drivers\MySQL;
11
12
/**
13
 * The MySQL Protocol Parser.
14
 * @internal
15
 */
16
class ProtocolParser implements \Evenement\EventEmitterInterface {
17
    use \Evenement\EventEmitterTrait;
18
    
19
    /**
20
     * @var int
21
     */
22
    const STATE_INIT = 0;
23
    
24
    /**
25
     * @var int
26
     */
27
    const STATE_HANDSHAKE = 1;
28
    
29
    /**
30
     * @var int
31
     */
32
    const STATE_HANDSHAKE_ERROR = 2;
33
    
34
    /**
35
     * @var int
36
     */
37
    const STATE_AUTH = 5;
38
    
39
    /**
40
     * @var int
41
     */
42
    const STATE_AUTH_SENT = 6;
43
    
44
    /**
45
     * @var int
46
     */
47
    const STATE_AUTH_ERROR = 7;
48
    
49
    /**
50
     * @var int
51
     */
52
    const STATE_OK = 9;
53
    
54
    /**
55
     * @var int
56
     */
57
    const CLIENT_CAPABILITIES = (
58
        \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_LONG_PASSWORD |
59
        \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_LONG_FLAG |
60
        \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_LOCAL_FILES |
61
        \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_INTERACTIVE |
62
        \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_TRANSACTIONS |
63
        \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_SECURE_CONNECTION |
64
        \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_PROTOCOL_41 |
65
        \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_DEPRECATE_EOF
66
    );
67
    
68
    /**
69
     * @var int
70
     */
71
    const CLIENT_MAX_PACKET_SIZE = 0x1000000;
72
    
73
    /**
74
     * @var int
75
     */
76
    const CLIENT_CHARSET_NUMBER = 0x21;
77
    
78
    /**
79
     * @var \Plasma\Drivers\MySQL\Driver
80
     */
81
    protected $driver;
82
    
83
    /**
84
     * @var \React\Socket\ConnectionInterface
85
     */
86
    protected $connection;
87
    
88
    /**
89
     * @var int
90
     */
91
    protected $state = ProtocolParser::STATE_INIT;
92
    
93
    /**
94
     * @var \Plasma\BinaryBuffer
95
     */
96
    protected $buffer;
97
    
98
    /**
99
     * @var \Plasma\BinaryBuffer
100
     */
101
    protected $messageBuffer;
102
    
103
    /**
104
     * The sequence ID is incremented with each packet and may wrap around.
105
     * It starts at 0 and is reset to 0 when a new command begins in the Command Phase.
106
     * @var int
107
     * @see https://dev.mysql.com/doc/internals/en/sequence-id.html
108
     */
109
    protected $sequenceID = 0;
110
    
111
    /**
112
     * @var \Plasma\Drivers\MySQL\Messages\HandshakeMessage|null
113
     */
114
    protected $handshakeMessage;
115
    
116
    /**
117
     * @var \Plasma\CommandInterface|null
118
     */
119
    protected $currentCommand;
120
    
121
    /**
122
     * @var callable|null
123
     */
124
    protected $parseCallback;
125
    
126
    /**
127
     * Constructor.
128
     * @param \Plasma\Drivers\MySQL\Driver       $driver
129
     * @param \React\Socket\ConnectionInterface  $connection
130
     */
131 13
    function __construct(\Plasma\Drivers\MySQL\Driver $driver, \React\Socket\ConnectionInterface $connection) {
132 13
        $this->driver = $driver;
133 13
        $this->connection = $connection;
134
        
135 13
        $this->buffer = new \Plasma\BinaryBuffer();
136 13
        $this->messageBuffer = new \Plasma\BinaryBuffer();
137
        
138 13
        $this->addEvents();
139 13
    }
140
    
141
    /**
142
     * Invoke a command to execute.
143
     * @param \Plasma\CommandInterface|null  $command
144
     * @return void
145
     */
146 13
    function invokeCommand(?\Plasma\CommandInterface $command): void {
147 13
        if($command === null) {
148
            return;
149
        }
150
        
151 13
        $this->currentCommand = $command;
152 13
        $this->processCommand();
153 13
    }
154
    
155
    /**
156
     * Executes a command, without handling any aftermath.
157
     * The `onComplete` callback will be immediately invoked, regardless of the `waitForCompletion` value.
158
     * @param \Plasma\CommandInterface  $command
159
     * @return void
160
     */
161
    function executeCommand(\Plasma\CommandInterface $command): void {
162
        $this->processCommand($command);
163
    }
164
    
165
    /**
166
     * Marks the command itself as finished, if currently running.
167
     * @param \Plasma\Drivers\MySQL\Commands\CommandInterface  $command
168
     * @return void
169
     */
170
    function markCommandAsFinished(\Plasma\CommandInterface $command): void {
171
        if($command === $this->currentCommand) {
172
            $this->currentCommand = null;
173
        }
174
        
175
        $command->onComplete();
176
    }
177
    
178
    /**
179
     * Get the parser state.
180
     * @return int
181
     */
182 1
    function getState(): int {
183 1
        return $this->state;
184
    }
185
    
186
    /**
187
     * Get the handshake message, or null.
188
     * @return \Plasma\Drivers\MySQL\Messages\HandshakeMessage|null
189
     */
190 11
    function getHandshakeMessage(): ?\Plasma\Drivers\MySQL\Messages\HandshakeMessage {
191 11
        return $this->handshakeMessage;
192
    }
193
    
194
    /**
195
     * Sends a packet to the server.
196
     * @return void
197
     */
198 13
    function sendPacket(string $packet): void {
199 13
        $length = \Plasma\BinaryBuffer::writeInt3(\strlen($packet));
200 13
        $sequence = \Plasma\BinaryBuffer::writeInt1((++$this->sequenceID));
201
        
202 13
        $this->connection->write($length.$sequence.$packet);
203 13
    }
204
    
205
    /**
206
     * Sets the parse callback.
207
     * @param callable $callback
208
     * @return void
209
     */
210
    function setParseCallback(callable $callback): void {
211
        $this->parseCallback($callback);
212
    }
213
    
214
    /**
215
     * Processes a command.
216
     * @param \Plasma\CommandInterface|null  $command
217
     * @return void
218
     */
219 13
    protected function processCommand(?\Plasma\CommandInterface $command = null) {
220 13
        if($command === null && $this->currentCommand instanceof \Plasma\CommandInterface) {
221 13
            $command = $this->currentCommand;
222
            
223 13
            if($this->currentCommand instanceof \Plasma\Drivers\MySQL\Commands\CommandInterface) {
224 13
                $state = $command->setParserState();
225 13
                if($state !== -1) {
226 13
                    $this->state = $state;
227
                }
228
            }
229
        }
230
        
231 13
        if($command === null) {
232
            return;
233
        }
234
        
235 13
        if($command instanceof \Plasma\Drivers\MySQL\Commands\CommandInterface && $command->resetSequence()) {
236
            $this->sequenceID = -1;
237
        }
238
        
239 13
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Processing command '.get_class($command)) || true));
240
        
241 13
        $this->sendPacket($command->getEncodedMessage());
242
        
243 13
        if($command !== $this->currentCommand || !$command->waitForCompletion()) {
244 2
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Mark command as completed') || true));
245 2
            $command->onComplete();
246
            
247 2
            if($command === $this->currentCommand) {
248 2
                $this->currentCommand = null;
249
            }
250
        }
251 13
    }
252
    
253
    /**
254
     * Processes the buffer.
255
     * @return void
256
     */
257 13
    protected function processBuffer() {
258 13
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('ProtocolParser::processBuffer called') || true));
259
        
260 13
        if($this->buffer->getSize() < 4) {
261
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Not enough data received for packet header ('.$this->buffer->getSize().')') || true));
262
            return;
263
        }
264
        
265 13
        $buffer = clone $this->buffer;
266
        
267 13
        $length = $buffer->readInt3();
268 13
        $this->sequenceID = $buffer->readInt1();
269
        
270 13
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('First 10 bytes: '.implode(', ', unpack('C*', \substr($this->buffer->getContents(), 0, 10)))) || true));
271 13
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Read packet header length ('.$length.') and sequence ('.$this->sequenceID.')') || true));
272
        
273 13
        if($length === 0xFFFFFF) {
274
            $this->buffer->read(($length + 4));
275
            $this->messageBuffer->append($buffer->read($length));
276
            
277
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('returned, 16mb packet received, waiting for the last one to arrive') || true));
278
            return;
279 13
        } elseif($this->messageBuffer->getSize() > 0) {
280
            $this->messageBuffer->append($buffer->read($length));
281
            $buffer = $this->messageBuffer;
282
            $this->messageBuffer = new \Plasma\BinaryBuffer();
283
        }
284
        
285 13
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('regular packet') || true));
286
        
287 13
        if($buffer->getSize() < $length) {
288
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('returned, insufficent length: '.$buffer->getSize().', '.$length.' required') || true));
289
            return;
290
        }
291
        
292 13
        if($length > 0) {
293 13
            $this->buffer->read(($length + 4));
294 13
            $buffer->slice(0, $length);
295
        } else {
296
            $this->buffer->slice($buffer->getSize());
297
        }
298
        
299 13
        if($buffer->getSize() === 0) {
300
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Buffer length is 0') || true));
301
            return;
302
        }
303
        
304
        /** @var \Plasma\Drivers\MySQL\Messages\MessageInterface  $message */
305 13
        $message = null;
306
        
307 13
        if($this->state === static::STATE_INIT) {
308 13
            $message = new \Plasma\Drivers\MySQL\Messages\HandshakeMessage($this);
309
        } else {
310 11
            $firstChar = $buffer->read(1);
311 11
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Received Message char "'.$firstChar.'" (0x'.\dechex(\ord($firstChar)).') - buffer length: '.$buffer->getSize()) || true));
312
            
313
            switch(true) {
314 11
                case ($firstChar === \Plasma\Drivers\MySQL\Messages\ErrResponseMessage::getID()):
315 1
                    $message = new \Plasma\Drivers\MySQL\Messages\ErrResponseMessage($this);
316 1
                break;
317 10
                case ($firstChar === \Plasma\Drivers\MySQL\Messages\EOFMessage::getID() && $length < 6):
318
                    $message = new \Plasma\Drivers\MySQL\Messages\EOFMessage($this);
319
                break;
320 10
                case ($this->currentCommand instanceof \Plasma\Drivers\MySQL\Commands\PrepareCommand && $firstChar === "\x00"):
321
                    $message = new \Plasma\Drivers\MySQL\Messages\PrepareStatementOkMessage($this);
322
                break;
323 10
                case ($firstChar === "\x00"):
324 10
                    $message = new \Plasma\Drivers\MySQL\Messages\OkResponseMessage($this);
325 10
                break;
326
                default:
327
                    $buffer->prepend($firstChar);
328
                    
329
                    if($this->parseCallback !== null) {
330
                        $parse = $this->parseCallback;
331
                        $this->parseCallback = null;
332
                        
333
                        $caller = new \Plasma\Drivers\MySQL\ProtocolOnNextCaller($this, $buffer);
334
                        $parse($caller);
335
                    } else {
336
                        $caller = new \Plasma\Drivers\MySQL\ProtocolOnNextCaller($this, $buffer);
337
                        $this->currentCommand->onNext($caller);
338
                    }
339
                    
340
                    \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Left over buffer: '.$buffer->getSize()) || true));
341
                    /*if($buffer->getSize() > 0) {
342
                        $this->buffer->prepend($buffer->getContents());
343
                    }*/
344
                    
345
                    if($this->buffer->getSize() > 0) {
346
                        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Scheduling future read with '.$this->buffer->getSize().' bytes') || true));
347
                        
348
                        $this->driver->getLoop()->futureTick(function () {
349
                            $this->processBuffer();
350
                        });
351
                    }
352
                    
353
                    return;
354
                break;
1 ignored issue
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
355
            }
356
        }
357
        
358 13
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Received Message '.\get_class($message)) || true));
359
        
360 13
        $state = $message->setParserState();
361 13
        if($state !== -1) {
362 13
            $this->state = $state;
363
        }
364
        
365 13
        if($message instanceof \Plasma\Drivers\MySQL\Messages\HandshakeMessage) {
366 13
            $this->handshakeMessage = $message;
367
        }
368
        
369 13
        $this->handleMessage($buffer, $message);
370 13
    }
371
    
372
    /**
373
     * Handles an incoming message.
374
     * @param \Plasma\BinaryBuffer                             $buffer
375
     * @param \Plasma\Drivers\MySQL\Messages\MessageInterface  $message
376
     * @return void
377
     */
378 13
    function handleMessage(\Plasma\BinaryBuffer $buffer, \Plasma\Drivers\MySQL\Messages\MessageInterface $message) {
379
        try {
380 13
            $buffer = $message->parseMessage($buffer, $this);
381 13
            if(!$buffer) {
382 10
                \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('returned handle (unsufficent buffer length)') || true));
383 10
                return;
384
            }
385
            
386 13
            if($this->currentCommand !== null) {
387
                if(
388 1
                    ($message instanceof \Plasma\Drivers\MySQL\Messages\OkResponseMessage || $message instanceof \Plasma\Drivers\MySQL\Messages\EOFMessage)
389 1
                    && $this->currentCommand->hasFinished()
390
                ) {
391
                    $command = $this->currentCommand;
392
                    $this->currentCommand = null;
393
                    
394
                    $command->onComplete();
395 1
                } elseif($message instanceof \Plasma\Drivers\MySQL\Messages\ErrResponseMessage) {
396 1
                    $error = new \Plasma\Exception($message->errorMessage, $message->errorCode);
397
                    
398 1
                    $command = $this->currentCommand;
399 1
                    $this->currentCommand = null;
400
                    
401 1
                    $command->onError($error);
402
                } else {
403
                    $command = $this->currentCommand;
404
                    $command->onNext($message);
405
                    
406
                    if($command->hasFinished()) {
407
                        if($this->currentCommand === $command) {
408
                            $this->currentCommand = null;
409
                        }
410
                        
411 1
                        $command->onComplete();
412
                    }
413
                }
414 13
            } elseif($message instanceof \Plasma\Drivers\MySQL\Messages\ErrResponseMessage) {
415
                $error = new \Plasma\Exception($message->errorMessage, $message->errorCode);
416
                $this->emit('error', array($error));
417
            }
418
            
419 13
            $this->emit('message', array($message));
420
        } catch (\Plasma\Drivers\MySQL\Messages\ParseException $e) {
421
            $state = $e->getState();
422
            if($state !== null) {
423
                $this->state = $state;
424
            }
425
            
426
            $buffer = $e->getBuffer();
427
            if($buffer !== null) {
428
                $this->buffer->clear();
429
                $this->buffer->append($buffer);
430
            }
431
            
432
            if($this->currentCommand !== null) {
433
                $this->currentCommand->onError($e);
434
            }
435
            
436
            $this->emit('error', array($e));
437
            $this->connection->close();
438
        }
439
        
440 13
        if($this->buffer->getSize() > 0) {
441
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Scheduling future read (msg) with '.$this->buffer->getSize().' bytes') || true));
442
            
443
            $this->driver->getLoop()->futureTick(function () {
444
                $this->processBuffer();
445
            });
446
        }
447 13
    }
448
    
449
    /**
450
     * Adds the events to the connection.
451
     * @return void
452
     */
453 13
    protected function addEvents() {
454
        $this->connection->on('data', function ($chunk) {
455 13
            $this->buffer->append($chunk);
456 13
            $this->processBuffer();
457 13
        });
458
        
459
        $this->connection->on('close', function () {
460 2
            $this->handleClose();
461 13
        });
462 13
    }
463
    
464
    /**
465
     * Connection close handler.
466
     * @return void
467
     */
468 2
    protected function handleClose() {
469 2
        if($this->state === static::STATE_AUTH || $this->state === static::STATE_AUTH_SENT) {
470
            $this->state = static::STATE_AUTH_ERROR;
471
        }
472 2
    }
473
}
474