Completed
Push — work-fleets ( 15d822...d7c9ad )
by SuperNova.WS
06:04
created

db_mysql::db_get_host_info()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
ccs 0
cts 3
cp 0
crap 2
1
<?php
2
3
/**
4
 * User: Gorlum
5
 * Date: 01.09.2015
6
 * Time: 15:58
7
 */
8
class db_mysql {
9
  const TRANSACTION_SERIALIZABLE = 'SERIALIZABLE';
10
  const TRANSACTION_REPEATABLE_READ = 'REPEATABLE READ';
11
  const TRANSACTION_READ_COMMITTED = 'READ COMMITTED';
12
  const TRANSACTION_READ_UNCOMMITTED = 'READ UNCOMMITTED';
13
14
  /**
15
   * Статус соеднения с MySQL
16
   *
17
   * @var bool
18
   */
19
  public $connected = false;
20
  /**
21
   * Префикс названий таблиц в БД
22
   *
23
   * @var string
24
   */
25
  public $db_prefix = '';
26
  /**
27
   * Список таблиц в БД
28
   *
29
   * @var array
30
   */
31
  public $table_list = array();
32
33
  /**
34
   * Настройки БД
35
   *
36
   * @var array
37
   */
38
  protected $dbsettings = array();
39
  /**
40
   * Драйвер для прямого обращения к MySQL
41
   *
42
   * @var db_mysql_v5 $driver
43
   */
44
  public $driver = null;
45
46
  /**
47
   * Общее время запросов
48
   *
49
   * @var float $time_mysql_total
50
   */
51
  public $time_mysql_total = 0.0;
52
53
  /**
54
   * Amount of queries on this DB
55
   *
56
   * @var int
57
   */
58
  public $queryCount = 0;
59
60
  public $isWatching = false;
61
62
  public function __construct() {
63
  }
64
65
  public function load_db_settings() {
66
    $dbsettings = array();
67
68
    require(SN_ROOT_PHYSICAL . "config" . DOT_PHP_EX);
69
70
    $this->dbsettings = $dbsettings;
71
  }
72
73
  public function sn_db_connect($external_db_settings = null) {
74
    $this->db_disconnect();
75
76
    if (!empty($external_db_settings) && is_array($external_db_settings)) {
77
      $this->dbsettings = $external_db_settings;
78
    }
79
80
    if (empty($this->dbsettings)) {
81
      $this->load_db_settings();
82
    }
83
84
    // TODO - фатальные (?) ошибки на каждом шагу. Хотя - скорее Эксепшны
85
    if (!empty($this->dbsettings)) {
86
      $driver_name = empty($this->dbsettings['sn_driver']) ? 'db_mysql_v5' : $this->dbsettings['sn_driver'];
87
      $this->driver = new $driver_name();
88
      $this->db_prefix = $this->dbsettings['prefix'];
89
90
      $this->connected = $this->connected || $this->driver_connect();
91
92
      if ($this->connected) {
93
        $this->table_list = $this->db_get_table_list();
94
        // TODO Проверка на пустоту
95
      }
96
    } else {
97
      $this->connected = false;
98
    }
99
100
    return $this->connected;
101
  }
102
103
  protected function driver_connect() {
104
    if (!is_object($this->driver)) {
105
      classSupernova::$debug->error_fatal('DB Error - No driver for MySQL found!');
106
    }
107
108
    if (!method_exists($this->driver, 'mysql_connect')) {
109
      classSupernova::$debug->error_fatal('DB Error - WRONG MySQL driver!');
110
    }
111
112
    return $this->driver->mysql_connect($this->dbsettings);
113
  }
114
115
  public function db_disconnect() {
116
    if ($this->connected) {
117
      $this->connected = !$this->driver_disconnect();
118
      $this->connected = false;
119
    }
120
121
    return !$this->connected;
122
  }
123
124
  /**
125
   * @param string $query
126
   *
127
   * @return mixed|string
128
   */
129
  public function replaceTablePlaceholders($query) {
130
    $sql = $query;
131
    if (strpos($sql, '{{') !== false) {
132
      foreach ($this->table_list as $tableName) {
133
        $sql = str_replace("{{{$tableName}}}", $this->db_prefix . $tableName, $sql);
134
      }
135
    }
136
137
    return $sql;
138
  }
139
140
  /**
141
   * @param       $query
142
   * @param       $fetch
143
   */
144
  protected function logQuery($query, $fetch) {
145
    if (!classSupernova::$config->debug) {
146
      return;
147
    }
148
149
    $this->queryCount++;
150
    $arr = debug_backtrace();
151
    $file = end(explode('/', $arr[0]['file']));
0 ignored issues
show
Bug introduced by
explode('/', $arr[0]['file']) cannot be passed to end() as the parameter $array expects a reference.
Loading history...
152
    $line = $arr[0]['line'];
153
    classSupernova::$debug->add("<tr><th>Query {$this->queryCount}: </th><th>$query</th><th>{$file} @ {$line}</th><th>&nbsp;</th><th> " . ($fetch ? '+' : '&nbsp;') . " </th></tr>");
154
  }
155
156
157
  /**
158
   * @return string
159
   */
160
  public function queryTrace() {
161
    if (!defined('DEBUG_SQL_COMMENT') || constant('DEBUG_SQL_ERROR') !== true) {
162
      return '';
163
    }
164
    $backtrace = debug_backtrace();
165
    $sql_comment = classSupernova::$debug->compact_backtrace($backtrace, defined('DEBUG_SQL_COMMENT_LONG'));
166
167
    if (defined('DEBUG_SQL_ERROR') && constant('DEBUG_SQL_ERROR') === true) {
168
//      array_unshift($sql_comment, $sql_one_liner);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
169
      classSupernova::$debug->add_to_array($sql_comment);
170
    }
171
172
    $sql_commented = '/* ' . implode("<br />", $sql_comment) . '<br /> */ ';
173
    if (defined('DEBUG_SQL_ONLINE') && constant('DEBUG_SQL_ONLINE') === true) {
174
      classSupernova::$debug->warning($sql_commented, 'SQL Debug', LOG_DEBUG_SQL);
175
    }
176
177
    return $sql_commented;
178
  }
179
180
  /**
181
   * Prepares sql string to execution and execute it
182
   *
183
   * @param string $sqlQuery
184
   * @param array  $values
185
   *
186
   * @return array|bool|mysqli_result|null
187
   */
188
  public function sqlPrepareAndExecute($sqlQuery, $values = array()) {
189
    return $this->doquery(DbSqlPrepare::build($sqlQuery, $values));
190
  }
191
192
193
194
  /**
195
   * @param string|DbSqlStatement $statement
196
   *
197
   * @return array|bool|mysqli_result|null
198
   */
199
  public function execute($statement) {
200
    return $this->doquery($statement);
0 ignored issues
show
Bug introduced by
It seems like $statement defined by parameter $statement on line 199 can also be of type object<DbSqlStatement>; however, db_mysql::doquery() does only seem to accept string|object<DbSqlPrepare>, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
201
  }
202
203
  /**
204
   * @param DbSqlStatement $statement
205
   *
206
   * @return array|null
207
   */
208
  public function fetchOne($statement) {
209
    $query = $this->execute($statement->fetchOne());
210
211
    return $this->db_fetch($query);
212
  }
213
214
  /**
215
   * @param string|DbSqlPrepare $query
216
   * @param string              $table
217
   * @param bool                $fetch
218
   * @param bool                $skip_query_check
219
   *
220
   * @return array|bool|mysqli_result|null
221
   */
222
  public function doquery($query, $table = '', $fetch = false, $skip_query_check = false) {
223
    if (!is_string($table)) {
224
      $fetch = $table;
225
    }
226
227
    if (!$this->connected) {
228
      $this->sn_db_connect();
229
    }
230
231
    $stringQuery = $query instanceof DbSqlPrepare ? $query->query : $query;
232
    $stringQuery = trim($stringQuery);
233
    $stringQuery = preg_replace("/\s+/", ' ', $stringQuery);
234
235
    $this->security_watch_user_queries($stringQuery);
236
    $this->security_query_check_bad_words($stringQuery, $skip_query_check);
237
    $this->logQuery($stringQuery, $fetch);
238
239
    $stringQuery = $this->replaceTablePlaceholders($stringQuery);
240
241
    $queryTrace = $this->queryTrace();
242
243
    $queryResult = null;
244
    try {
245
      if ($query instanceof DbSqlPrepare) {
246
        // MYSQLI ONLY!!!
247
        $queryResult = $query
248
          ->setQuery($stringQuery)
249
          ->comment($queryTrace)
250
          ->compileMySqlI()
251
          ->statementGet($this)
252
          ->execute()
253
          ->getResult();
254
      } else {
255
        $queryResult = $this->db_sql_query($stringQuery . $queryTrace);
256
      }
257
      if (!$queryResult) {
258
        throw new Exception();
259
      }
260
    } catch (Exception $e) {
261
      classSupernova::$debug->error($this->db_error() . "<br />{$query}<br />", 'SQL Error');
262
    }
263
264
    if ($fetch) {
265
      $queryResult = $this->db_fetch($queryResult);
266
      // DO NOT CLOSE STATEMENT HERE TO MAKE STATEMENT CACHING WORK!
267
    }
268
269
    return $queryResult;
270
  }
271
272
273
  // TODO Заменить это на новый логгер
274
  protected function security_watch_user_queries($query) {
275
    global $user;
276
277
    if (
278
      !$this->isWatching // Not already watching
279
      && !empty(classSupernova::$config->game_watchlist_array) // There is some players in watchlist
280
      && in_array($user['id'], classSupernova::$config->game_watchlist_array) // Current player is in watchlist
281
      && !preg_match('/^(select|commit|rollback|start transaction)/i', $query) // Current query should be watched
282
    ) {
283
      $this->isWatching = true;
284
      $msg = "\$query = \"{$query}\"\n\r";
285
      if (!empty($_POST)) {
286
        $msg .= "\n\r" . dump($_POST, '$_POST');
287
      }
288
      if (!empty($_GET)) {
289
        $msg .= "\n\r" . dump($_GET, '$_GET');
290
      }
291
      classSupernova::$debug->warning($msg, "Watching user {$user['id']}", 399, array('base_dump' => true));
0 ignored issues
show
Documentation introduced by
array('base_dump' => true) is of type array<string,boolean,{"base_dump":"boolean"}>, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
292
      $this->isWatching = false;
293
    }
294
  }
295
296
297
  public function security_query_check_bad_words($query, $skip_query_check = false) {
298
    if ($skip_query_check) {
299
      return;
300
    }
301
302
    global $user, $dm_change_legit, $mm_change_legit;
303
304
    switch(true) {
305
      case stripos($query, 'RUNCATE TABL') != false:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'RUNCATE TABL') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
306
      case stripos($query, 'ROP TABL') != false:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'ROP TABL') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
307
      case stripos($query, 'ENAME TABL') != false:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'ENAME TABL') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
308
      case stripos($query, 'REATE DATABAS') != false:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'REATE DATABAS') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
309
      case stripos($query, 'REATE TABL') != false:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'REATE TABL') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
310
      case stripos($query, 'ET PASSWOR') != false:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'ET PASSWOR') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
311
      case stripos($query, 'EOAD DAT') != false:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'EOAD DAT') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
312
      case stripos($query, 'RPG_POINTS') != false && stripos(trim($query), 'UPDATE ') === 0 && !$dm_change_legit:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'RPG_POINTS') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
313
      case stripos($query, 'METAMATTER') != false && stripos(trim($query), 'UPDATE ') === 0 && !$mm_change_legit:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'METAMATTER') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
314
      case stripos($query, 'AUTHLEVEL') != false && $user['authlevel'] < 3 && stripos($query, 'SELECT') !== 0:
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stripos($query, 'AUTHLEVEL') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
315
        $report = "Hacking attempt (" . date("d.m.Y H:i:s") . " - [" . time() . "]):\n";
316
        $report .= ">Database Inforamation\n";
317
        $report .= "\tID - " . $user['id'] . "\n";
318
        $report .= "\tUser - " . $user['username'] . "\n";
319
        $report .= "\tAuth level - " . $user['authlevel'] . "\n";
320
        $report .= "\tAdmin Notes - " . $user['adminNotes'] . "\n";
