Completed
Push — master ( ce0c7a...c87855 )
by Alexander
57:20 queued 52:39
created

Request::getTrustedIpHeaders()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 5

Importance

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

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

497
        return stripos(/** @scrutinizer ignore-type */ $userAgent, 'Shockwave') !== false
Loading history...
498
            || stripos($userAgent, 'Flash') !== false;
499
    }
500
501
    private $_rawBody;
502
503
    /**
504
     * Returns the raw HTTP request body.
505
     * @return string the request body
506
     */
507 3
    public function getRawBody()
508
    {
509 3
        if ($this->_rawBody === null) {
510 3
            $this->_rawBody = file_get_contents('php://input');
511
        }
512
513 3
        return $this->_rawBody;
514
    }
515
516
    /**
517
     * Sets the raw HTTP request body, this method is mainly used by test scripts to simulate raw HTTP requests.
518
     * @param string $rawBody the request body
519
     */
520
    public function setRawBody($rawBody)
521
    {
522
        $this->_rawBody = $rawBody;
523
    }
524
525
    private $_bodyParams;
526
527
    /**
528
     * Returns the request parameters given in the request body.
529
     *
530
     * Request parameters are determined using the parsers configured in [[parsers]] property.
531
     * If no parsers are configured for the current [[contentType]] it uses the PHP function `mb_parse_str()`
532
     * to parse the [[rawBody|request body]].
533
     * @return array the request parameters given in the request body.
534
     * @throws \yii\base\InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]].
535
     * @see getMethod()
536
     * @see getBodyParam()
537
     * @see setBodyParams()
538
     */
539 7
    public function getBodyParams()
540
    {
541 7
        if ($this->_bodyParams === null) {
542 4
            if (isset($_POST[$this->methodParam])) {
543 1
                $this->_bodyParams = $_POST;
544 1
                unset($this->_bodyParams[$this->methodParam]);
545 1
                return $this->_bodyParams;
546
            }
547
548 3
            $rawContentType = $this->getContentType();
549 3
            if (($pos = strpos($rawContentType, ';')) !== false) {
550
                // e.g. text/html; charset=UTF-8
551
                $contentType = substr($rawContentType, 0, $pos);
552
            } else {
553 3
                $contentType = $rawContentType;
554
            }
555
556 3
            if (isset($this->parsers[$contentType])) {
557
                $parser = Yii::createObject($this->parsers[$contentType]);
558
                if (!($parser instanceof RequestParserInterface)) {
559
                    throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
560
                }
561
                $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
562 3
            } elseif (isset($this->parsers['*'])) {
563
                $parser = Yii::createObject($this->parsers['*']);
564
                if (!($parser instanceof RequestParserInterface)) {
565
                    throw new InvalidConfigException('The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface.');
566
                }
567
                $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
568 3
            } elseif ($this->getMethod() === 'POST') {
569
                // PHP has already parsed the body so we have all params in $_POST
570
                $this->_bodyParams = $_POST;
571
            } else {
572 3
                $this->_bodyParams = [];
573 3
                mb_parse_str($this->getRawBody(), $this->_bodyParams);
574
            }
575
        }
576
577 7
        return $this->_bodyParams;
578
    }
579
580
    /**
581
     * Sets the request body parameters.
582
     * @param array $values the request body parameters (name-value pairs)
583
     * @see getBodyParam()
584
     * @see getBodyParams()
585
     */
586 6
    public function setBodyParams($values)
587
    {
588 6
        $this->_bodyParams = $values;
589 6
    }
590
591
    /**
592
     * Returns the named request body parameter value.
593
     * If the parameter does not exist, the second parameter passed to this method will be returned.
594
     * @param string $name the parameter name
595
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
596
     * @return mixed the parameter value
597
     * @see getBodyParams()
598
     * @see setBodyParams()
599
     */
600 7
    public function getBodyParam($name, $defaultValue = null)
601
    {
602 7
        $params = $this->getBodyParams();
603
604 7
        if (is_object($params)) {
0 ignored issues
show
introduced by
The condition is_object($params) is always false.
Loading history...
605
            // unable to use `ArrayHelper::getValue()` due to different dots in key logic and lack of exception handling
606
            try {
607 1
                return $params->{$name};
608 1
            } 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...
609 1
                return $defaultValue;
610
            }
611
        }
612
613 7
        return isset($params[$name]) ? $params[$name] : $defaultValue;
614
    }
615
616
    /**
617
     * Returns POST parameter with a given name. If name isn't specified, returns an array of all POST parameters.
618
     *
619
     * @param string $name the parameter name
620
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
621
     * @return array|mixed
622
     */
