Passed
Pull Request — 1.7 (#4191)
by
unknown
07:32
created

Filter   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 488
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 115
dl 0
loc 488
rs 6.96
c 1
b 0
f 0
wmc 53

27 Methods

Rating   Name   Duplication   Size   Complexity  
A get() 0 3 1
A getCsrfToken() 0 12 3
A expandUrls() 0 8 1
A post() 0 3 1
A input() 0 27 5
A formatText() 0 7 2
A getBool() 0 3 1
A postArray() 0 3 1
A getCsrf() 0 3 1
A unescapeHtml() 0 3 1
A postBool() 0 3 1
A server() 0 11 4
A escapeUrl() 0 3 1
A getEmail() 0 3 2
A postEmail() 0 3 2
A getArray() 0 3 1
A escapeHtml() 0 7 2
A escapeLike() 0 8 1
A postUrl() 0 3 2
A getInteger() 0 3 1
A getUrl() 0 3 2
A cookie() 0 3 1
A markdown() 0 20 2
A postInteger() 0 3 1
A escapeJs() 0 13 4
A checkCsrf() 0 10 2
B inputArray() 0 35 7

How to fix   Complexity   

Complex Class

Complex classes like Filter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Filter, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * webtrees: online genealogy
4
 * Copyright (C) 2019 webtrees development team
5
 * This program is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
 * GNU General Public License for more details.
13
 * You should have received a copy of the GNU General Public License
14
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15
 */
16
namespace Fisharebest\Webtrees;
17
18
use HTMLPurifier;
19
use HTMLPurifier_Config;
20
use Michelf\MarkdownExtra;
21
22
/**
23
 * Filter input and escape output.
24
 */
25
class Filter
26
{
27
    // REGEX to match a URL
28
    // Some versions of RFC3987 have an appendix B which gives the following regex
29
    // (([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
30
    // This matches far too much while a “precise” regex is several pages long.
31
    // This is a compromise.
32
    const URL_REGEX = '((https?|ftp]):)(//([^\s/?#<>]*))?([^\s?#<>]*)(\?([^\s#<>]*))?(#[^\s?#<>]+)?';
33
34
    /**
35
     * Escape a string for use in HTML
36
     *
37
     * @param string $string
38
     *
39
     * @return string
40
     */
41
    public static function escapeHtml($string)
42
    {
43
        if (defined('ENT_SUBSTITUTE')) {
44
            // PHP5.4 allows us to substitute invalid UTF8 sequences
45
            return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
46
        } else {
47
            return htmlspecialchars($string, ENT_QUOTES, 'UTF-8');
48
        }
49
    }
50
51
    /**
52
     * Escape a string for use in a URL
53
     *
54
     * @param string $string
55
     *
56
     * @return string
57
     */
58
    public static function escapeUrl($string)
59
    {
60
        return rawurlencode($string);
61
    }
62
63
    /**
64
     * Escape a string for use in Javascript
65
     *
66
     * @param string $string
67
     *
68
     * @return string
69
     */
70
    public static function escapeJs($string)
71
    {
72
        return preg_replace_callback('/[^A-Za-z0-9,. _]/Su', function ($x) {
73
            if (strlen($x[0]) == 1) {
74
                return sprintf('\\x%02X', ord($x[0]));
75
            } elseif (function_exists('iconv')) {
76
                return sprintf('\\u%04s', strtoupper(bin2hex(iconv('UTF-8', 'UTF-16BE', $x[0]))));
77
            } elseif (function_exists('mb_convert_encoding')) {
78
                return sprintf('\\u%04s', strtoupper(bin2hex(mb_convert_encoding($x[0], 'UTF-16BE', 'UTF-8'))));
0 ignored issues
show
Bug introduced by
It seems like mb_convert_encoding($x[0], 'UTF-16BE', 'UTF-8') can also be of type array; however, parameter $string of bin2hex() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

78
                return sprintf('\\u%04s', strtoupper(bin2hex(/** @scrutinizer ignore-type */ mb_convert_encoding($x[0], 'UTF-16BE', 'UTF-8'))));
Loading history...
79
            } else {
80
                return $x[0];
81
            }
82
        }, $string);
83
    }
84
85
    /**
86
     * Escape a string for use in a SQL "LIKE" clause
87
     *
88
     * @param string $string
89
     *
90
     * @return string
91
     */
92
    public static function escapeLike($string)
93
    {
94
        return strtr(
95
            $string,
96
            array(
97
                '\\' => '\\\\',
98
                '%'  => '\%',
99
                '_'  => '\_',
100
            )
101
        );
102
    }
103
104
    /**
105
     * Unescape an HTML string, giving just the literal text
106
     *
107
     * @param string $string
108
     *
109
     * @return string
110
     */
111
    public static function unescapeHtml($string)
112
    {
113
        return html_entity_decode(strip_tags($string), ENT_QUOTES, 'UTF-8');
114
    }
115
116
    /**
117
     * Format block-level text such as notes or transcripts, etc.
118
     *
119
     * @param string  $text
120
     * @param Tree $WT_TREE
121
     *
122
     * @return string
123
     */
124
    public static function formatText($text, Tree $WT_TREE)
125
    {
126
        switch ($WT_TREE->getPreference('FORMAT_TEXT')) {
127
            case 'markdown':
128
                return '<div class="markdown" dir="auto">' . self::markdown($text) . '</div>';
129
            default:
130
                return '<div style="white-space: pre-wrap;" dir="auto">' . self::expandUrls($text) . '</div>';
131
        }
132
    }
133
134
    /**
135
     * Escape a string for use in HTML, and additionally convert URLs to links.
136
     *
137
     * @param string $text
138
     *
139
     * @return string
140
     */
141
    public static function expandUrls($text)
142
    {
143
        return preg_replace_callback(
144
            '/' . addcslashes('(?!>)' . self::URL_REGEX . '(?!</a>)', '/') . '/i',
145
            function ($m) {
146
                return '<a href="' . $m[0] . '">' . $m[0] . '</a>';
147
            },
148
            self::escapeHtml($text)
149
        );
150
    }
151
152
    /**
153
     * Format a block of text, using "Markdown".
154
     *
155
     * @param string $text
156
     *
157
     * @return string
158
     */
159
    public static function markdown($text)
160
    {
161
        $parser                       = new MarkdownExtra;
162
        $parser->empty_element_suffix = '>';
163
        $parser->no_markup            = true;
164
        $text                         = $parser->transform($text);
165
166
        // HTMLPurifier needs somewhere to write temporary files
167
        $HTML_PURIFIER_CACHE_DIR = WT_DATA_DIR . 'html_purifier_cache';
168
169
        if (!is_dir($HTML_PURIFIER_CACHE_DIR)) {
170
            mkdir($HTML_PURIFIER_CACHE_DIR);
171
        }
172
173
        $config = HTMLPurifier_Config::createDefault();
174
        $config->set('Cache.SerializerPath', $HTML_PURIFIER_CACHE_DIR);
175
        $purifier = new HTMLPurifier($config);
176
        $text     = $purifier->purify($text);
177
178
        return $text;
179
    }
180
181
    /**
182
     * Validate INPUT parameters
183
     *
184
     * @param string      $source
185
     * @param string      $variable
186
     * @param string|null $regexp
187
     * @param string|null $default
188
     *
189
     * @return string|null
190
     */
191
    private static function input($source, $variable, $regexp = null, $default = null)
192
    {
193
        if ($regexp) {
194
            return filter_input(
195
                $source,
0 ignored issues
show
Bug introduced by
$source of type string is incompatible with the type integer expected by parameter $type of filter_input(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

195
                /** @scrutinizer ignore-type */ $source,
Loading history...
196
                $variable,
197
                FILTER_VALIDATE_REGEXP,
198
                array(
199
                    'options' => array(
200
                        'regexp'  => '/^(' . $regexp . ')$/u',
201
                        'default' => $default,
202
                    ),
203
                )
204
            );
205
        } else {
206
            $tmp = filter_input(
207
                $source,
208
                $variable,
209
                FILTER_CALLBACK,
210
                array(
211
                    'options' => function ($x) {
212
                        return mb_check_encoding($x, 'UTF-8') ? $x : false;
213
                    },
214
                )
215
            );
216
217
            return ($tmp === null || $tmp === false) ? $default : $tmp;
218
        }
219
    }
220
221
    /**
222
     * Validate array INPUT parameters
223
     *
224
     * @param string      $source
225
     * @param string      $variable
226
     * @param string|null $regexp
227
     * @param string|null $default
228
     *
229
     * @return string[]
230
     */
231
    private static function inputArray($source, $variable, $regexp = null, $default = null)
232
    {
233
        if ($regexp) {
234
            // PHP5.3 requires the $tmp variable
235
            $tmp = filter_input_array(
236
                $source,
0 ignored issues
show
Bug introduced by
$source of type string is incompatible with the type integer expected by parameter $type of filter_input_array(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

236
                /** @scrutinizer ignore-type */ $source,
Loading history...
237
                array(
238
                    $variable => array(
239
                        'flags'   => FILTER_REQUIRE_ARRAY,
240
                        'filter'  => FILTER_VALIDATE_REGEXP,
241
                        'options' => array(
242
                            'regexp'  => '/^(' . $regexp . ')$/u',
243
                            'default' => $default,
244
                        ),
245
                    ),
246
                )
247
            );
248
249
            return $tmp[$variable] ?: array();
250
        } else {
251
            // PHP5.3 requires the $tmp variable
252
            $tmp = filter_input_array(
253
                $source,
254
                array(
255
                    $variable => array(
256
                        'flags'   => FILTER_REQUIRE_ARRAY,
257
                        'filter'  => FILTER_CALLBACK,
258
                        'options' => function ($x) {
259
                            return !function_exists('mb_convert_encoding') || mb_check_encoding($x, 'UTF-8') ? $x : false;
260
                        },
261
                    ),
262
                )
263
            );
264
265
            if (is_array($tmp)) return $tmp[$variable] ?: array();
266
        }
267
    }
268
269
    /**
270
     * Validate GET parameters
271
     *
272
     * @param string      $variable
273
     * @param string|null $regexp
274
     * @param string|null $default
275
     *
276
     * @return null|string
277
     */
278
    public static function get($variable, $regexp = null, $default = null)
279
    {
280
        return self::input(INPUT_GET, $variable, $regexp, $default);
281
    }
282
283
    /**
284
     * Validate array GET parameters
285
     *
286
     * @param string      $variable
287
     * @param string|null $regexp
288
     * @param string|null $default
289
     *
290
     * @return string[]
291
     */
292
    public static function getArray($variable, $regexp = null, $default = null)
293
    {
294
        return self::inputArray(INPUT_GET, $variable, $regexp, $default);
295
    }
296
297
    /**
298
     * Validate boolean GET parameters
299
     *
300
     * @param string $variable
301
     *
302
     * @return bool
303
     */
304
    public static function getBool($variable)
305
    {
306
        return (bool) filter_input(INPUT_GET, $variable, FILTER_VALIDATE_BOOLEAN);
307
    }
308
309
    /**
310
     * Validate integer GET parameters
311
     *
312
     * @param string $variable
313
     * @param int    $min
314
     * @param int    $max
315
     * @param int    $default
316
     *
317
     * @return int
318
     */
319
    public static function getInteger($variable, $min = 0, $max = PHP_INT_MAX, $default = 0)
320
    {
321
        return filter_input(INPUT_GET, $variable, FILTER_VALIDATE_INT, array('options' => array('min_range' => $min, 'max_range' => $max, 'default' => $default)));
322
    }
323
324
    /**
325
     * Validate email GET parameters
326
     *
327
     * @param string      $variable
328
     * @param string|null $default
329
     *
330
     * @return null|string
331
     */
332
    public static function getEmail($variable, $default = null)
333
    {
334
        return filter_input(INPUT_GET, $variable, FILTER_VALIDATE_EMAIL) ?: $default;
335
    }
336
337
    /**
338
     * Validate URL GET parameters
339
     *
340
     * @param string      $variable
341
     * @param string|null $default
342
     *
343
     * @return null|string
344
     */
345
    public static function getUrl($variable, $default = null)
346
    {
347
        return filter_input(INPUT_GET, $variable, FILTER_VALIDATE_URL) ?: $default;
348
    }
349
350
    /**
351
     * Validate POST parameters
352
     *
353
     * @param string      $variable
354
     * @param string|null $regexp
355
     * @param string|null $default
356
     *
357
     * @return null|string
358
     */
359
    public static function post($variable, $regexp = null, $default = null)
360
    {
361
        return self::input(INPUT_POST, $variable, $regexp, $default);
362
    }
363
364
    /**
365
     * Validate array POST parameters
366
     *
367
     * @param string      $variable
368
     * @param string|null $regexp
369
     * @param string|null $default
370
     *
371
     * @return string[]
372
     */
373
    public static function postArray($variable, $regexp = null, $default = null)
374
    {
375
        return self::inputArray(INPUT_POST, $variable, $regexp, $default);
376
    }
377
378
    /**
379
     * Validate boolean POST parameters
380
     *
381
     * @param string $variable
382
     *
383
     * @return bool
384
     */
385
    public static function postBool($variable)
386
    {
387
        return (bool) filter_input(INPUT_POST, $variable, FILTER_VALIDATE_BOOLEAN);
388
    }
389
390
    /**
391
     * Validate integer POST parameters
392
     *
393
     * @param string $variable
394
     * @param int    $min
395
     * @param int    $max
396
     * @param int    $default
397
     *
398
     * @return int
399
     */
400
    public static function postInteger($variable, $min = 0, $max = PHP_INT_MAX, $default = 0)
401
    {
402
        return filter_input(INPUT_POST, $variable, FILTER_VALIDATE_INT, array('options' => array('min_range' => $min, 'max_range' => $max, 'default' => $default)));
403
    }
404
405
    /**
406
     * Validate email POST parameters
407
     *
408
     * @param string      $variable
409
     * @param string|null $default
410
     *
411
     * @return null|string
412
     */
413
    public static function postEmail($variable, $default = null)
414
    {
415
        return filter_input(INPUT_POST, $variable, FILTER_VALIDATE_EMAIL) ?: $default;
416
    }
417
418
    /**
419
     * Validate URL GET parameters
420
     *
421
     * @param string      $variable
422
     * @param string|null $default
423
     *
424
     * @return null|string
425
     */
426
    public static function postUrl($variable, $default = null)
427
    {
428
        return filter_input(INPUT_POST, $variable, FILTER_VALIDATE_URL) ?: $default;
429
    }
430
431
    /**
432
     * Validate COOKIE parameters
433
     *
434
     * @param string      $variable
435
     * @param string|null $regexp
436
     * @param string|null $default
437
     *
438
     * @return null|string
439
     */
440
    public static function cookie($variable, $regexp = null, $default = null)
441
    {
442
        return self::input(INPUT_COOKIE, $variable, $regexp, $default);
443
    }
444
445
    /**
446
     * Validate SERVER parameters
447
     *
448
     * @param string      $variable
449
     * @param string|null $regexp
450
     * @param string|null $default
451
     *
452
     * @return null|string
453
     */
454
    public static function server($variable, $regexp = null, $default = null)
455
    {
456
        // On some servers, variables that are present in $_SERVER cannot be
457
        // found via filter_input(INPUT_SERVER). Instead, they are found via
458
        // filter_input(INPUT_ENV). Since we cannot rely on filter_input(),
459
        // we must use the superglobal directly.
460
        if (array_key_exists($variable, $_SERVER) && ($regexp === null || preg_match('/^(' . $regexp . ')$/',
461
        $_SERVER[$variable]))) {
462
            return $_SERVER[$variable];
463
        } else {
464
            return $default;
465
        }
466
    }
467
468
    /**
469
     * Cross-Site Request Forgery tokens - ensure that the user is submitting
470
     * a form that was generated by the current session.
471
     *
472
     * @return string
473
     */
474
    public static function getCsrfToken()
475
    {
476
        if (!Session::has('CSRF_TOKEN')) {
477
            $charset    = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcedfghijklmnopqrstuvwxyz0123456789';
478
            $csrf_token = '';
479
            for ($n = 0; $n < 32; ++$n) {
480
                $csrf_token .= substr($charset, mt_rand(0, 61), 1);
481
            }
482
            Session::put('CSRF_TOKEN', $csrf_token);
483
        }
484
485
        return Session::get('CSRF_TOKEN');
486
    }
487
488
    /**
489
     * Generate an <input> element - to protect the current form from CSRF attacks.
490
     *
491
     * @return string
492
     */
493
    public static function getCsrf()
494
    {
495
        return '<input type="hidden" name="csrf" value="' . self::getCsrfToken() . '">';
496
    }
497
498
    /**
499
     * Check that the POST request contains the CSRF token generated above.
500
     *
501
     * @return bool
502
     */
503
    public static function checkCsrf()
504
    {
505
        if (self::post('csrf') !== self::getCsrfToken()) {
506
            // Oops. Something is not quite right
507
            FlashMessages::addMessage(I18N::translate('This form has expired. Try again.'), 'error');
508
509
            return false;
510
        }
511
512
        return true;
513
    }
514
}
515