Completed
Push — master ( b97460...3f6e36 )
by Maurício
08:29
created

libraries/classes/Dbi/DbiMysqli.php (3 issues)

1
<?php
2
/* vim: set expandtab sw=4 ts=4 sts=4: */
3
/**
4
 * Interface to the MySQL Improved extension (MySQLi)
5
 *
6
 * @package    PhpMyAdmin-DBI
7
 * @subpackage MySQLi
8
 */
9
declare(strict_types=1);
10
11
namespace PhpMyAdmin\Dbi;
12
13
use PhpMyAdmin\DatabaseInterface;
14
15
/**
16
 * Interface to the MySQL Improved extension (MySQLi)
17
 *
18
 * @package    PhpMyAdmin-DBI
19
 * @subpackage MySQLi
20
 */
21
class DbiMysqli implements DbiExtension
22
{
23
    /**
24
     * @var array
25
     */
26
    private static $pma_mysqli_flag_names = [
27
        MYSQLI_NUM_FLAG => 'num',
28
        MYSQLI_PART_KEY_FLAG => 'part_key',
29
        MYSQLI_SET_FLAG => 'set',
30
        MYSQLI_TIMESTAMP_FLAG => 'timestamp',
31
        MYSQLI_AUTO_INCREMENT_FLAG => 'auto_increment',
32
        MYSQLI_ENUM_FLAG => 'enum',
33
        MYSQLI_ZEROFILL_FLAG => 'zerofill',
34
        MYSQLI_UNSIGNED_FLAG => 'unsigned',
35
        MYSQLI_BLOB_FLAG => 'blob',
36
        MYSQLI_MULTIPLE_KEY_FLAG => 'multiple_key',
37
        MYSQLI_UNIQUE_KEY_FLAG => 'unique_key',
38
        MYSQLI_PRI_KEY_FLAG => 'primary_key',
39
        MYSQLI_NOT_NULL_FLAG => 'not_null',
40
    ];
41
42
    /**
43
     * connects to the database server
44
     *
45
     * @param string $user     mysql user name
46
     * @param string $password mysql user password
47
     * @param array  $server   host/port/socket/persistent
48
     *
49
     * @return \mysqli|bool false on error or a mysqli object on success
50
     */
51
    public function connect($user, $password, array $server)
52
    {
53
        if ($server) {
54
            $server['host'] = empty($server['host'])
55
                ? 'localhost'
56
                : $server['host'];
57
        }
58
59
        $mysqli = \mysqli_init();
60
61
        $client_flags = 0;
62
63
        /* Optionally compress connection */
64
        if ($server['compress'] && defined('MYSQLI_CLIENT_COMPRESS')) {
65
            $client_flags |= MYSQLI_CLIENT_COMPRESS;
66
        }
67
68
        /* Optionally enable SSL */
69
        if ($server['ssl']) {
70
            $client_flags |= MYSQLI_CLIENT_SSL;
71
            if (! empty($server['ssl_key']) ||
72
                ! empty($server['ssl_cert']) ||
73
                ! empty($server['ssl_ca']) ||
74
                ! empty($server['ssl_ca_path']) ||
75
                ! empty($server['ssl_ciphers'])
76
            ) {
77
                $mysqli->ssl_set(
78
                    $server['ssl_key'],
79
                    $server['ssl_cert'],
80
                    $server['ssl_ca'],
81
                    $server['ssl_ca_path'],
82
                    $server['ssl_ciphers']
83
                );
84
            }
85
            /*
86
             * disables SSL certificate validation on mysqlnd for MySQL 5.6 or later
87
             * @link https://bugs.php.net/bug.php?id=68344
88
             * @link https://github.com/phpmyadmin/phpmyadmin/pull/11838
89
             */
90
            if (! $server['ssl_verify']) {
91
                $mysqli->options(
92
                    MYSQLI_OPT_SSL_VERIFY_SERVER_CERT,
93
                    $server['ssl_verify']
94
                );
95
                $client_flags |= MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
96
            }
97
        }
98
99
        if ($GLOBALS['cfg']['PersistentConnections']) {
100
            $host = 'p:' . $server['host'];
101
        } else {
102
            $host = $server['host'];
103
        }
104
105
        $return_value = $mysqli->real_connect(
106
            $host,
107
            $user,
108
            $password,
109
            '',
110
            $server['port'],
111
            (string) $server['socket'],
112
            $client_flags
113
        );
114
115
        if ($return_value === false || is_null($return_value)) {
116
            /*
117
             * Switch to SSL if server asked us to do so, unfortunately
118
             * there are more ways MySQL server can tell this:
119
             *
120
             * - MySQL 8.0 and newer should return error 3159
121
             * - #2001 - SSL Connection is required. Please specify SSL options and retry.
122
             * - #9002 - SSL connection is required. Please specify SSL options and retry.
123
             */
124
            $error_number = $mysqli->connect_errno;
125
            $error_message = $mysqli->connect_error;
126
            if (! $server['ssl'] && ($error_number == 3159 ||
127
                (($error_number == 2001 || $error_number == 9002) && stripos($error_message, 'SSL Connection is required') !== false))
128
            ) {
129
                    trigger_error(
130
                        __('SSL connection enforced by server, automatically enabling it.'),
131
                        E_USER_WARNING
132
                    );
133
                    $server['ssl'] = true;
134
                    return self::connect($user, $password, $server);
135
            }
136
            return false;
137
        }
138
139
        if (defined('PMA_ENABLE_LDI')) {
140
            $mysqli->options(MYSQLI_OPT_LOCAL_INFILE, true);
141
        } else {
142
            $mysqli->options(MYSQLI_OPT_LOCAL_INFILE, false);
143
        }
144
145
        return $mysqli;
146
    }
147
148
    /**
149
     * selects given database
150
     *
151
     * @param string  $databaseName database name to select
152
     * @param \mysqli $mysqli       the mysqli object
153
     *
154
     * @return boolean
155
     */
156
    public function selectDb($databaseName, $mysqli)
157
    {
158
        return $mysqli->select_db($databaseName);
159
    }
160
161
    /**
162
     * runs a query and returns the result
163
     *
164
     * @param string  $query   query to execute
165
     * @param \mysqli $mysqli  mysqli object
166
     * @param int     $options query options
167
     *
168
     * @return \mysqli_result|bool
169
     */
170
    public function realQuery($query, $mysqli, $options)
171
    {
172
        if ($options == ($options | DatabaseInterface::QUERY_STORE)) {
173
            $method = MYSQLI_STORE_RESULT;
174
        } elseif ($options == ($options | DatabaseInterface::QUERY_UNBUFFERED)) {
175
            $method = MYSQLI_USE_RESULT;
176
        } else {
177
            $method = 0;
178
        }
179
180
        return $mysqli->query($query, $method);
181
    }
182
183
    /**
184
     * Run the multi query and output the results
185
     *
186
     * @param \mysqli $mysqli mysqli object
187
     * @param string  $query  multi query statement to execute
188
     *
189
     * @return bool
190
     */
191
    public function realMultiQuery($mysqli, $query)
192
    {
193
        return $mysqli->multi_query($query);
194
    }
195
196
    /**
197
     * returns array of rows with associative and numeric keys from $result
198
     *
199
     * @param \mysqli_result $result result set identifier
200
     *
201
     * @return array|null
202
     */
203
    public function fetchArray($result)
204
    {
205
        if (! $result instanceof \mysqli_result) {
206
            return null;
207
        }
208
        return $result->fetch_array(MYSQLI_BOTH);
209
    }
210
211
    /**
212
     * returns array of rows with associative keys from $result
213
     *
214
     * @param \mysqli_result $result result set identifier
215
     *
216
     * @return array|null
217
     */
218
    public function fetchAssoc($result)
219
    {
220
        if (! $result instanceof \mysqli_result) {
221
            return null;
222
        }
223
        return $result->fetch_array(MYSQLI_ASSOC);
224
    }
225
226
    /**
227
     * returns array of rows with numeric keys from $result
228
     *
229
     * @param \mysqli_result $result result set identifier
230
     *
231
     * @return array|null
232
     */
233
    public function fetchRow($result)
234
    {
235
        if (! $result instanceof \mysqli_result) {
236
            return null;
237
        }
238
        return $result->fetch_array(MYSQLI_NUM);
239
    }
240
241
    /**
242
     * Adjusts the result pointer to an arbitrary row in the result
243
     *
244
     * @param \mysqli_result $result database result
245
     * @param integer        $offset offset to seek
246
     *
247
     * @return bool true on success, false on failure
248
     */
249
    public function dataSeek($result, $offset)
250
    {
251
        return $result->data_seek($offset);
252
    }
253
254
    /**
255
     * Frees memory associated with the result
256
     *
257
     * @param \mysqli_result $result database result
258
     *
259
     * @return void
260
     */
261
    public function freeResult($result)
262
    {
263
        if ($result instanceof \mysqli_result) {
264
            $result->close();
265
        }
266
    }
267
268
    /**
269
     * Check if there are any more query results from a multi query
270
     *
271
     * @param \mysqli $mysqli the mysqli object
272
     *
273
     * @return bool true or false
274
     */
275
    public function moreResults($mysqli)
276
    {
277
        return $mysqli->more_results();
278
    }
279
280
    /**
281
     * Prepare next result from multi_query
282
     *
283
     * @param \mysqli $mysqli the mysqli object
284
     *
285
     * @return bool true or false
286
     */
287
    public function nextResult($mysqli)
288
    {
289
        return $mysqli->next_result();
290
    }
291
292
    /**
293
     * Store the result returned from multi query
294
     *
295
     * @param \mysqli $mysqli the mysqli object
296
     *
297
     * @return \mysqli_result|bool false when empty results / result set when not empty
298
     */
299
    public function storeResult($mysqli)
300
    {
301
        return $mysqli->store_result();
302
    }
303
304
    /**
305
     * Returns a string representing the type of connection used
306
     *
307
     * @param \mysqli $mysqli mysql link
308
     *
309
     * @return string type of connection used
310
     */
311
    public function getHostInfo($mysqli)
312
    {
313
        return $mysqli->host_info;
314
    }
315
316
    /**
317
     * Returns the version of the MySQL protocol used
318
     *
319
     * @param \mysqli $mysqli mysql link
320
     *
321
     * @return string version of the MySQL protocol used
322
     */
323
    public function getProtoInfo($mysqli)
324
    {
325
        return $mysqli->protocol_version;
326
    }
327
328
    /**
329
     * returns a string that represents the client library version
330
     *
331
     * @return string MySQL client library version
332
     */
333
    public function getClientInfo()
334
    {
335
        return mysqli_get_client_info();
336
    }
337
338
    /**
339
     * returns last error message or false if no errors occurred
340
     *
341
     * @param \mysqli $mysqli mysql link
342
     *
343
     * @return string|bool error or false
344
     */
345
    public function getError($mysqli)
346
    {
347
        $GLOBALS['errno'] = 0;
348
349
        if (null !== $mysqli && false !== $mysqli) {
350
            $error_number = $mysqli->errno;
351
            $error_message = $mysqli->error;
352
        } else {
353
            $error_number = $mysqli->connect_errno;
354
            $error_message = $mysqli->connect_error;
355
        }
356
        if (0 == $error_number) {
357
            return false;
358
        }
359
360
        // keep the error number for further check after
361
        // the call to getError()
362
        $GLOBALS['errno'] = $error_number;
363
364
        return $GLOBALS['dbi']->formatError($error_number, $error_message);
365
    }
366
367
    /**
368
     * returns the number of rows returned by last query
369
     *
370
     * @param \mysqli_result $result result set identifier
371
     *
372
     * @return string|int
373
     */
374
    public function numRows($result)
375
    {
376
        // see the note for tryQuery();
377
        if (is_bool($result)) {
378
            return 0;
379
        }
380
381
        return $result->num_rows;
382
    }
383
384
    /**
385
     * returns the number of rows affected by last query
386
     *
387
     * @param \mysqli $mysqli the mysqli object
388
     *
389
     * @return int
390
     */
391
    public function affectedRows($mysqli)
392
    {
393
        return $mysqli->affected_rows;
394
    }
395
396
    /**
397
     * returns meta info for fields in $result
398
     *
399
     * @param \mysqli_result $result result set identifier
400
     *
401
     * @return array|bool meta info for fields in $result
402
     */
403
    public function getFieldsMeta($result)
404
    {
405
        if (! $result instanceof \mysqli_result) {
406
            return false;
407
        }
408
        // Build an associative array for a type look up
409
        $typeAr = [];
410
        $typeAr[MYSQLI_TYPE_DECIMAL]     = 'real';
411
        $typeAr[MYSQLI_TYPE_NEWDECIMAL]  = 'real';
412
        $typeAr[MYSQLI_TYPE_BIT]         = 'int';
413
        $typeAr[MYSQLI_TYPE_TINY]        = 'int';
414
        $typeAr[MYSQLI_TYPE_SHORT]       = 'int';
415
        $typeAr[MYSQLI_TYPE_LONG]        = 'int';
416
        $typeAr[MYSQLI_TYPE_FLOAT]       = 'real';
417
        $typeAr[MYSQLI_TYPE_DOUBLE]      = 'real';
418
        $typeAr[MYSQLI_TYPE_NULL]        = 'null';
419
        $typeAr[MYSQLI_TYPE_TIMESTAMP]   = 'timestamp';
420
        $typeAr[MYSQLI_TYPE_LONGLONG]    = 'int';
421
        $typeAr[MYSQLI_TYPE_INT24]       = 'int';
422
        $typeAr[MYSQLI_TYPE_DATE]        = 'date';
423
        $typeAr[MYSQLI_TYPE_TIME]        = 'time';
424
        $typeAr[MYSQLI_TYPE_DATETIME]    = 'datetime';
425
        $typeAr[MYSQLI_TYPE_YEAR]        = 'year';
426
        $typeAr[MYSQLI_TYPE_NEWDATE]     = 'date';
427
        $typeAr[MYSQLI_TYPE_ENUM]        = 'unknown';
428
        $typeAr[MYSQLI_TYPE_SET]         = 'unknown';
429
        $typeAr[MYSQLI_TYPE_TINY_BLOB]   = 'blob';
430
        $typeAr[MYSQLI_TYPE_MEDIUM_BLOB] = 'blob';
431
        $typeAr[MYSQLI_TYPE_LONG_BLOB]   = 'blob';
432
        $typeAr[MYSQLI_TYPE_BLOB]        = 'blob';
433
        $typeAr[MYSQLI_TYPE_VAR_STRING]  = 'string';
434
        $typeAr[MYSQLI_TYPE_STRING]      = 'string';
435
        // MySQL returns MYSQLI_TYPE_STRING for CHAR
436
        // and MYSQLI_TYPE_CHAR === MYSQLI_TYPE_TINY
437
        // so this would override TINYINT and mark all TINYINT as string
438
        // see https://github.com/phpmyadmin/phpmyadmin/issues/8569
439
        //$typeAr[MYSQLI_TYPE_CHAR]        = 'string';
440
        $typeAr[MYSQLI_TYPE_GEOMETRY]    = 'geometry';
441
        $typeAr[MYSQLI_TYPE_BIT]         = 'bit';
442
        $typeAr[MYSQLI_TYPE_JSON]        = 'json';
443
444
        $fields = $result->fetch_fields();
445
446
        if (! is_array($fields)) {
447
            return false;
448
        }
449
450
        foreach ($fields as $k => $field) {
451
            $fields[$k]->_type = $field->type;
452
            $fields[$k]->type = $typeAr[$field->type];
453
            $fields[$k]->_flags = $field->flags;
454
            $fields[$k]->flags = $this->fieldFlags($result, $k);
455
456
            // Enhance the field objects for mysql-extension compatibility
457
            //$flags = explode(' ', $fields[$k]->flags);
458
            //array_unshift($flags, 'dummy');
459
            $fields[$k]->multiple_key
460
                = (int) (bool) ($fields[$k]->_flags & MYSQLI_MULTIPLE_KEY_FLAG);
461
            $fields[$k]->primary_key
462
                = (int) (bool) ($fields[$k]->_flags & MYSQLI_PRI_KEY_FLAG);
463
            $fields[$k]->unique_key
464
                = (int) (bool) ($fields[$k]->_flags & MYSQLI_UNIQUE_KEY_FLAG);
465
            $fields[$k]->not_null
466
                = (int) (bool) ($fields[$k]->_flags & MYSQLI_NOT_NULL_FLAG);
467
            $fields[$k]->unsigned
468
                = (int) (bool) ($fields[$k]->_flags & MYSQLI_UNSIGNED_FLAG);
469
            $fields[$k]->zerofill
470
                = (int) (bool) ($fields[$k]->_flags & MYSQLI_ZEROFILL_FLAG);
471
            $fields[$k]->numeric
472
                = (int) (bool) ($fields[$k]->_flags & MYSQLI_NUM_FLAG);
473
            $fields[$k]->blob
474
                = (int) (bool) ($fields[$k]->_flags & MYSQLI_BLOB_FLAG);
475
        }
476
        return $fields;
477
    }
478
479
    /**
480
     * return number of fields in given $result
481
     *
482
     * @param \mysqli_result $result result set identifier
483
     *
484
     * @return int field count
485
     */
486
    public function numFields($result)
487
    {
488
        return $result->field_count;
489
    }
490
491
    /**
492
     * returns the length of the given field $i in $result
493
     *
494
     * @param \mysqli_result $result result set identifier
495
     * @param int            $i      field
496
     *
497
     * @return int|bool length of field
498
     */
499
    public function fieldLen($result, $i)
500
    {
501
        if ($i >= $this->numFields($result)) {
502
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the return type mandated by PhpMyAdmin\Dbi\DbiExtension::fieldLen() of integer.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
503
        }
504
        /** @var \stdClass $fieldDefinition */
505
        $fieldDefinition = $result->fetch_field_direct($i);
506
        if ($fieldDefinition !== false) {
507
            return $fieldDefinition->length;
508
        }
509
        return false;
510
    }
511
512
    /**
513
     * returns name of $i. field in $result
514
     *
515
     * @param \mysqli_result $result result set identifier
516
     * @param int            $i      field
517
     *
518
     * @return string|bool name of $i. field in $result
519
     */
520
    public function fieldName($result, $i)
521
    {
522
        if ($i >= $this->numFields($result)) {
523
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the return type mandated by PhpMyAdmin\Dbi\DbiExtension::fieldName() of string.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
524
        }
525
        /** @var \stdClass $fieldDefinition */
526
        $fieldDefinition = $result->fetch_field_direct($i);
527
        if ($fieldDefinition !== false) {
528
            return $fieldDefinition->name;
529
        }
530
        return false;
531
    }
532
533
    /**
534
     * returns concatenated string of human readable field flags
535
     *
536
     * @param \mysqli_result $result result set identifier
537
     * @param int            $i      field
538
     *
539
     * @return string field flags
540
     */
541
    public function fieldFlags($result, $i)
542
    {
543
        if ($i >= $this->numFields($result)) {
544
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
545
        }
546
        /** @var \stdClass $fieldDefinition */
547
        $fieldDefinition = $result->fetch_field_direct($i);
548
        if ($fieldDefinition !== false) {
549
            $type = $fieldDefinition->type;
550
            $charsetNumber = $fieldDefinition->charsetnr;
551
            $fieldDefinitionFlags = $fieldDefinition->flags;
552
            $flags = [];
553
            foreach (self::$pma_mysqli_flag_names as $flag => $name) {
554
                if ($fieldDefinitionFlags & $flag) {
555
                    $flags[] = $name;
556
                }
557
            }
558
            // See https://dev.mysql.com/doc/refman/6.0/en/c-api-datatypes.html:
559
            // to determine if a string is binary, we should not use MYSQLI_BINARY_FLAG
560
            // but instead the charsetnr member of the MYSQL_FIELD
561
            // structure. Watch out: some types like DATE returns 63 in charsetnr
562
            // so we have to check also the type.
563
            // Unfortunately there is no equivalent in the mysql extension.
564
            if (($type == MYSQLI_TYPE_TINY_BLOB || $type == MYSQLI_TYPE_BLOB
565
                || $type == MYSQLI_TYPE_MEDIUM_BLOB || $type == MYSQLI_TYPE_LONG_BLOB
566
                || $type == MYSQLI_TYPE_VAR_STRING || $type == MYSQLI_TYPE_STRING)
567
                && 63 == $charsetNumber
568
            ) {
569
                $flags[] = 'binary';
570
            }
571
            return implode(' ', $flags);
572
        } else {
573
            return '';
574
        }
575
    }
576
577
    /**
578
     * returns properly escaped string for use in MySQL queries
579
     *
580
     * @param \mysqli $mysqli database link
581
     * @param string  $string string to be escaped
582
     *
583
     * @return string a MySQL escaped string
584
     */
585
    public function escapeString($mysqli, $string)
586
    {
587
        return $mysqli->real_escape_string($string);
588
    }
589
}
590