Passed
Push — master ( e6d102...7dc300 )
by Eric
12:39
created

Environment::url()   B

Complexity

Conditions 9
Paths 192

Size

Total Lines 32
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 13
nc 192
nop 0
dl 0
loc 32
rs 7.2888
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Utility - Collection of various PHP utility functions.
7
 *
8
 * @author    Eric Sizemore <[email protected]>
9
 * @version   2.0.0
10
 * @copyright (C) 2017 - 2024 Eric Sizemore
11
 * @license   The MIT License (MIT)
12
 *
13
 * Copyright (C) 2017 - 2024 Eric Sizemore <https://www.secondversion.com>.
14
 *
15
 * Permission is hereby granted, free of charge, to any person obtaining a copy
16
 * of this software and associated documentation files (the "Software"), to
17
 * deal in the Software without restriction, including without limitation the
18
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
19
 * sell copies of the Software, and to permit persons to whom the Software is
20
 * furnished to do so, subject to the following conditions:
21
 *
22
 * The above copyright notice and this permission notice shall be included in
23
 * all copies or substantial portions of the Software.
24
 *
25
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
31
 * THE SOFTWARE.
32
 */
33
34
namespace Esi\Utility;
35
36
// Exceptions
37
use RuntimeException;
38
use ArgumentCountError;
39
40
// Functions
41
use function explode;
42
use function count;
43
use function inet_ntop;
44
use function inet_pton;
45
use function filter_var;
46
use function trim;
47
use function preg_match;
48
use function preg_replace;
49
use function sprintf;
50
use function ini_get;
51
use function ini_set;
52
use function function_exists;
53
54
// Constants
55
use const FILTER_VALIDATE_IP;
56
use const FILTER_FLAG_IPV4;
57
use const FILTER_FLAG_IPV6;
58
use const FILTER_FLAG_NO_PRIV_RANGE;
59
use const FILTER_FLAG_NO_RES_RANGE;
60
61
/**
62
 * Environment utilities.
63
 */