623
    public function post($name = null, $defaultValue = null)
624
    {
625
        if ($name === null) {
626
            return $this->getBodyParams();
627
        }
628
629
        return $this->getBodyParam($name, $defaultValue);
630
    }
631
632
    private $_queryParams;
633
634
    /**
635
     * Returns the request parameters given in the [[queryString]].
636
     *
637
     * This method will return the contents of `$_GET` if params where not explicitly set.
638
     * @return array the request GET parameter values.
639
     * @see setQueryParams()
640
     */
641 36
    public function getQueryParams()
642
    {
643 36
        if ($this->_queryParams === null) {
644 29
            return $_GET;
645
        }
646
647 9
        return $this->_queryParams;
648
    }
649
650
    /**
651
     * Sets the request [[queryString]] parameters.
652
     * @param array $values the request query parameters (name-value pairs)
653
     * @see getQueryParam()
654
     * @see getQueryParams()
655
     */
656 9
    public function setQueryParams($values)
657
    {
658 9
        $this->_queryParams = $values;
659 9
    }
660
661
    /**
662
     * Returns GET parameter with a given name. If name isn't specified, returns an array of all GET parameters.
663
     *
664
     * @param string $name the parameter name
665
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
666
     * @return array|mixed
667
     */
668 24
    public function get($name = null, $defaultValue = null)
669
    {
670 24
        if ($name === null) {
671
            return $this->getQueryParams();
672
        }
673
674 24
        return $this->getQueryParam($name, $defaultValue);
675
    }
676
677
    /**
678
     * Returns the named GET parameter value.
679
     * If the GET parameter does not exist, the second parameter passed to this method will be returned.
680
     * @param string $name the GET parameter name.
681
     * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
682
     * @return mixed the GET parameter value
683
     * @see getBodyParam()
684
     */
685 27
    public function getQueryParam($name, $defaultValue = null)
686
    {
687 27
        $params = $this->getQueryParams();
688
689 27
        return isset($params[$name]) ? $params[$name] : $defaultValue;
690
    }
691
692
    private $_hostInfo;
693
    private $_hostName;
694
695
    /**
696
     * Returns the schema and host part of the current request URL.
697
     *
698
     * The returned URL does not have an ending slash.
699
     *
700
     * By default this value is based on the user request information. This method will
701
     * return the value of `$_SERVER['HTTP_HOST']` if it is available or `$_SERVER['SERVER_NAME']` if not.
702
     * You may want to check out the [PHP documentation](https://secure.php.net/manual/en/reserved.variables.server.php)
703
     * for more information on these variables.
704
     *
705
     * You may explicitly specify it by setting the [[setHostInfo()|hostInfo]] property.
706
     *
707
     * > Warning: Dependent on the server configuration this information may not be
708
     * > reliable and [may be faked by the user sending the HTTP request](https://www.acunetix.com/vulnerabilities/web/host-header-attack).
709
     * > If the webserver is configured to serve the same site independent of the value of
710
     * > the `Host` header, this value is not reliable. In such situations you should either
711
     * > fix your webserver configuration or explicitly set the value by setting the [[setHostInfo()|hostInfo]] property.
712
     * > If you don't have access to the server configuration, you can setup [[\yii\filters\HostControl]] filter at
713
     * > application level in order to protect against such kind of attack.
714
     *
715
     * @property string|null schema and hostname part (with port number if needed) of the request URL
716
     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
717
     * See [[getHostInfo()]] for security related notes on this property.
718
     * @return string|null schema and hostname part (with port number if needed) of the request URL
719
     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
720
     * @see setHostInfo()
721
     */
722 35
    public function getHostInfo()
723
    {
724 35
        if ($this->_hostInfo === null) {
725 31
            $secure = $this->getIsSecureConnection();
726 31
            $http = $secure ? 'https' : 'http';
727
728 31
            if ($this->headers->has('X-Forwarded-Host')) {
729 2
                $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; 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

729
                $this->_hostInfo = $http . '://' . trim(explode(',', /** @scrutinizer ignore-type */ $this->headers->get('X-Forwarded-Host'))[0]);
Loading history...
730 29
            } elseif ($this->headers->has('Host')) {
731 9
                $this->_hostInfo = $http . '://' . $this->headers->get('Host');
0 ignored issues
show
Bug introduced by
Are you sure $this->headers->get('Host') of type array|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

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

1072
                    if (strcasecmp(/** @scrutinizer ignore-type */ $headerValue, $value) === 0) {
Loading history...
1073 46
                        return true;
1074
                    }
1075
                }
1076
            }
1077
        }
1078
1079 44
        return false;
1080
    }
1081
1082
    /**
1083
     * Returns the server name.
1084
     * @return string server name, null if not available
1085
     */
1086 1
    public function getServerName()
1087
    {
1088 1
        return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null;
1089
    }
1090
1091
    /**
1092
     * Returns the server port number.
1093
     * @return int|null server port number, null if not available
1094
     */
1095 2
    public function getServerPort()
1096
    {
1097 2
        return isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : null;
1098
    }
1099
1100
    /**
1101
     * Returns the URL referrer.
1102
     * @return string|null URL referrer, null if not available
1103
     */
1104
    public function getReferrer()
1105
    {
1106
        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...
1107
    }
1108
1109
    /**
1110
     * Returns the URL origin of a CORS request.
1111
     *
1112
     * The return value is taken from the `Origin` [[getHeaders()|header]] sent by the browser.
1113
     *
1114
     * Note that the origin request header indicates where a fetch originates from.
1115
     * It doesn't include any path information, but only the server name.
1116
     * It is sent with a CORS requests, as well as with POST requests.
1117
     * It is similar to the referer header, but, unlike this header, it doesn't disclose the whole path.
1118
     * Please refer to <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin> for more information.
1119
     *
1120
     * @return string|null URL origin of a CORS request, `null` if not available.
1121
     * @see getHeaders()
1122
     * @since 2.0.13
1123
     */
1124 1
    public function getOrigin()
1125
    {
1126 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...
1127
    }
1128
1129
    /**
1130
     * Returns the user agent.
1131
     * @return string|null user agent, null if not available
1132
     */
1133 1
    public function getUserAgent()
1134
    {
1135 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...
1136
    }
1137
1138
    /**
1139
     * Returns the user IP address.
1140
     * The IP is determined using headers and / or `$_SERVER` variables.
1141
     * @return string|null user IP address, null if not available
1142
     */
1143 70
    public function getUserIP()
1144
    {
1145 70
        foreach($this->getTrustedIpHeaders() as $ipHeader) {
1146 9
            if ($this->headers->has($ipHeader)) {
1147 9
                $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

1147
                $ip = $this->getUserIpFromIpHeader(/** @scrutinizer ignore-type */ $this->headers->get($ipHeader));
Loading history...
1148 9
                if ($ip !== null) {
1149 9
                    return $ip;
1150
                }
1151
            }
1152
        }
1153
1154 61
        return $this->getRemoteIP();
1155
    }
1156
1157
    /**
1158
     * Return user IP's from IP header.
1159
     *
1160
     * @param string $ips comma separated IP list
1161
     * @return string|null IP as string. Null is returned if IP can not be determined from header.
1162
     * @see $getUserHost
1163
     * @see $ipHeader
1164
     * @see $trustedHeaders
1165
     * @since 2.0.28
1166
     */
1167 9
    protected function getUserIpFromIpHeader($ips)
1168
    {
1169 9
        $ips = trim($ips);
1170 9
        if ($ips === '') {
1171
            return null;
1172
        }
1173 9
        $ips = preg_split('/\s*,\s*/', $ips, -1, PREG_SPLIT_NO_EMPTY);
1174 9
        krsort($ips);
0 ignored issues
show
Bug introduced by
It seems like $ips can also be of type false; however, parameter $array of krsort() does only seem to accept 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

1174
        krsort(/** @scrutinizer ignore-type */ $ips);
Loading history...
1175 9
        $validator = $this->getIpValidator();
1176 9
        $resultIp = null;
1177 9
        foreach ($ips as $ip) {
1178 9
            $validator->setRanges('any');
1179 9
            if (!$validator->validate($ip) /* checking IP format */) {
1180 1
                break;
1181
            }
1182 9
            $resultIp = $ip;
1183 9
            $isTrusted = false;
1184 9
            foreach ($this->trustedHosts as $trustedCidr => $trustedCidrOrHeaders) {
1185 9
                if (!is_array($trustedCidrOrHeaders)) {
1186 9
                    $trustedCidr = $trustedCidrOrHeaders;
1187
                }
1188 9
                $validator->setRanges($trustedCidr);
1189 9
                if ($validator->validate($ip) /* checking trusted range */) {
1190 7
                    $isTrusted = true;
1191 9
                    break;
1192
                }
1193
            }
1194 9
            if (!$isTrusted) {
1195 9
                break;
1196
            }
1197
        }
1198 9
        return $resultIp;
1199
    }
1200
1201
    /**
1202
     * Returns the user host name.
1203
     * The HOST is determined using headers and / or `$_SERVER` variables.
1204
     * @return string|null user host name, null if not available
1205
     */
1206
    public function getUserHost()