321
        $report .= "\tCurrent Planet - " . $user['current_planet'] . "\n";
322
        $report .= "\tUser IP - " . $user['user_lastip'] . "\n";
323
        $report .= "\tUser IP at Reg - " . $user['ip_at_reg'] . "\n";
324
        $report .= "\tUser Agent- " . $_SERVER['HTTP_USER_AGENT'] . "\n";
325
        $report .= "\tCurrent Page - " . $user['current_page'] . "\n";
326
        $report .= "\tRegister Time - " . $user['register_time'] . "\n";
327
        $report .= "\n";
328
329
        $report .= ">Query Information\n";
330
        $report .= "\tQuery - " . $query . "\n";
331
        $report .= "\n";
332
333
        $report .= ">\$_SERVER Information\n";
334
        $report .= "\tIP - " . $_SERVER['REMOTE_ADDR'] . "\n";
335
        $report .= "\tHost Name - " . $_SERVER['HTTP_HOST'] . "\n";
336
        $report .= "\tUser Agent - " . $_SERVER['HTTP_USER_AGENT'] . "\n";
337
        $report .= "\tRequest Method - " . $_SERVER['REQUEST_METHOD'] . "\n";
338
        $report .= "\tCame From - " . $_SERVER['HTTP_REFERER'] . "\n";
339
        $report .= "\tPage is - " . $_SERVER['SCRIPT_NAME'] . "\n";
340
        $report .= "\tUses Port - " . $_SERVER['REMOTE_PORT'] . "\n";
341
        $report .= "\tServer Protocol - " . $_SERVER['SERVER_PROTOCOL'] . "\n";
342
343
        $report .= "\n--------------------------------------------------------------------------------------------------\n";
344
345
        $fp = fopen(SN_ROOT_PHYSICAL . 'badqrys.txt', 'a');
346
        fwrite($fp, $report);