64
final class Environment
65
{
66
    /**
67
     * requestMethod()
68
     *
69
     * Gets the request method.
70
     *
71
     * @return  string
72
     */
73
    public static function requestMethod(): string
74
    {
75
        /** @var string $method */
76
        $method = (
77
            Environment::var('HTTP_X_HTTP_METHOD_OVERRIDE', Environment::var('REQUEST_METHOD', 'GET'))
78
        );
79
        return Strings::upper($method);
80
    }
81
82
    /**
83
     * var()
84
     *
85
     * Gets a variable from $_SERVER using $default if not provided.
86
     *
87
     * @param   string           $var      Variable name.
88
     * @param   string|int|null  $default  Default value to substitute.
89
     * @return  string|int|null
90
     */
91
    public static function var(string $var, string | int | null $default = ''): string | int | null
92
    {
93
        /** @var string|int|null $value */
94
        $value = Arrays::get($_SERVER, $var) ?? $default;
95
96
        return $value;
97
    }
98
99
    /**
100
     * ipAddress()
101
     *
102
     * Return the visitor's IP address.
103
     *
104
     * @param   bool    $trustProxy  Whether to trust HTTP_CLIENT_IP and HTTP_X_FORWARDED_FOR.
105
     * @return  string
106
     */
107
    public static function ipAddress(bool $trustProxy = false): string
108
    {
109
        // Pretty self-explanatory. Try to get an 'accurate' IP
110
        $cloudflare = Environment::var('HTTP_CF_CONNECTING_IP');
111
112
        if ($cloudflare !== '') {
113
            Arrays::set($_SERVER, 'REMOTE_ADDR', $cloudflare);
114
        }
115
116
        if (!$trustProxy) {
117
            /** @var string */
118
            return Environment::var('REMOTE_ADDR');
119
        }
120
121
        $ip = '';
122
        $ips = [];
123
124
        /** @var string $forwarded */
125
        $forwarded = Environment::var('HTTP_X_FORWARDED_FOR');
126
        /** @var string $realip */
127
        $realip = Environment::var('HTTP_X_REAL_IP');
128
129
        if ($forwarded !== '') {
130
            /** @var list<string> $ips */
131
            $ips = explode(',', $forwarded);
132
        } elseif ($realip !== '') {
133
            /** @var list<string> $ips */
134
            $ips = explode(',', $realip);
135
        }
136
137
        /** @var list<string> $ips */
138
        $ips = Arrays::mapDeep($ips, 'trim');
139
140
        if (count($ips) > 0) {
141
            foreach ($ips as $val) {
142
                // @phpstan-ignore-next-line
143
                if (inet_ntop(inet_pton($val)) === $val && Environment::isPublicIp($val)) {
144
                    /** @var string $ip */
145
                    $ip = $val;
146
                    break;
147
                }
148
            }
149
        }
150
        unset($ips);
151
152
        if ($ip === '') {
153
            /** @var string $ip */
154
            $ip = Environment::var('HTTP_CLIENT_IP', Environment::var('REMOTE_ADDR'));
155
        }
156
        return $ip;
157
    }
158
159
    /**
160
     * isPrivateIp()
161
     *
162
     * Determines if an IP address is within the private range.
163
     *
164
     * @param   string  $ipaddress  IP address to check.
165
     * @return  bool
166
     */
167
    public static function isPrivateIp(string $ipaddress): bool
168
    {
169
        return !(bool) filter_var(
170
            $ipaddress,
171
            FILTER_VALIDATE_IP,
172
            FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE
173
        );
174
    }
175
176
    /**
177
     * isReservedIp()
178
     *
179
     * Determines if an IP address is within the reserved range.
180
     *
181
     * @param   string  $ipaddress  IP address to check.
182
     * @return  bool
183
     */
184
    public static function isReservedIp(string $ipaddress): bool
185
    {
186
        return !(bool) filter_var(
187
            $ipaddress,
188
            FILTER_VALIDATE_IP,
189
            FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6 | FILTER_FLAG_NO_RES_RANGE
190
        );
191
    }
192
193
    /**
194
     * isPublicIp()
195
     *
196
     * Determines if an IP address is not within the private or reserved ranges.
197
     *
198
     * @param   string  $ipaddress  IP address to check.
199
     * @return  bool
200
     */
201
    public static function isPublicIp(string $ipaddress): bool
202
    {
203
        return (!Environment::isPrivateIp($ipaddress) && !Environment::isReservedIp($ipaddress));
204
    }
205
206
    /**
207
     * host()
208
     *
209
     * Determines current hostname.
210
     *
211
     * @param   bool    $stripWww         True to strip www. off the host, false to leave it be.
212
     * @param   bool    $acceptForwarded  True to accept, false otherwise.
213
     * @return  string
214
     */
215
    public static function host(bool $stripWww = false, bool $acceptForwarded = false): string
216
    {
217
        /** @var string $forwarded */
218
        $forwarded = Environment::var('HTTP_X_FORWARDED_HOST');
219
220
        /** @var string $host */
221
        $host = (($acceptForwarded && ($forwarded !== '')) ? $forwarded : (Environment::var('HTTP_HOST', Environment::var('SERVER_NAME'))));
222
        $host = trim($host);
223
224
        if ($host === '' || preg_match('#^\[?(?:[a-z0-9-:\]_]+\.?)+$#', $host) === 0) {
225
            $host = 'localhost';
226
        }
227
228
        $host = Strings::lower($host);
229
230
        // Strip 'www.'
231
        if ($stripWww) {
232
            $strippedHost = preg_replace('#^www\.#', '', $host);
233
        }
234
        return ($strippedHost ?? $host);
235
    }
236
237
    /**
238
     * isHttps()
239
     *
240
     * Checks to see if SSL is in use.
241
     *
242
     * @return  bool
243
     */
244
    public static function isHttps(): bool
245
    {
246
        $headers = \getallheaders();
247
248
        $server = Environment::var('HTTPS');
249
        $frontEnd = Arrays::get($headers, 'X-Forwarded-Proto', '');
1 ignored issue
show
Bug introduced by
It seems like $headers can also be of type true; however, parameter $array of Esi\Utility\Arrays::get() does only seem to accept ArrayAccess|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

249
        $frontEnd = Arrays::get(/** @scrutinizer ignore-type */ $headers, 'X-Forwarded-Proto', '');
Loading history...
250
        $forwarded = Arrays::get($headers, 'Front-End-Https', '');
251
252
        if ($server !== 'off' && $server !== '') {
253
            return true;
254
        }
255
        return $forwarded === 'https' || ($frontEnd !== '' && $frontEnd !== 'off');
256
    }
257
258
    /**
259
     * url()
260
     *
261
     * Retrieve the current URL.
262
     *
263
     * @return  string
264
     */
265
    public static function url(): string
266
    {
267
        // Scheme
268
        $scheme = (Environment::isHttps()) ? 'https://' : 'http://';
269
270
        // Auth
271
        $authUser = Environment::var('PHP_AUTH_USER');
272
        $authPwd = Environment::var('PHP_AUTH_PW');
273
        $auth = ($authUser !== '' ? $authUser . ($authPwd !== '' ? ":$authPwd" : '') . '@' : '');
274
275
        // Host and port
276
        $host = Environment::host();
277
278
        /** @var int $port */
279
        $port = Environment::var('SERVER_PORT', 0);
280
        $port = ($port === (Environment::isHttps() ? 443 : 80) || $port === 0) ? '' : ":$port";
281
282
        // Path
283
        /** @var string $self */
284
        $self = Environment::var('PHP_SELF');
285
        /** @var string $query */
286
        $query = Environment::var('QUERY_STRING');
287
        /** @var string $request */
288
        $request = Environment::var('REQUEST_URI');
289
        /** @var string $path */
290
        $path = ($request === '' ? $self . ($query !== '' ? '?' . $query : '') : $request);
291
292
        // Put it all together
293
        /** @var non-falsy-string $url */
294
        $url = sprintf('%s%s%s%s%s', $scheme, $auth, $host, $port, $path);
295
296
        return $url;
297
    }
298
299
    /**
300
     * iniGet()
301
     *
302
     * Safe ini_get taking into account its availability.
303
     *
304
     * @param   string  $option       The configuration option name.
305
     * @param   bool    $standardize  Standardize returned values to 1 or 0?
306
     * @return  string
307
     *
308
     * @throws  RuntimeException|ArgumentCountError
309
     */
310
    public static function iniGet(string $option, bool $standardize = false): string
311
    {
312
        // @codeCoverageIgnoreStart
313
        if (!function_exists('ini_get')) {
314
            // disabled_functions?
315
            throw new RuntimeException('Native ini_get function not available.');
316
        }
317
        // @codeCoverageIgnoreEnd
318
        $value = ini_get($option);
319
320
        if ($value === false) {
321
            throw new RuntimeException('$option does not exist.');
322
        }
323
324
        $value = trim($value);
325
326
        if ($standardize) {
327
            return match (Strings::lower($value)) {
328
                'yes', 'on', 'true', '1' => '1',
329
                'no', 'off', 'false', '0' => '0',
330
                default => $value
331
            };
332
        }
333
        return $value;
334
    }
335
336
    /**
337
     * iniSet()
338
     *
339
     * Safe ini_set taking into account its availability.
340
     *
341
     * @param   string  $option  The configuration option name.
342
     * @param   string|int|float|bool|null $value   The new value for the option.
343
     * @return  string|false
344
     *
345
     * @throws RuntimeException|ArgumentCountError
346
     */
347
    public static function iniSet(string $option, string|int|float|bool|null $value): string | false
348
    {
349
        // @codeCoverageIgnoreStart
350
        if (!function_exists('ini_set')) {
351
            // disabled_functions?
352
            throw new RuntimeException('Native ini_set function not available.');
353
        }
354
        // @codeCoverageIgnoreEnd
355
        return ini_set($option, $value);
1 ignored issue
show
Bug introduced by
It seems like $value can also be of type boolean and null; however, parameter $value of ini_set() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

355
        return ini_set($option, /** @scrutinizer ignore-type */ $value);
Loading history...
356
    }
357
}
358