Passed
Push — master ( 41f798...e39e74 )
by Alexander
08:33
created

Request::getSecurePort()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 4
nc 5
nop 0
dl 0
loc 8
ccs 5
cts 5
cp 1
crap 4
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\web;
9
10
use Yii;
11
use yii\base\InvalidConfigException;
12
use yii\validators\IpValidator;
13
14
/**
15
 * The web Request class represents an HTTP request.
16
 *
17
 * It encapsulates the $_SERVER variable and resolves its inconsistency among different Web servers.
18
 * Also it provides an interface to retrieve request parameters from $_POST, $_GET, $_COOKIES and REST
19
 * parameters sent via other HTTP methods like PUT or DELETE.
20
 *
21
 * Request is configured as an application component in [[\yii\web\Application]] by default.
22
 * You can access that instance via `Yii::$app->request`.
23
 *
24
 * For more details and usage information on Request, see the [guide article on requests](guide:runtime-requests).
25
 *
26
 * @property-read string $absoluteUrl The currently requested absolute URL.
27
 * @property array $acceptableContentTypes The content types ordered by the quality score. Types with the
28
 * highest scores will be returned first. The array keys are the content types, while the array values are the
29
 * corresponding quality score and other parameters as given in the header.
30
 * @property array $acceptableLanguages The languages ordered by the preference level. The first element
31
 * represents the most preferred language.
32
 * @property-read array $authCredentials That contains exactly two elements: - 0: the username sent via HTTP
33
 * authentication, `null` if the username is not given - 1: the password sent via HTTP authentication, `null` if
34
 * the password is not given.
35
 * @property-read string|null $authPassword The password sent via HTTP authentication, `null` if the password
36
 * is not given.
37
 * @property-read string|null $authUser The username sent via HTTP authentication, `null` if the username is
38
 * not given.
39
 * @property string $baseUrl The relative URL for the application.
40
 * @property array|object $bodyParams The request parameters given in the request body. Note that the type of
41
 * this property differs in getter and setter. See [[getBodyParams()]] and [[setBodyParams()]] for details.
42
 * @property-read string $contentType Request content-type. Empty string is returned if this information is
43
 * not available.
44
 * @property-read CookieCollection $cookies The cookie collection.
45
 * @property-read string $csrfToken The token used to perform CSRF validation.
46
 * @property-read string $csrfTokenFromHeader The CSRF token sent via [[CSRF_HEADER]] by browser. Null is
47
 * returned if no such header is sent.
48
 * @property-read array $eTags The entity tags.
49
 * @property-read HeaderCollection $headers The header collection.
50
 * @property string|null $hostInfo Schema and hostname part (with port number if needed) of the request URL
51
 * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set. See
52
 * [[getHostInfo()]] for security related notes on this property.
53
 * @property-read string|null $hostName Hostname part of the request URL (e.g. `www.yiiframework.com`).
54
 * @property-read bool $isAjax Whether this is an AJAX (XMLHttpRequest) request.
55
 * @property-read bool $isDelete Whether this is a DELETE request.
56
 * @property-read bool $isFlash Whether this is an Adobe Flash or Adobe Flex request.
57
 * @property-read bool $isGet Whether this is a GET request.
58
 * @property-read bool $isHead Whether this is a HEAD request.
59
 * @property-read bool $isOptions Whether this is a OPTIONS request.
60
 * @property-read bool $isPatch Whether this is a PATCH request.
61
 * @property-read bool $isPjax Whether this is a PJAX request.
62
 * @property-read bool $isPost Whether this is a POST request.
63
 * @property-read bool $isPut Whether this is a PUT request.
64
 * @property-read bool $isSecureConnection If the request is sent via secure channel (https).
65
 * @property-read string $method Request method, such as GET, POST, HEAD, PUT, PATCH, DELETE. The value
66
 * returned is turned into upper case.
67
 * @property-read string|null $origin URL origin of a CORS request, `null` if not available.
68
 * @property string $pathInfo Part of the request URL that is after the entry script and before the question
69
 * mark. Note, the returned path info is already URL-decoded.
70
 * @property int $port Port number for insecure requests.
71
 * @property array $queryParams The request GET parameter values.
72
 * @property-read string $queryString Part of the request URL that is after the question mark.
73
 * @property string $rawBody The request body.
74
 * @property-read string|null $referrer URL referrer, null if not available.
75
 * @property-read string|null $remoteHost Remote host name, `null` if not available.
76
 * @property-read string|null $remoteIP Remote IP address, `null` if not available.
77
 * @property string $scriptFile The entry script file path.
78
 * @property string $scriptUrl The relative URL of the entry script.
79
 * @property int $securePort Port number for secure requests.
80
 * @property-read string $serverName Server name, null if not available.
81
 * @property-read int|null $serverPort Server port number, null if not available.
82
 * @property string $url The currently requested relative URL. Note that the URI returned may be URL-encoded
83
 * depending on the client.
84
 * @property-read string|null $userAgent User agent, null if not available.
85
 * @property-read string|null $userHost User host name, null if not available.
86
 * @property-read string|null $userIP User IP address, null if not available.
87
 *
88
 * @author Qiang Xue <[email protected]>
89
 * @since 2.0
90
 * @SuppressWarnings(PHPMD.SuperGlobals)
91
 */
