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 ( 888a68...c84b74 )
by Charlotte
05:53
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 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
            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
    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 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
        $length = \Plasma\BinaryBuffer::writeInt3(\strlen($packet));
200 16
        $sequence = \Plasma\BinaryBuffer::writeInt1((++$this->sequenceID));
201
        
202 16
        $this->connection->write($length.$sequence.$packet);
203 16
    }
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 16
    protected function processCommand(?\Plasma\CommandInterface $command = null) {
220 16
        if($command === null && $this->currentCommand instanceof \Plasma\CommandInterface) {
221 16
            $command = $this->currentCommand;
222
            
223 16
            if($this->currentCommand instanceof \Plasma\Drivers\MySQL\Commands\CommandInterface) {
224 16
                $state = $command->setParserState();
225 16
                if($state !== -1) {
226 16
                    $this->state = $state;
227
                }
228
            }
229
        }
230
        
231 16
        if($command === null) {
232
            return;
233
        }
234
        
235 16
        if($command instanceof \Plasma\Drivers\MySQL\Commands\CommandInterface && $command->resetSequence()) {
236
            $this->sequenceID = -1;
237
        }
238
        
239 16
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Processing command '.get_class($command)) || true));
240
        
241 16
        $this->sendPacket($command->getEncodedMessage());
242
        
243 16
        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 16
    }
252
    
253
    /**
254
     * Processes the buffer.
255
     * @return void
256
     */
257 16
    protected function processBuffer() {
258 16
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('ProtocolParser::processBuffer called') || true));
259
        
260 16
        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 16
        $buffer = clone $this->buffer;
266
        
267 16
        $length = $buffer->readInt3();
268 16
        $this->sequenceID = $buffer->readInt1();
269
        
270 16
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('First 10 bytes: '.implode(', ', unpack('C*', \substr($this->buffer->getContents(), 0, 10)))) || true));
271 16
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Read packet header length ('.$length.') and sequence ('.$this->sequenceID.')') || true));
272
        
273 16
        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 16
        } 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 16
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('regular packet') || true));
286
        
287 16
        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 16
        if($length > 0) {
293 16
            $this->buffer->read(($length + 4));
294 16
            $buffer->slice(0, $length);
295
        } else {
296
            $this->buffer->slice($buffer->getSize());
297
        }
298
        
299 16
        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 16
        $message = null;
306
        
307 16
        if($this->state === static::STATE_INIT) {
308 16
            $message = new \Plasma\Drivers\MySQL\Messages\HandshakeMessage($this);
309
        } else {
310 14
            $firstChar = $buffer->read(1);
311 14
            \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 14
                case ($firstChar === \Plasma\Drivers\MySQL\Messages\ErrResponseMessage::getID()):
315 1
                    $message = new \Plasma\Drivers\MySQL\Messages\ErrResponseMessage($this);
316 1
                break;
317 13
                case ($firstChar === \Plasma\Drivers\MySQL\Messages\EOFMessage::getID() && $length < 6):
318
                    $message = new \Plasma\Drivers\MySQL\Messages\EOFMessage($this);
319
                break;
320 13
                case ($this->currentCommand instanceof \Plasma\Drivers\MySQL\Commands\PrepareCommand && $firstChar === "\x00"):
321
                    $message = new \Plasma\Drivers\MySQL\Messages\PrepareStatementOkMessage($this);
322
                break;
323 13
                case ($firstChar === "\x00"):
324 13
                    $message = new \Plasma\Drivers\MySQL\Messages\OkResponseMessage($this);
325 13
                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
                    } elseif($this->currentCommand !== null) {
1 ignored issue
show
Line indented incorrectly; expected 16 spaces, found 20
Loading history...
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
                    
342
                    if($this->buffer->getSize() > 0) {
343
                        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Scheduling future read with '.$this->buffer->getSize().' bytes') || true));
344
                        
345
                        $this->driver->getLoop()->futureTick(function () {
346
                            $this->processBuffer();
347
                        });
348
                    }
349
                    
350
                    return;
351
                break;
352
            }
353
        }
354
        
355 16
        \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Received Message '.\get_class($message)) || true));
356
        
357 16
        $state = $message->setParserState();
358 16
        if($state !== -1) {
359 16
            $this->state = $state;
360
        }