1207
    {
1208
        foreach ($this->ipHeaders as $ipHeader) {
1209
            if ($this->headers->has($ipHeader)) {
1210
                return gethostbyaddr(trim(explode(',', $this->headers->get($ipHeader))[0]));
0 ignored issues
show
Bug introduced by
It seems like $this->headers->get($ipHeader) can also be of type array; 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

1210
                return gethostbyaddr(trim(explode(',', /** @scrutinizer ignore-type */ $this->headers->get($ipHeader))[0]));
Loading history...
1211
            }
1212
        }
1213
1214
        return $this->getRemoteHost();
1215
    }
1216
1217
    /**
1218
     * Returns the IP on the other end of this connection.
1219
     * This is always the next hop, any headers are ignored.
1220
     * @return string|null remote IP address, `null` if not available.
1221
     * @since 2.0.13
1222
     */
1223 91
    public function getRemoteIP()
1224
    {
1225 91
        return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
1226
    }
1227
1228
    /**
1229
     * Returns the host name of the other end of this connection.
1230
     * This is always the next hop, any headers are ignored.
1231
     * @return string|null remote host name, `null` if not available
1232
     * @see getUserHost()
1233
     * @see getRemoteIP()
1234
     * @since 2.0.13
1235
     */
1236
    public function getRemoteHost()
1237
    {
1238
        return isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : null;
1239
    }
1240
1241
    /**
1242
     * @return string|null the username sent via HTTP authentication, `null` if the username is not given
1243
     * @see getAuthCredentials() to get both username and password in one call
1244
     */
1245 9
    public function getAuthUser()
1246
    {
1247 9
        return $this->getAuthCredentials()[0];
1248
    }
1249
1250
    /**
1251
     * @return string|null the password sent via HTTP authentication, `null` if the password is not given
1252
     * @see getAuthCredentials() to get both username and password in one call
1253
     */
1254 9
    public function getAuthPassword()
1255
    {
1256 9
        return $this->getAuthCredentials()[1];
1257
    }
1258
1259
    /**
1260
     * @return array that contains exactly two elements:
1261
     * - 0: the username sent via HTTP authentication, `null` if the username is not given
1262
     * - 1: the password sent via HTTP authentication, `null` if the password is not given
1263
     * @see getAuthUser() to get only username
1264
     * @see getAuthPassword() to get only password
1265
     * @since 2.0.13
1266
     */
1267 34
    public function getAuthCredentials()
1268
    {
1269 34
        $username = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
1270 34
        $password = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
1271 34
        if ($username !== null || $password !== null) {
1272 16
            return [$username, $password];
1273
        }
1274
1275
        /*
1276
         * Apache with php-cgi does not pass HTTP Basic authentication to PHP by default.
1277
         * To make it work, add the following line to to your .htaccess file:
1278
         *
1279
         * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
1280
         */
1281 18
        $auth_token = $this->getHeaders()->get('HTTP_AUTHORIZATION') ?: $this->getHeaders()->get('REDIRECT_HTTP_AUTHORIZATION');
1282 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 $str1 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

1282
        if ($auth_token !== null && strncasecmp(/** @scrutinizer ignore-type */ $auth_token, 'basic', 5) === 0) {
Loading history...
1283
            $parts = array_map(function ($value) {
1284 18
                return strlen($value) === 0 ? null : $value;
1285 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 $str 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

1285
            }, explode(':', base64_decode(mb_substr(/** @scrutinizer ignore-type */ $auth_token, 6)), 2));
Loading history...
1286
1287 18
            if (count($parts) < 2) {
1288 2
                return [$parts[0], null];
1289
            }
1290
1291 16
            return $parts;
1292
        }
1293
1294
        return [null, null];
1295
    }
1296
1297
    private $_port;
1298
1299
    /**
1300
     * Returns the port to use for insecure requests.
1301
     * Defaults to 80, or the port specified by the server if the current
1302
     * request is insecure.
1303
     * @return int port number for insecure requests.
1304
     * @see setPort()
1305
     */
1306 1
    public function getPort()
1307
    {
1308 1
        if ($this->_port === null) {
1309 1
            $serverPort = $this->getServerPort();
1310 1
            $this->_port = !$this->getIsSecureConnection() && $serverPort !== null ? $serverPort : 80;
1311
        }
1312
1313 1
        return $this->_port;
1314
    }
1315
1316
    /**
1317
     * Sets the port to use for insecure requests.
1318
     * This setter is provided in case a custom port is necessary for certain
1319
     * server configurations.
1320
     * @param int $value port number.
1321
     */
1322
    public function setPort($value)