92
class Request extends \yii\base\Request
93
{
94
    /**
95
     * The name of the HTTP header for sending CSRF token.
96
     */
97
    const CSRF_HEADER = 'X-CSRF-Token';
98
    /**
99
     * The length of the CSRF token mask.
100
     * @deprecated since 2.0.12. The mask length is now equal to the token length.
101
     */
102
    const CSRF_MASK_LENGTH = 8;
103
104
    /**
105
     * @var bool whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to true.
106
     * When CSRF validation is enabled, forms submitted to an Yii Web application must be originated
107
     * from the same application. If not, a 400 HTTP exception will be raised.
108
     *
109
     * Note, this feature requires that the user client accepts cookie. Also, to use this feature,
110
     * forms submitted via POST method must contain a hidden input whose name is specified by [[csrfParam]].
111
     * You may use [[\yii\helpers\Html::beginForm()]] to generate his hidden input.
112
     *
113
     * In JavaScript, you may get the values of [[csrfParam]] and [[csrfToken]] via `yii.getCsrfParam()` and
114
     * `yii.getCsrfToken()`, respectively. The [[\yii\web\YiiAsset]] asset must be registered.
115
     * You also need to include CSRF meta tags in your pages by using [[\yii\helpers\Html::csrfMetaTags()]].
116
     *
117
     * @see Controller::enableCsrfValidation
118
     * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery
119
     */
120
    public $enableCsrfValidation = true;
121
    /**
122
     * @var string the name of the token used to prevent CSRF. Defaults to '_csrf'.
123
     * This property is used only when [[enableCsrfValidation]] is true.
124
     */
125
    public $csrfParam = '_csrf';
126
    /**
127
     * @var array the configuration for creating the CSRF [[Cookie|cookie]]. This property is used only when
128
     * both [[enableCsrfValidation]] and [[enableCsrfCookie]] are true.
129
     */
130
    public $csrfCookie = ['httpOnly' => true];
131
    /**
132
     * @var bool whether to use cookie to persist CSRF token. If false, CSRF token will be stored
133
     * in session under the name of [[csrfParam]]. Note that while storing CSRF tokens in session increases
134
     * security, it requires starting a session for every page, which will degrade your site performance.
135
     */
136
    public $enableCsrfCookie = true;
137
    /**
138
     * @var bool whether cookies should be validated to ensure they are not tampered. Defaults to true.
139
     */
140
    public $enableCookieValidation = true;
141
    /**
142
     * @var string a secret key used for cookie validation. This property must be set if [[enableCookieValidation]] is true.
143
     */
144
    public $cookieValidationKey;
145
    /**
146
     * @var string the name of the POST parameter that is used to indicate if a request is a PUT, PATCH or DELETE
147
     * request tunneled through POST. Defaults to '_method'.
148
     * @see getMethod()
149
     * @see getBodyParams()
150
     */
151
    public $methodParam = '_method';
152
    /**
153
     * @var array the parsers for converting the raw HTTP request body into [[bodyParams]].
154
     * The array keys are the request `Content-Types`, and the array values are the
155
     * corresponding configurations for [[Yii::createObject|creating the parser objects]].
156
     * A parser must implement the [[RequestParserInterface]].
157
     *
158
     * To enable parsing for JSON requests you can use the [[JsonParser]] class like in the following example:
159
     *
160
     * ```
161
     * [
162
     *     'application/json' => 'yii\web\JsonParser',
163
     * ]
164
     * ```
165
     *
166
     * To register a parser for parsing all request types you can use `'*'` as the array key.
167
     * This one will be used as a fallback in case no other types match.
168
     *
169
     * @see getBodyParams()
170
     */
171
    public $parsers = [];
172
    /**
173
     * @var array the configuration for trusted security related headers.
174
     *
175
     * An array key is an IPv4 or IPv6 IP address in CIDR notation for matching a client.
176
     *
177
     * An array value is a list of headers to trust. These will be matched against
178
     * [[secureHeaders]] to determine which headers are allowed to be sent by a specified host.
179
     * The case of the header names must be the same as specified in [[secureHeaders]].
180
     *
181
     * For example, to trust all headers listed in [[secureHeaders]] for IP addresses
182
     * in range `192.168.0.0-192.168.0.254` write the following:
183
     *
184
     * ```php
185
     * [
186
     *     '192.168.0.0/24',
187
     * ]
188
     * ```
189
     *
190
     * To trust just the `X-Forwarded-For` header from `10.0.0.1`, use:
191
     *
192
     * ```
193
     * [
194
     *     '10.0.0.1' => ['X-Forwarded-For']
195
     * ]
196
     * ```
197
     *
198
     * Default is to trust all headers except those listed in [[secureHeaders]] from all hosts.
199
     * Matches are tried in order and searching is stopped when IP matches.
200
     *
201
     * > Info: Matching is performed using [[IpValidator]].
202
     * See [[IpValidator::::setRanges()|IpValidator::setRanges()]]
203
     * and [[IpValidator::networks]] for advanced matching.
204
     *
205
     * @see secureHeaders
206
     * @since 2.0.13
207
     */
208
    public $trustedHosts = [];
209
    /**
210
     * @var array lists of headers that are, by default, subject to the trusted host configuration.
211
     * These headers will be filtered unless explicitly allowed in [[trustedHosts]].
212
     * If the list contains the `Forwarded` header, processing will be done according to RFC 7239.
213
     * The match of header names is case-insensitive.
214
     * @see https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
215
     * @see https://datatracker.ietf.org/doc/html/rfc7239
216
     * @see trustedHosts
217
     * @since 2.0.13
218
     */
219
    public $secureHeaders = [
220
        // Common:
221
        'X-Forwarded-For',
222
        'X-Forwarded-Host',
223
        'X-Forwarded-Proto',
224
225
        // Microsoft:
226
        'Front-End-Https',
227
        'X-Rewrite-Url',
228
229
        // ngrok:
230
        'X-Original-Host',
231
    ];
232
    /**
233
     * @var string[] List of headers where proxies store the real client IP.
234
     * It's not advisable to put insecure headers here.
235
     * To use the `Forwarded` header according to RFC 7239, the header must be added to [[secureHeaders]] list.
236
     * The match of header names is case-insensitive.
237
     * @see trustedHosts
238
     * @see secureHeaders
239
     * @since 2.0.13
240
     */
241
    public $ipHeaders = [
242
        'X-Forwarded-For', // Common
243
    ];
244
    /**
245
     * @var array list of headers to check for determining whether the connection is made via HTTPS.
246
     * The array keys are header names and the array value is a list of header values that indicate a secure connection.
247
     * The match of header names and values is case-insensitive.
248
     * It's not advisable to put insecure headers here.
249
     * @see trustedHosts
250
     * @see secureHeaders
251
     * @since 2.0.13
252
     */
253
    public $secureProtocolHeaders = [
254
        'X-Forwarded-Proto' => ['https'], // Common
255
        'Front-End-Https' => ['on'], // Microsoft
256
    ];
257
258
    /**
259
     * @var CookieCollection Collection of request cookies.
260
     */
261
    private $_cookies;
262
    /**
263
     * @var HeaderCollection Collection of request headers.
264
     */
265
    private $_headers;
266
267
268
    /**
269
     * Resolves the current request into a route and the associated parameters.
270
     * @return array the first element is the route, and the second is the associated parameters.
271
     * @throws NotFoundHttpException if the request cannot be resolved.
272
     */
273 1
    public function resolve()
274
    {
275 1
        $result = Yii::$app->getUrlManager()->parseRequest($this);
276 1
        if ($result !== false) {
277 1
            list($route, $params) = $result;
278 1
            if ($this->_queryParams === null) {
279 1
                $_GET = $params + $_GET; // preserve numeric keys
280
            } else {
281 1
                $this->_queryParams = $params + $this->_queryParams;
282
            }
283
284 1
            return [$route, $this->getQueryParams()];
285
        }
286
287
        throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
288
    }
289
290
    /**
291
     * Filters headers according to the [[trustedHosts]].
292
     * @param HeaderCollection $headerCollection
293
     * @since 2.0.13
294
     */
295 253
    protected function filterHeaders(HeaderCollection $headerCollection)
296
    {
297 253
        $trustedHeaders = $this->getTrustedHeaders();
298
299
        // remove all secure headers unless they are trusted
300 253
        foreach ($this->secureHeaders as $secureHeader) {
301 253
            if (!in_array($secureHeader, $trustedHeaders)) {
302 235
                $headerCollection->remove($secureHeader);
303
            }
304
        }
305 253
    }
306
307
    /**
308
     * Trusted headers according to the [[trustedHosts]].
309
     * @return array
310
     * @since 2.0.28
311
     */
312 253
    protected function getTrustedHeaders()
313
    {
314
        // do not trust any of the [[secureHeaders]] by default
315 253
        $trustedHeaders = [];
316
317
        // check if the client is a trusted host
318 253
        if (!empty($this->trustedHosts)) {
319 79
            $validator = $this->getIpValidator();
320 79
            $ip = $this->getRemoteIP();
321 79
            foreach ($this->trustedHosts as $cidr => $headers) {
322 79
                if (!is_array($headers)) {
323 77
                    $cidr = $headers;
324 77
                    $headers = $this->secureHeaders;
325
                }
326 79
                $validator->setRanges($cidr);
327 79
                if ($validator->validate($ip)) {
328 38
                    $trustedHeaders = $headers;
329 38
                    break;
330
                }
331
            }
332
        }
333 253
        return $trustedHeaders;
334
    }
335
336
    /**
337
     * Creates instance of [[IpValidator]].
338
     * You can override this method to adjust validator or implement different matching strategy.
339
     *
340
     * @return IpValidator
341
     * @since 2.0.13
342
     */
343 172
    protected function getIpValidator()
344
    {
345 172
        return new IpValidator();
346
    }
347
348
    /**
349
     * Returns the header collection.
350
     * The header collection contains incoming HTTP headers.
351
     * @return HeaderCollection the header collection
352
     */
353 253
    public function getHeaders()
354
    {
355 253
        if ($this->_headers === null) {
356 253
            $this->_headers = new HeaderCollection();
357 253
            if (function_exists('getallheaders')) {
358
                $headers = getallheaders();
359
                foreach ($headers as $name => $value) {
360
                    $this->_headers->add($name, $value);
361
                }
362 253
            } elseif (function_exists('http_get_request_headers')) {
363
                $headers = http_get_request_headers();
364
                foreach ($headers as $name => $value) {
365
                    $this->_headers->add($name, $value);
366
                }
367
            } else {
368
                // ['prefix' => length]
369 253
                $headerPrefixes = ['HTTP_' => 5, 'REDIRECT_HTTP_' => 14];
370
371 253
                foreach ($_SERVER as $name => $value) {
372 249
                    foreach ($headerPrefixes as $prefix => $length) {
373 249
                        if (strncmp($name, $prefix, $length) === 0) {
374 113
                            $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, $length)))));
375 113
                            $this->_headers->add($name, $value);
376 113
                            continue 2;
377
                        }
378
                    }
379
                }
380
            }
381 253
            $this->filterHeaders($this->_headers);
382
        }
383
384 253
        return $this->_headers;
385
    }
386
387
    /**
388
     * Returns the method of the current request (e.g. GET, POST, HEAD, PUT, PATCH, DELETE).
389
     * @return string request method, such as GET, POST, HEAD, PUT, PATCH, DELETE.
390
     * The value returned is turned into upper case.
391
     */
392 43
    public function getMethod()
393
    {
394
        if (
395 43
            isset($_POST[$this->methodParam])
396
            // Never allow to downgrade request from WRITE methods (POST, PATCH, DELETE, etc)
397
            // to read methods (GET, HEAD, OPTIONS) for security reasons.
398 43
            && !in_array(strtoupper($_POST[$this->methodParam]), ['GET', 'HEAD', 'OPTIONS'], true)
399
        ) {
400 6
            return strtoupper($_POST[$this->methodParam]);
401
        }
402
403 41
        if ($this->headers->has('X-Http-Method-Override')) {
404 1
            return strtoupper($this->headers->get('X-Http-Method-Override'));
0 ignored issues
show
Bug introduced by
It seems like $this->headers->get('X-Http-Method-Override') can also be of type array and null; however, parameter $string of strtoupper() 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

404
            return strtoupper(/** @scrutinizer ignore-type */ $this->headers->get('X-Http-Method-Override'));
Loading history...
405
        }
406
407 40
        if (isset($_SERVER['REQUEST_METHOD'])) {
408 10
            return strtoupper($_SERVER['REQUEST_METHOD']);
409
        }
410
411 31
        return 'GET';
412
    }
413
414
    /**
415
     * Returns whether this is a GET request.
416
     * @return bool whether this is a GET request.
417
     */
418 2
    public function getIsGet()
419
    {
420 2
        return $this->getMethod() === 'GET';
421
    }
422
423
    /**
424
     * Returns whether this is an OPTIONS request.
425
     * @return bool whether this is a OPTIONS request.
426
     */
427 3
    public function getIsOptions()
428
    {
429 3
        return $this->getMethod() === 'OPTIONS';
430
    }
431
432
    /**
433
     * Returns whether this is a HEAD request.
434
     * @return bool whether this is a HEAD request.
435
     */
436 15
    public function getIsHead()
437
    {
438 15
        return $this->getMethod() === 'HEAD';
439
    }
440
441
    /**
442
     * Returns whether this is a POST request.
443
     * @return bool whether this is a POST request.
444
     */
445
    public function getIsPost()
446
    {
447
        return $this->getMethod() === 'POST';
448
    }
449
450
    /**
451
     * Returns whether this is a DELETE request.
452
     * @return bool whether this is a DELETE request.
453
     */
454
    public function getIsDelete()
455
    {
456
        return $this->getMethod() === 'DELETE';
457
    }
458
459
    /**
460
     * Returns whether this is a PUT request.
461
     * @return bool whether this is a PUT request.
462
     */
463
    public function getIsPut()
464
    {
465
        return $this->getMethod() === 'PUT';
466
    }
467
468
    /**
469
     * Returns whether this is a PATCH request.
470
     * @return bool whether this is a PATCH request.
471
     */
472
    public function getIsPatch()
473
    {
474
        return $this->getMethod() === 'PATCH';
475
    }
