Completed
Push — master ( b6b34a...efa664 )
by Lars
02:59
created

Bootup   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 185
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 1

Test Coverage

Coverage 94.12%

Importance

Changes 0
Metric Value
wmc 19
lcom 0
cbo 1
dl 0
loc 185
ccs 64
cts 68
cp 0.9412
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A is_php() 0 12 2
B filterRequestInputs() 0 38 5
B filterRequestUri() 0 57 7
A filterString() 0 4 1
A get_random_bytes() 0 14 3
A initAll() 0 6 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace voku\helper;
6
7
/**
8
 * Class Bootup
9
 *
10
 * this is a bootstrap for the polyfills (iconv / intl / mbstring / normalizer / xml)
11
 *
12
 * @package voku\helper
13
 */
14
class Bootup
15
{
16
  /**
17
   * filter request inputs
18
   *
19
   * Ensures inputs are well formed UTF-8
20
   * When not, assumes Windows-1252 and converts to UTF-8
21
   * Tests only values, not keys
22
   *
23
   * @param int    $normalization_form
24
   * @param string $leading_combining
25
   */
26 1
  public static function filterRequestInputs($normalization_form = 4 /* n::NFC */, $leading_combining = '◌')
27
  {
28
    $a = [
29 1
        &$_FILES,
30 1
        &$_ENV,
31 1
        &$_GET,
32 1
        &$_POST,
33 1
        &$_COOKIE,
34 1
        &$_SERVER,
35 1
        &$_REQUEST,
36
    ];
37
38
    /** @noinspection ReferenceMismatchInspection */
39
    /** @noinspection ForeachSourceInspection */
40 1
    foreach ($a[0] as &$r) {
41 1
      $a[] = [
42 1
          &$r['name'],
43 1
          &$r['type'],
44
      ];
45
    }
46 1
    unset($r, $a[0]);
47
48 1
    $len = \count($a) + 1;
49 1
    for ($i = 1; $i < $len; ++$i) {
50
      /** @noinspection ReferenceMismatchInspection */
51
      /** @noinspection ForeachSourceInspection */
52 1
      foreach ($a[$i] as &$r) {
53
        /** @noinspection ReferenceMismatchInspection */
54 1
        $s = $r; // $r is a reference, $s a copy
55 1
        if (\is_array($s)) {
56 1
          $a[$len++] = &$r;
57
        } else {
58 1
          $r = self::filterString($s, $normalization_form, $leading_combining);
59
        }
60
      }
61 1
      unset($r, $a[$i]);
62
    }
63 1
  }
64
65
  /**
66
   * Filter current REQUEST_URI .
67
   *
68
   * @param string|null $uri <p>If null is set, then the server REQUEST_URI will be used.</p>
69
   * @param bool        $exit
70
   *
71
   * @return mixed
72
   */
73 1
  public static function filterRequestUri($uri = null, $exit = true)
74
  {
75 1
    if (null === $uri) {
76
77 1
      if (!isset($_SERVER['REQUEST_URI'])) {
78 1
        return false;
79
      }
80
81 1
      $uri = $_SERVER['REQUEST_URI'];
82
    }
83
84 1
    $uriOrig = $uri;
85
86
    //
87
    // Ensures the URL is well formed UTF-8
88
    //
89
90 1
    if (UTF8::is_utf8(\rawurldecode($uri)) === true) {
91 1
      return $uri;
92
    }
93
94
    //
95
    // When not, assumes Windows-1252 and redirects to the corresponding UTF-8 encoded URL
96
    //
97
98 1
    $uri = (string)\preg_replace_callback(
99 1
        '/[\x80-\xFF]+/',
100 1
        function ($m) {
101 1
          return \rawurlencode($m[0]);
102 1
        },
103 1
        $uri
104
    );
105
106 1
    $uri = (string)\preg_replace_callback(
107 1
        '/(?:%[89A-F][0-9A-F])+/i',
108 1
        function ($m) {
109 1
          return \rawurlencode(UTF8::rawurldecode($m[0]));
110 1
        },
111 1
        $uri
112
    );
113
114
    if (
115 1
        $uri !== $uriOrig
116
        &&
117 1
        $exit === true
118
        &&
119 1
        \headers_sent() === false
120
    ) {
121
      // Use ob_start() to buffer content and avoid problem of headers already sent...
122
      $severProtocol = ($_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1');
123
      \header($severProtocol . ' 301 Moved Permanently');
124
      \header('Location: ' . $uri);
0 ignored issues
show
Security Response Splitting introduced by
'Location: ' . $uri can contain request data and is used in response header context(s) leading to a potential security vulnerability.

1 path for user data to reach this point

  1. Fetching key REQUEST_URI from $_SERVER, and $uri is assigned
    in src/voku/helper/Bootup.php on line 81
  2. $uri is passed through preg_replace_callback()
    in src/voku/helper/Bootup.php on line 103
  3. $uri is assigned
    in src/voku/helper/Bootup.php on line 98
  4. $uri is passed through preg_replace_callback()
    in src/voku/helper/Bootup.php on line 111
  5. $uri is assigned
    in src/voku/helper/Bootup.php on line 106

Response Splitting Attacks

Allowing an attacker to set a response header, opens your application to response splitting attacks; effectively allowing an attacker to send any response, he would like.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
125
      exit();
126
    }
127
128 1
    return $uri;
129
  }
130
131
  /**
132
   * Normalizes to UTF-8 NFC, converting from WINDOWS-1252 when needed.
133
   *
134
   * @param mixed  $input
135
   * @param int    $normalization_form
136
   * @param string $leading_combining
137
   *
138
   * @return mixed
139
   */
140 1
  public static function filterString($input, int $normalization_form = 4 /* n::NFC */, string $leading_combining = '◌')
141
  {
142 1
    return UTF8::filter($input, $normalization_form, $leading_combining);
143
  }
144
145
  /**
146
   * Get random bytes via "random_bytes()" (+ polyfill).
147
   *
148
   * @ref https://github.com/paragonie/random_compat/
149
   *
150
   * @param  int $length Output length
151
   *
152
   * @return  string|false false on error
153
   */
154 1
  public static function get_random_bytes($length)
155
  {
156 1
    if (!$length) {
157 1
      return false;
158
    }
159
160 1
    $length = (int)$length;
161
162 1
    if ($length <= 0) {
163 1
      return false;
164
    }
165
166 1
    return random_bytes($length);
167
  }
168
169
  /**
170
   * bootstrap
171
   */
172 1
  public static function initAll()
173
  {
174 1
    \ini_set('default_charset', 'UTF-8');
175
176
    // everything is init via composer, so we are done here ...
177 1
  }
178
179
  /**
180
   * Determines if the current version of PHP is equal to or greater than the supplied value.
181
   *
182
   * @param string $version
183
   *
184
   * @return bool <p>Return <strong>true</strong> if the current version is $version or higher</p>
185
   */
186 4
  public static function is_php($version): bool
187
  {
188 4
    static $_IS_PHP;
189
190 4
    $version = (string)$version;
191
192 4
    if (!isset($_IS_PHP[$version])) {
193 2
      $_IS_PHP[$version] = \version_compare(PHP_VERSION, $version, '>=');
194
    }
195
196 4
    return $_IS_PHP[$version];
197
  }
198
}
199