1323
    {
1324
        if ($value != $this->_port) {
1325
            $this->_port = (int) $value;
1326
            $this->_hostInfo = null;
1327
        }
1328
    }
1329
1330
    private $_securePort;
1331
1332
    /**
1333
     * Returns the port to use for secure requests.
1334
     * Defaults to 443, or the port specified by the server if the current
1335
     * request is secure.
1336
     * @return int port number for secure requests.
1337
     * @see setSecurePort()
1338
     */
1339
    public function getSecurePort()
1340
    {
1341
        if ($this->_securePort === null) {
1342
            $serverPort = $this->getServerPort();
1343
            $this->_securePort = $this->getIsSecureConnection() && $serverPort !== null ? $serverPort : 443;
1344
        }
1345
1346
        return $this->_securePort;
1347
    }
1348
1349
    /**
1350
     * Sets the port to use for secure requests.
1351
     * This setter is provided in case a custom port is necessary for certain
1352
     * server configurations.
1353
     * @param int $value port number.
1354
     */
1355
    public function setSecurePort($value)
1356
    {
1357
        if ($value != $this->_securePort) {
1358
            $this->_securePort = (int) $value;
1359
            $this->_hostInfo = null;
1360
        }
1361
    }
1362
1363
    private $_contentTypes;
1364
1365
    /**
1366
     * Returns the content types acceptable by the end user.
1367
     *
1368
     * This is determined by the `Accept` HTTP header. For example,
1369
     *
1370
     * ```php
1371
     * $_SERVER['HTTP_ACCEPT'] = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1372
     * $types = $request->getAcceptableContentTypes();
1373
     * print_r($types);
1374
     * // displays:
1375
     * // [
1376
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1377
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1378
     * //           'text/plain' => ['q' => 0.5],
1379
     * // ]
1380
     * ```
1381
     *
1382
     * @return array the content types ordered by the quality score. Types with the highest scores
1383
     * will be returned first. The array keys are the content types, while the array values
1384
     * are the corresponding quality score and other parameters as given in the header.
1385
     */
1386 3
    public function getAcceptableContentTypes()
1387
    {
1388 3
        if ($this->_contentTypes === null) {
1389 3
            if ($this->headers->get('Accept') !== null) {
0 ignored issues
show
introduced by
The condition $this->headers->get('Accept') !== null is always true.
Loading history...
1390 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

1390
                $this->_contentTypes = $this->parseAcceptHeader(/** @scrutinizer ignore-type */ $this->headers->get('Accept'));
Loading history...
1391
            } else {
1392 2
                $this->_contentTypes = [];
1393
            }
1394
        }
1395
1396 3
        return $this->_contentTypes;
1397
    }
1398
1399
    /**
1400
     * Sets the acceptable content types.
1401
     * Please refer to [[getAcceptableContentTypes()]] on the format of the parameter.
1402
     * @param array $value the content types that are acceptable by the end user. They should
1403
     * be ordered by the preference level.
1404
     * @see getAcceptableContentTypes()
1405
     * @see parseAcceptHeader()
1406
     */
1407
    public function setAcceptableContentTypes($value)
1408
    {
1409
        $this->_contentTypes = $value;
1410
    }
1411
1412
    /**
1413
     * Returns request content-type
1414
     * The Content-Type header field indicates the MIME type of the data
1415
     * contained in [[getRawBody()]] or, in the case of the HEAD method, the
1416
     * media type that would have been sent had the request been a GET.
1417
     * For the MIME-types the user expects in response, see [[acceptableContentTypes]].
1418
     * @return string request content-type. Null is returned if this information is not available.
1419
     * @link https://tools.ietf.org/html/rfc2616#section-14.17
1420
     * HTTP 1.1 header field definitions
1421
     */
1422 3
    public function getContentType()
1423
    {
1424 3
        if (isset($_SERVER['CONTENT_TYPE'])) {
1425
            return $_SERVER['CONTENT_TYPE'];
1426
        }
1427
1428
        //fix bug https://bugs.php.net/bug.php?id=66606
1429 3
        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...
1430
    }
1431
1432
    private $_languages;
1433
1434
    /**
1435
     * Returns the languages acceptable by the end user.
1436
     * This is determined by the `Accept-Language` HTTP header.
1437
     * @return array the languages ordered by the preference level. The first element
1438
     * represents the most preferred language.
1439
     */
1440 2
    public function getAcceptableLanguages()
1441
    {
1442 2
        if ($this->_languages === null) {
1443 1
            if ($this->headers->has('Accept-Language')) {
1444
                $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

1444
                $this->_languages = array_keys($this->parseAcceptHeader(/** @scrutinizer ignore-type */ $this->headers->get('Accept-Language')));
Loading history...
1445
            } else {
1446 1
                $this->_languages = [];
1447
            }
1448
        }
1449
1450 2
        return $this->_languages;
1451
    }