476
477
    /**
478
     * Returns whether this is an AJAX (XMLHttpRequest) request.
479
     *
480
     * Note that in case of cross domain requests, browser doesn't set the X-Requested-With header by default:
481
     * https://stackoverflow.com/questions/8163703/cross-domain-ajax-doesnt-send-x-requested-with-header
482
     *
483
     * In case you are using `fetch()`, pass header manually:
484
     *
485
     * ```
486
     * fetch(url, {
487
     *    method: 'GET',
488
     *    headers: {'X-Requested-With': 'XMLHttpRequest'}
489
     * })
490
     * ```
491
     *
492
     * @return bool whether this is an AJAX (XMLHttpRequest) request.
493
     */
494 16
    public function getIsAjax()
495
    {
496 16
        return $this->headers->get('X-Requested-With') === 'XMLHttpRequest';
497
    }
498
499
    /**
500
     * Returns whether this is a PJAX request.
501
     * @return bool whether this is a PJAX request
502
     */
503 3
    public function getIsPjax()
504
    {
505 3
        return $this->getIsAjax() && $this->headers->has('X-Pjax');
506
    }
507
508
    /**
509
     * Returns whether this is an Adobe Flash or Flex request.
510
     * @return bool whether this is an Adobe Flash or Adobe Flex request.
511
     */
512
    public function getIsFlash()
513
    {
514
        $userAgent = $this->headers->get('User-Agent', '');
515
        return stripos($userAgent, 'Shockwave') !== false
0 ignored issues
show
Bug introduced by
It seems like $userAgent can also be of type array and null; however, parameter $haystack of stripos() 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

515
        return stripos(/** @scrutinizer ignore-type */ $userAgent, 'Shockwave') !== false
Loading history...
516
            || stripos($userAgent, 'Flash') !== false;
517
    }
518
519
    private $_rawBody;
520
521
    /**
522
     * Returns the raw HTTP request body.
523
     * @return string the request body
524
     */
525 7
    public function getRawBody()
526
    {
527 7
        if ($this->_rawBody === null) {
528 4
            $this->_rawBody = file_get_contents('php://input');
529
        }
530
531 7
        return $this->_rawBody;
532
    }
533
534
    /**
535
     * Sets the raw HTTP request body, this method is mainly used by test scripts to simulate raw HTTP requests.
536
     * @param string $rawBody the request body
537
     */
538 3
    public function setRawBody($rawBody)
539
    {
540 3
        $this->_rawBody = $rawBody;
541 3
    }
542
543
    private $_bodyParams;
544
545
    /**
546
     * Returns the request parameters given in the request body.
547
     *
548
     * Request parameters are determined using the parsers configured in [[parsers]] property.
549
     * If no parsers are configured for the current [[contentType]] it uses the PHP function `mb_parse_str()`
550
     * to parse the [[rawBody|request body]].
551
     * @return array|object the request parameters given in the request body.
552
     * @throws \yii\base\InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]].
553
     * @see getMethod()
554
     * @see getBodyParam()
555
     * @see setBodyParams()
556
     */
557 16
    public function getBodyParams()
558
    {
559 16
        if ($this->_bodyParams === null) {
560 8
            if (isset($_POST[$this->methodParam])) {
561 1
                $this->_bodyParams = $_POST;
562 1
                unset($this->_bodyParams[$this->methodParam]);
563 1
                return $this->_bodyParams;
564
            }
565
566 7
            $rawContentType = $this->getContentType();
567 7
            if (($pos = strpos((string)$rawContentType, ';')) !== false) {
568
                // e.g. text/html; charset=UTF-8
569
                $contentType = substr($rawContentType, 0, $pos);
570
            } else {
571 7
                $contentType = $rawContentType;
572
            }
573
574 7
            if (isset($this->parsers[$contentType])) {
575 2
                $parser = Yii::createObject($this->parsers[$contentType]);
576 2
                if (!($parser instanceof RequestParserInterface)) {
577
                    throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
578
                }
579 2
                $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
580 5
            } elseif (isset($this->parsers['*'])) {
581
                $parser = Yii::createObject($this->parsers['*']);
582
                if (!($parser instanceof RequestParserInterface)) {
583
                    throw new InvalidConfigException('The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface.');
584
                }
585
                $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
586 5
            } elseif ($this->getMethod() === 'POST') {
587
                // PHP has already parsed the body so we have all params in $_POST
588
                $this->_bodyParams = $_POST;
589
            } else {
590 5
                $this->_bodyParams = [];
591 5
                mb_parse_str($this->getRawBody(), $this->_bodyParams);
592
            }
593
        }
594
595 16
        return $this->_bodyParams;
596
    }
597
598
    /**
599
     * Sets the request body parameters.
600
     *
601
     * @param array|object $values the request body parameters (name-value pairs)
602
     * @see getBodyParams()
603
     */
604 11
    public function setBodyParams($values)
605
    {
606 11
        $this->_bodyParams = $values;
607 11
    }
608
609
    /**
610
     * Returns the named request body parameter value.
611
     *
612
     * If the parameter does not exist, the second parameter passed to this method will be returned.
613
     *
614
     * @param string $name the parameter name
615
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
616
     * @return mixed the parameter value
617
     * @see getBodyParams()
618
     * @see setBodyParams()
619
     */
620 7
    public function getBodyParam($name, $defaultValue = null)
621
    {
622 7
        $params = $this->getBodyParams();
623
624 7
        if (is_object($params)) {
625
            // unable to use `ArrayHelper::getValue()` due to different dots in key logic and lack of exception handling
626
            try {
627 1
                return isset($params->{$name}) ? $params->{$name} : $defaultValue;
628
            } catch (\Exception $e) {
0 ignored issues
show
Unused Code introduced by
catch (\Exception $e) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
629
                return $defaultValue;
630
            }
631
        }
632
633 7
        return isset($params[$name]) ? $params[$name] : $defaultValue;
634
    }
635
636
    /**
637
     * Returns POST parameter with a given name. If name isn't specified, returns an array of all POST parameters.
638
     *
639
     * @param string $name the parameter name
640
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
641
     * @return array|mixed
642
     */
643
    public function post($name = null, $defaultValue = null)
644
    {
645
        if ($name === null) {
646
            return $this->getBodyParams();
647
        }
648
649
        return $this->getBodyParam($name, $defaultValue);
650
    }
651
652
    private $_queryParams;
653
654
    /**
655
     * Returns the request parameters given in the [[queryString]].
656
     *
657
     * This method will return the contents of `$_GET` if params where not explicitly set.
658
     * @return array the request GET parameter values.
659
     * @see setQueryParams()
660
     */
661 65
    public function getQueryParams()
662
    {
663 65
        if ($this->_queryParams === null) {
664 58
            return $_GET;
665
        }
666
667 9
        return $this->_queryParams;
668
    }
669
670
    /**
671
     * Sets the request [[queryString]] parameters.
672
     * @param array $values the request query parameters (name-value pairs)
673
     * @see getQueryParam()
674
     * @see getQueryParams()
675
     */
676 9
    public function setQueryParams($values)
677
    {
678 9
        $this->_queryParams = $values;
679 9
    }
680
681
    /**
682
     * Returns GET parameter with a given name. If name isn't specified, returns an array of all GET parameters.
683
     *
684
     * @param string $name the parameter name
685
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
686
     * @return array|mixed
687
     */
688 29
    public function get($name = null, $defaultValue = null)
689
    {
690 29
        if ($name === null) {
691
            return $this->getQueryParams();
692
        }
693
694 29
        return $this->getQueryParam($name, $defaultValue);
695
    }
696
697
    /**
698
     * Returns the named GET parameter value.
699
     * If the GET parameter does not exist, the second parameter passed to this method will be returned.
700
     * @param string $name the GET parameter name.
701
     * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
702
     * @return mixed the GET parameter value
703
     * @see getBodyParam()
704
     */
705 32
    public function getQueryParam($name, $defaultValue = null)
706
    {
707 32
        $params = $this->getQueryParams();
708
709 32
        return isset($params[$name]) ? $params[$name] : $defaultValue;
710
    }
711
712
    private $_hostInfo;
713
    private $_hostName;
714
715
    /**
716
     * Returns the schema and host part of the current request URL.
717
     *
718
     * The returned URL does not have an ending slash.
719
     *
720
     * By default this value is based on the user request information. This method will
721
     * return the value of `$_SERVER['HTTP_HOST']` if it is available or `$_SERVER['SERVER_NAME']` if not.
722
     * You may want to check out the [PHP documentation](https://www.php.net/manual/en/reserved.variables.server.php)
723
     * for more information on these variables.
724
     *
725
     * You may explicitly specify it by setting the [[setHostInfo()|hostInfo]] property.
726
     *
727
     * > Warning: Dependent on the server configuration this information may not be
728
     * > reliable and [may be faked by the user sending the HTTP request](https://www.acunetix.com/vulnerabilities/web/host-header-attack).
729
     * > If the webserver is configured to serve the same site independent of the value of
730
     * > the `Host` header, this value is not reliable. In such situations you should either
731
     * > fix your webserver configuration or explicitly set the value by setting the [[setHostInfo()|hostInfo]] property.
732
     * > If you don't have access to the server configuration, you can setup [[\yii\filters\HostControl]] filter at
733
     * > application level in order to protect against such kind of attack.
734
     *
735
     * @property string|null schema and hostname part (with port number if needed) of the request URL
736
     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
737
     * See [[getHostInfo()]] for security related notes on this property.
738
     * @return string|null schema and hostname part (with port number if needed) of the request URL
739
     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
740
     * @see setHostInfo()
741
     */
742 55
    public function getHostInfo()
