Sanitize.php 10 KB
Newer Older
1
2
3
4
5
6
7
8
<?php
/**
 * Created by PhpStorm.
 * User: crose
 * Date: 1/2/16
 * Time: 10:57 PM
 */

Marc Egger's avatar
Marc Egger committed
9
namespace IMATHUZH\Qfq\Core\Helper;
10
11


Carsten  Rose's avatar
Carsten Rose committed
12
/**
Carsten  Rose's avatar
Carsten Rose committed
13
 * Class Sanitize
Carsten  Rose's avatar
Carsten Rose committed
14
15
 * @package qfq
 */
Carsten  Rose's avatar
Carsten Rose committed
16
class Sanitize {
17

18
    private static $sanitizePattern = [
19
        SANITIZE_ALLOW_ALNUMX => PATTERN_ALNUMX, // ':alnum:' does not work here in FF
20
21
22
        SANITIZE_ALLOW_DIGIT => PATTERN_DIGIT,
        SANITIZE_ALLOW_NUMERICAL => PATTERN_NUMERICAL,
        SANITIZE_ALLOW_EMAIL => PATTERN_EMAIL,
23
        SANITIZE_ALLOW_ALLBUT => PATTERN_ALLBUT,
24
        SANITIZE_ALLOW_ALL => PATTERN_ALL,
25
26
27
28
        SANITIZE_ALLOW_PATTERN => '',
    ];

    private static $sanitizeMessage = [
29
30
31
32
        SANITIZE_ALLOW_ALNUMX => SANITIZE_ALLOW_ALNUMX_MESSAGE,
        SANITIZE_ALLOW_DIGIT => SANITIZE_ALLOW_DIGIT_MESSAGE,
        SANITIZE_ALLOW_NUMERICAL => SANITIZE_ALLOW_NUMERICAL_MESSAGE,
        SANITIZE_ALLOW_EMAIL => SANITIZE_ALLOW_EMAIL_MESSAGE,
33
        SANITIZE_ALLOW_ALLBUT => SANITIZE_ALLOW_ALLBUT_MESSAGE,
34
        SANITIZE_ALLOW_ALL => '',
35
        SANITIZE_ALLOW_PATTERN => F_FE_DATA_PATTERN_ERROR_DEFAULT,
36
37
    ];

38
39
40
41
42
    private function __construct() {
        // Class should never be instantiated
    }

    /**
43
     * Check $value against given checkType/pattern. If check succeed, returns values.
44
45
46
47
48
49
50
     * If check fails, depending on $mode
     * a) throws an UserException
     * b) return message:
     *    $typeMessageViolate=SANITIZE_TYPE_MESSAGE_VIOLATE_EMPTY  - return empty string
     *    $typeMessageViolate=SANITIZE_TYPE_MESSAGE_VIOLATE_ZERO  - return '0'
     *    $typeMessageViolate=SANITIZE_TYPE_MESSAGE_VIOLATE_EMPTY  - return '!!<sanitize class>!!'
     *    $typeMessageViolate=<nothing from the above>  - return '$typeMessageViolate'
51
     *
52
     * @param string $value value to check
53
54
     * @param string $sanitizeClass
     * @param string $pattern Pattern as regexp
55
     * @param string $decimalFormat with 'size,precision'
Carsten  Rose's avatar
Carsten Rose committed
56
     * @param string $mode SANITIZE_EXCEPTION | SANITIZE_EMPTY_STRING
57
     * @param string $dataPatternErrorText
58
     * @param string $typeMessageViolate SANITIZE_TYPE_MESSAGE_VIOLATE_EMPTY | SANITIZE_TYPE_MESSAGE_VIOLATE_ZERO | SANITIZE_TYPE_MESSAGE_VIOLATE_CLASS | <custom>
59
     * @return string
Marc Egger's avatar
Marc Egger committed
60
61
     * @throws \CodeException
     * @throws \UserFormException
62
     */
63
64
    public static function sanitize($value, $sanitizeClass = SANITIZE_DEFAULT, $pattern = '', $decimalFormat = '',
                                    $mode = SANITIZE_EMPTY_STRING, $dataPatternErrorText = '', $typeMessageViolate = SANITIZE_TYPE_MESSAGE_VIOLATE_CLASS) {
65

66
        $pattern = self::getInputCheckPattern($sanitizeClass, $pattern, $decimalFormat, $sanitizeMessage);
67
68
69
70
71
72
73
74
75

        // Pattern check
        if ($pattern === '' || preg_match("/$pattern/", $value) === 1) {
            return $value;
        }

        // check failed
        if ($mode === SANITIZE_EXCEPTION) {
            $errorCode = ERROR_PATTERN_VIOLATION;
76
77
78
79
80
81
82
83
84
85
86
87

            // Depending on default regexp & custom data-pattern-error, define error text.
            if ($dataPatternErrorText == '') {
                if ($sanitizeMessage == '') {
                    $errorText = "Value '$value' violates check rule " . $sanitizeClass . " with pattern '$pattern'.";
                } else {
                    $errorText = $sanitizeMessage;
                }
            } else {
                $errorText = $dataPatternErrorText;
            }

Marc Egger's avatar
Marc Egger committed
88
            throw new \UserFormException($errorText, $errorCode);
89
90
        }

91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
        switch ($typeMessageViolate) {
            case SANITIZE_TYPE_MESSAGE_VIOLATE_EMPTY:
                $message = '';
                break;
            case SANITIZE_TYPE_MESSAGE_VIOLATE_ZERO:
                $message = '0';
                break;
            case SANITIZE_TYPE_MESSAGE_VIOLATE_CLASS:
                $message = SANITIZE_VIOLATE . $sanitizeClass . SANITIZE_VIOLATE;
                break;
            default:
                $message = $typeMessageViolate;
        }

        return $message;
106
107
108
109
    }

    /**
     * Returns the final validation pattern based on a given $checkType, $pattern, and $decimalFormat
110
     * @param string $checkType SANITIZE_DEFAULT, ...
111
112
113
     * @param string $pattern
     * @param string $decimalFormat e.g. "10,2"
     *
114
     * @param string $rcSanitizeMessage Message specific to a pattern
115
     * @return string
Marc Egger's avatar
Marc Egger committed
116
     * @throws \CodeException
117
     */
118
119
120
    public static function getInputCheckPattern($checkType, $pattern, $decimalFormat, &$rcSanitizeMessage) {

        $rcSanitizeMessage = self::$sanitizeMessage[$checkType] ?? '';
121

122
        switch ($checkType) {
123
            case SANITIZE_ALLOW_PATTERN:
124
125
126
127
                return $pattern;

            case SANITIZE_ALLOW_ALL:
                $pattern = '';
128
129
                break;

130
            case SANITIZE_ALLOW_DIGIT:
131
            case SANITIZE_ALLOW_NUMERICAL:
132
133
            case SANITIZE_ALLOW_EMAIL:
            case SANITIZE_ALLOW_ALNUMX:
134
            case SANITIZE_ALLOW_ALLBUT:
135
                $pattern = self::$sanitizePattern[$checkType];
136
137
138
                break;

            default:
Marc Egger's avatar
Marc Egger committed
139
                throw new \CodeException("Unknown checkType: " . $checkType, ERROR_UNKNOWN_CHECKTYPE);
140
141
        }

142
        // decimalFormat
143
144
145
        if ($decimalFormat != '' && $checkType !== SANITIZE_ALLOW_DIGIT) {
            // overwrite pattern with decimalFormat pattern
            $decimalFormatArray = explode(',', $decimalFormat);
146
            $pattern = "^-?[0-9]{0," . ($decimalFormatArray[0] - $decimalFormatArray[1]) . "}(\.[0-9]{0,$decimalFormatArray[1]})?$";
147
            $rcSanitizeMessage = "Requested decimal format (mantis,decimal): $decimalFormat";
148
149
        }

150
        return $pattern;
151
152
153
154
155
156
157
    }

