Passed
Push — master ( 0bb995...0feae3 )
by Maurício
07:57
created

Sanitize::sanitizeMessage()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 52
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 31
nc 4
nop 3
dl 0
loc 52
rs 9.424
c 0
b 0
f 0

How to fix   Long Method   

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