1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Core functions used all over the scripts. |
4
|
|
|
* This script is distinct from libraries/common.inc.php because this |
5
|
|
|
* script is called from /test. |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
declare(strict_types=1); |
9
|
|
|
|
10
|
|
|
namespace PhpMyAdmin; |
11
|
|
|
|
12
|
|
|
use PhpMyAdmin\Display\Error as DisplayError; |
13
|
|
|
use Symfony\Component\DependencyInjection\ContainerBuilder; |
14
|
|
|
use const DATE_RFC1123; |
15
|
|
|
use const E_USER_ERROR; |
16
|
|
|
use const E_USER_WARNING; |
17
|
|
|
use const FILTER_VALIDATE_IP; |
18
|
|
|
use function array_keys; |
19
|
|
|
use function array_pop; |
20
|
|
|
use function array_walk_recursive; |
21
|
|
|
use function chr; |
22
|
|
|
use function count; |
23
|
|
|
use function date_default_timezone_get; |
24
|
|
|
use function date_default_timezone_set; |
25
|
|
|
use function defined; |
26
|
|
|
use function explode; |
27
|
|
|
use function extension_loaded; |
28
|
|
|
use function filter_var; |
29
|
|
|
use function function_exists; |
30
|
|
|
use function getenv; |
31
|
|
|
use function gettype; |
32
|
|
|
use function gmdate; |
33
|
|
|
use function hash_equals; |
34
|
|
|
use function hash_hmac; |
35
|
|
|
use function header; |
|
|
|
|
36
|
|
|
use function htmlspecialchars; |
37
|
|
|
use function http_build_query; |
38
|
|
|
use function implode; |
39
|
|
|
use function in_array; |
40
|
|
|
use function ini_get; |
41
|
|
|
use function ini_set; |
42
|
|
|
use function intval; |
43
|
|
|
use function is_array; |
44
|
|
|
use function is_numeric; |
45
|
|
|
use function is_scalar; |
46
|
|
|
use function is_string; |
47
|
|
|
use function json_encode; |
48
|
|
|
use function mb_internal_encoding; |
49
|
|
|
use function mb_strlen; |
50
|
|
|
use function mb_strpos; |
51
|
|
|
use function mb_strrpos; |
52
|
|
|
use function mb_substr; |
53
|
|
|
use function parse_str; |
54
|
|
|
use function parse_url; |
55
|
|
|
use function preg_match; |
56
|
|
|
use function preg_replace; |
57
|
|
|
use function session_id; |
58
|
|
|
use function session_write_close; |
59
|
|
|
use function sprintf; |
60
|
|
|
use function str_replace; |
61
|
|
|
use function strlen; |
62
|
|
|
use function strpos; |
63
|
|
|
use function strtolower; |
64
|
|
|
use function strtr; |
65
|
|
|
use function substr; |
66
|
|
|
use function trigger_error; |
67
|
|
|
use function unserialize; |
68
|
|
|
use function urldecode; |
69
|
|
|
use function vsprintf; |
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Core class |
73
|
|
|
*/ |
74
|
|
|
class Core |
75
|
|
|
{ |
76
|
|
|
/** |
77
|
|
|
* checks given $var and returns it if valid, or $default of not valid |
78
|
|
|
* given $var is also checked for type being 'similar' as $default |
79
|
|
|
* or against any other type if $type is provided |
80
|
|
|
* |
81
|
|
|
* <code> |
82
|
|
|
* // $_REQUEST['db'] not set |
83
|
|
|
* echo Core::ifSetOr($_REQUEST['db'], ''); // '' |
84
|
|
|
* // $_POST['sql_query'] not set |
85
|
|
|
* echo Core::ifSetOr($_POST['sql_query']); // null |
86
|
|
|
* // $cfg['EnableFoo'] not set |
87
|
|
|
* echo Core::ifSetOr($cfg['EnableFoo'], false, 'boolean'); // false |
88
|
|
|
* echo Core::ifSetOr($cfg['EnableFoo']); // null |
89
|
|
|
* // $cfg['EnableFoo'] set to 1 |
90
|
|
|
* echo Core::ifSetOr($cfg['EnableFoo'], false, 'boolean'); // false |
91
|
|
|
* echo Core::ifSetOr($cfg['EnableFoo'], false, 'similar'); // 1 |
92
|
|
|
* echo Core::ifSetOr($cfg['EnableFoo'], false); // 1 |
93
|
|
|
* // $cfg['EnableFoo'] set to true |
94
|
|
|
* echo Core::ifSetOr($cfg['EnableFoo'], false, 'boolean'); // true |
95
|
|
|
* </code> |
96
|
|
|
* |
97
|
|
|
* @see self::isValid() |
98
|
|
|
* |
99
|
|
|
* @param mixed $var param to check |
100
|
|
|
* @param mixed $default default value |
101
|
|
|
* @param mixed $type var type or array of values to check against $var |
102
|
|
|
* |
103
|
|
|
* @return mixed $var or $default |
104
|
|
|
*/ |
105
|
92 |
|
public static function ifSetOr(&$var, $default = null, $type = 'similar') |
106
|
|
|
{ |
107
|
92 |
|
if (! self::isValid($var, $type, $default)) { |
108
|
80 |
|
return $default; |
109
|
|
|
} |
110
|
|
|
|
111
|
12 |
|
return $var; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
/** |
115
|
|
|
* checks given $var against $type or $compare |
116
|
|
|
* |
117
|
|
|
* $type can be: |
118
|
|
|
* - false : no type checking |
119
|
|
|
* - 'scalar' : whether type of $var is integer, float, string or boolean |
120
|
|
|
* - 'numeric' : whether type of $var is any number representation |
121
|
|
|
* - 'length' : whether type of $var is scalar with a string length > 0 |
122
|
|
|
* - 'similar' : whether type of $var is similar to type of $compare |
123
|
|
|
* - 'equal' : whether type of $var is identical to type of $compare |
124
|
|
|
* - 'identical' : whether $var is identical to $compare, not only the type! |
125
|
|
|
* - or any other valid PHP variable type |
126
|
|
|
* |
127
|
|
|
* <code> |
128
|
|
|
* // $_REQUEST['doit'] = true; |
129
|
|
|
* Core::isValid($_REQUEST['doit'], 'identical', 'true'); // false |
130
|
|
|
* // $_REQUEST['doit'] = 'true'; |
131
|
|
|
* Core::isValid($_REQUEST['doit'], 'identical', 'true'); // true |
132
|
|
|
* </code> |
133
|
|
|
* |
134
|
|
|
* NOTE: call-by-reference is used to not get NOTICE on undefined vars, |
135
|
|
|
* but the var is not altered inside this function, also after checking a var |
136
|
|
|
* this var exists nut is not set, example: |
137
|
|
|
* <code> |
138
|
|
|
* // $var is not set |
139
|
|
|
* isset($var); // false |
140
|
|
|
* functionCallByReference($var); // false |
141
|
|
|
* isset($var); // true |
142
|
|
|
* functionCallByReference($var); // true |
143
|
|
|
* </code> |
144
|
|
|
* |
145
|
|
|
* to avoid this we set this var to null if not isset |
146
|
|
|
* |
147
|
|
|
* @see https://secure.php.net/gettype |
148
|
|
|
* |
149
|
|
|
* @param mixed $var variable to check |
150
|
|
|
* @param mixed $type var type or array of valid values to check against $var |
151
|
|
|
* @param mixed $compare var to compare with $var |
152
|
|
|
* |
153
|
|
|
* @return bool whether valid or not |
154
|
|
|
* |
155
|
|
|
* @todo add some more var types like hex, bin, ...? |
156
|
|
|
*/ |
157
|
280 |
|
public static function isValid(&$var, $type = 'length', $compare = null): bool |
158
|
|
|
{ |
159
|
280 |
|
if (! isset($var)) { |
160
|
|
|
// var is not even set |
161
|
128 |
|
return false; |
162
|
|
|
} |
163
|
|
|
|
164
|
164 |
|
if ($type === false) { |
165
|
|
|
// no vartype requested |
166
|
48 |
|
return true; |
167
|
|
|
} |
168
|
|
|
|
169
|
116 |
|
if (is_array($type)) { |
170
|
8 |
|
return in_array($var, $type); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
// allow some aliases of var types |
174
|
108 |
|
$type = strtolower($type); |
175
|
81 |
|
switch ($type) { |
176
|
108 |
|
case 'identic': |
177
|
4 |
|
$type = 'identical'; |
178
|
4 |
|
break; |
179
|
104 |
|
case 'len': |
180
|
4 |
|
$type = 'length'; |
181
|
4 |
|
break; |
182
|
104 |
|
case 'bool': |
183
|
4 |
|
$type = 'boolean'; |
184
|
4 |
|
break; |
185
|
104 |
|
case 'float': |
186
|
4 |
|
$type = 'double'; |
187
|
4 |
|
break; |
188
|
104 |
|
case 'int': |
189
|
8 |
|
$type = 'integer'; |
190
|
8 |
|
break; |
191
|
104 |
|
case 'null': |
192
|
4 |
|
$type = 'NULL'; |
193
|
4 |
|
break; |
194
|
|
|
} |
195
|
|
|
|
196
|
108 |
|
if ($type === 'identical') { |
197
|
4 |
|
return $var === $compare; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
// whether we should check against given $compare |
201
|
104 |
|
if ($type === 'similar') { |
202
|
28 |
|
switch (gettype($compare)) { |
203
|
28 |
|
case 'string': |
204
|
20 |
|
case 'boolean': |
205
|
12 |
|
$type = 'scalar'; |
206
|
12 |
|
break; |
207
|
16 |
|
case 'integer': |
208
|
12 |
|
case 'double': |
209
|
8 |
|
$type = 'numeric'; |
210
|
8 |
|
break; |
211
|
|
|
default: |
212
|
28 |
|
$type = gettype($compare); |
213
|
|
|
} |
214
|
100 |
|
} elseif ($type === 'equal') { |
215
|
24 |
|
$type = gettype($compare); |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
// do the check |
219
|
104 |
|
if ($type === 'length' || $type === 'scalar') { |
220
|
60 |
|
$is_scalar = is_scalar($var); |
221
|
60 |
|
if ($is_scalar && $type === 'length') { |
222
|
32 |
|
return strlen((string) $var) > 0; |
223
|
|
|
} |
224
|
|
|
|
225
|
32 |
|
return $is_scalar; |
226
|
|
|
} |
227
|
|
|
|
228
|
68 |
|
if ($type === 'numeric') { |
229
|
24 |
|
return is_numeric($var); |
230
|
|
|
} |
231
|
|
|
|
232
|
52 |
|
return gettype($var) === $type; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* Removes insecure parts in a path; used before include() or |
237
|
|
|
* require() when a part of the path comes from an insecure source |
238
|
|
|
* like a cookie or form. |
239
|
|
|
* |
240
|
|
|
* @param string $path The path to check |
241
|
|
|
* |
242
|
|
|
* @return string The secured path |
243
|
|
|
* |
244
|
|
|
* @access public |
245
|
|
|
*/ |
246
|
8 |
|
public static function securePath(string $path): string |
247
|
|
|
{ |
248
|
|
|
// change .. to . |
249
|
8 |
|
return preg_replace('@\.\.*@', '.', $path); |
250
|
|
|
} // end function |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* displays the given error message on phpMyAdmin error page in foreign language, |
254
|
|
|
* ends script execution and closes session |
255
|
|
|
* |
256
|
|
|
* loads language file if not loaded already |
257
|
|
|
* |
258
|
|
|
* @param string $error_message the error message or named error message |
259
|
|
|
* @param string|array $message_args arguments applied to $error_message |
260
|
|
|
*/ |
261
|
24 |
|
public static function fatalError( |
262
|
|
|
string $error_message, |
263
|
|
|
$message_args = null |
264
|
|
|
): void { |
265
|
|
|
/* Use format string if applicable */ |
266
|
24 |
|
if (is_string($message_args)) { |
267
|
4 |
|
$error_message = sprintf($error_message, $message_args); |
268
|
24 |
|
} elseif (is_array($message_args)) { |
269
|
4 |
|
$error_message = vsprintf($error_message, $message_args); |
270
|
|
|
} |
271
|
|
|
|
272
|
|
|
/* |
273
|
|
|
* Avoid using Response class as config does not have to be loaded yet |
274
|
|
|
* (this can happen on early fatal error) |
275
|
|
|
*/ |
276
|
24 |
|
if (isset($GLOBALS['dbi'], $GLOBALS['PMA_Config']) && $GLOBALS['dbi'] !== null |
277
|
24 |
|
&& $GLOBALS['PMA_Config']->get('is_setup') === false |
278
|
24 |
|
&& Response::getInstance()->isAjax()) { |
279
|
|
|
$response = Response::getInstance(); |
280
|
|
|
$response->setRequestStatus(false); |
281
|
|
|
$response->addJSON('message', Message::error($error_message)); |
282
|
24 |
|
} elseif (! empty($_REQUEST['ajax_request'])) { |
283
|
|
|
// Generate JSON manually |
284
|
|
|
self::headerJSON(); |
285
|
|
|
echo json_encode( |
286
|
|
|
[ |
287
|
|
|
'success' => false, |
288
|
|
|
'message' => Message::error($error_message)->getDisplay(), |
289
|
|
|
] |
290
|
|
|
); |
291
|
|
|
} else { |
292
|
24 |
|
$error_message = strtr($error_message, ['<br>' => '[br]']); |
293
|
24 |
|
$error_header = __('Error'); |
294
|
24 |
|
$lang = $GLOBALS['lang'] ?? 'en'; |
295
|
24 |
|
$dir = $GLOBALS['text_dir'] ?? 'ltr'; |
296
|
|
|
|
297
|
24 |
|
echo DisplayError::display(new Template(), $lang, $dir, $error_header, $error_message); |
298
|
|
|
} |
299
|
24 |
|
if (! defined('TESTSUITE')) { |
300
|
|
|
exit; |
|
|
|
|
301
|
|
|
} |
302
|
24 |
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* Returns a link to the PHP documentation |
306
|
|
|
* |
307
|
|
|
* @param string $target anchor in documentation |
308
|
|
|
* |
309
|
|
|
* @return string the URL |
310
|
|
|
* |
311
|
|
|
* @access public |
312
|
|
|
*/ |
313
|
24 |
|
public static function getPHPDocLink(string $target): string |
314
|
|
|
{ |
315
|
|
|
/* List of PHP documentation translations */ |
316
|
|
|
$php_doc_languages = [ |
317
|
24 |
|
'pt_BR', |
318
|
|
|
'zh', |
319
|
|
|
'fr', |
320
|
|
|
'de', |
321
|
|
|
'it', |
322
|
|
|
'ja', |
323
|
|
|
'pl', |
324
|
|
|
'ro', |
325
|
|
|
'ru', |
326
|
|
|
'fa', |
327
|
|
|
'es', |
328
|
|
|
'tr', |
329
|
|
|
]; |
330
|
|
|
|
331
|
24 |
|
$lang = 'en'; |
332
|
24 |
|
if (isset($GLOBALS['lang']) && in_array($GLOBALS['lang'], $php_doc_languages)) { |
333
|
|
|
$lang = $GLOBALS['lang']; |
334
|
|
|
} |
335
|
|
|
|
336
|
24 |
|
return self::linkURL('https://secure.php.net/manual/' . $lang . '/' . $target); |
337
|
|
|
} |
338
|
|
|
|
339
|
|
|
/** |
340
|
|
|
* Warn or fail on missing extension. |
341
|
|
|
* |
342
|
|
|
* @param string $extension Extension name |
343
|
|
|
* @param bool $fatal Whether the error is fatal. |
344
|
|
|
* @param string $extra Extra string to append to message. |
345
|
|
|
*/ |
346
|
8 |
|
public static function warnMissingExtension( |
347
|
|
|
string $extension, |
348
|
|
|
bool $fatal = false, |
349
|
|
|
string $extra = '' |
350
|
|
|
): void { |
351
|
|
|
/** @var ErrorHandler $error_handler */ |
352
|
8 |
|
global $error_handler; |
353
|
|
|
|
354
|
|
|
/* Gettext does not have to be loaded yet here */ |
355
|
8 |
|
if (function_exists('__')) { |
356
|
8 |
|
$message = __( |
357
|
8 |
|
'The %s extension is missing. Please check your PHP configuration.' |
358
|
|
|
); |
359
|
|
|
} else { |
360
|
|
|
$message |
361
|
|
|
= 'The %s extension is missing. Please check your PHP configuration.'; |
362
|
|
|
} |
363
|
8 |
|
$doclink = self::getPHPDocLink('book.' . $extension . '.php'); |
364
|
8 |
|
$message = sprintf( |
365
|
8 |
|
$message, |
366
|
8 |
|
'[a@' . $doclink . '@Documentation][em]' . $extension . '[/em][/a]' |
367
|
|
|
); |
368
|
8 |
|
if ($extra != '') { |
369
|
4 |
|
$message .= ' ' . $extra; |
370
|
|
|
} |
371
|
8 |
|
if ($fatal) { |
372
|
8 |
|
self::fatalError($message); |
373
|
|
|
|
374
|
8 |
|
return; |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
$error_handler->addError( |
378
|
|
|
$message, |
379
|
|
|
E_USER_WARNING, |
380
|
|
|
'', |
381
|
|
|
0, |
382
|
|
|
false |
383
|
|
|
); |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
/** |
387
|
|
|
* returns count of tables in given db |
388
|
|
|
* |
389
|
|
|
* @param string $db database to count tables for |
390
|
|
|
* |
391
|
|
|
* @return int count of tables in $db |
392
|
|
|
*/ |
393
|
|
|
public static function getTableCount(string $db): int |
394
|
|
|
{ |
395
|
|
|
$tables = $GLOBALS['dbi']->tryQuery( |
396
|
|
|
'SHOW TABLES FROM ' . Util::backquote($db) . ';', |
397
|
|
|
DatabaseInterface::CONNECT_USER, |
398
|
|
|
DatabaseInterface::QUERY_STORE |
399
|
|
|
); |
400
|
|
|
if ($tables) { |
401
|
|
|
$num_tables = $GLOBALS['dbi']->numRows($tables); |
402
|
|
|
$GLOBALS['dbi']->freeResult($tables); |
403
|
|
|
} else { |
404
|
|
|
$num_tables = 0; |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
return $num_tables; |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
/** |
411
|
|
|
* Converts numbers like 10M into bytes |
412
|
|
|
* Used with permission from Moodle (https://moodle.org) by Martin Dougiamas |
413
|
|
|
* (renamed with PMA prefix to avoid double definition when embedded |
414
|
|
|
* in Moodle) |
415
|
|
|
* |
416
|
|
|
* @param string|int $size size (Default = 0) |
417
|
|
|
*/ |
418
|
9008 |
|
public static function getRealSize($size = 0): int |
419
|
|
|
{ |
420
|
9008 |
|
if (! $size) { |
421
|
4 |
|
return 0; |
422
|
|
|
} |
423
|
|
|
|
424
|
|
|
$binaryprefixes = [ |
425
|
9008 |
|
'T' => 1099511627776, |
426
|
|
|
't' => 1099511627776, |
427
|
|
|
'G' => 1073741824, |
428
|
|
|
'g' => 1073741824, |
429
|
|
|
'M' => 1048576, |
430
|
|
|
'm' => 1048576, |
431
|
|
|
'K' => 1024, |
432
|
|
|
'k' => 1024, |
433
|
|
|
]; |
434
|
|
|
|
435
|
9008 |
|
if (preg_match('/^([0-9]+)([KMGT])/i', $size, $matches)) { |
436
|
9008 |
|
return (int) ($matches[1] * $binaryprefixes[$matches[2]]); |
437
|
|
|
} |
438
|
|
|
|
439
|
4 |
|
return (int) $size; |
440
|
|
|
} // end getRealSize() |
441
|
|
|
|
442
|
|
|
/** |
443
|
|
|
* Checks given $page against given $allowList and returns true if valid |
444
|
|
|
* it optionally ignores query parameters in $page (script.php?ignored) |
445
|
|
|
* |
446
|
|
|
* @param string $page page to check |
447
|
|
|
* @param array $allowList allow list to check page against |
448
|
|
|
* @param bool $include whether the page is going to be included |
449
|
|
|
* |
450
|
|
|
* @return bool whether $page is valid or not (in $allowList or not) |
451
|
|
|
*/ |
452
|
32 |
|
public static function checkPageValidity(&$page, array $allowList = [], $include = false): bool |
453
|
|
|
{ |
454
|
32 |
|
if (empty($allowList)) { |
455
|
8 |
|
$allowList = ['index.php']; |
456
|
|
|
} |
457
|
32 |
|
if (empty($page)) { |
458
|
8 |
|
return false; |
459
|
|
|
} |
460
|
|
|
|
461
|
24 |
|
if (in_array($page, $allowList)) { |
462
|
|
|
return true; |
463
|
|
|
} |
464
|
24 |
|
if ($include) { |
465
|
12 |
|
return false; |
466
|
|
|
} |
467
|
|
|
|
468
|
12 |
|
$_page = mb_substr( |
469
|
12 |
|
$page, |
470
|
12 |
|
0, |
471
|
12 |
|
mb_strpos($page . '?', '?') |
472
|
|
|
); |
473
|
12 |
|
if (in_array($_page, $allowList)) { |
474
|
4 |
|
return true; |
475
|
|
|
} |
476
|
|
|
|
477
|
8 |
|
$_page = urldecode($page); |
478
|
8 |
|
$_page = mb_substr( |
479
|
8 |
|
$_page, |
480
|
8 |
|
0, |
481
|
8 |
|
mb_strpos($_page . '?', '?') |
482
|
|
|
); |
483
|
|
|
|
484
|
8 |
|
return in_array($_page, $allowList); |
485
|
|
|
} |
486
|
|
|
|
487
|
|
|
/** |
488
|
|
|
* tries to find the value for the given environment variable name |
489
|
|
|
* |
490
|
|
|
* searches in $_SERVER, $_ENV then tries getenv() and apache_getenv() |
491
|
|
|
* in this order |
492
|
|
|
* |
493
|
|
|
* @param string $var_name variable name |
494
|
|
|
* |
495
|
|
|
* @return string value of $var or empty string |
496
|
|
|
*/ |
497
|
9008 |
|
public static function getenv(string $var_name): string |
498
|
|
|
{ |
499
|
9008 |
|
if (isset($_SERVER[$var_name])) { |
500
|
508 |
|
return (string) $_SERVER[$var_name]; |
501
|
|
|
} |
502
|
|
|
|
503
|
9008 |
|
if (isset($_ENV[$var_name])) { |
504
|
|
|
return (string) $_ENV[$var_name]; |
505
|
|
|
} |
506
|
|
|
|
507
|
9008 |
|
if (getenv($var_name)) { |
508
|
|
|
return getenv($var_name); |
509
|
|
|
} |
510
|
|
|
|
511
|
9008 |
|
if (function_exists('apache_getenv') |
512
|
9008 |
|
&& apache_getenv($var_name, true) |
513
|
|
|
) { |
514
|
|
|
return apache_getenv($var_name, true); |
515
|
|
|
} |
516
|
|
|
|
517
|
9008 |
|
return ''; |
518
|
|
|
} |
519
|
|
|
|
520
|
|
|
/** |
521
|
|
|
* Send HTTP header, taking IIS limits into account (600 seems ok) |
522
|
|
|
* |
523
|
|
|
* @param string $uri the header to send |
524
|
|
|
* @param bool $use_refresh whether to use Refresh: header when running on IIS |
525
|
|
|
*/ |
526
|
52 |
|
public static function sendHeaderLocation(string $uri, bool $use_refresh = false): void |
527
|
|
|
{ |
528
|
52 |
|
if ($GLOBALS['PMA_Config']->get('PMA_IS_IIS') && mb_strlen($uri) > 600) { |
529
|
4 |
|
Response::getInstance()->disable(); |
530
|
|
|
|
531
|
4 |
|
$template = new Template(); |
532
|
4 |
|
echo $template->render('header_location', ['uri' => $uri]); |
533
|
|
|
|
534
|
4 |
|
return; |
535
|
|
|
} |
536
|
|
|
|
537
|
|
|
/* |
538
|
|
|
* Avoid relative path redirect problems in case user entered URL |
539
|
|
|
* like /phpmyadmin/index.php/ which some web servers happily accept. |
540
|
|
|
*/ |
541
|
48 |
|
if ($uri[0] == '.') { |
542
|
20 |
|
$uri = $GLOBALS['PMA_Config']->getRootPath() . substr($uri, 2); |
543
|
|
|
} |
544
|
|
|
|
545
|
48 |
|
$response = Response::getInstance(); |
546
|
|
|
|
547
|
48 |
|
session_write_close(); |
548
|
48 |
|
if ($response->headersSent()) { |
549
|
|
|
trigger_error( |
550
|
|
|
'Core::sendHeaderLocation called when headers are already sent!', |
551
|
|
|
E_USER_ERROR |
552
|
|
|
); |
553
|
|
|
} |
554
|
|
|
// bug #1523784: IE6 does not like 'Refresh: 0', it |
555
|
|
|
// results in a blank page |
556
|
|
|
// but we need it when coming from the cookie login panel) |
557
|
48 |
|
if ($GLOBALS['PMA_Config']->get('PMA_IS_IIS') && $use_refresh) { |
558
|
4 |
|
$response->header('Refresh: 0; ' . $uri); |
559
|
|
|
} else { |
560
|
48 |
|
$response->header('Location: ' . $uri); |
561
|
|
|
} |
562
|
48 |
|
} |
563
|
|
|
|
564
|
|
|
/** |
565
|
|
|
* Outputs application/json headers. This includes no caching. |
566
|
|
|
*/ |
567
|
|
|
public static function headerJSON(): void |
568
|
|
|
{ |
569
|
|
|
if (defined('TESTSUITE')) { |
570
|
|
|
return; |
571
|
|
|
} |
572
|
|
|
// No caching |
573
|
|
|
self::noCacheHeader(); |
574
|
|
|
// MIME type |
575
|
|
|
header('Content-Type: application/json; charset=UTF-8'); |
576
|
|
|
// Disable content sniffing in browser |
577
|
|
|
// This is needed in case we include HTML in JSON, browser might assume it's |
578
|
|
|
// html to display |
579
|
|
|
header('X-Content-Type-Options: nosniff'); |
580
|
|
|
} |
581
|
|
|
|
582
|
|
|
/** |
583
|
|
|
* Outputs headers to prevent caching in browser (and on the way). |
584
|
|
|
*/ |
585
|
|
|
public static function noCacheHeader(): void |
586
|
|
|
{ |
587
|
|
|
if (defined('TESTSUITE')) { |
588
|
|
|
return; |
589
|
|
|
} |
590
|
|
|
// rfc2616 - Section 14.21 |
591
|
|
|
header('Expires: ' . gmdate(DATE_RFC1123)); |
592
|
|
|
// HTTP/1.1 |
593
|
|
|
header( |
594
|
|
|
'Cache-Control: no-store, no-cache, must-revalidate,' |
595
|
|
|
. ' pre-check=0, post-check=0, max-age=0' |
596
|
|
|
); |
597
|
|
|
|
598
|
|
|
header('Pragma: no-cache'); // HTTP/1.0 |
599
|
|
|
// test case: exporting a database into a .gz file with Safari |
600
|
|
|
// would produce files not having the current time |
601
|
|
|
// (added this header for Safari but should not harm other browsers) |
602
|
|
|
header('Last-Modified: ' . gmdate(DATE_RFC1123)); |
603
|
|
|
} |
604
|
|
|
|
605
|
|
|
/** |
606
|
|
|
* Sends header indicating file download. |
607
|
|
|
* |
608
|
|
|
* @param string $filename Filename to include in headers if empty, |
609
|
|
|
* none Content-Disposition header will be sent. |
610
|
|
|
* @param string $mimetype MIME type to include in headers. |
611
|
|
|
* @param int $length Length of content (optional) |
612
|
|
|
* @param bool $no_cache Whether to include no-caching headers. |
613
|
|
|
*/ |
614
|
|
|
public static function downloadHeader( |
615
|
|
|
string $filename, |
616
|
|
|
string $mimetype, |
617
|
|
|
int $length = 0, |
618
|
|
|
bool $no_cache = true |
619
|
|
|
): void { |
620
|
|
|
if ($no_cache) { |
621
|
|
|
self::noCacheHeader(); |
622
|
|
|
} |
623
|
|
|
/* Replace all possibly dangerous chars in filename */ |
624
|
|
|
$filename = Sanitize::sanitizeFilename($filename); |
625
|
|
|
if (! empty($filename)) { |
626
|
|
|
header('Content-Description: File Transfer'); |
627
|
|
|
header('Content-Disposition: attachment; filename="' . $filename . '"'); |
628
|
|
|
} |
629
|
|
|
header('Content-Type: ' . $mimetype); |
630
|
|
|
// inform the server that compression has been done, |
631
|
|
|
// to avoid a double compression (for example with Apache + mod_deflate) |
632
|
|
|
$notChromeOrLessThan43 = PMA_USR_BROWSER_AGENT != 'CHROME' // see bug #4942 |
|
|
|
|
633
|
|
|
|| (PMA_USR_BROWSER_AGENT == 'CHROME' && PMA_USR_BROWSER_VER < 43); |
634
|
|
|
if (strpos($mimetype, 'gzip') !== false && $notChromeOrLessThan43) { |
635
|
|
|
header('Content-Encoding: gzip'); |
636
|
|
|
} |
637
|
|
|
header('Content-Transfer-Encoding: binary'); |
638
|
|
|
if ($length <= 0) { |
639
|
|
|
return; |
640
|
|
|
} |
641
|
|
|
|
642
|
|
|
header('Content-Length: ' . $length); |
643
|
|
|
} |
644
|
|
|
|
645
|
|
|
/** |
646
|
|
|
* Returns value of an element in $array given by $path. |
647
|
|
|
* $path is a string describing position of an element in an associative array, |
648
|
|
|
* eg. Servers/1/host refers to $array[Servers][1][host] |
649
|
|
|
* |
650
|
|
|
* @param string $path path in the array |
651
|
|
|
* @param array $array the array |
652
|
|
|
* @param mixed $default default value |
653
|
|
|
* |
654
|
|
|
* @return array|mixed|null array element or $default |
655
|
|
|
*/ |
656
|
159 |
|
public static function arrayRead(string $path, array $array, $default = null) |
657
|
|
|
{ |
658
|
159 |
|
$keys = explode('/', $path); |
659
|
159 |
|
$value =& $array; |
660
|
159 |
|
foreach ($keys as $key) { |
661
|
159 |
|
if (! isset($value[$key])) { |
662
|
132 |
|
return $default; |
663
|
|
|
} |
664
|
115 |
|
$value =& $value[$key]; |
665
|
|
|
} |
666
|
|
|
|
667
|
103 |
|
return $value; |
668
|
|
|
} |
669
|
|
|
|
670
|
|
|
/** |
671
|
|
|
* Stores value in an array |
672
|
|
|
* |
673
|
|
|
* @param string $path path in the array |
674
|
|
|
* @param array $array the array |
675
|
|
|
* @param mixed $value value to store |
676
|
|
|
*/ |
677
|
80 |
|
public static function arrayWrite(string $path, array &$array, $value): void |
678
|
|
|
{ |
679
|
80 |
|
$keys = explode('/', $path); |
680
|
80 |
|
$last_key = array_pop($keys); |
681
|
80 |
|
$a =& $array; |
682
|
80 |
|
foreach ($keys as $key) { |
683
|
52 |
|
if (! isset($a[$key])) { |
684
|
52 |
|
$a[$key] = []; |
685
|
|
|
} |
686
|
52 |
|
$a =& $a[$key]; |
687
|
|
|
} |
688
|
80 |
|
$a[$last_key] = $value; |
689
|
80 |
|
} |
690
|
|
|
|
691
|
|
|
/** |
692
|
|
|
* Removes value from an array |
693
|
|
|
* |
694
|
|
|
* @param string $path path in the array |
695
|
|
|
* @param array $array the array |
696
|
|
|
*/ |
697
|
32 |
|
public static function arrayRemove(string $path, array &$array): void |
698
|
|
|
{ |
699
|
32 |
|
$keys = explode('/', $path); |
700
|
32 |
|
$keys_last = array_pop($keys); |
701
|
32 |
|
$path = []; |
702
|
32 |
|
$depth = 0; |
703
|
|
|
|
704
|
32 |
|
$path[0] =& $array; |
705
|
32 |
|
$found = true; |
706
|
|
|
// go as deep as required or possible |
707
|
32 |
|
foreach ($keys as $key) { |
708
|
24 |
|
if (! isset($path[$depth][$key])) { |
709
|
20 |
|
$found = false; |
710
|
20 |
|
break; |
711
|
|
|
} |
712
|
12 |
|
$depth++; |
713
|
12 |
|
$path[$depth] =& $path[$depth - 1][$key]; |
714
|
|
|
} |
715
|
|
|
// if element found, remove it |
716
|
32 |
|
if ($found) { |
717
|
28 |
|
unset($path[$depth][$keys_last]); |
718
|
28 |
|
$depth--; |
719
|
|
|
} |
720
|
|
|
|
721
|
|
|
// remove empty nested arrays |
722
|
32 |
|
for (; $depth >= 0; $depth--) { |
723
|
24 |
|
if (isset($path[$depth + 1]) && count($path[$depth + 1]) !== 0) { |
724
|
12 |
|
break; |
725
|
|
|
} |
726
|
|
|
|
727
|
20 |
|
unset($path[$depth][$keys[$depth]]); |
728
|
|
|
} |
729
|
32 |
|
} |
730
|
|
|
|
731
|
|
|
/** |
732
|
|
|
* Returns link to (possibly) external site using defined redirector. |
733
|
|
|
* |
734
|
|
|
* @param string $url URL where to go. |
735
|
|
|
* |
736
|
|
|
* @return string URL for a link. |
737
|
|
|
*/ |
738
|
422 |
|
public static function linkURL(string $url): string |
739
|
|
|
{ |
740
|
422 |
|
if (! preg_match('#^https?://#', $url)) { |
741
|
12 |
|
return $url; |
742
|
|
|
} |
743
|
|
|
|
744
|
414 |
|
$params = []; |
745
|
414 |
|
$params['url'] = $url; |
746
|
|
|
|
747
|
414 |
|
$url = Url::getCommon($params); |
748
|
|
|
//strip off token and such sensitive information. Just keep url. |
749
|
414 |
|
$arr = parse_url($url); |
750
|
|
|
|
751
|
414 |
|
if (! is_array($arr)) { |
|
|
|
|
752
|
|
|
$arr = []; |
753
|
|
|
} |
754
|
|
|
|
755
|
414 |
|
parse_str($arr['query'] ?? '', $vars); |
756
|
414 |
|
$query = http_build_query(['url' => $vars['url']]); |
757
|
|
|
|
758
|
414 |
|
if ($GLOBALS['PMA_Config'] !== null && $GLOBALS['PMA_Config']->get('is_setup')) { |
759
|
|
|
$url = '../url.php?' . $query; |
760
|
|
|
} else { |
761
|
414 |
|
$url = './url.php?' . $query; |
762
|
|
|
} |
763
|
|
|
|
764
|
414 |
|
return $url; |
765
|
|
|
} |
766
|
|
|
|
767
|
|
|
/** |
768
|
|
|
* Checks whether domain of URL is an allowed domain or not. |
769
|
|
|
* Use only for URLs of external sites. |
770
|
|
|
* |
771
|
|
|
* @param string $url URL of external site. |
772
|
|
|
* |
773
|
|
|
* @return bool True: if domain of $url is allowed domain, |
774
|
|
|
* False: otherwise. |
775
|
|
|
*/ |
776
|
32 |
|
public static function isAllowedDomain(string $url): bool |
777
|
|
|
{ |
778
|
32 |
|
$arr = parse_url($url); |
779
|
|
|
|
780
|
32 |
|
if (! is_array($arr)) { |
|
|
|
|
781
|
|
|
$arr = []; |
782
|
|
|
} |
783
|
|
|
|
784
|
|
|
// We need host to be set |
785
|
32 |
|
if (! isset($arr['host']) || strlen($arr['host']) == 0) { |
786
|
4 |
|
return false; |
787
|
|
|
} |
788
|
|
|
// We do not want these to be present |
789
|
|
|
$blocked = [ |
790
|
28 |
|
'user', |
791
|
|
|
'pass', |
792
|
|
|
'port', |
793
|
|
|
]; |
794
|
28 |
|
foreach ($blocked as $part) { |
795
|
28 |
|
if (isset($arr[$part]) && strlen((string) $arr[$part]) != 0) { |
796
|
19 |
|
return false; |
797
|
|
|
} |
798
|
|
|
} |
799
|
12 |
|
$domain = $arr['host']; |
800
|
|
|
$domainAllowList = [ |
801
|
|
|
/* Include current domain */ |
802
|
12 |
|
$_SERVER['SERVER_NAME'], |
803
|
|
|
/* phpMyAdmin domains */ |
804
|
12 |
|
'wiki.phpmyadmin.net', |
805
|
12 |
|
'www.phpmyadmin.net', |
806
|
12 |
|
'phpmyadmin.net', |
807
|
12 |
|
'demo.phpmyadmin.net', |
808
|
12 |
|
'docs.phpmyadmin.net', |
809
|
|
|
/* mysql.com domains */ |
810
|
12 |
|
'dev.mysql.com', |
811
|
12 |
|
'bugs.mysql.com', |
812
|
|
|
/* mariadb domains */ |
813
|
12 |
|
'mariadb.org', |
814
|
12 |
|
'mariadb.com', |
815
|
|
|
/* php.net domains */ |
816
|
12 |
|
'php.net', |
817
|
12 |
|
'secure.php.net', |
818
|
|
|
/* Github domains*/ |
819
|
12 |
|
'github.com', |
820
|
12 |
|
'www.github.com', |
821
|
|
|
/* Percona domains */ |
822
|
12 |
|
'www.percona.com', |
823
|
|
|
/* Following are doubtful ones. */ |
824
|
12 |
|
'mysqldatabaseadministration.blogspot.com', |
825
|
|
|
]; |
826
|
|
|
|
827
|
12 |
|
return in_array($domain, $domainAllowList); |
828
|
|
|
} |
829
|
|
|
|
830
|
|
|
/** |
831
|
|
|
* Replace some html-unfriendly stuff |
832
|
|
|
* |
833
|
|
|
* @param string $buffer String to process |
834
|
|
|
* |
835
|
|
|
* @return string Escaped and cleaned up text suitable for html |
836
|
|
|
*/ |
837
|
20 |
|
public static function mimeDefaultFunction(string $buffer): string |
838
|
|
|
{ |
839
|
20 |
|
$buffer = htmlspecialchars($buffer); |
840
|
20 |
|
$buffer = str_replace(' ', ' ', $buffer); |
841
|
|
|
|
842
|
20 |
|
return preg_replace("@((\015\012)|(\015)|(\012))@", '<br>' . "\n", $buffer); |
843
|
|
|
} |
844
|
|
|
|
845
|
|
|
/** |
846
|
|
|
* Displays SQL query before executing. |
847
|
|
|
* |
848
|
|
|
* @param array|string $query_data Array containing queries or query itself |
849
|
|
|
*/ |
850
|
|
|
public static function previewSQL($query_data): void |
851
|
|
|
{ |
852
|
|
|
$retval = '<div class="preview_sql">'; |
853
|
|
|
if (empty($query_data)) { |
854
|
|
|
$retval .= __('No change'); |
855
|
|
|
} elseif (is_array($query_data)) { |
856
|
|
|
foreach ($query_data as $query) { |
857
|
|
|
$retval .= Html\Generator::formatSql($query); |
858
|
|
|
} |
859
|
|
|
} else { |
860
|
|
|
$retval .= Html\Generator::formatSql($query_data); |
861
|
|
|
} |
862
|
|
|
$retval .= '</div>'; |
863
|
|
|
$response = Response::getInstance(); |
864
|
|
|
$response->addJSON('sql_data', $retval); |
865
|
|
|
exit; |
|
|
|
|
866
|
|
|
} |
867
|
|
|
|
868
|
|
|
/** |
869
|
|
|
* recursively check if variable is empty |
870
|
|
|
* |
871
|
|
|
* @param mixed $value the variable |
872
|
|
|
* |
873
|
|
|
* @return bool true if empty |
874
|
|
|
*/ |
875
|
|
|
public static function emptyRecursive($value): bool |
876
|
|
|
{ |
877
|
|
|
$empty = true; |
878
|
|
|
if (is_array($value)) { |
879
|
|
|
array_walk_recursive( |
880
|
|
|
$value, |
881
|
|
|
/** |
882
|
|
|
* @param mixed $item |
883
|
|
|
*/ |
884
|
|
|
static function ($item) use (&$empty) { |
885
|
|
|
$empty = $empty && empty($item); |
886
|
|
|
} |
887
|
|
|
); |
888
|
|
|
} else { |
889
|
|
|
$empty = empty($value); |
890
|
|
|
} |
891
|
|
|
|
892
|
|
|
return $empty; |
893
|
|
|
} |
894
|
|
|
|
895
|
|
|
/** |
896
|
|
|
* Creates some globals from $_POST variables matching a pattern |
897
|
|
|
* |
898
|
|
|
* @param array $post_patterns The patterns to search for |
899
|
|
|
*/ |
900
|
|
|
public static function setPostAsGlobal(array $post_patterns): void |
901
|
|
|
{ |
902
|
|
|
global $containerBuilder; |
903
|
|
|
|
904
|
|
|
foreach (array_keys($_POST) as $post_key) { |
905
|
|
|
foreach ($post_patterns as $one_post_pattern) { |
906
|
|
|
if (! preg_match($one_post_pattern, $post_key)) { |
907
|
|
|
continue; |
908
|
|
|
} |
909
|
|
|
|
910
|
|
|
$GLOBALS[$post_key] = $_POST[$post_key]; |
911
|
|
|
$containerBuilder->setParameter($post_key, $GLOBALS[$post_key]); |
912
|
|
|
} |
913
|
|
|
} |
914
|
|
|
} |
915
|
|
|
|
916
|
|
|
public static function setDatabaseAndTableFromRequest(ContainerBuilder $containerBuilder): void |
917
|
|
|
{ |
918
|
|
|
global $db, $table, $url_params; |
919
|
|
|
|
920
|
|
|
$databaseFromRequest = $_POST['db'] ?? $_GET['db'] ?? $_REQUEST['db'] ?? null; |
921
|
|
|
$tableFromRequest = $_POST['table'] ?? $_GET['table'] ?? $_REQUEST['table'] ?? null; |
922
|
|
|
|
923
|
|
|
$db = self::isValid($databaseFromRequest) ? $databaseFromRequest : ''; |
924
|
|
|
$table = self::isValid($tableFromRequest) ? $tableFromRequest : ''; |
925
|
|
|
|
926
|
|
|
$url_params['db'] = $db; |
927
|
|
|
$url_params['table'] = $table; |
928
|
|
|
$containerBuilder->setParameter('db', $db); |
929
|
|
|
$containerBuilder->setParameter('table', $table); |
930
|
|
|
$containerBuilder->setParameter('url_params', $url_params); |
931
|
|
|
} |
932
|
|
|
|
933
|
|
|
/** |
934
|
|
|
* PATH_INFO could be compromised if set, so remove it from PHP_SELF |
935
|
|
|
* and provide a clean PHP_SELF here |
936
|
|
|
*/ |
937
|
32 |
|
public static function cleanupPathInfo(): void |
938
|
|
|
{ |
939
|
32 |
|
global $PMA_PHP_SELF; |
940
|
|
|
|
941
|
32 |
|
$PMA_PHP_SELF = self::getenv('PHP_SELF'); |
942
|
32 |
|
if (empty($PMA_PHP_SELF)) { |
943
|
20 |
|
$PMA_PHP_SELF = urldecode(self::getenv('REQUEST_URI')); |
944
|
|
|
} |
945
|
32 |
|
$_PATH_INFO = self::getenv('PATH_INFO'); |
946
|
32 |
|
if (! empty($_PATH_INFO) && ! empty($PMA_PHP_SELF)) { |
947
|
12 |
|
$question_pos = mb_strpos($PMA_PHP_SELF, '?'); |
948
|
12 |
|
if ($question_pos != false) { |
|
|
|
|
949
|
4 |
|
$PMA_PHP_SELF = mb_substr($PMA_PHP_SELF, 0, $question_pos); |
950
|
|
|
} |
951
|
12 |
|
$path_info_pos = mb_strrpos($PMA_PHP_SELF, $_PATH_INFO); |
952
|
12 |
|
if ($path_info_pos !== false) { |
953
|
12 |
|
$path_info_part = mb_substr($PMA_PHP_SELF, $path_info_pos, mb_strlen($_PATH_INFO)); |
954
|
12 |
|
if ($path_info_part == $_PATH_INFO) { |
955
|
12 |
|
$PMA_PHP_SELF = mb_substr($PMA_PHP_SELF, 0, $path_info_pos); |
956
|
|
|
} |
957
|
|
|
} |
958
|
|
|
} |
959
|
|
|
|
960
|
32 |
|
$path = []; |
961
|
32 |
|
foreach (explode('/', $PMA_PHP_SELF) as $part) { |
962
|
|
|
// ignore parts that have no value |
963
|
32 |
|
if (empty($part) || $part === '.') { |
964
|
32 |
|
continue; |
965
|
|
|
} |
966
|
|
|
|
967
|
32 |
|
if ($part !== '..') { |
968
|
|
|
// cool, we found a new part |
969
|
32 |
|
$path[] = $part; |
970
|
8 |
|
} elseif (count($path) > 0) { |
971
|
|
|
// going back up? sure |
972
|
14 |
|
array_pop($path); |
973
|
|
|
} |
974
|
|
|
// Here we intentionall ignore case where we go too up |
975
|
|
|
// as there is nothing sane to do |
976
|
|
|
} |
977
|
|
|
|
978
|
32 |
|
$PMA_PHP_SELF = htmlspecialchars('/' . implode('/', $path)); |
979
|
32 |
|
} |
980
|
|
|
|
981
|
|
|
/** |
982
|
|
|
* Checks that required PHP extensions are there. |
983
|
|
|
*/ |
984
|
|
|
public static function checkExtensions(): void |
985
|
|
|
{ |
986
|
|
|
/** |
987
|
|
|
* Warning about mbstring. |
988
|
|
|
*/ |
989
|
|
|
if (! function_exists('mb_detect_encoding')) { |
990
|
|
|
self::warnMissingExtension('mbstring'); |
991
|
|
|
} |
992
|
|
|
|
993
|
|
|
/** |
994
|
|
|
* We really need this one! |
995
|
|
|
*/ |
996
|
|
|
if (! function_exists('preg_replace')) { |
997
|
|
|
self::warnMissingExtension('pcre', true); |
998
|
|
|
} |
999
|
|
|
|
1000
|
|
|
/** |
1001
|
|
|
* JSON is required in several places. |
1002
|
|
|
*/ |
1003
|
|
|
if (! function_exists('json_encode')) { |
1004
|
|
|
self::warnMissingExtension('json', true); |
1005
|
|
|
} |
1006
|
|
|
|
1007
|
|
|
/** |
1008
|
|
|
* ctype is required for Twig. |
1009
|
|
|
*/ |
1010
|
|
|
if (! function_exists('ctype_alpha')) { |
1011
|
|
|
self::warnMissingExtension('ctype', true); |
1012
|
|
|
} |
1013
|
|
|
|
1014
|
|
|
/** |
1015
|
|
|
* hash is required for cookie authentication. |
1016
|
|
|
*/ |
1017
|
|
|
if (function_exists('hash_hmac')) { |
1018
|
|
|
return; |
1019
|
|
|
} |
1020
|
|
|
|
1021
|
|
|
self::warnMissingExtension('hash', true); |
1022
|
|
|
} |
1023
|
|
|
|
1024
|
|
|
/** |
1025
|
|
|
* Gets the "true" IP address of the current user |
1026
|
|
|
* |
1027
|
|
|
* @return string|bool the ip of the user |
1028
|
|
|
* |
1029
|
|
|
* @access private |
1030
|
|
|
*/ |
1031
|
108 |
|
public static function getIp() |
1032
|
|
|
{ |
1033
|
|
|
/* Get the address of user */ |
1034
|
108 |
|
if (empty($_SERVER['REMOTE_ADDR'])) { |
1035
|
|
|
/* We do not know remote IP */ |
1036
|
56 |
|
return false; |
1037
|
|
|
} |
1038
|
|
|
|
1039
|
56 |
|
$direct_ip = $_SERVER['REMOTE_ADDR']; |
1040
|
|
|
|
1041
|
|
|
/* Do we trust this IP as a proxy? If yes we will use it's header. */ |
1042
|
56 |
|
if (! isset($GLOBALS['cfg']['TrustedProxies'][$direct_ip])) { |
1043
|
|
|
/* Return true IP */ |
1044
|
44 |
|
return $direct_ip; |
1045
|
|
|
} |
1046
|
|
|
|
1047
|
|
|
/** |
1048
|
|
|
* Parse header in form: |
1049
|
|
|
* X-Forwarded-For: client, proxy1, proxy2 |
1050
|
|
|
*/ |
1051
|
|
|
// Get header content |
1052
|
12 |
|
$value = self::getenv($GLOBALS['cfg']['TrustedProxies'][$direct_ip]); |
1053
|
|
|
// Grab first element what is client adddress |
1054
|
12 |
|
$value = explode(',', $value)[0]; |
1055
|
|
|
// checks that the header contains only one IP address, |
1056
|
12 |
|
$is_ip = filter_var($value, FILTER_VALIDATE_IP); |
1057
|
|
|
|
1058
|
12 |
|
if ($is_ip !== false) { |
1059
|
|
|
// True IP behind a proxy |
1060
|
8 |
|
return $value; |
1061
|
|
|
} |
1062
|
|
|
|
1063
|
|
|
// We could not parse header |
1064
|
4 |
|
return false; |
1065
|
|
|
} // end of the 'getIp()' function |
1066
|
|
|
|
1067
|
|
|
/** |
1068
|
|
|
* Sanitizes MySQL hostname |
1069
|
|
|
* |
1070
|
|
|
* * strips p: prefix(es) |
1071
|
|
|
* |
1072
|
|
|
* @param string $name User given hostname |
1073
|
|
|
*/ |
1074
|
20 |
|
public static function sanitizeMySQLHost(string $name): string |
1075
|
|
|
{ |
1076
|
20 |
|
while (strtolower(substr($name, 0, 2)) == 'p:') { |
1077
|
12 |
|
$name = substr($name, 2); |
1078
|
|
|
} |
1079
|
|
|
|
1080
|
20 |
|
return $name; |
1081
|
|
|
} |
1082
|
|
|
|
1083
|
|
|
/** |
1084
|
|
|
* Sanitizes MySQL username |
1085
|
|
|
* |
1086
|
|
|
* * strips part behind null byte |
1087
|
|
|
* |
1088
|
|
|
* @param string $name User given username |
1089
|
|
|
*/ |
1090
|
28 |
|
public static function sanitizeMySQLUser(string $name): string |
1091
|
|
|
{ |
1092
|
28 |
|
$position = strpos($name, chr(0)); |
1093
|
28 |
|
if ($position !== false) { |
1094
|
|
|
return substr($name, 0, $position); |
1095
|
|
|
} |
1096
|
|
|
|
1097
|
28 |
|
return $name; |
1098
|
|
|
} |
1099
|
|
|
|
1100
|
|
|
/** |
1101
|
|
|
* Safe unserializer wrapper |
1102
|
|
|
* |
1103
|
|
|
* It does not unserialize data containing objects |
1104
|
|
|
* |
1105
|
|
|
* @param string $data Data to unserialize |
1106
|
|
|
* |
1107
|
|
|
* @return mixed|null |
1108
|
|
|
*/ |
1109
|
40 |
|
public static function safeUnserialize(string $data) |
1110
|
|
|
{ |
1111
|
40 |
|
if (! is_string($data)) { |
|
|
|
|
1112
|
|
|
return null; |
1113
|
|
|
} |
1114
|
|
|
|
1115
|
|
|
/* validate serialized data */ |
1116
|
40 |
|
$length = strlen($data); |
1117
|
40 |
|
$depth = 0; |
1118
|
40 |
|
for ($i = 0; $i < $length; $i++) { |
1119
|
40 |
|
$value = $data[$i]; |
1120
|
|
|
|
1121
|
30 |
|
switch ($value) { |
1122
|
40 |
|
case '}': |
1123
|
|
|
/* end of array */ |
1124
|
8 |
|
if ($depth <= 0) { |
1125
|
|
|
return null; |
1126
|
|
|
} |
1127
|
8 |
|
$depth--; |
1128
|
8 |
|
break; |
1129
|
40 |
|
case 's': |
1130
|
|
|
/* string */ |
1131
|
|
|
// parse sting length |
1132
|
20 |
|
$strlen = intval(substr($data, $i + 2)); |
1133
|
|
|
// string start |
1134
|
20 |
|
$i = strpos($data, ':', $i + 2); |
1135
|
20 |
|
if ($i === false) { |
1136
|
|
|
return null; |
1137
|
|
|
} |
1138
|
|
|
// skip string, quotes and ; |
1139
|
20 |
|
$i += 2 + $strlen + 1; |
1140
|
20 |
|
if ($data[$i] != ';') { |
1141
|
|
|
return null; |
1142
|
|
|
} |
1143
|
20 |
|
break; |
1144
|
|
|
|
1145
|
32 |
|
case 'b': |
1146
|
28 |
|
case 'i': |
1147
|
28 |
|
case 'd': |
1148
|
|
|
/* bool, integer or double */ |
1149
|
|
|
// skip value to sepearator |
1150
|
16 |
|
$i = strpos($data, ';', $i); |
1151
|
16 |
|
if ($i === false) { |
1152
|
|
|
return null; |
1153
|
|
|
} |
1154
|
16 |
|
break; |
1155
|
28 |
|
case 'a': |
1156
|
|
|
/* array */ |
1157
|
|
|
// find array start |
1158
|
16 |
|
$i = strpos($data, '{', $i); |
1159
|
16 |
|
if ($i === false) { |
1160
|
|
|
return null; |
1161
|
|
|
} |
1162
|
|
|
// remember nesting |
1163
|
16 |
|
$depth++; |
1164
|
16 |
|
break; |
1165
|
20 |
|
case 'N': |
1166
|
|
|
/* null */ |
1167
|
|
|
// skip to end |
1168
|
|
|
$i = strpos($data, ';', $i); |
1169
|
|
|
if ($i === false) { |
1170
|
|
|
return null; |
1171
|
|
|
} |
1172
|
|
|
break; |
1173
|
|
|
default: |
1174
|
|
|
/* any other elements are not wanted */ |
1175
|
20 |
|
return null; |
1176
|
|
|
} |
1177
|
|
|
} |
1178
|
|
|
|
1179
|
|
|
// check unterminated arrays |
1180
|
20 |
|
if ($depth > 0) { |
1181
|
|
|
return null; |
1182
|
|
|
} |
1183
|
|
|
|
1184
|
20 |
|
return unserialize($data); |
1185
|
|
|
} |
1186
|
|
|
|
1187
|
|
|
/** |
1188
|
|
|
* Applies changes to PHP configuration. |
1189
|
|
|
*/ |
1190
|
|
|
public static function configure(): void |
1191
|
|
|
{ |
1192
|
|
|
/** |
1193
|
|
|
* Set utf-8 encoding for PHP |
1194
|
|
|
*/ |
1195
|
|
|
ini_set('default_charset', 'utf-8'); |
1196
|
|
|
mb_internal_encoding('utf-8'); |
1197
|
|
|
|
1198
|
|
|
/** |
1199
|
|
|
* Set precision to sane value, with higher values |
1200
|
|
|
* things behave slightly unexpectedly, for example |
1201
|
|
|
* round(1.2, 2) returns 1.199999999999999956. |
1202
|
|
|
*/ |
1203
|
|
|
ini_set('precision', '14'); |
1204
|
|
|
|
1205
|
|
|
/** |
1206
|
|
|
* check timezone setting |
1207
|
|
|
* this could produce an E_WARNING - but only once, |
1208
|
|
|
* if not done here it will produce E_WARNING on every date/time function |
1209
|
|
|
*/ |
1210
|
|
|
date_default_timezone_set(@date_default_timezone_get()); |
1211
|
|
|
} |
1212
|
|
|
|
1213
|
|
|
/** |
1214
|
|
|
* Check whether PHP configuration matches our needs. |
1215
|
|
|
*/ |
1216
|
|
|
public static function checkConfiguration(): void |
1217
|
|
|
{ |
1218
|
|
|
/** |
1219
|
|
|
* As we try to handle charsets by ourself, mbstring overloads just |
1220
|
|
|
* break it, see bug 1063821. |
1221
|
|
|
* |
1222
|
|
|
* We specifically use empty here as we are looking for anything else than |
1223
|
|
|
* empty value or 0. |
1224
|
|
|
*/ |
1225
|
|
|
if (extension_loaded('mbstring') && ! empty(ini_get('mbstring.func_overload'))) { |
1226
|
|
|
self::fatalError( |
1227
|
|
|
__( |
1228
|
|
|
'You have enabled mbstring.func_overload in your PHP ' |
1229
|
|
|
. 'configuration. This option is incompatible with phpMyAdmin ' |
1230
|
|
|
. 'and might cause some data to be corrupted!' |
1231
|
|
|
) |
1232
|
|
|
); |
1233
|
|
|
} |
1234
|
|
|
|
1235
|
|
|
/** |
1236
|
|
|
* The ini_set and ini_get functions can be disabled using |
1237
|
|
|
* disable_functions but we're relying quite a lot of them. |
1238
|
|
|
*/ |
1239
|
|
|
if (function_exists('ini_get') && function_exists('ini_set')) { |
1240
|
|
|
return; |
1241
|
|
|
} |
1242
|
|
|
|
1243
|
|
|
self::fatalError( |
1244
|
|
|
__( |
1245
|
|
|
'The ini_get and/or ini_set functions are disabled in php.ini. ' |
1246
|
|
|
. 'phpMyAdmin requires these functions!' |
1247
|
|
|
) |
1248
|
|
|
); |
1249
|
|
|
} |
1250
|
|
|
|
1251
|
|
|
/** |
1252
|
|
|
* Checks request and fails with fatal error if something problematic is found |
1253
|
|
|
*/ |
1254
|
|
|
public static function checkRequest(): void |
1255
|
|
|
{ |
1256
|
|
|
if (isset($_REQUEST['GLOBALS']) || isset($_FILES['GLOBALS'])) { |
1257
|
|
|
self::fatalError(__('GLOBALS overwrite attempt')); |
1258
|
|
|
} |
1259
|
|
|
|
1260
|
|
|
/** |
1261
|
|
|
* protect against possible exploits - there is no need to have so much variables |
1262
|
|
|
*/ |
1263
|
|
|
if (count($_REQUEST) <= 1000) { |
1264
|
|
|
return; |
1265
|
|
|
} |
1266
|
|
|
|
1267
|
|
|
self::fatalError(__('possible exploit')); |
1268
|
|
|
} |
1269
|
|
|
|
1270
|
|
|
/** |
1271
|
|
|
* Sign the sql query using hmac using the session token |
1272
|
|
|
* |
1273
|
|
|
* @param string $sqlQuery The sql query |
1274
|
|
|
* |
1275
|
|
|
* @return string |
1276
|
|
|
*/ |
1277
|
32 |
|
public static function signSqlQuery($sqlQuery) |
1278
|
|
|
{ |
1279
|
32 |
|
global $cfg; |
1280
|
|
|
|
1281
|
32 |
|
$secret = $_SESSION[' HMAC_secret '] ?? ''; |
1282
|
|
|
|
1283
|
32 |
|
return hash_hmac('sha256', $sqlQuery, $secret . $cfg['blowfish_secret']); |
1284
|
|
|
} |
1285
|
|
|
|
1286
|
|
|
/** |
1287
|
|
|
* Check that the sql query has a valid hmac signature |
1288
|
|
|
* |
1289
|
|
|
* @param string $sqlQuery The sql query |
1290
|
|
|
* @param string $signature The Signature to check |
1291
|
|
|
* |
1292
|
|
|
* @return bool |
1293
|
|
|
*/ |
1294
|
24 |
|
public static function checkSqlQuerySignature($sqlQuery, $signature) |
1295
|
|
|
{ |
1296
|
24 |
|
global $cfg; |
1297
|
|
|
|
1298
|
24 |
|
$secret = $_SESSION[' HMAC_secret '] ?? ''; |
1299
|
24 |
|
$hmac = hash_hmac('sha256', $sqlQuery, $secret . $cfg['blowfish_secret']); |
1300
|
|
|
|
1301
|
24 |
|
return hash_equals($hmac, $signature); |
1302
|
|
|
} |
1303
|
|
|
|
1304
|
|
|
/** |
1305
|
|
|
* Check whether user supplied token is valid, if not remove any possibly |
1306
|
|
|
* dangerous stuff from request. |
1307
|
|
|
* |
1308
|
|
|
* Check for token mismatch only if the Request method is POST. |
1309
|
|
|
* GET Requests would never have token and therefore checking |
1310
|
|
|
* mis-match does not make sense. |
1311
|
|
|
*/ |
1312
|
4 |
|
public static function checkTokenRequestParam(): void |
1313
|
|
|
{ |
1314
|
4 |
|
global $token_mismatch, $token_provided; |
1315
|
|
|
|
1316
|
4 |
|
$token_mismatch = true; |
1317
|
4 |
|
$token_provided = false; |
1318
|
|
|
|
1319
|
4 |
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { |
1320
|
4 |
|
return; |
1321
|
|
|
} |
1322
|
|
|
|
1323
|
4 |
|
if (self::isValid($_POST['token'])) { |
1324
|
4 |
|
$token_provided = true; |
1325
|
4 |
|
$token_mismatch = ! @hash_equals($_SESSION[' PMA_token '], $_POST['token']); |
1326
|
|
|
} |
1327
|
|
|
|
1328
|
4 |
|
if (! $token_mismatch) { |
1329
|
4 |
|
return; |
1330
|
|
|
} |
1331
|
|
|
|
1332
|
|
|
// Warn in case the mismatch is result of failed setting of session cookie |
1333
|
4 |
|
if (isset($_POST['set_session']) && $_POST['set_session'] !== session_id()) { |
1334
|
|
|
trigger_error( |
1335
|
|
|
__( |
1336
|
|
|
'Failed to set session cookie. Maybe you are using ' |
1337
|
|
|
. 'HTTP instead of HTTPS to access phpMyAdmin.' |
1338
|
|
|
), |
1339
|
|
|
E_USER_ERROR |
1340
|
|
|
); |
1341
|
|
|
} |
1342
|
|
|
|
1343
|
|
|
/** |
1344
|
|
|
* We don't allow any POST operation parameters if the token is mismatched |
1345
|
|
|
* or is not provided. |
1346
|
|
|
*/ |
1347
|
4 |
|
$allowList = ['ajax_request']; |
1348
|
4 |
|
Sanitize::removeRequestVars($allowList); |
1349
|
4 |
|
} |
1350
|
|
|
} |
1351
|
|
|
|
Let?s assume that you have a directory layout like this:
and let?s assume the following content of
Bar.php
:If both files
OtherDir/Foo.php
andSomeDir/Foo.php
are loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php
However, as
OtherDir/Foo.php
does not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php
, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: