Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
33.33% covered (danger)
33.33%
12 / 36
33.33% covered (danger)
33.33%
1 / 3
CRAP
n/a
0 / 0
Automattic\Jetpack\Waf\wp_unslash
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
Automattic\Jetpack\Waf\flatten_array
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
5
Automattic\Jetpack\Waf\getallheaders
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
110
1<?php
2/**
3 * Utility functions for WAF.
4 *
5 * @package automattic/jetpack-waf
6 */
7
8namespace Automattic\Jetpack\Waf;
9
10/**
11 * A wrapper for WordPress's `wp_unslash()`.
12 *
13 * Even though PHP itself dropped the option to add slashes to superglobals a decade ago,
14 * WordPress still does it through some misguided extreme backwards compatibility. ðŸ™„
15 *
16 * If WordPress's function exists, assume it needs to be called. If not, assume it doesn't.
17 *
18 * @param string|array $value String or array of data to unslash.
19 * @return string|array Possibly unslashed $value.
20 */
21function wp_unslash( $value ) {
22    if ( function_exists( '\\wp_unslash' ) ) {
23        return \wp_unslash( $value );
24    } else {
25        return $value;
26    }
27}
28
29/**
30 * PHP helpfully parses request data into nested arrays in superglobals like $_GET and $_POST,
31 * and as part of that parsing turns field names like "myfield[x][y]" into a nested array
32 * that looks like [ "myfield" => [ "x" => [ "y" => "..." ] ] ]
33 * However, modsecurity (and thus our WAF rules) expect the original (non-nested) names.
34 *
35 * Therefore, this method takes an array of any depth and returns a single-depth array with nested
36 * keys translated back to a single string with brackets.
37 *
38 * Because there might be multiple items with the same name, this function will return an array of tuples,
39 * with the first item in the tuple the re-created original field name, and the second item the value.
40 *
41 * @example
42 * flatten_array( [ "field1" => "abc", "field2" => [ "d", "e", "f" ] ] )
43 * => [
44 *       [ "field1", "abc" ],
45 *       [ "field2[0]", "d" ],
46 *       [ "field2[1]", "e" ],
47 *       [ "field2[2]", "f" ],
48 * ]
49 *
50 * @param array     $array         An array that resembles one of the PHP superglobals like $_GET or $_POST.
51 * @param string    $key_prefix    String that should be prepended to the keys output by this function.
52 *                                 Usually only used internally as part of recursion when flattening a nested array.
53 * @param bool|null $dot_notation  Whether to use dot notation instead of bracket notation.
54 *
55 * @return array{0: string, 1: scalar}[]  $key_prefix  An array of key/value tuples, one for each distinct value in the input array.
56 */
57function flatten_array( $array, $key_prefix = '', $dot_notation = null ) {
58    $return = array();
59    foreach ( $array as $source_key => $source_value ) {
60        $key = $source_key;
61        if ( ! empty( $key_prefix ) ) {
62            $key = $dot_notation ? "$key_prefix.$source_key" : $key_prefix . "[$source_key]";
63        }
64
65        if ( ! is_array( $source_value ) ) {
66            $return[] = array( $key, $source_value );
67        } else {
68            $return = array_merge( $return, flatten_array( $source_value, $key, $dot_notation ) );
69        }
70    }
71    return $return;
72}
73
74/**
75 * Polyfill for getallheaders, which is not available in all PHP environments.
76 *
77 * @link https://github.com/ralouphie/getallheaders
78 */
79if ( ! function_exists( 'getallheaders' ) ) {
80    /**
81     * Get all HTTP header key/values as an associative array for the current request.
82     *
83     * @return array The HTTP header key/value pairs.
84     */
85    function getallheaders() {
86        // phpcs:disable WordPress.Security.ValidatedSanitizedInput
87        $headers = array();
88
89        $copy_server = array(
90            'CONTENT_TYPE'   => 'Content-Type',
91            'CONTENT_LENGTH' => 'Content-Length',
92            'CONTENT_MD5'    => 'Content-Md5',
93        );
94
95        foreach ( $_SERVER as $key => $value ) {
96            if ( substr( $key, 0, 5 ) === 'HTTP_' ) {
97                $key = substr( $key, 5 );
98                if ( ! isset( $copy_server[ $key ] ) || ! isset( $_SERVER[ $key ] ) ) {
99                    $key             = str_replace( ' ', '-', ucwords( strtolower( str_replace( '_', ' ', $key ) ) ) );
100                    $headers[ $key ] = $value;
101                }
102            } elseif ( isset( $copy_server[ $key ] ) ) {
103                $headers[ $copy_server[ $key ] ] = $value;
104            }
105        }
106
107        if ( ! isset( $headers['Authorization'] ) ) {
108            if ( isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ) {
109                $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
110            } elseif ( isset( $_SERVER['PHP_AUTH_USER'] ) ) {
111                $basic_pass               = $_SERVER['PHP_AUTH_PW'] ?? '';
112                $headers['Authorization'] = 'Basic ' . base64_encode( $_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass );
113            } elseif ( isset( $_SERVER['PHP_AUTH_DIGEST'] ) ) {
114                $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST'];
115            }
116        }
117
118        return $headers;
119    }
120}