1452
1453
    /**
1454
     * @param array $value the languages that are acceptable by the end user. They should
1455
     * be ordered by the preference level.
1456
     */
1457 1
    public function setAcceptableLanguages($value)
1458
    {
1459 1
        $this->_languages = $value;
1460 1
    }
1461
1462
    /**
1463
     * Parses the given `Accept` (or `Accept-Language`) header.
1464
     *
1465
     * This method will return the acceptable values with their quality scores and the corresponding parameters
1466
     * as specified in the given `Accept` header. The array keys of the return value are the acceptable values,
1467
     * while the array values consisting of the corresponding quality scores and parameters. The acceptable
1468
     * values with the highest quality scores will be returned first. For example,
1469
     *
1470
     * ```php
1471
     * $header = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1472
     * $accepts = $request->parseAcceptHeader($header);
1473
     * print_r($accepts);
1474
     * // displays:
1475
     * // [
1476
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1477
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1478
     * //           'text/plain' => ['q' => 0.5],
1479
     * // ]
1480
     * ```
1481
     *
1482
     * @param string $header the header to be parsed
1483
     * @return array the acceptable values ordered by their quality score. The values with the highest scores
1484
     * will be returned first.
1485
     */
1486 3
    public function parseAcceptHeader($header)
1487
    {
1488 3
        $accepts = [];
1489 3
        foreach (explode(',', $header) as $i => $part) {
1490 3
            $params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY);
1491 3
            if (empty($params)) {
1492 1
                continue;
1493
            }
1494
            $values = [
1495 3
                'q' => [$i, array_shift($params), 1],
1496
            ];
1497 3
            foreach ($params as $param) {
1498 2
                if (strpos($param, '=') !== false) {
1499 2
                    list($key, $value) = explode('=', $param, 2);
1500 2
                    if ($key === 'q') {
1501 2
                        $values['q'][2] = (float) $value;
1502
                    } else {
1503 2
                        $values[$key] = $value;
1504
                    }
1505
                } else {
1506 2
                    $values[] = $param;
1507
                }
1508
            }
1509 3
            $accepts[] = $values;
1510
        }
1511
1512 3
        usort($accepts, function ($a, $b) {
1513 3
            $a = $a['q']; // index, name, q
1514 3
            $b = $b['q'];
1515 3
            if ($a[2] > $b[2]) {
1516 2
                return -1;
1517
            }
1518
1519 2
            if ($a[2] < $b[2]) {
1520 1
                return 1;
1521
            }
1522
1523 2
            if ($a[1] === $b[1]) {
1524
                return $a[0] > $b[0] ? 1 : -1;
1525
            }
1526
1527 2
            if ($a[1] === '*/*') {
1528
                return 1;
1529
            }
1530
1531 2
            if ($b[1] === '*/*') {
1532
                return -1;
1533
            }
1534
1535 2
            $wa = $a[1][strlen($a[1]) - 1] === '*';
1536 2
            $wb = $b[1][strlen($b[1]) - 1] === '*';
1537 2
            if ($wa xor $wb) {
1538
                return $wa ? 1 : -1;
1539
            }
1540
1541 2
            return $a[0] > $b[0] ? 1 : -1;
1542 3
        });
1543
1544 3
        $result = [];
1545 3
        foreach ($accepts as $accept) {
1546 3
            $name = $accept['q'][1];
1547 3
            $accept['q'] = $accept['q'][2];
1548 3
            $result[$name] = $accept;
1549
        }
1550
1551 3
        return $result;
1552
    }
1553
1554
    /**
1555
     * Returns the user-preferred language that should be used by this application.
1556
     * The language resolution is based on the user preferred languages and the languages
1557
     * supported by the application. The method will try to find the best match.
1558
     * @param array $languages a list of the languages supported by the application. If this is empty, the current
1559
     * application language will be returned without further processing.
1560
     * @return string the language that the application should use.
1561
     */
1562 1
    public function getPreferredLanguage(array $languages = [])
1563
    {
1564 1
        if (empty($languages)) {
1565 1
            return Yii::$app->language;
1566
        }
1567 1
        foreach ($this->getAcceptableLanguages() as $acceptableLanguage) {
1568 1
            $acceptableLanguage = str_replace('_', '-', strtolower($acceptableLanguage));
1569 1
            foreach ($languages as $language) {
1570 1
                $normalizedLanguage = str_replace('_', '-', strtolower($language));
1571
1572
                if (
1573 1
                    $normalizedLanguage === $acceptableLanguage // en-us==en-us
1574 1
                    || strpos($acceptableLanguage, $normalizedLanguage . '-') === 0 // en==en-us
1575 1
                    || strpos($normalizedLanguage, $acceptableLanguage . '-') === 0 // en-us==en
1576
                ) {
1577 1
                    return $language;
1578
                }
1579
            }
1580
        }
1581
1582 1
        return reset($languages);
1583
    }
1584
1585
    /**
1586
     * Gets the Etags.
1587
     *
1588
     * @return array The entity tags
1589
     */
1590
    public function getETags()
1591
    {
1592
        if ($this->headers->has('If-None-Match')) {
1593
            return preg_split('/[\s,]+/', str_replace('-gzip', '', $this->headers->get('If-None-Match')), -1, PREG_SPLIT_NO_EMPTY);
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_split('/[\s,...eb\PREG_SPLIT_NO_EMPTY) could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
1594
        }
1595
1596
        return [];
1597
    }
1598
1599
    /**
1600
     * Returns the cookie collection.
1601
     *
1602
     * Through the returned cookie collection, you may access a cookie using the following syntax:
1603
     *
1604
     * ```php
1605
     * $cookie = $request->cookies['name']
1606
     * if ($cookie !== null) {
1607
     *     $value = $cookie->value;
1608
     * }
1609
     *
1610
     * // alternatively
1611
     * $value = $request->cookies->getValue('name');
1612
     * ```
1613
     *
1614
     * @return CookieCollection the cookie collection.
1615
     */
1616 70
    public function getCookies()
1617
    {
1618 70
        if ($this->_cookies === null) {
1619 70
            $this->_cookies = new CookieCollection($this->loadCookies(), [
1620 69
                'readOnly' => true,
1621
            ]);
1622
        }
1623
1624 69
        return $this->_cookies;
1625
    }
1626
1627
    /**
1628
     * Converts `$_COOKIE` into an array of [[Cookie]].
1629
     * @return array the cookies obtained from request
1630
     * @throws InvalidConfigException if [[cookieValidationKey]] is not set when [[enableCookieValidation]] is true
1631
     */
1632 70
    protected function loadCookies()
1633
    {
1634 70
        $cookies = [];
1635 70
        if ($this->enableCookieValidation) {
1636 69
            if ($this->cookieValidationKey == '') {
1637 1
                throw new InvalidConfigException(get_class($this) . '::cookieValidationKey must be configured with a secret key.');
1638
            }
1639 68
            foreach ($_COOKIE as $name => $value) {
1640
                if (!is_string($value)) {
1641
                    continue;
1642
                }
1643
                $data = Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey);
1644
                if ($data === false) {
1645
                    continue;
1646
                }
1647
                if (defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 70000) {
1648
                    $data = @unserialize($data, ['allowed_classes' => false]);
1649
                } else {
1650
                    $data = @unserialize($data);
1651
                }
1652
                if (is_array($data) && isset($data[0], $data[1]) && $data[0] === $name) {
1653
                    $cookies[$name] = Yii::createObject([
1654 68
                        'class' => 'yii\web\Cookie',
1655
                        'name' => $name,
1656
                        'value' => $data[1],
1657
                        'expire' => null,
1658
                    ]);
1659
                }
1660
            }
1661
        } else {
1662 1
            foreach ($_COOKIE as $name => $value) {
1663 1
                $cookies[$name] = Yii::createObject([
1664 1
                    'class' => 'yii\web\Cookie',
1665 1
                    'name' => $name,
1666 1
                    'value' => $value,
1667
                    'expire' => null,
1668
                ]);
1669
            }
1670
        }
1671
1672 69
        return $cookies;
1673
    }
1674
1675
    private $_csrfToken;
1676
1677
    /**
1678
     * Returns the token used to perform CSRF validation.
1679
     *
1680
     * This token is generated in a way to prevent [BREACH attacks](http://breachattack.com/). It may be passed
1681
     * along via a hidden field of an HTML form or an HTTP header value to support CSRF validation.
1682
     * @param bool $regenerate whether to regenerate CSRF token. When this parameter is true, each time
1683
     * this method is called, a new CSRF token will be generated and persisted (in session or cookie).
1684
     * @return string the token used to perform CSRF validation.
1685
     */
1686 76
    public function getCsrfToken($regenerate = false)
1687
    {
1688 76
        if ($this->_csrfToken === null || $regenerate) {
1689 76
            $token = $this->loadCsrfToken();
1690 75
            if ($regenerate || empty($token)) {
1691 72
                $token = $this->generateCsrfToken();
1692
            }
1693 75
            $this->_csrfToken = Yii::$app->security->maskToken($token);
1694
        }
1695
1696 75
        return $this->_csrfToken;
1697
    }