0 ignored issues
show
Security File Manipulation introduced by
$report can contain request data and is used in file manipulation context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
347
        fclose($fp);
348
349
        $message = 'Привет, я не знаю то, что Вы пробовали сделать, но команда, которую Вы только послали базе данных, не выглядела очень дружественной и она была заблокированна.<br /><br />Ваш IP, и другие данные переданны администрации сервера. Удачи!.';
350
        die($message);
351
      break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
352
    }
353
  }
354
355
  /**
356
   * @param bool $prefixed_only
357
   *
358
   * @return array
359
   */
360
  public function db_get_table_list($prefixed_only = true) {
361
    $query = $this->mysql_get_table_list();
362
363
    $prefix_length = strlen($this->db_prefix);
364
365
    $tl = array();
366
    while($row = $this->db_fetch($query)) {
367
      foreach ($row as $table_name) {
368
        if (strpos($table_name, $this->db_prefix) === 0) {
369
          $table_name = substr($table_name, $prefix_length);
370
        } elseif ($prefixed_only) {
371
          continue;
372
        }
373
        // $table_name = str_replace($db_prefix, '', $table_name);
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
374
        $tl[$table_name] = $table_name;
375
      }
376
    }
377
378
    return $tl;
379
  }
380
381
  /**
382
   * @param string $statement
383
   *
384
   * @return bool|mysqli_stmt
385
   */
386 View Code Duplication
  public function db_prepare($statement) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
387
    $microtime = microtime(true);
388
    $result = $this->driver->mysql_prepare($statement);
389
    $this->time_mysql_total += microtime(true) - $microtime;
390
391
    return $result;
392
  }
393
394
395
  /**
396
   * L1 perform the query
397
   *
398
   * @param $query_string
399
   *
400
   * @return bool|mysqli_result
401
   */
402 View Code Duplication
  public function db_sql_query($query_string) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
403
    $microtime = microtime(true);
404
    $result = $this->driver->mysql_query($query_string);
405
    $this->time_mysql_total += microtime(true) - $microtime;
406
407
    return $result;
408
  }
409
410
  /**
411
   * L1 fetch assoc array
412
   *
413
   * @param $query
414
   *
415
   * @return array|null
416
   */
417 View Code Duplication
  public function db_fetch(&$query) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
418
    $microtime = microtime(true);
419
    $result = $this->driver->mysql_fetch_assoc($query);
420
    $this->time_mysql_total += microtime(true) - $microtime;
421
422
    return $result;
423
  }
424
425
  public function db_fetch_row(&$query) {
426
    return $this->driver->mysql_fetch_row($query);
427
  }
428
429
  public function db_escape($unescaped_string) {
430
    return $this->driver->mysql_real_escape_string($unescaped_string);
431
  }
432
433
  public function driver_disconnect() {
434
    return $this->driver->mysql_close_link();
435
  }
436
437
  public function db_error() {
438
    return $this->driver->mysql_error();
439
  }
440
441
  public function db_insert_id() {
442
    return $this->driver->mysql_insert_id();
443
  }
444
445
  public function db_num_rows(&$result) {
446
    return $this->driver->mysql_num_rows($result);
447
  }
448
449
  public function db_affected_rows() {
450
    return $this->driver->mysql_affected_rows();
451
  }
452
453
  /**
454
   * @return string
455
   */
456
  public function db_get_client_info() {
457
    return $this->driver->mysql_get_client_info();
458
  }
459
460
  /**
461
   * @return string
462
   */
463
  public function db_get_server_info() {
464
    return $this->driver->mysql_get_server_info();
465
  }
466
467
  /**
468
   * @return string
469
   */
470
  public function db_get_host_info() {
471
    return $this->driver->mysql_get_host_info();
472
  }
473
474
  public function db_get_server_stat() {
475
    $result = array();
476
477
    $status = explode('  ', $this->driver->mysql_stat());
478
    foreach ($status as $value) {
479
      $row = explode(': ', $value);
480
      $result[$row[0]] = $row[1];
481
    }
482
483
    return $result;
484
  }
485
486
  /**
487
   * @return array
488
   * @throws Exception
489
   */
490
  public function db_core_show_status() {
491
    $result = array();
492
493
    $query = $this->db_sql_query('SHOW STATUS;');
494
    if (is_bool($query)) {
495
      throw new Exception('Result of SHOW STATUS command is boolean - which should never happen. Connection to DB is lost?');
496
    }
497
    while($row = db_fetch($query)) {
498
      $result[$row['Variable_name']] = $row['Value'];
499
    }
500
501
    return $result;
502
  }
503
504
  public function mysql_get_table_list() {
505
    return $this->db_sql_query('SHOW TABLES;');
506
  }
507
508
  public function mysql_get_innodb_status() {
509
    return $this->db_sql_query('SHOW ENGINE INNODB STATUS;');
510
  }
511
512
}
513