Passed
Push — 12539-fix-accept-response-code ( 9f381c )
by Alexander
86:02 queued 79:15
created

Request   F

Complexity

Total Complexity 273

Size/Duplication

Total Lines 1828
Duplicated Lines 0 %

Test Coverage

Coverage 75.23%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 510
dl 0
loc 1828
ccs 410
cts 545
cp 0.7523
rs 2
c 2
b 0
f 0
wmc 273

83 Methods

Rating   Name   Duplication   Size   Complexity  
A setScriptFile() 0 3 1
A getServerPort() 0 3 2
A getIsPut() 0 3 1
A getUserAgent() 0 3 1
A setScriptUrl() 0 3 2
A getIsOptions() 0 3 1
A getIsPost() 0 3 1
C getScriptUrl() 0 21 12
A getIsFlash() 0 5 2
A getOrigin() 0 3 1
A utf8Encode() 0 12 4
A getIsHead() 0 3 1
A getServerName() 0 3 2
B getIsSecureConnection() 0 21 9
C getHostInfo() 0 22 12
A get() 0 7 2
A getQueryParams() 0 7 2
A getTrustedHeaders() 0 22 5
A resolve() 0 15 3
A getQueryString() 0 3 2
A getBodyParam() 0 14 4
A getBaseUrl() 0 7 2
A filterHeaders() 0 8 3
A getIsDelete() 0 3 1
B resolvePathInfo() 0 43 9
B getHeaders() 0 26 8
A post() 0 7 2
B resolveRequestUri() 0 19 7
A setPathInfo() 0 3 2
A setHostInfo() 0 4 2
A setQueryParams() 0 3 1
B getBodyParams() 0 39 9
A getIsPjax() 0 3 2
A getHostName() 0 7 2
A getScriptFile() 0 11 3
A setBaseUrl() 0 3 1
A setUrl() 0 3 1
A getUrl() 0 7 2
A getIsAjax() 0 3 1
A getRawBody() 0 7 2
A getReferrer() 0 3 1
A getPathInfo() 0 7 2
A setRawBody() 0 3 1
A getIsGet() 0 3 1
A getAbsoluteUrl() 0 3 1
A getQueryParam() 0 5 2
A getIpValidator() 0 3 1
A getIsPatch() 0 3 1
A getMethod() 0 20 5
A setBodyParams() 0 3 1
A getAuthPassword() 0 3 1
A setSecurePort() 0 5 2
A setAcceptableLanguages() 0 3 1
A getSecurePort() 0 8 4
A getRemoteIP() 0 3 2
B getPreferredLanguage() 0 21 7
B getUserIpFromIpHeaders() 0 24 7
A getPort() 0 8 4
B getAuthCredentials() 0 28 10
A validateCsrfTokenInternal() 0 9 2
A createCsrfCookie() 0 7 1
A getAcceptableContentTypes() 0 11 3
A getETags() 0 7 2
A generateCsrfToken() 0 11 2
A getContentType() 0 8 2
A getUserIP() 0 4 2
A getCsrfToken() 0 11 5
A loadCsrfToken() 0 7 2
B getUserIpFromIpHeader() 0 32 8
A getUserHost() 0 7 2
A getAcceptableLanguages() 0 11 3
A getRemoteHost() 0 3 2
A validateCsrfToken() 0 16 5
A getAuthUser() 0 3 1
A setPort() 0 5 2
A getCookies() 0 9 2
C loadCookies() 0 41 12
A getCsrfTokenFromHeader() 0 3 1
C parseAcceptHeader() 0 66 16
A getSecureForwardedHeaderTrustedParts() 0 7 2
B getSecureForwardedHeaderParts() 0 38 7
A getSecureForwardedHeaderTrustedPart() 0 11 4
A setAcceptableContentTypes() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Request often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Request, and based on these observations, apply Extract Interface, too.

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 226
    protected function filterHeaders(HeaderCollection $headerCollection)
296
    {
297 226
        $trustedHeaders = $this->getTrustedHeaders();
298
299
        // remove all secure headers unless they are trusted
300 226
        foreach ($this->secureHeaders as $secureHeader) {
301 226
            if (!in_array($secureHeader, $trustedHeaders)) {
302 226
                $headerCollection->remove($secureHeader);
303
            }
304
        }
305 226
    }
306
307
    /**
308
     * Trusted headers according to the [[trustedHosts]].
309
     * @return array
310
     * @since 2.0.28
311
     */
312 226
    protected function getTrustedHeaders()
313
    {
314
        // do not trust any of the [[secureHeaders]] by default
315 226
        $trustedHeaders = [];
316
317
        // check if the client is a trusted host
318 226
        if (!empty($this->trustedHosts)) {
319 71
            $validator = $this->getIpValidator();
320 71
            $ip = $this->getRemoteIP();
321 71
            foreach ($this->trustedHosts as $cidr => $headers) {
322 71
                if (!is_array($headers)) {
323 71
                    $cidr = $headers;
324 71
                    $headers = $this->secureHeaders;
325
                }
326 71
                $validator->setRanges($cidr);
327 71
                if ($validator->validate($ip)) {
328 36
                    $trustedHeaders = $headers;
329 71
                    break;
330
                }
331
            }
332
        }
333 226
        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 151
    protected function getIpValidator()
344
    {
345 151
        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 226
    public function getHeaders()
354
    {
355 226
        if ($this->_headers === null) {
356 226
            $this->_headers = new HeaderCollection();
357 226
            if (function_exists('getallheaders')) {
358
                $headers = getallheaders();
359
                foreach ($headers as $name => $value) {
360
                    $this->_headers->add($name, $value);
361
                }
362 226
            } 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 226
                foreach ($_SERVER as $name => $value) {
369 222
                    if (strncmp($name, 'HTTP_', 5) === 0) {
370 89
                        $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
371 222
                        $this->_headers->add($name, $value);
372
                    }
373
                }
374
            }
375 226
            $this->filterHeaders($this->_headers);
376
        }
377
378 226
        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 38
    public function getMethod()
387
    {
388
        if (
389 38
            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 38
            && !in_array(strtoupper($_POST[$this->methodParam]), ['GET', 'HEAD', 'OPTIONS'], true)
393
        ) {
394 6
            return strtoupper($_POST[$this->methodParam]);
395
        }
396
397 36
        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 35
        if (isset($_SERVER['REQUEST_METHOD'])) {
402 10
            return strtoupper($_SERVER['REQUEST_METHOD']);
403
        }
404
405 26
        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 11
    public function getIsHead()
431
    {
432 11
        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 jQuery doesn't set the header in case of cross domain
475
     * requests: https://stackoverflow.com/questions/8163703/cross-domain-ajax-doesnt-send-x-requested-with-header
476
     *
477
     * @return bool whether this is an AJAX (XMLHttpRequest) request.
478
     */
479 16
    public function getIsAjax()
480
    {
481 16
        return $this->headers->get('X-Requested-With') === 'XMLHttpRequest';
482
    }
483
484
    /**
485
     * Returns whether this is a PJAX request.
486
     * @return bool whether this is a PJAX request
487
     */
488 3
    public function getIsPjax()
489
    {
490 3
        return $this->getIsAjax() && $this->headers->has('X-Pjax');
491
    }
492
493
    /**
494
     * Returns whether this is an Adobe Flash or Flex request.
495
     * @return bool whether this is an Adobe Flash or Adobe Flex request.
496
     */
497
    public function getIsFlash()
498
    {
499
        $userAgent = $this->headers->get('User-Agent', '');
500
        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

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

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

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

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

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

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

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

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

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

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

1742
        return Yii::$app->/** @scrutinizer ignore-call */ getSession()->get($this->csrfParam);
Loading history...
1743
    }
1744
1745
    /**
1746
     * Generates an unmasked random token used to perform CSRF validation.
1747
     * @return string the random token for CSRF validation.
1748
     */
1749 72
    protected function generateCsrfToken()
1750
    {
1751 72
        $token = Yii::$app->getSecurity()->generateRandomString();
1752 72
        if ($this->enableCsrfCookie) {
1753 71
            $cookie = $this->createCsrfCookie($token);
1754 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

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