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

namespace qfq;

Carsten  Rose's avatar
Carsten Rose committed
11
use qfq;
12
13
14

//use qfq\CodeException;
//use qfq\UserFormException;
15

16
require_once(__DIR__ . '/../../core/Constants.php');
17
//require_once(__DIR__ . '/../exceptions/UserFormException.php');
18

Carsten  Rose's avatar
Carsten Rose committed
19
/**
Carsten  Rose's avatar
Carsten Rose committed
20
 * Class Sanitize
Carsten  Rose's avatar
Carsten Rose committed
21
22
 * @package qfq
 */
Carsten  Rose's avatar
Carsten Rose committed
23
class Sanitize {
24

25
    private static $sanitizePattern = [
26
27
28
29
30
31
        SANITIZE_ALLOW_ALNUMX =>  PATTERN_ALNUMX, // ':alnum:' does not work here in FF
        SANITIZE_ALLOW_DIGIT => PATTERN_DIGIT,
        SANITIZE_ALLOW_NUMERICAL => PATTERN_NUMERICAL,
        SANITIZE_ALLOW_EMAIL => PATTERN_EMAIL,
        SANITIZE_ALLOW_ALLBUT =>  PATTERN_ALLBUT,
        SANITIZE_ALLOW_ALL => PATTERN_ALL,
32
33
34
35
36
37
38
        SANITIZE_ALLOW_PATTERN => '',
    ];

    private static $sanitizeMessage = [
        SANITIZE_ALLOW_ALNUMX => 'Allowed characters: 0...9, [latin character], @-_.m;: /()',
        SANITIZE_ALLOW_DIGIT => 'Allowed characters: 0...9',
        SANITIZE_ALLOW_NUMERICAL => 'Allowed characters: 0...9 and .+-',
39
        SANITIZE_ALLOW_EMAIL => 'Requested format: string@domain.tld',
40
        SANITIZE_ALLOW_ALLBUT => 'Forbidden characters: ^[]{}%\#',
41
        SANITIZE_ALLOW_ALL => '',
42
        SANITIZE_ALLOW_PATTERN => 'Please match the requested format',
43
44
    ];

45

46
47
48
49
50
    private function __construct() {
        // Class should never be instantiated
    }

    /**
51
52
     * Check $value against given checkType/pattern. If check succeed, returns values.
     *   If check fails, depending on $mode, throws an UserException or return an empty string.
53
     *
54
     * @param string $value value to check
55
56
     * @param string $sanitizeClass
     * @param string $pattern Pattern as regexp
57
     * @param string $decimalFormat with 'size,precision'
Carsten  Rose's avatar
Carsten Rose committed
58
     * @param string $mode SANITIZE_EXCEPTION | SANITIZE_EMPTY_STRING
59
     * @param string $dataPatternErrorText
60
     * @return string
61
     * @throws CodeException
62
     * @throws UserFormException
63
     */
64
    public static function sanitize($value, $sanitizeClass = SANITIZE_DEFAULT, $pattern = '', $decimalFormat = '', $mode = SANITIZE_EMPTY_STRING, $dataPatternErrorText = '') {
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;
            }

88
89
90
91
92
93
94
95
            throw new UserFormException($errorText, $errorCode);
        }

        return SANITIZE_VIOLATE . $sanitizeClass . SANITIZE_VIOLATE;
    }

    /**
     * Returns the final validation pattern based on a given $checkType, $pattern, and $decimalFormat
96
     * @param string $checkType SANITIZE_DEFAULT, ...
97
98
99
     * @param string $pattern
     * @param string $decimalFormat e.g. "10,2"
     *
100
     * @param string $rcSanitizeMessage Message specific to a pattern
101
102
103
     * @return string
     * @throws CodeException
     */
104
105
106
    public static function getInputCheckPattern($checkType, $pattern, $decimalFormat, &$rcSanitizeMessage) {

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

108
        switch ($checkType) {
109
            case SANITIZE_ALLOW_PATTERN:
110
111
112
113
                return $pattern;

            case SANITIZE_ALLOW_ALL:
                $pattern = '';
114
115
                break;

116
            case SANITIZE_ALLOW_DIGIT:
117
            case SANITIZE_ALLOW_NUMERICAL:
118
119
            case SANITIZE_ALLOW_EMAIL:
            case SANITIZE_ALLOW_ALNUMX:
120
            case SANITIZE_ALLOW_ALLBUT:
121
                $pattern = self::$sanitizePattern[$checkType];
122
123
124
                break;

            default:
125
                throw new CodeException("Unknown checkType: " . $checkType, ERROR_UNKNOWN_CHECKTYPE);
126
127
        }

128
        // decimalFormat
129
130
131
        if ($decimalFormat != '' && $checkType !== SANITIZE_ALLOW_DIGIT) {
            // overwrite pattern with decimalFormat pattern
            $decimalFormatArray = explode(',', $decimalFormat);
132
            $pattern = "^-?[0-9]{0," . ($decimalFormatArray[0] - $decimalFormatArray[1]) . "}(\.[0-9]{0,$decimalFormatArray[1]})?$";
133
            $rcSanitizeMessage = "Requested decimal format (mantis,decimal): $decimalFormat";
134
135
        }

136
        return $pattern;
137
138
139
140
141
142
143
    }

    /**
     * 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
144
145
     * @param $min
     * @param $max
146
147
148
149
     * @param string $mode SANITIZE_EXCEPTION | SANITIZE_EMPTY_STRING
     * @return string
     * @throws UserFormException
     */
