Passed
Push — 17932-fix-regression-in-ajax-r... ( 0f46a3 )
by Alexander
10:18
created

Request::getRawBody()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 7
ccs 4
cts 4
cp 1
crap 2
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 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
     * If the list contains the `Forwarded` header, processing will be done according to RFC 7239.
216
     * The match of header names is case-insensitive.
217
     * @see https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
218
     * @see https://tools.ietf.org/html/rfc7239
219
     * @see $trustedHosts
220
     * @since 2.0.13
221
     */
222
    public $secureHeaders = [
223
        // Common:
224
        'X-Forwarded-For',
225
        'X-Forwarded-Host',
226
        'X-Forwarded-Proto',
227
228
        // Microsoft:
229
        'Front-End-Https',
230
        'X-Rewrite-Url',
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 231
    protected function filterHeaders(HeaderCollection $headerCollection)
296
    {
297 231
        $trustedHeaders = $this->getTrustedHeaders();
298
299
        // remove all secure headers unless they are trusted
300 231
        foreach ($this->secureHeaders as $secureHeader) {
301 231
            if (!in_array($secureHeader, $trustedHeaders)) {
302 231
                $headerCollection->remove($secureHeader);
303
            }
304
        }
305 231
    }
306
307
    /**
308
     * Trusted headers according to the [[trustedHosts]].
309
     * @return array
310
     * @since 2.0.28
311
     */
312 231
    protected function getTrustedHeaders()
313
    {
314
        // do not trust any of the [[secureHeaders]] by default
315 231
        $trustedHeaders = [];
316
317
        // check if the client is a trusted host
318 231
        if (!empty($this->trustedHosts)) {
319 73
            $validator = $this->getIpValidator();
320 73
            $ip = $this->getRemoteIP();
321 73
            foreach ($this->trustedHosts as $cidr => $headers) {
322 73
                if (!is_array($headers)) {
323 71
                    $cidr = $headers;
324 71
                    $headers = $this->secureHeaders;
325
                }
326 73
                $validator->setRanges($cidr);
327 73
                if ($validator->validate($ip)) {
328 38
                    $trustedHeaders = $headers;
329 73
                    break;
330
                }
331
            }
332
        }
333 231
        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 154
    protected function getIpValidator()
344
    {
345 154
        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 231
    public function getHeaders()
354
    {
355 231
        if ($this->_headers === null) {
356 231
            $this->_headers = new HeaderCollection();
357 231
            if (function_exists('getallheaders')) {
358
                $headers = getallheaders();
359
                foreach ($headers as $name => $value) {
360
                    $this->_headers->add($name, $value);
361
                }
362 231
            } 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 231
                foreach ($_SERVER as $name => $value) {
369 227
                    if (strncmp($name, 'HTTP_', 5) === 0) {
370 91
                        $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
371 227
                        $this->_headers->add($name, $value);
372
                    }
373
                }
374
            }
375 231
            $this->filterHeaders($this->_headers);
376
        }
377
378 231
        return $this->_headers;
379
    }
380
381
    /**
382
     * Returns the method of the current request (e.g. GET, POST, HEAD, PUT, PATCH, DELETE).
383
     * @return string request method, such as GET, POST, HEAD, PUT, PATCH, DELETE.
384
     * The value returned is turned into upper case.
385
     */
386 40
    public function getMethod()
387
    {
388
        if (
389 40
            isset($_POST[$this->methodParam])
390
            // Never allow to downgrade request from WRITE methods (POST, PATCH, DELETE, etc)
391
            // to read methods (GET, HEAD, OPTIONS) for security reasons.
392 40
            && !in_array(strtoupper($_POST[$this->methodParam]), ['GET', 'HEAD', 'OPTIONS'], true)
393
        ) {
394 6
            return strtoupper($_POST[$this->methodParam]);
395
        }
396
397 38
        if ($this->headers->has('X-Http-Method-Override')) {
398 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

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

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

743
                $this->_hostInfo = $http . '://' . trim(explode(',', /** @scrutinizer ignore-type */ $this->headers->get('X-Forwarded-Host'))[0]);
Loading history...
744 32
            } elseif ($this->headers->has('Host')) {
745 11
                $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

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

1091
                    if (strcasecmp(/** @scrutinizer ignore-type */ $headerValue, $value) === 0) {
Loading history...
1092 60
                        return true;
1093
                    }
1094
                }
1095
            }
1096
        }
1097
1098 57
        return false;
1099
    }
1100
1101
    /**
1102
     * Returns the server name.
1103
     * @return string server name, null if not available
1104
     */
1105 1
    public function getServerName()
1106
    {
1107 1
        return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null;
1108
    }
1109
1110
    /**
1111
     * Returns the server port number.
1112
     * @return int|null server port number, null if not available
1113
     */
1114 2
    public function getServerPort()
1115
    {
1116 2
        return isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : null;
1117
    }
1118
1119
    /**
1120
     * Returns the URL referrer.
1121
     * @return string|null URL referrer, null if not available
1122
     */
1123
    public function getReferrer()
1124
    {
1125
        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...
1126
    }
1127
1128
    /**
1129
     * Returns the URL origin of a CORS request.
1130
     *
1131
     * The return value is taken from the `Origin` [[getHeaders()|header]] sent by the browser.
1132
     *
1133
     * Note that the origin request header indicates where a fetch originates from.
1134
     * It doesn't include any path information, but only the server name.
1135
     * It is sent with a CORS requests, as well as with POST requests.
1136
     * It is similar to the referer header, but, unlike this header, it doesn't disclose the whole path.
1137
     * Please refer to <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin> for more information.
1138
     *
1139
     * @return string|null URL origin of a CORS request, `null` if not available.
1140
     * @see getHeaders()
1141
     * @since 2.0.13
1142
     */
1143 1
    public function getOrigin()
1144
    {
1145 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...
1146
    }
1147
1148
    /**
1149
     * Returns the user agent.
1150
     * @return string|null user agent, null if not available
1151
     */
1152 1
    public function getUserAgent()
1153
    {
1154 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...
1155
    }
1156
1157
    /**
1158
     * Returns the user IP address from [[ipHeaders]].
1159
     * @return string|null user IP address, null if not available
1160
     * @see $ipHeaders
1161
     * @since 2.0.28
1162
     */
1163 95
    protected function getUserIpFromIpHeaders()
1164
    {
1165 95
        $ip = $this->getSecureForwardedHeaderTrustedPart('for');
1166 95
        if ($ip !== null && preg_match(
1167 14
            '/^\[?(?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]+))?$/',
1168 14
            $ip,
1169 95
            $matches
1170
        )) {
1171 14
            $ip = $this->getUserIpFromIpHeader($matches['ip']);
1172 14
            if ($ip !== null) {
1173 14
                return $ip;
1174
            }
1175
        }
1176
1177
1178 81
        foreach ($this->ipHeaders as $ipHeader) {
1179 78
            if ($this->headers->has($ipHeader)) {
1180 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

1180
                $ip = $this->getUserIpFromIpHeader(/** @scrutinizer ignore-type */ $this->headers->get($ipHeader));
Loading history...
1181 10
                if ($ip !== null) {
1182 78
                    return $ip;
1183
                }
1184
            }
1185
        }
1186 71
        return null;
1187
    }
1188
1189
    /**
1190
     * Returns the user IP address.
1191
     * The IP is determined using headers and / or `$_SERVER` variables.
1192
     * @return string|null user IP address, null if not available
1193
     */
1194 95
    public function getUserIP()
1195
    {
1196 95
        $ip = $this->getUserIpFromIpHeaders();
1197 95
        return $ip === null ? $this->getRemoteIP() : $ip;
1198
    }
1199
1200
    /**
1201
     * Return user IP's from IP header.
1202
     *
1203
     * @param string $ips comma separated IP list
1204
     * @return string|null IP as string. Null is returned if IP can not be determined from header.
1205
     * @see $getUserHost
1206
     * @see $ipHeader
1207
     * @see $trustedHeaders
1208
     * @since 2.0.28
1209
     */
1210 24
    protected function getUserIpFromIpHeader($ips)
1211
    {
1212 24
        $ips = trim($ips);
1213 24
        if ($ips === '') {
1214
            return null;
1215
        }
1216 24
        $ips = preg_split('/\s*,\s*/', $ips, -1, PREG_SPLIT_NO_EMPTY);
1217 24
        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

1217
        krsort(/** @scrutinizer ignore-type */ $ips);
Loading history...
1218 24
        $validator = $this->getIpValidator();
1219 24
        $resultIp = null;
1220 24
        foreach ($ips as $ip) {
1221 24
            $validator->setRanges('any');
1222 24
            if (!$validator->validate($ip) /* checking IP format */) {
1223 1
                break;
1224
            }
1225 24
            $resultIp = $ip;
1226 24
            $isTrusted = false;
1227 24
            foreach ($this->trustedHosts as $trustedCidr => $trustedCidrOrHeaders) {
1228 24
                if (!is_array($trustedCidrOrHeaders)) {
1229 24
                    $trustedCidr = $trustedCidrOrHeaders;
1230
                }
1231 24
                $validator->setRanges($trustedCidr);
1232 24
                if ($validator->validate($ip) /* checking trusted range */) {
1233 7
                    $isTrusted = true;
1234 24
                    break;
1235
                }
1236
            }
1237 24
            if (!$isTrusted) {
1238 24
                break;
1239
            }
1240
        }
1241 24
        return $resultIp;
1242
    }
1243
1244
    /**
1245
     * Returns the user host name.
1246
     * The HOST is determined using headers and / or `$_SERVER` variables.
1247
     * @return string|null user host name, null if not available
1248
     */
1249
    public function getUserHost()
1250
    {
1251
        $userIp = $this->getUserIpFromIpHeaders();
1252
        if($userIp === null) {
1253
            return $this->getRemoteHost();
1254
        }
1255
        return gethostbyaddr($userIp);
1256
    }
1257
1258
    /**
1259
     * Returns the IP on the other end of this connection.
1260
     * This is always the next hop, any headers are ignored.
1261
     * @return string|null remote IP address, `null` if not available.
1262
     * @since 2.0.13
1263
     */
1264 128
    public function getRemoteIP()
1265
    {
1266 128
        return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
1267
    }
1268
1269
    /**
1270
     * Returns the host name of the other end of this connection.
1271
     * This is always the next hop, any headers are ignored.
1272
     * @return string|null remote host name, `null` if not available
1273
     * @see getUserHost()
1274
     * @see getRemoteIP()
1275
     * @since 2.0.13
1276
     */
1277
    public function getRemoteHost()
1278
    {
1279
        return isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : null;
1280
    }
1281
1282
    /**
1283
     * @return string|null the username sent via HTTP authentication, `null` if the username is not given
1284
     * @see getAuthCredentials() to get both username and password in one call
1285
     */
1286 9
    public function getAuthUser()
1287
    {
1288 9
        return $this->getAuthCredentials()[0];
1289
    }
1290
1291
    /**
1292
     * @return string|null the password sent via HTTP authentication, `null` if the password is not given
1293
     * @see getAuthCredentials() to get both username and password in one call
1294
     */
1295 9
    public function getAuthPassword()
1296
    {
1297 9
        return $this->getAuthCredentials()[1];
1298
    }
1299
1300
    /**
1301
     * @return array that contains exactly two elements:
1302
     * - 0: the username sent via HTTP authentication, `null` if the username is not given
1303
     * - 1: the password sent via HTTP authentication, `null` if the password is not given
1304
     * @see getAuthUser() to get only username
1305
     * @see getAuthPassword() to get only password
1306
     * @since 2.0.13
1307
     */
1308 34
    public function getAuthCredentials()
1309
    {
1310 34
        $username = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
1311 34
        $password = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
1312 34
        if ($username !== null || $password !== null) {
1313 16
            return [$username, $password];
1314
        }
1315
1316
        /*
1317
         * Apache with php-cgi does not pass HTTP Basic authentication to PHP by default.
1318
         * To make it work, add the following line to to your .htaccess file:
1319
         *
1320
         * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
1321
         */
1322 18
        $auth_token = $this->getHeaders()->get('HTTP_AUTHORIZATION') ?: $this->getHeaders()->get('REDIRECT_HTTP_AUTHORIZATION');
1323 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

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

1326
            }, explode(':', base64_decode(mb_substr(/** @scrutinizer ignore-type */ $auth_token, 6)), 2));
Loading history...
1327
1328 18
            if (count($parts) < 2) {
1329 2
                return [$parts[0], null];
1330
            }
1331
1332 16
            return $parts;
1333
        }
1334
1335
        return [null, null];
1336
    }
1337
1338
    private $_port;
1339
1340
    /**
1341
     * Returns the port to use for insecure requests.
1342
     * Defaults to 80, or the port specified by the server if the current
1343
     * request is insecure.
1344
     * @return int port number for insecure requests.
1345
     * @see setPort()
1346
     */
1347 1
    public function getPort()
1348
    {
1349 1
        if ($this->_port === null) {
1350 1
            $serverPort = $this->getServerPort();
1351 1
            $this->_port = !$this->getIsSecureConnection() && $serverPort !== null ? $serverPort : 80;
1352
        }
1353
1354 1
        return $this->_port;
1355
    }
1356
1357
    /**
1358
     * Sets the port to use for insecure requests.
1359
     * This setter is provided in case a custom port is necessary for certain
1360
     * server configurations.
1361
     * @param int $value port number.
1362
     */
1363
    public function setPort($value)
1364
    {
1365
        if ($value != $this->_port) {
1366
            $this->_port = (int) $value;
1367
            $this->_hostInfo = null;
1368
        }
1369
    }
1370
1371
    private $_securePort;
1372
1373
    /**
1374
     * Returns the port to use for secure requests.
1375
     * Defaults to 443, or the port specified by the server if the current
1376
     * request is secure.
1377
     * @return int port number for secure requests.
1378
     * @see setSecurePort()
1379
     */
1380
    public function getSecurePort()
1381
    {
1382
        if ($this->_securePort === null) {
1383
            $serverPort = $this->getServerPort();
1384
            $this->_securePort = $this->getIsSecureConnection() && $serverPort !== null ? $serverPort : 443;
1385
        }
1386
1387
        return $this->_securePort;
1388
    }
1389
1390
    /**
1391
     * Sets the port to use for secure requests.
1392
     * This setter is provided in case a custom port is necessary for certain
1393
     * server configurations.
1394
     * @param int $value port number.
1395
     */
1396
    public function setSecurePort($value)
1397
    {
1398
        if ($value != $this->_securePort) {
1399
            $this->_securePort = (int) $value;
1400
            $this->_hostInfo = null;
1401
        }
1402
    }
1403
1404
    private $_contentTypes;
1405
1406
    /**
1407
     * Returns the content types acceptable by the end user.
1408
     *
1409
     * This is determined by the `Accept` HTTP header. For example,
1410
     *
1411
     * ```php
1412
     * $_SERVER['HTTP_ACCEPT'] = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1413
     * $types = $request->getAcceptableContentTypes();
1414
     * print_r($types);
1415
     * // displays:
1416
     * // [
1417
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1418
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1419
     * //           'text/plain' => ['q' => 0.5],
1420
     * // ]
1421
     * ```
1422
     *
1423
     * @return array the content types ordered by the quality score. Types with the highest scores
1424
     * will be returned first. The array keys are the content types, while the array values
1425
     * are the corresponding quality score and other parameters as given in the header.
1426
     */
1427 4
    public function getAcceptableContentTypes()
1428
    {
1429 4
        if ($this->_contentTypes === null) {
1430 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...
1431 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

1431
                $this->_contentTypes = $this->parseAcceptHeader(/** @scrutinizer ignore-type */ $this->headers->get('Accept'));
Loading history...
1432
            } else {
1433 2
                $this->_contentTypes = [];
1434
            }
1435
        }
1436
1437 4
        return $this->_contentTypes;
1438
    }
1439
1440
    /**
1441
     * Sets the acceptable content types.
1442
     * Please refer to [[getAcceptableContentTypes()]] on the format of the parameter.
1443
     * @param array $value the content types that are acceptable by the end user. They should
1444
     * be ordered by the preference level.
1445
     * @see getAcceptableContentTypes()
1446
     * @see parseAcceptHeader()
1447
     */
1448 1
    public function setAcceptableContentTypes($value)
1449
    {
1450 1
        $this->_contentTypes = $value;
1451 1
    }
1452
1453
    /**
1454
     * Returns request content-type
1455
     * The Content-Type header field indicates the MIME type of the data
1456
     * contained in [[getRawBody()]] or, in the case of the HEAD method, the
1457
     * media type that would have been sent had the request been a GET.
1458
     * For the MIME-types the user expects in response, see [[acceptableContentTypes]].
1459
     * @return string request content-type. Null is returned if this information is not available.
1460
     * @link https://tools.ietf.org/html/rfc2616#section-14.17
1461
     * HTTP 1.1 header field definitions
1462
     */
1463 3
    public function getContentType()
1464
    {
1465 3
        if (isset($_SERVER['CONTENT_TYPE'])) {
1466
            return $_SERVER['CONTENT_TYPE'];
1467
        }
1468
1469
        //fix bug https://bugs.php.net/bug.php?id=66606
1470 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...
1471
    }
1472
1473
    private $_languages;
1474
1475
    /**
1476
     * Returns the languages acceptable by the end user.
1477
     * This is determined by the `Accept-Language` HTTP header.
1478
     * @return array the languages ordered by the preference level. The first element
1479
     * represents the most preferred language.
1480
     */
1481 2
    public function getAcceptableLanguages()
1482
    {
1483 2
        if ($this->_languages === null) {
1484 1
            if ($this->headers->has('Accept-Language')) {
1485
                $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

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

1751
        return Yii::$app->/** @scrutinizer ignore-call */ getSession()->get($this->csrfParam);
Loading history...
1752
    }
1753
1754
    /**
1755
     * Generates an unmasked random token used to perform CSRF validation.
1756
     * @return string the random token for CSRF validation.
1757
     */
1758 73
    protected function generateCsrfToken()
1759
    {
1760 73
        $token = Yii::$app->getSecurity()->generateRandomString();
1761 73
        if ($this->enableCsrfCookie) {
1762 72
            $cookie = $this->createCsrfCookie($token);
1763 72
            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

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