1698
1699
    /**
1700
     * Loads the CSRF token from cookie or session.
1701
     * @return string the CSRF token loaded from cookie or session. Null is returned if the cookie or session
1702
     * does not have CSRF token.
1703
     */
1704 76
    protected function loadCsrfToken()
1705
    {
1706 76
        if ($this->enableCsrfCookie) {
1707 72
            return $this->getCookies()->getValue($this->csrfParam);
1708
        }
1709
1710 4
        return Yii::$app->getSession()->get($this->csrfParam);
0 ignored issues
show
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

1710
        return Yii::$app->/** @scrutinizer ignore-call */ getSession()->get($this->csrfParam);
Loading history...
1711
    }
1712
1713
    /**
1714
     * Generates an unmasked random token used to perform CSRF validation.
1715
     * @return string the random token for CSRF validation.
1716
     */
1717 72
    protected function generateCsrfToken()
1718
    {
1719 72
        $token = Yii::$app->getSecurity()->generateRandomString();
1720 72
        if ($this->enableCsrfCookie) {
1721 71
            $cookie = $this->createCsrfCookie($token);
1722 71
            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

1722
            Yii::$app->getResponse()->/** @scrutinizer ignore-call */ getCookies()->add($cookie);
Loading history...
1723
        } else {
1724 1
            Yii::$app->getSession()->set($this->csrfParam, $token);
1725
        }
1726
1727 72
        return $token;
1728
    }
1729
1730
    /**
1731
     * @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent.
1732
     */
1733 3
    public function getCsrfTokenFromHeader()
1734
    {
1735 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 string.
Loading history...
1736
    }
1737
1738
    /**
1739
     * Creates a cookie with a randomly generated CSRF token.
1740
     * Initial values specified in [[csrfCookie]] will be applied to the generated cookie.
1741
     * @param string $token the CSRF token
1742
     * @return Cookie the generated cookie
1743
     * @see enableCsrfValidation
1744
     */
1745 71
    protected function createCsrfCookie($token)
1746
    {
1747 71
        $options = $this->csrfCookie;
1748 71
        return Yii::createObject(array_merge($options, [
1749 71
            'class' => 'yii\web\Cookie',
1750 71
            'name' => $this->csrfParam,
1751 71
            'value' => $token,
1752
        ]));
1753
    }
1754
1755
    /**
1756
     * Performs the CSRF validation.
1757
     *
1758
     * This method will validate the user-provided CSRF token by comparing it with the one stored in cookie or session.
1759
     * This method is mainly called in [[Controller::beforeAction()]].
1760
     *
1761
     * Note that the method will NOT perform CSRF validation if [[enableCsrfValidation]] is false or the HTTP method
1762
     * is among GET, HEAD or OPTIONS.
1763
     *
1764
     * @param string $clientSuppliedToken the user-provided CSRF token to be validated. If null, the token will be retrieved from
1765
     * the [[csrfParam]] POST field or HTTP header.
1766
     * This parameter is available since version 2.0.4.
1767
     * @return bool whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true.
1768
     */
1769 7
    public function validateCsrfToken($clientSuppliedToken = null)
1770
    {
1771 7
        $method = $this->getMethod();
1772
        // only validate CSRF token on non-"safe" methods https://tools.ietf.org/html/rfc2616#section-9.1.1
1773 7
        if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
1774 6
            return true;
1775
        }
1776
1777 4
        $trueToken = $this->getCsrfToken();
1778
1779 4
        if ($clientSuppliedToken !== null) {
1780 2
            return $this->validateCsrfTokenInternal($clientSuppliedToken, $trueToken);
1781
        }
1782
1783 3
        return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
1784 3
            || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
1785
    }
1786
1787
    /**
1788
     * Validates CSRF token.
1789
     *
1790
     * @param string $clientSuppliedToken The masked client-supplied token.
1791
     * @param string $trueToken The masked true token.
1792
     * @return bool
1793
     */
1794 4
    private function validateCsrfTokenInternal($clientSuppliedToken, $trueToken)
1795
    {
1796 4
        if (!is_string($clientSuppliedToken)) {
0 ignored issues
show
introduced by
The condition is_string($clientSuppliedToken) is always true.
Loading history...
1797 3
            return false;
1798
        }
1799
1800 4
        $security = Yii::$app->security;
1801
1802 4
        return $security->compareString($security->unmaskToken($clientSuppliedToken), $security->unmaskToken($trueToken));
1803
    }
1804
}
1805