150
    public static function checkMinMax($value, $min, $max, $mode = SANITIZE_EMPTY_STRING) {
151
152
153
        $errorCode = 0;
        $errorText = '';

154
        if ($min !== '' && $value < $min) {
155
156
157
            $errorCode = ERROR_SMALLER_THAN_MIN;
            $errorText = "Value '$value' is smaller than the allowed minimum of '$min'.";
        }
158
        if ($max !== '' && $value > $max) {
159
160
            $errorCode = ERROR_LARGER_THAN_MAX;
            $errorText = "Value '$value' is larger than the allowed maximum of '$max'.";
161
162
        }

163
        if ($errorCode == 0)
Elias Villiger's avatar
Elias Villiger committed
164
165
            return $value;

166
167
168
        // check failed
        if ($mode === SANITIZE_EXCEPTION) {
            throw new UserFormException($errorText, $errorCode);
Elias Villiger's avatar
Elias Villiger committed
169
        }
170
171

        return '';
172
173
    }

174
    /**
175
     * Sanitizes a filename. Copied from http://www.phpit.net/code/filename-safe/
176
177
     *
     * @param $filename
Carsten  Rose's avatar
Carsten Rose committed
178
     *
179
180
     * @param bool $flagBaseName
     * @param bool $allowSlash
181
182
     * @return mixed
     */
183
184
185
    public static function safeFilename($filename, $flagBaseName = false, $allowSlash = false) {

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

188
189
190
191
192
193
194
        $search = array(
            // Definition of German Umlauts START
            '/ß/',
            '/ä/', '/Ä/',
            '/ö/', '/Ö/',
            '/ü/', '/Ü/',
            // Definition of German Umlauts ENDE
195
            $pattern,
196
197
198
199
200
201
202
        );

        $replace = array(
            'ss',
            'ae', 'Ae',
            'oe', 'Oe',
            'ue', 'Ue',
Carsten  Rose's avatar
Carsten Rose committed
203
            '_',
204
205
        );

206
207
208
209
        if ($flagBaseName) {
            $filename = basename($filename);
        }

210
211
212
        return preg_replace($search, $replace, $filename);
    } // safeFilename()

213
214
215
216
    /**
     * htmlentities($data) - if $data is an array, convert it recursively.
     *
     * @param string|array $data
Carsten  Rose's avatar
Carsten Rose committed
217
     * @param int $mode
Carsten  Rose's avatar
Carsten Rose committed
218
     *
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
     * @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;
    }

236
    /**
Carsten  Rose's avatar
Carsten Rose committed
237
     * Take the given $item (or iterates over all elements of the given array) and normalize the content.
238
     * Only strings will be normalized. Sub arrays will be normalized recursive. Numeric content is skipped.
Carsten  Rose's avatar
Carsten Rose committed
239
     * Throws an exception for unknown content.
240
241
     *
     * 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
242
     * with composed characters (happens e.g. on Apple Mac / Safari without special user invention).
243
     *
244
     * @param array|string $item
Carsten  Rose's avatar
Carsten Rose committed
245
     *
246
247
     * @return array|string
     * @throws CodeException
248
     */
249
250
251
252
253
254
255
256
257
258
259
    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)) {
260
261
262
                throw new qfq\CodeException ("Expect type 'string / numeric / array' - but there is something else.", ERROR_UNEXPECTED_TYPE);
            }
        }
Carsten  Rose's avatar
Carsten Rose committed
263

264
        return $item;
265
    }
266

267
268
269
270
271
272
273
274
275
276
277

    /**
     * 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) {

        if (isset($_GET[$key]) && !ctype_digit($_GET[$key])) {
278
            if (!empty($_GET[$key]) && ctype_digit($_GET[$key][0])) {
279
280
281
282
283
284
285
286
                $_GET[$key] = $_GET[$key][0];
            } else {
                $_GET[$key] = '';
            }
        }

    }

287
}