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.
Test Failed
Push — master ( c84b74...6c770b )
by Charlotte
03:58
created

src/ProtocolParser.php (3 issues)

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 16
    function __construct(\Plasma\Drivers\MySQL\Driver $driver, \React\Socket\ConnectionInterface $connection) {
132 16
        $this->driver = $driver;
133 16
        $this->connection = $connection;
134
        
135 16
        $this->buffer = new \Plasma\BinaryBuffer();
136 16
        $this->messageBuffer = new \Plasma\BinaryBuffer();
137
        
138 16
        $this->addEvents();
139 16
    }
140
    
141
    /**
142
     * Invoke a command to execute.
143
     * @param \Plasma\CommandInterface|null  $command
144
     * @return void
145
     */
146 16
    function invokeCommand(?\Plasma\CommandInterface $command): void {
147 16
        if($command === null) {
148 3
            return;
149
        }
150
        
151 16
        $this->currentCommand = $command;
152 16
        $this->processCommand();
153 16
    }
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 3
    function markCommandAsFinished(\Plasma\CommandInterface $command): void {
171 3
        if($command === $this->currentCommand) {
172 3
            $this->currentCommand = null;
173
        }
174
        
175 3
        $command->onComplete();
176 3
    }
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 14
    function getHandshakeMessage(): ?\Plasma\Drivers\MySQL\Messages\HandshakeMessage {
191 14
        return $this->handshakeMessage;
192
    }
193
    
194
    /**
195
     * Sends a packet to the server.
196
     * @return void
197
     */
198 16
    function sendPacket(string $packet): void {
199 16
        $maxSize = static::CLIENT_MAX_PACKET_SIZE - 4;
200
        
201
        do {
202 16
            $partial = \substr($packet, 0, $maxSize);
203 16
            $packet = \substr($packet, $maxSize);
204
            
205 16
            $length = \Plasma\BinaryBuffer::writeInt3(\strlen($partial));
206 16
            $sequence = \Plasma\BinaryBuffer::writeInt1((++$this->sequenceID));
207
            
208 16
            $this->connection->write($length.$sequence.$partial);
209 16
        } while(\strlen($packet) > $maxSize);
210 16
    }
211
    
212
    /**
213
     * Sets the parse callback.
214
     * @param callable $callback
215
     * @return void
216
     */
217
    function setParseCallback(callable $callback): void {
218
        $this->parseCallback($callback);
219
    }
220
    
221
    /**
222
     * Processes a command.
223
     * @param \Plasma\CommandInterface|null  $command
224
     * @return void
225
     */
226 16
    protected function processCommand(?\Plasma\CommandInterface $command = null) {
227 16
        if($command === null && $this->currentCommand instanceof \Plasma\CommandInterface) {
228 16
            $command = $this->currentCommand;
229
            
230 16
            if($this->currentCommand instanceof \Plasma\Drivers\MySQL\Commands\CommandInterface) {
231 16
                $state = $command->setParserState();
232 16
                if($state !== -1) {
233 16
                    $this->state = $state;
234
                }
235
            }
236
        }
237
        
238 16
        if($command === null) {
239
            return;
240
        }
241
        
242 16
        if(!($command instanceof \Plasma\Drivers\MySQL\Commands\CommandInterface) || $command->resetSequence()) {
243 9
            $this->sequenceID = -1;
244
        }
245
        
246 16
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Processing command '.get_class($command)) || true));
247
        
248 16
        $this->sendPacket($command->getEncodedMessage());
249
        
250 16
        if($command !== $this->currentCommand || !$command->waitForCompletion()) {
251 2
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Mark command as completed') || true));
252 2
            $command->onComplete();
253
            
254 2
            if($command === $this->currentCommand) {
255 2
                $this->currentCommand = null;
256
            }
257
        }
258 16
    }
259
    
260
    /**
261
     * Processes the buffer.
262
     * @return void
263
     */
264 16
    protected function processBuffer() {
265 16
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('ProtocolParser::processBuffer called') || true));
266
        
267 16
        if($this->buffer->getSize() < 4) {
268
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Not enough data received for packet header ('.$this->buffer->getSize().')') || true));
269
            return;
270
        }
271
        
272 16
        $buffer = clone $this->buffer;
273
        
274 16
        $length = $buffer->readInt3();
275 16
        $this->sequenceID = $buffer->readInt1();
276
        
277 16
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('First 10 bytes: '.implode(', ', unpack('C*', \substr($this->buffer->getContents(), 0, 10)))) || true));
278 16
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Read packet header length ('.$length.') and sequence ('.$this->sequenceID.')') || true));
279
        