361
        
362 16
        if($message instanceof \Plasma\Drivers\MySQL\Messages\HandshakeMessage) {
363 16
            $this->handshakeMessage = $message;
364
        }
365
        
366 16
        $this->handleMessage($buffer, $message);
367 16
    }
368
    
369
    /**
370
     * Handles an incoming message.
371
     * @param \Plasma\BinaryBuffer                             $buffer
372
     * @param \Plasma\Drivers\MySQL\Messages\MessageInterface  $message
373
     * @return void
374
     */
375 16
    function handleMessage(\Plasma\BinaryBuffer $buffer, \Plasma\Drivers\MySQL\Messages\MessageInterface $message) {
376
        try {
377 16
            $buffer = $message->parseMessage($buffer, $this);
378 16
            if(!$buffer) {
379 13
                \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('returned handle (unsufficent buffer length)') || true));
380 13
                return;
381
            }
382
            
383 16
            if($this->currentCommand !== null) {
384
                if(
385 1
                    ($message instanceof \Plasma\Drivers\MySQL\Messages\OkResponseMessage || $message instanceof \Plasma\Drivers\MySQL\Messages\EOFMessage)
386 1
                    && $this->currentCommand->hasFinished()
387
                ) {
388
                    $command = $this->currentCommand;
389
                    $this->currentCommand = null;
390
                    
391
                    $command->onComplete();
392 1
                } elseif($message instanceof \Plasma\Drivers\MySQL\Messages\ErrResponseMessage) {
393 1
                    $error = new \Plasma\Exception($message->errorMessage, $message->errorCode);
394
                    
395 1
                    $command = $this->currentCommand;
396 1
                    $this->currentCommand = null;
397
                    
398 1
                    $command->onError($error);
399
                } else {
400
                    $command = $this->currentCommand;
401
                    $command->onNext($message);
402
                    
403
                    if($command->hasFinished()) {
404
                        if($this->currentCommand === $command) {
405
                            $this->currentCommand = null;
406
                        }
407
                        
408 1
                        $command->onComplete();
409
                    }
410
                }
411 16
            } elseif($message instanceof \Plasma\Drivers\MySQL\Messages\ErrResponseMessage) {
412
                \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug(
413
                    'Received Error Response Message with message: '.$message->errorMessage
414
                ) || true));
415
                
416
                $error = new \Plasma\Exception($message->errorMessage, $message->errorCode);
417
                $this->emit('error', array($error));
418
            }
419
            
420 16
            $this->emit('message', array($message));
421
        } catch (\Plasma\Drivers\MySQL\Messages\ParseException $e) {
422
            $state = $e->getState();
423
            if($state !== null) {
424
                $this->state = $state;
425
            }
426
            
427
            $buffer = $e->getBuffer();
428
            if($buffer !== null) {
429
                $this->buffer->clear();
430
                $this->buffer->append($buffer);
431
            }
432
            
433
            if($this->currentCommand !== null) {
434
                $this->currentCommand->onError($e);
435
            }
436
            
437
            $this->emit('error', array($e));
438
            $this->connection->close();
439
        }
440
        
441 16
        if($this->buffer->getSize() > 0) {
442
            \assert((\Plasma\Drivers\MySQL\Messages\MessageUtility::debug('Scheduling future read (msg) with '.$this->buffer->getSize().' bytes') || true));
443
            
444
            $this->driver->getLoop()->futureTick(function () {
445
                $this->processBuffer();
446
            });
447
        }
448 16
    }
449
    
450
    /**
451
     * Adds the events to the connection.
452
     * @return void
453
     */
454 16
    protected function addEvents() {
455
        $this->connection->on('data', function ($chunk) {
456 16
            $this->buffer->append($chunk);
457 16
            $this->processBuffer();
458 16
        });
459
        
460
        $this->connection->on('close', function () {
461 2
            $this->handleClose();
462 16
        });
463 16
    }
464
    
465
    /**
466
     * Connection close handler.
467
     * @return void
468
     */
469 2
    protected function handleClose() {
470 2
        if($this->state === static::STATE_AUTH || $this->state === static::STATE_AUTH_SENT) {
471
            $this->state = static::STATE_AUTH_ERROR;
472
        }
473 2
    }
474
}
475