Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
58.33% covered (warning)
58.33%
56 / 96
75.00% covered (warning)
75.00%
21 / 28
CRAP
0.00% covered (danger)
0.00%
0 / 1
Waf_Transforms
58.33% covered (warning)
58.33%
56 / 96
75.00% covered (warning)
75.00%
21 / 28
169.60
0.00% covered (danger)
0.00%
0 / 1
 base64_decode_ext
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 base64_decode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 cmd_line
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
2
 sql_hex_decode
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 base64_encode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 compress_whitespace
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hex_encode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hex_decode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 html_entity_decode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 length
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 lowercase
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 md5
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 normalize_path
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
6
 normalize_path_win
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 remove_nulls
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 remove_whitespace
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 replace_comments
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 remove_comments_char
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 replace_nulls
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 url_decode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 url_decode_uni
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 js_decode
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 uppercase
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 sha1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 trim_left
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 trim_right
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 trim
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 utf8_to_unicode
77.27% covered (warning)
77.27%
17 / 22
0.00% covered (danger)
0.00%
0 / 1
9.95
1<?php
2/**
3 * Transforms for Jetpack Waf
4 *
5 * @package automattic/jetpack-waf
6 */
7
8namespace Automattic\Jetpack\Waf;
9
10/**
11 * Waf_Transforms class
12 */
13class Waf_Transforms {
14
15    /**
16     * Decode a Base64-encoded string. This runs the decode without strict mode, to match Modsecurity's 'base64DecodeExt' transform function.
17     *
18     * @param string $value value to be decoded.
19     * @return string
20     */
21    public function base64_decode_ext( $value ) {
22        return base64_decode( $value );
23    }
24
25    /**
26     * Characters to match when trimming a string.
27     * Emulates `std::isspace` used by ModSecurity.
28     *
29     * @see https://en.cppreference.com/w/cpp/string/byte/isspace
30     */
31    const TRIM_CHARS = " \n\r\t\v\f";
32
33    /**
34     * Decode a Base64-encoded string. This runs the decode with strict mode, to match Modsecurity's 'base64Decode' transform function.
35     *
36     * @param string $value value to be decoded.
37     * @return string
38     */
39    public function base64_decode( $value ) {
40        return base64_decode( $value, true );
41    }
42
43    /**
44     * Remove all characters that might escape a command line command
45     *
46     * @see https://github.com/SpiderLabs/ModSecurity/wiki/Reference-Manual-%28v2.x%29#cmdLine
47     * @param string $value value to be escaped.
48     * @return string
49     */
50    public function cmd_line( $value ) {
51        return strtolower(
52            preg_replace(
53                '/\s+/',
54                ' ',
55                str_replace(
56                    array( ',', ';' ),
57                    ' ',
58                    preg_replace(
59                        '/\s+(?=[\/\(])/',
60                        '',
61                        str_replace(
62                            array( '^', "'", '"', '\\' ),
63                            '',
64                            $value
65                        )
66                    )
67                )
68            )
69        );
70    }
71
72    /**
73     * Decode a SQL hex string.
74     *
75     * @example 414243 decodes to "ABC"
76     * @param string $value value to be decoded.
77     * @return string
78     */
79    public function sql_hex_decode( $value ) {
80        return preg_replace_callback(
81            '/0x[a-f0-9]+/i',
82            function ( $matches ) {
83                $str = substr( $matches[0], 2 );
84                if ( 0 !== strlen( $str ) % 2 ) {
85                    $str = '0' . $str;
86                }
87                return hex2bin( $str );
88            },
89            $value
90        );
91    }
92
93    /**
94     * Encode a string using Base64 encoding.
95     *
96     * @param string $value value to be decoded.
97     * @return string
98     */
99    public function base64_encode( $value ) {
100        return base64_encode( $value );
101    }
102
103    /**
104     * Convert all whitespace characters to a space and remove any repeated spaces.
105     *
106     * @param string $value value to be converted.
107     * @return string
108     */
109    public function compress_whitespace( $value ) {
110        return preg_replace( '/\s+/', ' ', $value );
111    }
112
113    /**
114     * Encode string (possibly containing binary characters) by replacing each input byte with two hexadecimal characters.
115     *
116     * @param string $value value to be encoded.
117     * @return string
118     */
119    public function hex_encode( $value ) {
120        return bin2hex( $value );
121    }
122
123    /**
124     * Decode string that was previously encoded by hexEncode()
125     *
126     * @param string $value value to be decoded.
127     * @return string
128     */
129    public function hex_decode( $value ) {
130        return pack( 'H*', $value );
131    }
132
133    /**
134     * Decode the characters encoded as HTML entities.
135     *
136     * @param mixed $value value do be decoded.
137     * @return string
138     */
139    public function html_entity_decode( $value ) {
140        return html_entity_decode( $value, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 );
141    }
142
143    /**
144     * Return the length of the input string.
145     *
146     * @param string $value input string.
147     * @return int
148     */
149    public function length( $value ) {
150        return strlen( $value );
151    }
152
153    /**
154     * Convert all characters to lowercase.
155     *
156     * @param string $value string to be converted.
157     * @return string
158     */
159    public function lowercase( $value ) {
160        return strtolower( $value );
161    }
162
163    /**
164     * Calculate an md5 hash for the given data
165     *
166     * @param mixed $value value to be hashed.
167     * @return string
168     */
169    public function md5( $value ) {
170        return md5( $value, true );
171    }
172
173    /**
174     * Removes multiple slashes, directory self-references, and directory back-references (except when at the beginning of the input) from input string.
175     *
176     * @param string $value value to be normalized.
177     * @return string
178     */
179    public function normalize_path( $value ) {
180        $parts = explode(
181            '/',
182            // replace any duplicate slashes with a single one.
183            preg_replace( '~/{2,}~', '/', $value )
184        );
185
186        $i = 0;
187        while ( isset( $parts[ $i ] ) ) {
188            switch ( $parts[ $i ] ) {
189                // If this folder is a self-reference, remove it.
190                case '..':
191                    // If this folder is a backreference, remove it unless we're already at the root.
192                    if ( isset( $parts[ $i - 1 ] ) && ! in_array( $parts[ $i - 1 ], array( '', '..' ), true ) ) {
193                        array_splice( $parts, $i - 1, 2 );
194                        --$i;
195                        continue 2;
196                    }
197                    break;
198                case '.':
199                    array_splice( $parts, $i, 1 );
200                    continue 2;
201            }
202            ++$i;
203        }
204
205        return implode( '/', $parts );
206    }
207
208    /**
209     * Convert backslash characters to forward slashes, and then normalize using `normalizePath`
210     *
211     * @param string $value to be normalized.
212     * @return string
213     */
214    public function normalize_path_win( $value ) {
215        return $this->normalize_path( str_replace( '\\', '/', $value ) );
216    }
217
218    /**
219     * Removes all NUL bytes from input.
220     *
221     * @param string $value value to be filtered.
222     * @return string
223     */
224    public function remove_nulls( $value ) {
225        return str_replace( "\x0", '', $value );
226    }
227
228    /**
229     * Remove all whitespace characters from input.
230     *
231     * @param string $value value to be filtered.
232     * @return string
233     */
234    public function remove_whitespace( $value ) {
235        return preg_replace( '/\s/', '', $value );
236    }
237
238    /**
239     * Replaces each occurrence of a C-style comment (/ * ... * /) with a single space.
240     * Unterminated comments will also be replaced with a space. However, a standalone termination of a comment (* /) will not be acted upon.
241     *
242     * @param string $value value to be filtered.
243     * @return string
244     */
245    public function replace_comments( $value ) {
246        $value = preg_replace( '~/\*.*?\*/|/\*.*?$~Ds', ' ', $value );
247        return explode( '/*', $value, 2 )[0];
248    }
249
250    /**
251     * Removes common comments chars (/ *, * /, --, #).
252     *
253     * @param string $value value to be filtered.
254     * @return string
255     */
256    public function remove_comments_char( $value ) {
257        return preg_replace( '~/*|*/|--|#|//~', '', $value );
258    }
259
260    /**
261     * Replaces each NUL byte in input with a space.
262     *
263     * @param string $value value to be filtered.
264     * @return string
265     */
266    public function replace_nulls( $value ) {
267        return str_replace( "\x0", ' ', $value );
268    }
269
270    /**
271     * Decode a URL-encoded input string.
272     *
273     * @param string $value value to be decoded.
274     * @return string
275     */
276    public function url_decode( $value ) {
277        return urldecode( $value );
278    }
279
280    /**
281     * Decode a URL-encoded input string.
282     *
283     * @param string $value value to be decoded.
284     * @return string
285     */
286    public function url_decode_uni( $value ) {
287        error_log( 'JETPACKWAF TRANSFORM NOT IMPLEMENTED: urlDecodeUni' );
288        return $value;
289    }
290
291    /**
292     * Decode a json encoded input string.
293     *
294     * @param string $value value to be decoded.
295     * @return string
296     */
297    public function js_decode( $value ) {
298        error_log( 'JETPACKWAF TRANSFORM NOT IMPLEMENTED: jsDecode' );
299        return $value;
300    }
301
302    /**
303     * Convert all characters to uppercase.
304     *
305     * @param string $value value to be encoded.
306     * @return string
307     */
308    public function uppercase( $value ) {
309        return strtoupper( $value );
310    }
311
312    /**
313     * Calculate a SHA1 hash from the input string.
314     *
315     * @param mixed $value value to be hashed.
316     * @return string
317     */
318    public function sha1( $value ) {
319        return sha1( $value, true );
320    }
321
322    /**
323     * Remove whitespace from the left side of the input string.
324     *
325     * @param string $value value to be trimmed.
326     * @return string
327     */
328    public function trim_left( $value ) {
329        return ltrim( $value, self::TRIM_CHARS );
330    }
331
332    /**
333     * Remove whitespace from the right side of the input string.
334     *
335     * @param string $value value to be trimmed.
336     * @return string
337     */
338    public function trim_right( $value ) {
339        return rtrim( $value, self::TRIM_CHARS );
340    }
341
342    /**
343     * Remove whitespace from both sides of the input string.
344     *
345     * @param string $value value to be trimmed.
346     * @return string
347     */
348    public function trim( $value ) {
349        return trim( $value, self::TRIM_CHARS );
350    }
351
352    /**
353     * Convert UTF-8 characters to Unicode characters.
354     *
355     * This function iterates through each character of the input string, checks the ASCII value,
356     * and converts it to its corresponding Unicode representation. It handles characters that are
357     * represented with 1 to 4 bytes in UTF-8.
358     *
359     * @param string $str The string value to be encoded from UTF-8 to Unicode.
360     * @return string The converted string with Unicode representation.
361     */
362    public function utf8_to_unicode( $str ) {
363        $unicodeStr = '';
364        $strLen     = strlen( $str );
365        $i          = 0;
366
367        // Iterate through each character of the input string.
368        while ( $i < $strLen ) {
369            // Get the ASCII value of the current character.
370            $value = ord( $str[ $i ] );
371
372            if ( $value < 128 ) {
373                // If the character is in the ASCII range (0-127), directly add it to the Unicode string.
374                $unicodeStr .= chr( $value );
375                ++$i;
376            } else {
377                // For characters outside the ASCII range, determine the number of bytes in the UTF-8 representation.
378                $unicodeValue = '';
379                if ( $value >= 192 && $value <= 223 ) {
380                    // For characters represented with 2 bytes in UTF-8.
381                    $unicodeValue = ( ord( $str[ $i ] ) & 0x1F ) << 6 | ( ord( $str[ $i + 1 ] ) & 0x3F );
382                    $i           += 2;
383                } elseif ( $value >= 224 && $value <= 239 ) {
384                    // For characters represented with 3 bytes in UTF-8.
385                    $unicodeValue = ( ord( $str[ $i ] ) & 0x0F ) << 12 | ( ord( $str[ $i + 1 ] ) & 0x3F ) << 6 | ( ord( $str[ $i + 2 ] ) & 0x3F );
386                    $i           += 3;
387                } elseif ( $value >= 240 && $value <= 247 ) {
388                    // For characters represented with 4 bytes in UTF-8.
389                    $unicodeValue = ( ord( $str[ $i ] ) & 0x07 ) << 18 | ( ord( $str[ $i + 1 ] ) & 0x3F ) << 12 | ( ord( $str[ $i + 2 ] ) & 0x3F ) << 6 | ( ord( $str[ $i + 3 ] ) & 0x3F );
390                    $i           += 4;
391                } else {
392                    // If the sequence does not match any known UTF-8 pattern, skip to the next character.
393                    ++$i;
394                    continue;
395                }
396                // Convert the Unicode value to a formatted string and append it to the Unicode string.
397                $unicodeStr .= sprintf( '%%u%04X', $unicodeValue );
398            }
399        }
400
401        return strtolower( $unicodeStr );
402    }
403}