743
    {
744 55
        if ($this->_hostInfo === null) {
745 51
            $secure = $this->getIsSecureConnection();
746 51
            $http = $secure ? 'https' : 'http';
747
748 51
            if ($this->getSecureForwardedHeaderTrustedPart('host') !== null) {
749 8
                $this->_hostInfo = $http . '://' . $this->getSecureForwardedHeaderTrustedPart('host');
750 43
            } elseif ($this->headers->has('X-Forwarded-Host')) {
751 3
                $this->_hostInfo = $http . '://' . trim(explode(',', $this->headers->get('X-Forwarded-Host'))[0]);
0 ignored issues
show
Bug introduced by
It seems like $this->headers->get('X-Forwarded-Host') can also be of type array and null; however, parameter $string of explode() 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

751
                $this->_hostInfo = $http . '://' . trim(explode(',', /** @scrutinizer ignore-type */ $this->headers->get('X-Forwarded-Host'))[0]);
Loading history...
752 40
            } elseif ($this->headers->has('X-Original-Host')) {
753
                $this->_hostInfo = $http . '://' . trim(explode(',', $this->headers->get('X-Original-Host'))[0]);
754
            } else {
755 40
                if ($this->headers->has('Host')) {
756 15
                    $this->_hostInfo = $http . '://' . $this->headers->get('Host');
0 ignored issues
show
Bug introduced by
Are you sure $this->headers->get('Host') of type array|null|string can be used in concatenation? ( Ignorable by Annotation )

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

756
                    $this->_hostInfo = $http . '://' . /** @scrutinizer ignore-type */ $this->headers->get('Host');
Loading history...
757 25
                } elseif (filter_has_var(INPUT_SERVER, 'SERVER_NAME')) {
758
                    $this->_hostInfo = $http . '://' . filter_input(INPUT_SERVER, 'SERVER_NAME');
759 25
                } elseif (isset($_SERVER['SERVER_NAME'])) {
760 3
                    $this->_hostInfo = $http . '://' . $_SERVER['SERVER_NAME'];
761
                }
762
763 40
                if ($this->_hostInfo !== null && !preg_match('/:\d+$/', $this->_hostInfo)) {
764 16
                    $port = $secure ? $this->getSecurePort() : $this->getPort();
765 16
                    if (($port !== 80 && !$secure) || ($port !== 443 && $secure)) {
766 2
                        $this->_hostInfo .= ':' . $port;
767
                    }
768
                }
769
            }
770
        }
771
772 55
        return $this->_hostInfo;
773
    }
774
775
    /**
776
     * Sets the schema and host part of the application URL.
777
     * This setter is provided in case the schema and hostname cannot be determined
778
     * on certain Web servers.
779
     * @param string|null $value the schema and host part of the application URL. The trailing slashes will be removed.
780
     * @see getHostInfo() for security related notes on this property.
781
     */
782 59
    public function setHostInfo($value)
783
    {
784 59
        $this->_hostName = null;
785 59
        $this->_hostInfo = $value === null ? null : rtrim($value, '/');
786 59
    }
787
788
    /**
789
     * Returns the host part of the current request URL.
790
     * Value is calculated from current [[getHostInfo()|hostInfo]] property.
791
     *
792
     * > Warning: The content of this value may not be reliable, dependent on the server
793
     * > configuration. Please refer to [[getHostInfo()]] for more information.
794
     *
795
     * @return string|null hostname part of the request URL (e.g. `www.yiiframework.com`)
796
     * @see getHostInfo()
797
     * @since 2.0.10
798
     */
799 26
    public function getHostName()
800
    {
801 26
        if ($this->_hostName === null) {
802 26
            $this->_hostName = parse_url((string)$this->getHostInfo(), PHP_URL_HOST);
803
        }
804
805 26
        return $this->_hostName;
806
    }
807
808
    private $_baseUrl;
809
810
    /**
811
     * Returns the relative URL for the application.
812
     * This is similar to [[scriptUrl]] except that it does not include the script file name,
813
     * and the ending slashes are removed.
814
     * @return string the relative URL for the application
815
     * @see setScriptUrl()
816
     */
817 449
    public function getBaseUrl()
818
    {
819 449
        if ($this->_baseUrl === null) {
820 447
            $this->_baseUrl = rtrim(dirname($this->getScriptUrl()), '\\/');
821
        }
822
823 449
        return $this->_baseUrl;
824
    }
825
826
    /**
827
     * Sets the relative URL for the application.
828
     * By default the URL is determined based on the entry script URL.
829
     * This setter is provided in case you want to change this behavior.
830
     * @param string $value the relative URL for the application
831
     */
832 2
    public function setBaseUrl($value)
833
    {
834 2
        $this->_baseUrl = $value;
835 2
    }
836
837
    private $_scriptUrl;
838
839
    /**
840
     * Returns the relative URL of the entry script.
841
     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
842
     * @return string the relative URL of the entry script.
843
     * @throws InvalidConfigException if unable to determine the entry script URL
844
     */
845 450
    public function getScriptUrl()
846
    {
847 450
        if ($this->_scriptUrl === null) {
848 1
            $scriptFile = $this->getScriptFile();
849
            $scriptName = basename($scriptFile);
850
            if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $scriptName) {
851
                $this->_scriptUrl = $_SERVER['SCRIPT_NAME'];
852
            } elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $scriptName) {
853
                $this->_scriptUrl = $_SERVER['PHP_SELF'];
854
            } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $scriptName) {
855
                $this->_scriptUrl = $_SERVER['ORIG_SCRIPT_NAME'];
856
            } elseif (isset($_SERVER['PHP_SELF']) && ($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) {
857
                $this->_scriptUrl = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName;
858
            } elseif (!empty($_SERVER['DOCUMENT_ROOT']) && strpos($scriptFile, $_SERVER['DOCUMENT_ROOT']) === 0) {
859
                $this->_scriptUrl = str_replace([$_SERVER['DOCUMENT_ROOT'], '\\'], ['', '/'], $scriptFile);
860
            } else {
861
                throw new InvalidConfigException('Unable to determine the entry script URL.');
862
            }
863
        }
864
865 449
        return $this->_scriptUrl;
866
    }
867
868
    /**
869
     * Sets the relative URL for the application entry script.
870
     * This setter is provided in case the entry script URL cannot be determined
871
     * on certain Web servers.
872
     * @param string $value the relative URL for the application entry script.
873
     */
874 460
    public function setScriptUrl($value)
875
    {
876 460
        $this->_scriptUrl = $value === null ? null : '/' . trim($value, '/');
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
877 460
    }
878
879
    private $_scriptFile;
880
881
    /**
882
     * Returns the entry script file path.
883
     * The default implementation will simply return `$_SERVER['SCRIPT_FILENAME']`.
884
     * @return string the entry script file path
885
     * @throws InvalidConfigException
886
     */
887 451
    public function getScriptFile()
888
    {
889 451
        if (isset($this->_scriptFile)) {
890 422
            return $this->_scriptFile;
891
        }
892
893 29
        if (isset($_SERVER['SCRIPT_FILENAME'])) {
894 27
            return $_SERVER['SCRIPT_FILENAME'];
895
        }
896
897 2
        throw new InvalidConfigException('Unable to determine the entry script file path.');
898
    }
899
900
    /**
901
     * Sets the entry script file path.
902
     * The entry script file path normally can be obtained from `$_SERVER['SCRIPT_FILENAME']`.
903
     * If your server configuration does not return the correct value, you may configure
904
     * this property to make it right.
905
     * @param string $value the entry script file path.
906
     */
907 422
    public function setScriptFile($value)
908
    {
909 422
        $this->_scriptFile = $value;
910 422
    }
911
912
    private $_pathInfo;
913
914
    /**
915
     * Returns the path info of the currently requested URL.
916
     * A path info refers to the part that is after the entry script and before the question mark (query string).
917
     * The starting and ending slashes are both removed.
918
     * @return string part of the request URL that is after the entry script and before the question mark.
919
     * Note, the returned path info is already URL-decoded.
920
     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
921
     */
922 19
    public function getPathInfo()
923
    {
924 19
        if ($this->_pathInfo === null) {
925
            $this->_pathInfo = $this->resolvePathInfo();
926
        }
927
928 19
        return $this->_pathInfo;
929
    }
930
931
    /**
932
     * Sets the path info of the current request.
933
     * This method is mainly provided for testing purpose.
934
     * @param string $value the path info of the current request
935
     */
936 20
    public function setPathInfo($value)
937
    {
938 20
        $this->_pathInfo = $value === null ? null : ltrim($value, '/');
0 ignored issues
show
introduced by
The condition $value === null is always false.
Loading history...
939 20
    }
940
941
    /**
942
     * Resolves the path info part of the currently requested URL.
943
     * A path info refers to the part that is after the entry script and before the question mark (query string).
944
     * The starting slashes are both removed (ending slashes will be kept).
945
     * @return string part of the request URL that is after the entry script and before the question mark.
946
     * Note, the returned path info is decoded.
947
     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
948
     */
949
    protected function resolvePathInfo()
950
    {
951
        $pathInfo = $this->getUrl();
952
953
        if (($pos = strpos($pathInfo, '?')) !== false) {
954
            $pathInfo = substr($pathInfo, 0, $pos);
955
        }
956
957
        $pathInfo = urldecode($pathInfo);
958
959
        // try to encode in UTF8 if not so
960
        // http://w3.org/International/questions/qa-forms-utf-8.html
961
        if (!preg_match('%^(?:
962
            [\x09\x0A\x0D\x20-\x7E]              # ASCII
963
            | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
964
            | \xE0[\xA0-\xBF][\x80-\xBF]         # excluding overlongs
965
            | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
966
            | \xED[\x80-\x9F][\x80-\xBF]         # excluding surrogates
967
            | \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
968
            | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
969
            | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
970
            )*$%xs', $pathInfo)
971
        ) {
972
            $pathInfo = $this->utf8Encode($pathInfo);
973
        }
974
975
        $scriptUrl = $this->getScriptUrl();
976
        $baseUrl = $this->getBaseUrl();
977
        if (strpos($pathInfo, $scriptUrl) === 0) {
978
            $pathInfo = substr($pathInfo, strlen($scriptUrl));
979
        } elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) {
980
            $pathInfo = substr($pathInfo, strlen($baseUrl));
981
        } elseif (isset($_SERVER['PHP_SELF']) && strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) {
982
            $pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl));
983
        } else {
984
            throw new InvalidConfigException('Unable to determine the path info of the current request.');
985
        }
986
987
        if (strncmp($pathInfo, '/', 1) === 0) {
988
            $pathInfo = substr($pathInfo, 1);
989
        }
990
991
        return (string) $pathInfo;
992
    }
993
994
    /**
995
     * Encodes an ISO-8859-1 string to UTF-8
996
     * @param string $s
997
     * @return string the UTF-8 translation of `s`.
998
     * @see https://github.com/symfony/polyfill-php72/blob/master/Php72.php#L24
999
     */
1000
    private function utf8Encode($s)
1001
    {
1002
        $s .= $s;
1003
        $len = \strlen($s);
1004
        for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) {
1005
            switch (true) {
1006
                case $s[$i] < "\x80": $s[$j] = $s[$i]; break;
1007
                case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break;
1008
                default: $s[$j] = "\xC3"; $s[++$j] = \chr(\ord($s[$i]) - 64); break;
1009
            }
1010
        }
1011
        return substr($s, 0, $j);
1012
    }
1013
1014
    /**
1015
     * Returns the currently requested absolute URL.
1016
     * This is a shortcut to the concatenation of [[hostInfo]] and [[url]].
1017
     * @return string the currently requested absolute URL.
1018
     */
1019 1
    public function getAbsoluteUrl()
1020
    {
1021 1
        return $this->getHostInfo() . $this->getUrl();
1022
    }
1023
1024
    private $_url;
1025
1026
    /**
1027
     * Returns the currently requested relative URL.
1028
     * This refers to the portion of the URL that is after the [[hostInfo]] part.
1029
     * It includes the [[queryString]] part if any.
1030
     * @return string the currently requested relative URL. Note that the URI returned may be URL-encoded depending on the client.
1031
     * @throws InvalidConfigException if the URL cannot be determined due to unusual server configuration
1032
     */
1033 11
    public function getUrl()
1034
    {
1035 11
        if ($this->_url === null) {
1036 3
            $this->_url = $this->resolveRequestUri();
1037
        }
1038
1039 11
        return $this->_url;
1040
    }
1041
1042
    /**
1043
     * Sets the currently requested relative URL.
1044
     * The URI must refer to the portion that is after [[hostInfo]].
1045
     * Note that the URI should be URL-encoded.
1046
     * @param string $value the request URI to be set
1047
     */
1048 25
    public function setUrl($value)
1049
    {
1050 25
        $this->_url = $value;
1051 25
    }
1052
1053
    /**
1054
     * Resolves the request URI portion for the currently requested URL.
1055
     * This refers to the portion that is after the [[hostInfo]] part. It includes the [[queryString]] part if any.
1056
     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
1057
     * @return string|bool the request URI portion for the currently requested URL.
1058
     * Note that the URI returned may be URL-encoded depending on the client.
1059
     * @throws InvalidConfigException if the request URI cannot be determined due to unusual server configuration
1060
     */
1061 3
    protected function resolveRequestUri()
1062
    {
1063 3
        if ($this->headers->has('X-Rewrite-Url')) { // IIS
1064
            $requestUri = $this->headers->get('X-Rewrite-Url');
1065 3
        } elseif (isset($_SERVER['REQUEST_URI'])) {
1066 3
            $requestUri = $_SERVER['REQUEST_URI'];
1067 3
            if ($requestUri !== '' && $requestUri[0] !== '/') {
1068 3
                $requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri);
1069
            }
1070
        } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0 CGI
1071
            $requestUri = $_SERVER['ORIG_PATH_INFO'];
1072
            if (!empty($_SERVER['QUERY_STRING'])) {
1073
                $requestUri .= '?' . $_SERVER['QUERY_STRING'];
1074
            }
1075
        } else {
1076
            throw new InvalidConfigException('Unable to determine the request URI.');
1077
        }
1078
1079 3
        return $requestUri;
1080
    }
1081
1082
    /**
1083
     * Returns part of the request URL that is after the question mark.
1084
     * @return string part of the request URL that is after the question mark
1085
     */
1086
    public function getQueryString()
1087
    {
1088
        return isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
1089
    }
1090
1091
    /**
1092
     * Return if the request is sent via secure channel (https).
1093
     * @return bool if the request is sent via secure channel (https)
1094
     */
1095 79
    public function getIsSecureConnection()
1096
    {
1097 79
        if (isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'], 'on') === 0 || $_SERVER['HTTPS'] == 1)) {
1098 2
            return true;
1099
        }
1100
1101 77
        if (($proto = $this->getSecureForwardedHeaderTrustedPart('proto')) !== null) {
1102 9
            return strcasecmp($proto, 'https') === 0;
1103
        }
1104
1105 68
        foreach ($this->secureProtocolHeaders as $header => $values) {
1106 68
            if (($headerValue = $this->headers->get($header, null)) !== null) {
1107 5
                foreach ($values as $value) {
1108 5
                    if (strcasecmp($headerValue, $value) === 0) {
0 ignored issues
show
Bug introduced by
It seems like $headerValue can also be of type array; however, parameter $string1 of strcasecmp() 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

1108
                    if (strcasecmp(/** @scrutinizer ignore-type */ $headerValue, $value) === 0) {
Loading history...
1109 3
                        return true;
1110
                    }
1111
                }
1112
            }
1113
        }
1114
1115 65
        return false;
1116
    }
1117
1118
    /**
1119
     * Returns the server name.
1120
     * @return string|null server name, null if not available
1121
     */
1122 1
    public function getServerName()
1123
    {
1124 1
        return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null;
1125
    }
1126
1127
    /**
1128
     * Returns the server port number.
1129
     * @return int|null server port number, null if not available
1130
     */
1131 17
    public function getServerPort()
1132
    {
1133 17
        return isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : null;
1134
    }
1135
1136
    /**
1137
     * Returns the URL referrer.
1138
     * @return string|null URL referrer, null if not available
1139
     */
1140
    public function getReferrer()
1141
    {
1142
        return $this->headers->get('Referer');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->headers->get('Referer') also could return the type array which is incompatible with the documented return type null|string.
Loading history...
1143
    }
1144
1145
    /**
1146
     * Returns the URL origin of a CORS request.
1147
     *
1148
     * The return value is taken from the `Origin` [[getHeaders()|header]] sent by the browser.
1149
     *
1150
     * Note that the origin request header indicates where a fetch originates from.
1151
     * It doesn't include any path information, but only the server name.
1152
     * It is sent with a CORS requests, as well as with POST requests.
1153
     * It is similar to the referer header, but, unlike this header, it doesn't disclose the whole path.
1154
     * Please refer to <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin> for more information.
1155
     *
1156
     * @return string|null URL origin of a CORS request, `null` if not available.
1157
     * @see getHeaders()
1158
     * @since 2.0.13
1159
     */
1160 1
    public function getOrigin()
1161
    {
1162 1
        return $this->getHeaders()->get('origin');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getHeaders()->get('origin') also could return the type array which is incompatible with the documented return type null|string.
Loading history...
1163
    }
1164
1165
    /**
1166
     * Returns the user agent.
1167
     * @return string|null user agent, null if not available
1168
     */
1169 1
    public function getUserAgent()
1170
    {
1171 1
        return $this->headers->get('User-Agent');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->headers->get('User-Agent') also could return the type array which is incompatible with the documented return type null|string.
Loading history...
1172
    }
1173
1174
    /**
1175
     * Returns the user IP address from [[ipHeaders]].
1176
     * @return string|null user IP address, null if not available
1177
     * @see ipHeaders
1178
     * @since 2.0.28
1179
     */
1180 105
    protected function getUserIpFromIpHeaders()
1181
    {
1182 105
        $ip = $this->getSecureForwardedHeaderTrustedPart('for');
1183 105
        if ($ip !== null && preg_match(
1184 105
            '/^\[?(?P<ip>(?:(?:(?:[0-9a-f]{1,4}:){1,6}(?:[0-9a-f]{1,4})?(?:(?::[0-9a-f]{1,4}){1,6}))|(?:[\d]{1,3}\.){3}[\d]{1,3}))\]?(?::(?P<port>[\d]+))?$/',
1185
            $ip,
1186
            $matches
1187
        )) {
1188 14
            $ip = $this->getUserIpFromIpHeader($matches['ip']);
1189 14
            if ($ip !== null) {
1190 14
                return $ip;
1191
            }
1192
        }
1193
1194
1195 91
        foreach ($this->ipHeaders as $ipHeader) {
1196 88
            if ($this->headers->has($ipHeader)) {
1197 10
                $ip = $this->getUserIpFromIpHeader($this->headers->get($ipHeader));
0 ignored issues
show
Bug introduced by
It seems like $this->headers->get($ipHeader) can also be of type array; however, parameter $ips of yii\web\Request::getUserIpFromIpHeader() 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

1197
                $ip = $this->getUserIpFromIpHeader(/** @scrutinizer ignore-type */ $this->headers->get($ipHeader));
Loading history...
1198 10
                if ($ip !== null) {
1199 10
                    return $ip;
1200
                }
1201
            }
1202
        }
1203 81
        return null;
1204
    }
1205
1206
    /**
1207
     * Returns the user IP address.
1208
     * The IP is determined using headers and / or `$_SERVER` variables.
1209
     * @return string|null user IP address, null if not available
1210
     */
1211 105
    public function getUserIP()
1212
    {
1213 105
        $ip = $this->getUserIpFromIpHeaders();
1214 105
        return $ip === null ? $this->getRemoteIP() : $ip;
1215
    }
1216
1217
    /**
1218
     * Return user IP's from IP header.
1219
     *
1220
     * @param string $ips comma separated IP list
1221
     * @return string|null IP as string. Null is returned if IP can not be determined from header.
1222
     * @see getUserHost()
1223
     * @see ipHeaders
1224
     * @see getTrustedHeaders()
1225
     * @since 2.0.28
1226
     */
1227 24
    protected function getUserIpFromIpHeader($ips)
1228
    {
1229 24
        $ips = trim($ips);
1230 24
        if ($ips === '') {
1231
            return null;
1232
        }
1233 24
        $ips = preg_split('/\s*,\s*/', $ips, -1, PREG_SPLIT_NO_EMPTY);
1234 24
        krsort($ips);
1235 24
        $validator = $this->getIpValidator();
1236 24
        $resultIp = null;
1237 24
        foreach ($ips as $ip) {
1238 24
            $validator->setRanges('any');
1239 24
            if (!$validator->validate($ip) /* checking IP format */) {
1240 1
                break;
1241
            }
1242 24
            $resultIp = $ip;
1243 24
            $isTrusted = false;
1244 24
            foreach ($this->trustedHosts as $trustedCidr => $trustedCidrOrHeaders) {
1245 24
                if (!is_array($trustedCidrOrHeaders)) {
1246 24
                    $trustedCidr = $trustedCidrOrHeaders;
1247
                }
1248 24
                $validator->setRanges($trustedCidr);
1249 24
                if ($validator->validate($ip) /* checking trusted range */) {
1250 7
                    $isTrusted = true;
1251 7
                    break;
1252
                }
1253
            }
1254 24
            if (!$isTrusted) {
1255 20
                break;
1256
            }
1257
        }
1258 24
        return $resultIp;
1259
    }
1260
1261
    /**
1262
     * Returns the user host name.
1263
     * The HOST is determined using headers and / or `$_SERVER` variables.
1264
     * @return string|null user host name, null if not available
1265
     */
1266
    public function getUserHost()
1267
    {
1268
        $userIp = $this->getUserIpFromIpHeaders();
1269
        if($userIp === null) {
1270
            return $this->getRemoteHost();
1271
        }
1272
        return gethostbyaddr($userIp);
1273
    }
1274
1275
    /**
1276
     * Returns the IP on the other end of this connection.
1277
     * This is always the next hop, any headers are ignored.
1278
     * @return string|null remote IP address, `null` if not available.
1279
     * @since 2.0.13
1280
     */
1281 144
    public function getRemoteIP()
1282
    {
1283 144
        return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
1284
    }
1285
1286
    /**
1287
     * Returns the host name of the other end of this connection.
1288
     * This is always the next hop, any headers are ignored.
1289
     * @return string|null remote host name, `null` if not available
1290
     * @see getUserHost()
1291
     * @see getRemoteIP()
1292
     * @since 2.0.13
1293
     */
1294
    public function getRemoteHost()
1295
    {
1296
        return isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : null;
1297
    }
1298
1299
    /**
1300
     * @return string|null the username sent via HTTP authentication, `null` if the username is not given
1301
     * @see getAuthCredentials() to get both username and password in one call
1302
     */
1303 9
    public function getAuthUser()
1304
    {
1305 9
        return $this->getAuthCredentials()[0];
1306
    }
1307
1308
    /**
1309
     * @return string|null the password sent via HTTP authentication, `null` if the password is not given
1310
     * @see getAuthCredentials() to get both username and password in one call
1311
     */
1312 9
    public function getAuthPassword()
1313
    {
1314 9
        return $this->getAuthCredentials()[1];
1315
    }
1316
1317
    /**
1318
     * @return array that contains exactly two elements:
1319
     * - 0: the username sent via HTTP authentication, `null` if the username is not given
1320
     * - 1: the password sent via HTTP authentication, `null` if the password is not given
1321
     * @see getAuthUser() to get only username
1322
     * @see getAuthPassword() to get only password
1323
     * @since 2.0.13
1324
     */
1325 39
    public function getAuthCredentials()
1326
    {
1327 39
        $username = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
1328 39
        $password = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
1329 39
        if ($username !== null || $password !== null) {
1330 21
            return [$username, $password];
1331
        }
1332
1333
        /**
1334
         * Apache with php-cgi does not pass HTTP Basic authentication to PHP by default.
1335
         * To make it work, add one of the following lines to to your .htaccess file:
1336
         *
1337
         * SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0
1338
         * --OR--
1339
         * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
1340
         */
1341 18
        $auth_token = $this->getHeaders()->get('Authorization');
1342
1343 18
        if ($auth_token !== null && strncasecmp($auth_token, 'basic', 5) === 0) {
0 ignored issues
show
Bug introduced by
It seems like $auth_token can also be of type array; however, parameter $string1 of strncasecmp() 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

1343
        if ($auth_token !== null && strncasecmp(/** @scrutinizer ignore-type */ $auth_token, 'basic', 5) === 0) {
Loading history...
1344
            $parts = array_map(function ($value) {
1345 18
                return strlen($value) === 0 ? null : $value;
1346 18
            }, explode(':', base64_decode(mb_substr($auth_token, 6)), 2));
0 ignored issues
show
Bug introduced by
It seems like $auth_token can also be of type array; however, parameter $string of mb_substr() 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

1346
            }, explode(':', base64_decode(mb_substr(/** @scrutinizer ignore-type */ $auth_token, 6)), 2));
Loading history...
1347
1348 18
            if (count($parts) < 2) {
1349 2
                return [$parts[0], null];
1350
            }
1351
1352 16
            return $parts;
1353
        }
1354
1355
        return [null, null];
1356
    }
1357
1358
    private $_port;
1359
1360
    /**
1361
     * Returns the port to use for insecure requests.
1362
     * Defaults to 80, or the port specified by the server if the current
1363
     * request is insecure.
1364
     * @return int port number for insecure requests.
1365
     * @see setPort()
1366
     */
1367 15
    public function getPort()
1368
    {
1369 15
        if ($this->_port === null) {
1370 15
            $serverPort = $this->getServerPort();
1371 15
            $this->_port = !$this->getIsSecureConnection() && $serverPort !== null ? $serverPort : 80;
1372
        }
1373
1374 15
        return $this->_port;
1375
    }
1376
1377
    /**
1378
     * Sets the port to use for insecure requests.
1379
     * This setter is provided in case a custom port is necessary for certain
1380
     * server configurations.
1381
     * @param int $value port number.
1382
     */
1383
    public function setPort($value)
1384
    {
1385
        if ($value != $this->_port) {
1386
            $this->_port = (int) $value;
1387
            $this->_hostInfo = null;
1388
        }
1389
    }
1390
1391
    private $_securePort;
1392
1393
    /**
1394
     * Returns the port to use for secure requests.
1395
     * Defaults to 443, or the port specified by the server if the current
1396
     * request is secure.
1397
     * @return int port number for secure requests.
1398
     * @see setSecurePort()
1399
     */
1400 1
    public function getSecurePort()
1401
    {
1402 1
        if ($this->_securePort === null) {
1403 1
            $serverPort = $this->getServerPort();
1404 1
            $this->_securePort = $this->getIsSecureConnection() && $serverPort !== null ? $serverPort : 443;
1405
        }
1406
1407 1
        return $this->_securePort;
1408
    }
1409
1410
    /**
1411
     * Sets the port to use for secure requests.
1412
     * This setter is provided in case a custom port is necessary for certain
1413
     * server configurations.
1414
     * @param int $value port number.
1415
     */
1416
    public function setSecurePort($value)
1417
    {
1418
        if ($value != $this->_securePort) {
1419
            $this->_securePort = (int) $value;
1420
            $this->_hostInfo = null;
1421
        }
1422
    }
1423
1424
    private $_contentTypes;
1425
1426
    /**
1427
     * Returns the content types acceptable by the end user.
1428
     *
1429
     * This is determined by the `Accept` HTTP header. For example,
1430
     *
1431
     * ```php
1432
     * $_SERVER['HTTP_ACCEPT'] = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1433
     * $types = $request->getAcceptableContentTypes();
1434
     * print_r($types);
1435
     * // displays:
1436
     * // [
1437
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1438
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1439
     * //           'text/plain' => ['q' => 0.5],
1440
     * // ]
1441
     * ```
1442
     *
1443
     * @return array the content types ordered by the quality score. Types with the highest scores
1444
     * will be returned first. The array keys are the content types, while the array values
1445
     * are the corresponding quality score and other parameters as given in the header.
1446
     */
1447 5
    public function getAcceptableContentTypes()
1448
    {
1449 5
        if ($this->_contentTypes === null) {
1450 4
            if ($this->headers->get('Accept') !== null) {
1451 2
                $this->_contentTypes = $this->parseAcceptHeader($this->headers->get('Accept'));
0 ignored issues
show
Bug introduced by
It seems like $this->headers->get('Accept') can also be of type array; however, parameter $header of yii\web\Request::parseAcceptHeader() 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

1451
                $this->_contentTypes = $this->parseAcceptHeader(/** @scrutinizer ignore-type */ $this->headers->get('Accept'));
Loading history...
1452
            } else {
1453 3
                $this->_contentTypes = [];
1454
            }
1455
        }
1456
1457 5
        return $this->_contentTypes;
1458
    }
1459
1460
    /**
1461
     * Sets the acceptable content types.
1462
     * Please refer to [[getAcceptableContentTypes()]] on the format of the parameter.
1463
     * @param array $value the content types that are acceptable by the end user. They should
1464
     * be ordered by the preference level.
1465
     * @see getAcceptableContentTypes()
1466
     * @see parseAcceptHeader()
1467
     */
1468 1
    public function setAcceptableContentTypes($value)
1469
    {
1470 1
        $this->_contentTypes = $value;
1471 1
    }
1472
1473
    /**
1474
     * Returns request content-type
1475
     * The Content-Type header field indicates the MIME type of the data
1476
     * contained in [[getRawBody()]] or, in the case of the HEAD method, the
1477
     * media type that would have been sent had the request been a GET.
1478
     * For the MIME-types the user expects in response, see [[acceptableContentTypes]].
1479
     * @return string request content-type. Empty string is returned if this information is not available.
1480
     * @link https://tools.ietf.org/html/rfc2616#section-14.17
1481
     * HTTP 1.1 header field definitions
1482
     */
1483 7
    public function getContentType()
1484
    {
1485 7
        if (isset($_SERVER['CONTENT_TYPE'])) {
1486 3
            return $_SERVER['CONTENT_TYPE'];
1487
        }
1488
1489
        //fix bug https://bugs.php.net/bug.php?id=66606
1490 4
        return $this->headers->get('Content-Type') ?: '';
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->headers->get('Content-Type') ?: '' also could return the type array which is incompatible with the documented return type string.
Loading history...
1491
    }
1492
1493
    private $_languages;
1494
1495
    /**
1496
     * Returns the languages acceptable by the end user.
1497
     * This is determined by the `Accept-Language` HTTP header.
1498
     * @return array the languages ordered by the preference level. The first element
1499
     * represents the most preferred language.
1500
     */
1501 2
    public function getAcceptableLanguages()
1502
    {
1503 2
        if ($this->_languages === null) {
1504 1
            if ($this->headers->has('Accept-Language')) {
1505
                $this->_languages = array_keys($this->parseAcceptHeader($this->headers->get('Accept-Language')));
0 ignored issues
show
Bug introduced by
It seems like $this->headers->get('Accept-Language') can also be of type array; however, parameter $header of yii\web\Request::parseAcceptHeader() 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

1505
                $this->_languages = array_keys($this->parseAcceptHeader(/** @scrutinizer ignore-type */ $this->headers->get('Accept-Language')));
Loading history...
1506
            } else {
1507 1
                $this->_languages = [];
1508
            }
1509
        }
1510
1511 2
        return $this->_languages;
1512
    }
1513
1514
    /**
1515
     * @param array $value the languages that are acceptable by the end user. They should
1516
     * be ordered by the preference level.
1517
     */
1518 1
    public function setAcceptableLanguages($value)
1519
    {
1520 1
        $this->_languages = $value;
1521 1
    }
1522
1523
    /**
1524
     * Parses the given `Accept` (or `Accept-Language`) header.
1525
     *
1526
     * This method will return the acceptable values with their quality scores and the corresponding parameters
1527
     * as specified in the given `Accept` header. The array keys of the return value are the acceptable values,
1528
     * while the array values consisting of the corresponding quality scores and parameters. The acceptable
1529
     * values with the highest quality scores will be returned first. For example,
1530
     *
1531
     * ```php
1532
     * $header = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1533
     * $accepts = $request->parseAcceptHeader($header);
1534
     * print_r($accepts);
1535
     * // displays:
1536
     * // [
1537
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1538
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1539
     * //           'text/plain' => ['q' => 0.5],
1540
     * // ]
1541
     * ```
1542
     *
1543
     * @param string $header the header to be parsed
1544
     * @return array the acceptable values ordered by their quality score. The values with the highest scores
1545
     * will be returned first.
1546
     */
1547 3
    public function parseAcceptHeader($header)
1548
    {
1549 3
        $accepts = [];
1550 3
        foreach (explode(',', $header) as $i => $part) {
1551 3
            $params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY);
1552 3
            if (empty($params)) {
1553 1
                continue;
1554
            }
1555
            $values = [
1556 3
                'q' => [$i, array_shift($params), 1],
1557
            ];
1558 3
            foreach ($params as $param) {
1559 2
                if (strpos($param, '=') !== false) {
1560 2
                    list($key, $value) = explode('=', $param, 2);
1561 2
                    if ($key === 'q') {
1562 2
                        $values['q'][2] = (float) $value;
1563
                    } else {
1564 2
                        $values[$key] = $value;
1565
                    }
1566
                } else {
1567 1
                    $values[] = $param;
1568
                }
1569
            }
1570 3
            $accepts[] = $values;
1571
        }
1572
1573
        usort($accepts, function ($a, $b) {
1574 3
            $a = $a['q']; // index, name, q
1575 3
            $b = $b['q'];
1576 3
            if ($a[2] > $b[2]) {
1577 2
                return -1;
1578
            }
1579
1580 2
            if ($a[2] < $b[2]) {
1581 1
                return 1;
1582
            }
1583
1584 2
            if ($a[1] === $b[1]) {
1585
                return $a[0] > $b[0] ? 1 : -1;
1586
            }
1587
1588 2
            if ($a[1] === '*/*') {
1589
                return 1;
1590
            }
1591
1592 2
            if ($b[1] === '*/*') {
1593
                return -1;
1594
            }
1595
1596 2
            $wa = $a[1][strlen($a[1]) - 1] === '*';
1597 2
            $wb = $b[1][strlen($b[1]) - 1] === '*';
1598 2
            if ($wa xor $wb) {
1599
                return $wa ? 1 : -1;
1600
            }
1601
1602 2
            return $a[0] > $b[0] ? 1 : -1;
1603 3
        });
1604
1605 3
        $result = [];
1606 3
        foreach ($accepts as $accept) {
1607 3
            $name = $accept['q'][1];
1608 3
            $accept['q'] = $accept['q'][2];
1609 3
            $result[$name] = $accept;
1610
        }
1611
1612 3
        return $result;
1613
    }
1614
1615
    /**
1616
     * Returns the user-preferred language that should be used by this application.
1617
     * The language resolution is based on the user preferred languages and the languages
1618
     * supported by the application. The method will try to find the best match.
1619
     * @param array $languages a list of the languages supported by the application. If this is empty, the current
1620
     * application language will be returned without further processing.
1621
     * @return string the language that the application should use.
1622
     */
1623 1
    public function getPreferredLanguage(array $languages = [])
1624
    {
1625 1
        if (empty($languages)) {
1626 1
            return Yii::$app->language;
1627
        }
1628 1
        foreach ($this->getAcceptableLanguages() as $acceptableLanguage) {
1629 1
            $acceptableLanguage = str_replace('_', '-', strtolower($acceptableLanguage));
1630 1
            foreach ($languages as $language) {
1631 1
                $normalizedLanguage = str_replace('_', '-', strtolower($language));
1632
1633
                if (
1634 1
                    $normalizedLanguage === $acceptableLanguage // en-us==en-us
1635 1
                    || strpos($acceptableLanguage, $normalizedLanguage . '-') === 0 // en==en-us
1636 1
                    || strpos($normalizedLanguage, $acceptableLanguage . '-') === 0 // en-us==en
1637
                ) {
1638 1
                    return $language;
1639
                }
1640
            }
1641
        }
1642
1643 1
        return reset($languages);
1644
    }
1645
1646
    /**
1647
     * Gets the Etags.
1648
     *
1649
     * @return array The entity tags
1650
     */
1651
    public function getETags()
1652
    {
1653
        if ($this->headers->has('If-None-Match')) {
1654
            return preg_split('/[\s,]+/', str_replace('-gzip', '', $this->headers->get('If-None-Match')), -1, PREG_SPLIT_NO_EMPTY);
1655
        }
1656
1657
        return [];
1658
    }
1659
1660
    /**
1661
     * Returns the cookie collection.
1662
     *
1663
     * Through the returned cookie collection, you may access a cookie using the following syntax:
1664
     *
1665
     * ```php
1666
     * $cookie = $request->cookies['name']
1667
     * if ($cookie !== null) {
1668
     *     $value = $cookie->value;
1669
     * }
1670
     *
1671
     * // alternatively
1672
     * $value = $request->cookies->getValue('name');
1673
     * ```
1674
     *
1675
     * @return CookieCollection the cookie collection.
1676
     */
1677 83
    public function getCookies()
1678
    {
1679 83
        if ($this->_cookies === null) {
1680 83
            $this->_cookies = new CookieCollection($this->loadCookies(), [
1681 82
                'readOnly' => true,
1682
            ]);
1683
        }
1684
1685 82
        return $this->_cookies;
1686
    }
1687
1688
    /**
1689
     * Converts `$_COOKIE` into an array of [[Cookie]].
1690
     * @return array the cookies obtained from request
1691
     * @throws InvalidConfigException if [[cookieValidationKey]] is not set when [[enableCookieValidation]] is true
1692
     */
1693 83
    protected function loadCookies()
1694
    {
1695 83
        $cookies = [];
1696 83
        if ($this->enableCookieValidation) {
1697 82
            if ($this->cookieValidationKey == '') {
1698 1
                throw new InvalidConfigException(get_class($this) . '::cookieValidationKey must be configured with a secret key.');
1699
            }
1700 81
            foreach ($_COOKIE as $name => $value) {
1701
                if (!is_string($value)) {
1702
                    continue;
1703
                }
1704
                $data = Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey);
1705
                if ($data === false) {
1706
                    continue;
1707
                }
1708
                if (defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 70000) {
1709
                    $data = @unserialize($data, ['allowed_classes' => false]);
1710
                } else {
1711
                    $data = @unserialize($data);
1712
                }
1713
                if (is_array($data) && isset($data[0], $data[1]) && $data[0] === $name) {
1714
                    $cookies[$name] = Yii::createObject([
1715
                        'class' => 'yii\web\Cookie',
1716
                        'name' => $name,
1717
                        'value' => $data[1],
1718
                        'expire' => null,
1719
                    ]);
1720
                }
1721
            }
1722
        } else {
1723 1
            foreach ($_COOKIE as $name => $value) {
1724 1
                $cookies[$name] = Yii::createObject([
1725 1
                    'class' => 'yii\web\Cookie',
1726 1
                    'name' => $name,
1727 1
                    'value' => $value,
1728
                    'expire' => null,
1729
                ]);
1730
            }
1731
        }
1732
1733 82
        return $cookies;
1734
    }
1735
1736
    private $_csrfToken;
