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

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