    /**
     * Check $value against $formElement's min/max values. If check succeeds, returns value.
     *   If check fails, depending on $mode, throws an UserException or return an empty string.
     *
     * @param string $value value to check
158
159
     * @param $min
     * @param $max
160
161
     * @param string $mode SANITIZE_EXCEPTION | SANITIZE_EMPTY_STRING
     * @return string
Marc Egger's avatar
Marc Egger committed
162
     * @throws \UserFormException
163
     */
164
    public static function checkMinMax($value, $min, $max, $mode = SANITIZE_EMPTY_STRING) {
165
166
167
        $errorCode = 0;
        $errorText = '';

168
        if ($min !== '' && $value < $min) {
169
170
171
            $errorCode = ERROR_SMALLER_THAN_MIN;
            $errorText = "Value '$value' is smaller than the allowed minimum of '$min'.";
        }
172
        if ($max !== '' && $value > $max) {
173
174
            $errorCode = ERROR_LARGER_THAN_MAX;
            $errorText = "Value '$value' is larger than the allowed maximum of '$max'.";
175
176
        }

177
        if ($errorCode == 0)
Elias Villiger's avatar
Elias Villiger committed
178
179
            return $value;

180
181
        // check failed
        if ($mode === SANITIZE_EXCEPTION) {
Marc Egger's avatar
Marc Egger committed
182
            throw new \UserFormException($errorText, $errorCode);
Elias Villiger's avatar
Elias Villiger committed
183
        }
184
185

        return '';
186
187
    }

188
    /**
189
     * Sanitizes a filename. Copied from http://www.phpit.net/code/filename-safe/
190
191
     *
     * @param $filename
Carsten  Rose's avatar
Carsten Rose committed
192
     *
193
194
     * @param bool $flagBaseName
     * @param bool $allowSlash
195
196
     * @return mixed
     */
197
198
199
    public static function safeFilename($filename, $flagBaseName = false, $allowSlash = false) {

        // Disallow 'none alphanumeric'. Allow dot or underscore and conditionally '/'.
200
        $pattern = ($allowSlash) ? '([^[:alnum:]._\-/])' : '([^[:alnum:]._\-])';
201

202
203
204
205
206
207
208
        $search = array(
            // Definition of German Umlauts START
            '/ß/',
            '/ä/', '/Ä/',
            '/ö/', '/Ö/',
            '/ü/', '/Ü/',
            // Definition of German Umlauts ENDE
209
            $pattern,
210
211
212
213
214
215
216
        );

        $replace = array(
            'ss',
            'ae', 'Ae',
            'oe', 'Oe',
            'ue', 'Ue',
Carsten  Rose's avatar
Carsten Rose committed
217
            '_',
218
219
        );

220
221
222
223
        if ($flagBaseName) {
            $filename = basename($filename);
        }

224
225
226
        return preg_replace($search, $replace, $filename);
    } // safeFilename()

227
228
229
230
    /**
     * htmlentities($data) - if $data is an array, convert it recursively.
     *
     * @param string|array $data
Carsten  Rose's avatar
Carsten Rose committed
231
     * @param int $mode
Carsten  Rose's avatar
Carsten Rose committed
232
     *
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
     * @return array|string
     */
    public static function htmlentitiesArr($data, $mode = ENT_QUOTES) {

        if (is_string($data)) {
            htmlentities($data, $mode);
        }

        if (is_array($data)) {
            foreach ($data as $key => $value) {
                $data[$key] = self::htmlentitiesArr($value, $mode);
            }
        }

        return $data;
    }

250
    /**
Carsten  Rose's avatar
Carsten Rose committed
251
     * Take the given $item (or iterates over all elements of the given array) and normalize the content.
252
     * Only strings will be normalized. Sub arrays will be normalized recursive. Numeric content is skipped.
Carsten  Rose's avatar
Carsten Rose committed
253
     * Throws an exception for unknown content.
254
255
     *
     * It's important to normalize the user input: e.g. someone is searching for a record and input the search string
Carsten  Rose's avatar
Carsten Rose committed
256
     * with composed characters (happens e.g. on Apple Mac / Safari without special user invention).
257
     *
258
     * @param array|string $item
Carsten  Rose's avatar
Carsten Rose committed
259
     *
260
     * @return array|string
Marc Egger's avatar
Marc Egger committed
261
     * @throws \CodeException
262
     */
263
264
265
266
267
268
269
270
271
272
273
    public static function normalize($item) {

        if (is_array($item)) {
            foreach ($item as $key => $value) {
                $value = self::normalize($value);
                $item[$key] = $value;
            }
        } else {
            if (is_string($item)) {
                $item = \normalizer::normalize($item, \Normalizer::FORM_C);
            } elseif (!is_numeric($item)) {
Marc Egger's avatar
Marc Egger committed
274
                throw new \CodeException ("Expect type 'string / numeric / array' - but there is something else.", ERROR_UNEXPECTED_TYPE);
275
276
            }
        }
Carsten  Rose's avatar
Carsten Rose committed
277

278
        return $item;
279
    }
280

281
282
283
284
285
286
287
288
289
290

    /**
     * Check a given $_GET[$key] is digit.
     * If yes: do nothing
     * If no: set to the first character (if it is a digit) else to an empty string.
     *
     * @param $key
     */
    public static function digitCheckAndCleanGet($key) {

Carsten  Rose's avatar
Carsten Rose committed
291
292
293
        if (!isset($_GET[$key])) {
            $_GET[$key] = '';
            return;
294
295
        }

Carsten  Rose's avatar
Carsten Rose committed
296
297
298
        if (ctype_digit($_GET[$key])) {
            return;
        }
299

Carsten  Rose's avatar
Carsten Rose committed
300
301
302
303
304
305
        if (ctype_digit($_GET[$key][0] ?? '')) {
            $_GET[$key] = $_GET[$key][0];
        } else {
            $_GET[$key] = '';
        }
    }
306
}