1737
1738
    /**
1739
     * Returns the token used to perform CSRF validation.
1740
     *
1741
     * This token is generated in a way to prevent [BREACH attacks](http://breachattack.com/). It may be passed
1742
     * along via a hidden field of an HTML form or an HTTP header value to support CSRF validation.
1743
     * @param bool $regenerate whether to regenerate CSRF token. When this parameter is true, each time
1744
     * this method is called, a new CSRF token will be generated and persisted (in session or cookie).
1745
     * @return string the token used to perform CSRF validation.
1746
     */
1747 89
    public function getCsrfToken($regenerate = false)
1748
    {
1749 89
        if ($this->_csrfToken === null || $regenerate) {
1750 89
            $token = $this->loadCsrfToken();
1751 88
            if ($regenerate || empty($token)) {
1752 85
                $token = $this->generateCsrfToken();
1753
            }
1754 88
            $this->_csrfToken = Yii::$app->security->maskToken($token);
1755
        }
1756
1757 88
        return $this->_csrfToken;
1758
    }
1759
1760
    /**
1761
     * Loads the CSRF token from cookie or session.
1762
     * @return string|null the CSRF token loaded from cookie or session. Null is returned if the cookie or session
1763
     * does not have CSRF token.
1764
     */
1765 89
    protected function loadCsrfToken()
1766
    {
1767 89
        if ($this->enableCsrfCookie) {
1768 85
            return $this->getCookies()->getValue($this->csrfParam);
1769
        }
1770
1771 4
        return Yii::$app->getSession()->get($this->csrfParam);
0 ignored issues
show
Bug introduced by
The method getSession() does not exist on yii\base\Application. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

1771
        return Yii::$app->/** @scrutinizer ignore-call */ getSession()->get($this->csrfParam);
Loading history...
Bug introduced by
The method getSession() does not exist on yii\console\Application. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

1771
        return Yii::$app->/** @scrutinizer ignore-call */ getSession()->get($this->csrfParam);
Loading history...
1772
    }
1773
1774
    /**
1775
     * Generates an unmasked random token used to perform CSRF validation.
1776
     * @return string the random token for CSRF validation.
1777
     */
1778 85
    protected function generateCsrfToken()
1779
    {
1780 85
        $token = Yii::$app->getSecurity()->generateRandomString();
1781 85
        if ($this->enableCsrfCookie) {
1782 84
            $cookie = $this->createCsrfCookie($token);
1783 84
            Yii::$app->getResponse()->getCookies()->add($cookie);
0 ignored issues
show
Bug introduced by
The method getCookies() does not exist on yii\console\Response. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

1783
            Yii::$app->getResponse()->/** @scrutinizer ignore-call */ getCookies()->add($cookie);
Loading history...
1784
        } else {
1785 1
            Yii::$app->getSession()->set($this->csrfParam, $token);
1786
        }
1787
1788 85
        return $token;
1789
    }
1790
1791
    /**
1792
     * @return string|null the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent.
1793
     */
1794 3
    public function getCsrfTokenFromHeader()
1795
    {
1796 3
        return $this->headers->get(static::CSRF_HEADER);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->headers->get(static::CSRF_HEADER) also could return the type array which is incompatible with the documented return type null|string.
Loading history...
1797
    }
1798
1799
    /**
1800
     * Creates a cookie with a randomly generated CSRF token.
1801
     * Initial values specified in [[csrfCookie]] will be applied to the generated cookie.
1802
     * @param string $token the CSRF token
1803
     * @return Cookie the generated cookie
1804
     * @see enableCsrfValidation
1805
     */
1806 84
    protected function createCsrfCookie($token)
1807
    {
1808 84
        $options = $this->csrfCookie;
1809 84
        return Yii::createObject(array_merge($options, [
1810 84
            'class' => 'yii\web\Cookie',
1811 84
            'name' => $this->csrfParam,
1812 84
            'value' => $token,
1813
        ]));
1814
    }
1815
1816
    /**
1817
     * Performs the CSRF validation.
1818
     *
1819
     * This method will validate the user-provided CSRF token by comparing it with the one stored in cookie or session.
1820
     * This method is mainly called in [[Controller::beforeAction()]].
1821
     *
1822
     * Note that the method will NOT perform CSRF validation if [[enableCsrfValidation]] is false or the HTTP method
1823
     * is among GET, HEAD or OPTIONS.
1824
     *
1825
     * @param string|null $clientSuppliedToken the user-provided CSRF token to be validated. If null, the token will be retrieved from
1826
     * the [[csrfParam]] POST field or HTTP header.
1827
     * This parameter is available since version 2.0.4.
1828
     * @return bool whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true.
1829
     */
1830 8
    public function validateCsrfToken($clientSuppliedToken = null)
1831
    {
1832 8
        $method = $this->getMethod();
1833
        // only validate CSRF token on non-"safe" methods https://tools.ietf.org/html/rfc2616#section-9.1.1
1834 8
        if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
1835 7
            return true;
1836
        }
1837
1838 4
        $trueToken = $this->getCsrfToken();
1839
1840 4
        if ($clientSuppliedToken !== null) {
1841 2
            return $this->validateCsrfTokenInternal($clientSuppliedToken, $trueToken);
1842
        }
1843
1844 3
        return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
1845 3
            || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
1846
    }
1847
1848
    /**
1849
     * Validates CSRF token.
1850
     *
1851
     * @param string $clientSuppliedToken The masked client-supplied token.
1852
     * @param string $trueToken The masked true token.
1853
     * @return bool
1854
     */
1855 4
    private function validateCsrfTokenInternal($clientSuppliedToken, $trueToken)
1856
    {
1857 4
        if (!is_string($clientSuppliedToken)) {
0 ignored issues
show
introduced by
The condition is_string($clientSuppliedToken) is always true.
Loading history...
1858 3
            return false;
1859
        }
1860
1861 4
        $security = Yii::$app->security;
1862
1863 4
        return $security->compareString($security->unmaskToken($clientSuppliedToken), $security->unmaskToken($trueToken));
1864
    }
1865
1866
    /**
1867
     * Gets first `Forwarded` header value for token
1868
     *
1869
     * @param string $token Header token
1870
     *
1871
     * @return string|null
1872
     *
1873
     * @since 2.0.31
1874
     */
1875 172
    protected function getSecureForwardedHeaderTrustedPart($token)
1876
    {
1877 172
        $token = strtolower($token);
1878
1879 172
        if ($parts = $this->getSecureForwardedHeaderTrustedParts()) {
1880 20
            $lastElement = array_pop($parts);
1881 20
            if ($lastElement && isset($lastElement[$token])) {
1882 20
                return $lastElement[$token];
1883
            }
1884
        }
1885 157
        return null;
1886
    }
1887
1888
    /**
1889
     * Gets only trusted `Forwarded` header parts
1890
     *
1891
     * @return array
1892
     *
1893
     * @since 2.0.31
1894
     */
1895 172
    protected function getSecureForwardedHeaderTrustedParts()
1896
    {
1897 172
        $validator = $this->getIpValidator();
1898 172
        $trustedHosts = [];
1899 172
        foreach ($this->trustedHosts as $trustedCidr => $trustedCidrOrHeaders) {
1900 80
            if (!is_array($trustedCidrOrHeaders)) {
1901 78
                $trustedCidr = $trustedCidrOrHeaders;
1902
            }
1903 80
            $trustedHosts[] = $trustedCidr;
1904
        }
1905 172
        $validator->setRanges($trustedHosts);
1906
1907
        return array_filter($this->getSecureForwardedHeaderParts(), function ($headerPart) use ($validator) {
1908 20
            return isset($headerPart['for']) ? !$validator->validate($headerPart['for']) : true;
1909 172
        });
1910
    }
1911
1912
    private $_secureForwardedHeaderParts;
1913
1914
    /**
1915
     * Returns decoded forwarded header
1916
     *
1917
     * @return array
1918
     *
1919
     * @since 2.0.31
1920
     */
1921 172
    protected function getSecureForwardedHeaderParts()
1922
    {
1923 172
        if ($this->_secureForwardedHeaderParts !== null) {
1924 89
            return $this->_secureForwardedHeaderParts;
1925
        }
1926 172
        if (count(preg_grep('/^forwarded$/i', $this->secureHeaders)) === 0) {
1927 109
            return $this->_secureForwardedHeaderParts = [];
1928
        }
1929
        /*
1930
         * First header is always correct, because proxy CAN add headers
1931
         * after last one is found.
1932
         * Keep in mind that it is NOT enforced, therefore we cannot be
1933
         * sure, that this is really a first one.
1934
         *
1935
         * FPM keeps last header sent which is a bug. You need to merge
1936
         * headers together on your web server before letting FPM handle it
1937
         * @see https://bugs.php.net/bug.php?id=78844
1938
         */
1939 63
        $forwarded = $this->headers->get('Forwarded', '');
1940 63
        if ($forwarded === '') {
1941 43
            return $this->_secureForwardedHeaderParts = [];
1942
        }
1943
1944 20
        preg_match_all('/(?:[^",]++|"[^"]++")+/', $forwarded, $forwardedElements);
1945
1946 20
        foreach ($forwardedElements[0] as $forwardedPairs) {
1947 20
            preg_match_all('/(?P<key>\w+)\s*=\s*(?:(?P<value>[^",;]*[^",;\s])|"(?P<value2>[^"]+)")/', $forwardedPairs,
1948 20
                $matches, PREG_SET_ORDER);
1949 20
            $this->_secureForwardedHeaderParts[] = array_reduce($matches, function ($carry, $item) {
1950 20
                $value = $item['value'];
1951 20
                if (isset($item['value2']) && $item['value2'] !== '') {
1952 4
                    $value = $item['value2'];
1953
                }
1954 20
                $carry[strtolower($item['key'])] = $value;
1955 20
                return $carry;
1956 20
            }, []);
1957
        }
1958 20
        return $this->_secureForwardedHeaderParts;
1959
    }
1960
}
1961