Passed
Push — master ( 4bc08b...df48b1 )
by Maurício
08:59
created

Sanitize::removeRequestVars()   B

Complexity

Conditions 11
Paths 18

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 28
rs 7.3166
c 0
b 0
f 0
cc 11
nc 18
nop 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/* vim: set expandtab sw=4 ts=4 sts=4: */
3
/**
4
 * This class includes various sanitization methods that can be called statically
5
 *
6
 * @package PhpMyAdmin
7
 */
8
declare(strict_types=1);
9
10
namespace PhpMyAdmin;
11
12
use PhpMyAdmin\Core;
13
use PhpMyAdmin\Util;
14
15
/**
16
 * This class includes various sanitization methods that can be called statically
17
 *
18
 * @package PhpMyAdmin
19
 */
20
class Sanitize
21
{
22
    /**
23
     * Checks whether given link is valid
24
     *
25
     * @param string  $url   URL to check
26
     * @param boolean $http  Whether to allow http links
27
     * @param boolean $other Whether to allow ftp and mailto links
28
     *
29
     * @return boolean True if string can be used as link
30
     */
31
    public static function checkLink($url, $http = false, $other = false)
32
    {
33
        $url = strtolower($url);
34
        $valid_starts = [
35
            'https://',
36
            './url.php?url=https%3a%2f%2f',
37
            './doc/html/',
38
            // possible return values from Util::getScriptNameForOption
39
            './index.php?',
40
            './db_sql.php?',
41
            './tbl_sql.php?',
42
            './sql.php?',
43
            // Hardcoded options in \PhpMyAdmin\Config\SpecialSchemaLinks
44
            './db_events.php?',
45
            './db_routines.php?',
46
        ];
47
        $is_setup = $GLOBALS['PMA_Config'] !== null && $GLOBALS['PMA_Config']->get('is_setup');
48
        // Adjust path to setup script location
49
        if ($is_setup) {
50
            foreach ($valid_starts as $key => $value) {
51
                if (substr($value, 0, 2) === './') {
52
                    $valid_starts[$key] = '.' . $value;
53
                }
54
            }
55
        }
56
        if ($other) {
57
            $valid_starts[] = 'mailto:';
58
            $valid_starts[] = 'ftp://';
59
        }
60
        if ($http) {
61
            $valid_starts[] = 'http://';
62
        }
63
        if ($is_setup) {
64
            $valid_starts[] = '?page=form&';
65
            $valid_starts[] = '?page=servers&';
66
        }
67
        foreach ($valid_starts as $val) {
68
            if (substr($url, 0, strlen($val)) == $val) {
69
                return true;
70
            }
71
        }
72
        return false;
73
    }
74
75
    /**
76
     * Callback function for replacing [a@link@target] links in bb code.
77
     *
78
     * @param array $found Array of preg matches
79
     *
80
     * @return string Replaced string
81
     */
82
    public static function replaceBBLink(array $found)
83
    {
84
        /* Check for valid link */
85
        if (! self::checkLink($found[1])) {
86
            return $found[0];
87
        }
88
        /* a-z and _ allowed in target */
89
        if (! empty($found[3]) && preg_match('/[^a-z_]+/i', $found[3])) {
90
            return $found[0];
91
        }
92
93
        /* Construct target */
94
        $target = '';
95
        if (! empty($found[3])) {
96
            $target = ' target="' . $found[3] . '"';
97
            if ($found[3] == '_blank') {
98
                $target .= ' rel="noopener noreferrer"';
99
            }
100
        }
101
102
        /* Construct url */
103
        if (substr($found[1], 0, 4) == 'http') {
104
            $url = Core::linkURL($found[1]);
105
        } else {
106
            $url = $found[1];
107
        }
108
109
        return '<a href="' . $url . '"' . $target . '>';
110
    }
111
112
    /**
113
     * Callback function for replacing [doc@anchor] links in bb code.
114
     *
115
     * @param array $found Array of preg matches
116
     *
117
     * @return string Replaced string
118
     */
119
    public static function replaceDocLink(array $found)
120
    {
121
        if (count($found) >= 4) {
122
            $page = $found[1];
123
            $anchor = $found[3];
124
        } else {
125
            $anchor = $found[1];
126
            if (strncmp('faq', $anchor, 3) == 0) {
127
                $page = 'faq';
128
            } elseif (strncmp('cfg', $anchor, 3) == 0) {
129
                $page = 'config';
130
            } else {
131
                /* Guess */
132
                $page = 'setup';
133
            }
134
        }
135
        $link = Util::getDocuLink($page, $anchor);
136
        return '<a href="' . $link . '" target="documentation">';
137
    }
138
139
    /**
140
     * Sanitizes $message, taking into account our special codes
141
     * for formatting.
142
     *
143
     * If you want to include result in element attribute, you should escape it.
144
     *
145
     * Examples:
146
     *
147
     * <p><?php echo Sanitize::sanitizeMessage($foo); ?></p>
148
     *
149
     * <a title="<?php echo Sanitize::sanitizeMessage($foo, true); ?>">bar</a>
150
     *
151
     * @param string  $message the message
152
     * @param boolean $escape  whether to escape html in result
153
     * @param boolean $safe    whether string is safe (can keep < and > chars)
154
     *
155
     * @return string   the sanitized message
156
     */
157
    public static function sanitizeMessage($message, $escape = false, $safe = false)
158
    {
159
        if (! $safe) {
160
            $message = strtr((string) $message, ['<' => '&lt;', '>' => '&gt;']);
161
        }
162
163
        /* Interpret bb code */
164
        $replace_pairs = [
165
            '[em]'      => '<em>',
166
            '[/em]'     => '</em>',
167
            '[strong]'  => '<strong>',
168
            '[/strong]' => '</strong>',
169
            '[code]'    => '<code>',
170
            '[/code]'   => '</code>',
171
            '[kbd]'     => '<kbd>',
172
            '[/kbd]'    => '</kbd>',
173
            '[br]'      => '<br>',
174
            '[/a]'      => '</a>',
175
            '[/doc]'      => '</a>',
176
            '[sup]'     => '<sup>',
177
            '[/sup]'    => '</sup>',
178
            // used in common.inc.php:
179
            '[conferr]' => '<iframe src="show_config_errors.php"><a href="show_config_errors.php">show_config_errors.php</a></iframe>',
180
            // used in libraries/Util.php
181
            '[dochelpicon]' => Util::getImage('b_help', __('Documentation')),
182
        ];
183
184
        $message = strtr($message, $replace_pairs);
185
186
        /* Match links in bb code ([a@url@target], where @target is options) */
187
        $pattern = '/\[a@([^]"@]*)(@([^]"]*))?\]/';
188
189
        /* Find and replace all links */
190
        $message = preg_replace_callback($pattern, function ($match) {
191
            return self::replaceBBLink($match);
192
        }, $message);
193
194
        /* Replace documentation links */
195
        $message = preg_replace_callback(
196
            '/\[doc@([a-zA-Z0-9_-]+)(@([a-zA-Z0-9_-]*))?\]/',
197
            function ($match) {
198
                return self::replaceDocLink($match);
199
            },
200
            $message
201
        );
202
203
        /* Possibly escape result */
204
        if ($escape) {
205
            $message = htmlspecialchars($message);
206
        }
207
208
        return $message;
209
    }
210
211
212
    /**
213
     * Sanitize a filename by removing anything besides legit characters
214
     *
215
     * Intended usecase:
216
     *    When using a filename in a Content-Disposition header
217
     *    the value should not contain ; or "
218
     *
219
     *    When exporting, avoiding generation of an unexpected double-extension file
220
     *
221
     * @param string  $filename    The filename
222
     * @param boolean $replaceDots Whether to also replace dots
223
     *
224
     * @return string  the sanitized filename
225
     *
226
     */
227
    public static function sanitizeFilename($filename, $replaceDots = false)
228
    {
229
        $pattern = '/[^A-Za-z0-9_';
230
        // if we don't have to replace dots
231
        if (! $replaceDots) {
232
            // then add the dot to the list of legit characters
233
            $pattern .= '.';
234
        }
235
        $pattern .= '-]/';
236
        $filename = preg_replace($pattern, '_', $filename);
237
        return $filename;
238
    }
239
240
    /**
241
     * Format a string so it can be a string inside JavaScript code inside an
242
     * eventhandler (onclick, onchange, on..., ).
243
     * This function is used to displays a javascript confirmation box for
244
     * "DROP/DELETE/ALTER" queries.
245
     *
246
     * @param string  $a_string       the string to format
247
     * @param boolean $add_backquotes whether to add backquotes to the string or not
248
     *
249
     * @return string   the formatted string
250
     *
251
     * @access  public
252
     */
253
    public static function jsFormat($a_string = '', $add_backquotes = true)
254
    {
255
        $a_string = htmlspecialchars((string) $a_string);
256
        $a_string = self::escapeJsString($a_string);
257
        // Needed for inline javascript to prevent some browsers
258
        // treating it as a anchor
259
        $a_string = str_replace('#', '\\#', $a_string);
260
261
        return $add_backquotes
262
            ? Util::backquote($a_string)
263
            : $a_string;
264
    } // end of the 'jsFormat' function
265
266
    /**
267
     * escapes a string to be inserted as string a JavaScript block
268
     * enclosed by <![CDATA[ ... ]]>
269
     * this requires only to escape ' with \' and end of script block
270
     *
271
     * We also remove NUL byte as some browsers (namely MSIE) ignore it and
272
     * inserting it anywhere inside </script would allow to bypass this check.
273
     *
274
     * @param string $string the string to be escaped
275
     *
276
     * @return string  the escaped string
277
     */
278
    public static function escapeJsString($string)
279
    {
280
        return preg_replace(
281
            '@</script@i',
282
            '</\' + \'script',
283
            strtr(
284
                (string) $string,
285
                [
286
                    "\000" => '',
287
                    '\\' => '\\\\',
288
                    '\'' => '\\\'',
289
                    '"' => '\"',
290
                    "\n" => '\n',
291
                    "\r" => '\r',
292
                ]
293
            )
294
        );
295
    }
296
297
    /**
298
     * Formats a value for javascript code.
299
     *
300
     * @param string $value String to be formatted.
301
     *
302
     * @return string formatted value.
303
     */
304
    public static function formatJsVal($value)
305
    {
306
        if (is_bool($value)) {
0 ignored issues
show
introduced by
The condition is_bool($value) is always false.
Loading history...
307
            if ($value) {
308
                return 'true';
309
            }
310
311
            return 'false';
312
        }
313
314
        if (is_int($value)) {
0 ignored issues
show
introduced by
The condition is_int($value) is always false.
Loading history...
315
            return (int) $value;
316
        }
317
318
        return '"' . self::escapeJsString($value) . '"';
319
    }
320
321
    /**
322
     * Formats an javascript assignment with proper escaping of a value
323
     * and support for assigning array of strings.
324
     *
325
     * @param string $key    Name of value to set
326
     * @param mixed  $value  Value to set, can be either string or array of strings
327
     * @param bool   $escape Whether to escape value or keep it as it is
328
     *                       (for inclusion of js code)
329
     *
330
     * @return string Javascript code.
331
     */
332
    public static function getJsValue($key, $value, $escape = true)
333
    {
334
        $result = $key . ' = ';
335
        if (! $escape) {
336
            $result .= $value;
337
        } elseif (is_array($value)) {
338
            $result .= '[';
339
            foreach ($value as $val) {
340
                $result .= self::formatJsVal($val) . ",";
341
            }
342
            $result .= "];\n";
343
        } else {
344
            $result .= self::formatJsVal($value) . ";\n";
345
        }
346
        return $result;
347
    }
348
349
    /**
350
     * Prints an javascript assignment with proper escaping of a value
351
     * and support for assigning array of strings.
352
     *
353
     * @param string $key   Name of value to set
354
     * @param mixed  $value Value to set, can be either string or array of strings
355
     *
356
     * @return void
357
     */
358
    public static function printJsValue($key, $value)
359
    {
360
        echo self::getJsValue($key, $value);
361
    }
362
363
    /**
364
     * Formats javascript assignment for form validation api
365
     * with proper escaping of a value.
366
     *
367
     * @param string  $key   Name of value to set
368
     * @param string  $value Value to set
369
     * @param boolean $addOn Check if $.validator.format is required or not
370
     * @param boolean $comma Check if comma is required
371
     *
372
     * @return string Javascript code.
373
     */
374
    public static function getJsValueForFormValidation($key, $value, $addOn, $comma)
375
    {
376
        $result = $key . ': ';
377
        if ($addOn) {
378
            $result .= '$.validator.format(';
379
        }
380
        $result .= self::formatJsVal($value);
381
        if ($addOn) {
382
            $result .= ')';
383
        }
384
        if ($comma) {
385
            $result .= ', ';
386
        }
387
        return $result;
388
    }
389
390
    /**
391
     * Prints javascript assignment for form validation api
392
     * with proper escaping of a value.
393
     *
394
     * @param string  $key   Name of value to set
395
     * @param string  $value Value to set
396
     * @param boolean $addOn Check if $.validator.format is required or not
397
     * @param boolean $comma Check if comma is required
398
     *
399
     * @return void
400
     */
401
    public static function printJsValueForFormValidation($key, $value, $addOn = false, $comma = true)
402
    {
403
        echo self::getJsValueForFormValidation($key, $value, $addOn, $comma);
404
    }
405
406
    /**
407
     * Removes all variables from request except whitelisted ones.
408
     *
409
     * @param string[] $whitelist list of variables to allow
410
     *
411
     * @return void
412
     * @access public
413
     */
414
    public static function removeRequestVars(&$whitelist): void
415
    {
416
        // do not check only $_REQUEST because it could have been overwritten
417
        // and use type casting because the variables could have become
418
        // strings
419
        $keys = array_keys(
420
            array_merge((array) $_REQUEST, (array) $_GET, (array) $_POST, (array) $_COOKIE)
421
        );
422
423
        foreach ($keys as $key) {
424
            if (! in_array($key, $whitelist)) {
425
                unset($_REQUEST[$key], $_GET[$key], $_POST[$key]);
426
                continue;
427
            }
428
429
            // allowed stuff could be compromised so escape it
430
            // we require it to be a string
431
            if (isset($_REQUEST[$key]) && ! is_string($_REQUEST[$key])) {
432
                unset($_REQUEST[$key]);
433
            }
434
            if (isset($_POST[$key]) && ! is_string($_POST[$key])) {
435
                unset($_POST[$key]);
436
            }
437
            if (isset($_COOKIE[$key]) && ! is_string($_COOKIE[$key])) {
438
                unset($_COOKIE[$key]);
439
            }
440
            if (isset($_GET[$key]) && ! is_string($_GET[$key])) {
441
                unset($_GET[$key]);
442
            }
443
        }
444
    }
445
}
446