280 16
        if($length === 0xFFFFFF) {
281
            $this->buffer->read(($length + 4));
282
            $this->messageBuffer->append($buffer->read($length));
283
            
284
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('returned, 16mb packet received, waiting for the last one to arrive') || true));
285
            return;
286 16
        } elseif($this->messageBuffer->getSize() > 0) {
287
            $this->messageBuffer->append($buffer->read($length));
288
            $buffer = $this->messageBuffer;
289
            $this->messageBuffer = new \Plasma\BinaryBuffer();
290
        }
291
        
292 16
        if($buffer->getSize() < $length) {
293
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('returned, insufficent length: '.$buffer->getSize().', '.$length.' required') || true));
294
            return;
295
        }
296
        
297 16
        if($length > 0) {
298 16
            $this->buffer->read(($length + 4));
299 16
            $buffer->slice(0, $length);
300
        } else {
301
            $this->buffer->slice($buffer->getSize());
302
        }
303
        
304 16
        if($buffer->getSize() === 0) {
305
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Buffer length is 0') || true));
306
            return;
307
        }
308
        
309
        /** @var \Plasma\Drivers\MySQL\Messages\MessageInterface  $message */
310 16
        $message = null;
311
        
312 16
        if($this->state === static::STATE_INIT) {
313 16
            $message = new \Plasma\Drivers\MySQL\Messages\HandshakeMessage($this);
314
        } else {
315 14
            $firstChar = $buffer->read(1);
316 14
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Received Message char "'.$firstChar.'" (0x'.\dechex(\ord($firstChar)).') - buffer length: '.$buffer->getSize()) || true));
317
            
318 14
            $okRespID = \Plasma\Drivers\MySQL\Messages\OkResponseMessage::getID();
319
            $isOkMessage = (
320
                (
321 14
                    $firstChar === $okRespID ||
322
                    (
323 6
                        $firstChar === \Plasma\Drivers\MySQL\Messages\EOFMessage::getID() &&
324 14
                        ($this->handshakeMessage->capability & \Plasma\Drivers\MySQL\CapabilityFlags::CLIENT_DEPRECATE_EOF) !== 0
325
                    )
326
                ) &&
327 14
                !($this->currentCommand instanceof \Plasma\Drivers\MySQL\Commands\StatementExecuteCommand)
328
            );
329
            
330
            switch(true) {
331 14
                case ($firstChar === \Plasma\Drivers\MySQL\Messages\ErrResponseMessage::getID()):
332 1
                    $message = new \Plasma\Drivers\MySQL\Messages\ErrResponseMessage($this);
333 1
                break;
1 ignored issue
show
Case breaking statement indented incorrectly; expected 20 spaces, found 16
Loading history...
334 13
                case ($this->currentCommand instanceof \Plasma\Drivers\MySQL\Commands\StatementPrepareCommand && $firstChar === $okRespID):
1 ignored issue
show
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
335 2
                    $message = new \Plasma\Drivers\MySQL\Messages\PrepareStatementOkMessage($this);
336 2
                break;
337 13
                case $isOkMessage:
1 ignored issue
show
Line indented incorrectly; expected 12 spaces, found 16
Loading history...
338 13
                    $message = new \Plasma\Drivers\MySQL\Messages\OkResponseMessage($this);
339 13
                break;
340 5
                case ($firstChar === \Plasma\Drivers\MySQL\Messages\EOFMessage::getID() && $length < 6):
341
                    $message = new \Plasma\Drivers\MySQL\Messages\EOFMessage($this);
342
                break;
343
                default:
344 5
                    $buffer->prepend($firstChar);
345
                    
346 5
                    if($this->parseCallback !== null) {
347
                        $parse = $this->parseCallback;
348
                        $this->parseCallback = null;
349
                        
350
                        $caller = new \Plasma\Drivers\MySQL\ProtocolOnNextCaller($this, $buffer);
351
                        $parse($caller);
352 5
                    } elseif($this->currentCommand !== null) {
353 5
                        $caller = new \Plasma\Drivers\MySQL\ProtocolOnNextCaller($this, $buffer);
354 5
                        $this->currentCommand->onNext($caller);
355
                    }
356
                    
357 5
                    \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Left over buffer: '.$buffer->getSize()) || true));
358
                    
359 5
                    if($this->buffer->getSize() > 0) {
360 5
                        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Scheduling future read with '.$this->buffer->getSize().' bytes') || true));
361
                        
362
                        $this->driver->getLoop()->futureTick(function () {
363 5
                            $this->processBuffer();
364 5
                        });
365
                    }
366
                    
367 5
                    return;
368
                break;
369
            }
370
        }
371
        
372 16
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Received Message '.\get_class($message)) || true));
373
        
374 16
        $state = $message->setParserState();
375 16
        if($state !== -1) {
376 16
            $this->state = $state;
377
        }
378
        
379 16
        if($message instanceof \Plasma\Drivers\MySQL\Messages\HandshakeMessage) {
380 16
            $this->handshakeMessage = $message;
381
        }
382
        
383 16
        $this->handleMessage($buffer, $message);
384 16
    }
385
    
386
    /**
387
     * Handles an incoming message.
388
     * @param \Plasma\BinaryBuffer                             $buffer
389
     * @param \Plasma\Drivers\MySQL\Messages\MessageInterface  $message
390
     * @return void
391
     */
392 16
    function handleMessage(\Plasma\BinaryBuffer $buffer, \Plasma\Drivers\MySQL\Messages\MessageInterface $message) {
393
        try {
394 16
            $buffer = $message->parseMessage($buffer, $this);
395 16
            if(!$buffer) {
396
                \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('returned handle (unsufficent buffer length)') || true));
397
                return;
398
            }
399
            
400 16
            if($this->currentCommand !== null) {
401
                if(
402 14
                    ($message instanceof \Plasma\Drivers\MySQL\Messages\OkResponseMessage || $message instanceof \Plasma\Drivers\MySQL\Messages\EOFMessage)
403 14
                    && $this->currentCommand->hasFinished()
404
                ) {
405 13
                    $command = $this->currentCommand;
406 13
                    $this->currentCommand = null;
407
                    
408 13
                    $command->onComplete();
409 6
                } elseif($message instanceof \Plasma\Drivers\MySQL\Messages\ErrResponseMessage) {
410 1
                    $error = new \Plasma\Exception($message->errorMessage, $message->errorCode);
411
                    
412 1
                    $command = $this->currentCommand;
413 1
                    $this->currentCommand = null;
414
                    
415 1
                    $command->onError($error);
416
                } else {
417 5
                    $command = $this->currentCommand;
418 5
                    $command->onNext($message);
419
                    
420 5
                    if($command->hasFinished()) {
421 3
                        if($this->currentCommand === $command) {
422
                            $this->currentCommand = null;
423
                        }
424
                        
425 14
                        $command->onComplete();
426
                    }
427
                }
428 16
            } elseif($message instanceof \Plasma\Drivers\MySQL\Messages\ErrResponseMessage) {
429
                \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug(
430
                    'Received Error Response Message with message: '.$message->errorMessage
431
                ) || true));
432
                
433
                $error = new \Plasma\Exception($message->errorMessage, $message->errorCode);
434
                $this->emit('error', array($error));
435
            }
436
            
437 16
            $this->emit('message', array($message));
438
        } catch (\Plasma\Drivers\MySQL\Messages\ParseException $e) {
439
            $state = $e->getState();
440
            if($state !== null) {
441
                $this->state = $state;
442
            }
443
            
444
            $buffer = $e->getBuffer();
445
            if($buffer !== null) {
446
                $this->buffer->clear();
447
                $this->buffer->append($buffer);
448
            }
449
            
450
            if($this->currentCommand !== null) {
451
                $this->currentCommand->onError($e);
452
            }
453
            
454
            $this->emit('error', array($e));
455
            $this->connection->close();
456
        }
457
        
458 16
        if($this->buffer->getSize() > 0) {
459 2
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Scheduling future read (msg) with '.$this->buffer->getSize().' bytes') || true));
460
            
461
            $this->driver->getLoop()->futureTick(function () {
462 2
                $this->processBuffer();
463 2
            });
464
        }
465 16
    }
466
    
467
    /**
468
     * Adds the events to the connection.
469
     * @return void
470
     */
471 16
    protected function addEvents() {
472
        $this->connection->on('data', function ($chunk) {
473 16
            $this->buffer->append($chunk);
474 16
            $this->processBuffer();
475 16
        });
476
        
477
        $this->connection->on('close', function () {
478 4
            $this->handleClose();
479 16
        });
480 16
    }
481
    
482
    /**
483
     * Connection close handler.
484
     * @return void
485
     */
486 4
    protected function handleClose() {
487 4
        if($this->state === static::STATE_AUTH || $this->state === static::STATE_AUTH_SENT) {
488
            $this->state = static::STATE_AUTH_ERROR;
489
        }
490 4
    }
491
}
492