Completed
Push — master ( 353cc7...e4eacc )
by Dmitry
13:48
created

Request::filterHeaders()   B

Complexity

Conditions 7
Paths 30

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 8.5226
c 0
b 0
f 0
ccs 17
cts 17
cp 1
cc 7
nc 30
nop 1
crap 7
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\web;
9
10
use Yii;
11
use yii\base\InvalidConfigException;
12
use yii\validators\IpValidator;
13
14
/**
15
 * The web Request class represents an HTTP request.
16
 *
17
 * It encapsulates the $_SERVER variable and resolves its inconsistency among different Web servers.
18
 * Also it provides an interface to retrieve request parameters from $_POST, $_GET, $_COOKIES and REST
19
 * parameters sent via other HTTP methods like PUT or DELETE.
20
 *
21
 * Request is configured as an application component in [[\yii\web\Application]] by default.
22
 * You can access that instance via `Yii::$app->request`.
23
 *
24
 * For more details and usage information on Request, see the [guide article on requests](guide:runtime-requests).
25
 *
26
 * @property string $absoluteUrl The currently requested absolute URL. This property is read-only.
27
 * @property array $acceptableContentTypes The content types ordered by the quality score. Types with the
28
 * highest scores will be returned first. The array keys are the content types, while the array values are the
29
 * corresponding quality score and other parameters as given in the header.
30
 * @property array $acceptableLanguages The languages ordered by the preference level. The first element
31
 * represents the most preferred language.
32
 * @property array $authCredentials That contains exactly two elements: - 0: the username sent via HTTP
33
 * authentication, `null` if the username is not given - 1: the password sent via HTTP authentication, `null` if
34
 * the password is not given. This property is read-only.
35
 * @property string|null $authPassword The password sent via HTTP authentication, `null` if the password is
36
 * not given. This property is read-only.
37
 * @property string|null $authUser The username sent via HTTP authentication, `null` if the username is not
38
 * given. This property is read-only.
39
 * @property string $baseUrl The relative URL for the application.
40
 * @property array $bodyParams The request parameters given in the request body.
41
 * @property string $contentType Request content-type. Null is returned if this information is not available.
42
 * This property is read-only.
43
 * @property CookieCollection $cookies The cookie collection. This property is read-only.
44
 * @property string $csrfToken The token used to perform CSRF validation. This property is read-only.
45
 * @property string $csrfTokenFromHeader The CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned
46
 * if no such header is sent. This property is read-only.
47
 * @property array $eTags The entity tags. This property is read-only.
48
 * @property HeaderCollection $headers The header collection. This property is read-only.
49
 * @property string|null $hostInfo Schema and hostname part (with port number if needed) of the request URL
50
 * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set. See
51
 * [[getHostInfo()]] for security related notes on this property.
52
 * @property string|null $hostName Hostname part of the request URL (e.g. `www.yiiframework.com`). This
53
 * property is read-only.
54
 * @property bool $isAjax Whether this is an AJAX (XMLHttpRequest) request. This property is read-only.
55
 * @property bool $isDelete Whether this is a DELETE request. This property is read-only.
56
 * @property bool $isFlash Whether this is an Adobe Flash or Adobe Flex request. This property is read-only.
57
 * @property bool $isGet Whether this is a GET request. This property is read-only.
58
 * @property bool $isHead Whether this is a HEAD request. This property is read-only.
59
 * @property bool $isOptions Whether this is a OPTIONS request. This property is read-only.
60
 * @property bool $isPatch Whether this is a PATCH request. This property is read-only.
61
 * @property bool $isPjax Whether this is a PJAX request. This property is read-only.
62
 * @property bool $isPost Whether this is a POST request. This property is read-only.
63
 * @property bool $isPut Whether this is a PUT request. This property is read-only.
64
 * @property bool $isSecureConnection If the request is sent via secure channel (https). This property is
65
 * read-only.
66
 * @property string $method Request method, such as GET, POST, HEAD, PUT, PATCH, DELETE. The value returned is
67
 * turned into upper case. This property is read-only.
68
 * @property string|null $origin URL origin of a CORS request, `null` if not available. This property is
69
 * read-only.
70
 * @property string $pathInfo Part of the request URL that is after the entry script and before the question
71
 * mark. Note, the returned path info is already URL-decoded.
72
 * @property int $port Port number for insecure requests.
73
 * @property array $queryParams The request GET parameter values.
74
 * @property string $queryString Part of the request URL that is after the question mark. This property is
75
 * read-only.
76
 * @property string $rawBody The request body.
77
 * @property string|null $referrer URL referrer, null if not available. This property is read-only.
78
 * @property string|null $remoteHost Remote host name, `null` if not available. This property is read-only.
79
 * @property string|null $remoteIP Remote IP address, `null` if not available. This property is read-only.
80
 * @property string $scriptFile The entry script file path.
81
 * @property string $scriptUrl The relative URL of the entry script.
82
 * @property int $securePort Port number for secure requests.
83
 * @property string $serverName Server name, null if not available. This property is read-only.
84
 * @property int|null $serverPort Server port number, null if not available. This property is read-only.
85
 * @property string $url The currently requested relative URL. Note that the URI returned may be URL-encoded
86
 * depending on the client.
87
 * @property string|null $userAgent User agent, null if not available. This property is read-only.
88
 * @property string|null $userHost User host name, null if not available. This property is read-only.
89
 * @property string|null $userIP User IP address, null if not available. This property is read-only.
90
 *
91
 * @author Qiang Xue <[email protected]>
92
 * @since 2.0
93
 * @SuppressWarnings(PHPMD.SuperGlobals)
94
 */
95
class Request extends \yii\base\Request
96
{
97
    /**
98
     * The name of the HTTP header for sending CSRF token.
99
     */
100
    const CSRF_HEADER = 'X-CSRF-Token';
101
    /**
102
     * The length of the CSRF token mask.
103
     * @deprecated since 2.0.12. The mask length is now equal to the token length.
104
     */
105
    const CSRF_MASK_LENGTH = 8;
106
107
    /**
108
     * @var bool whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to true.
109
     * When CSRF validation is enabled, forms submitted to an Yii Web application must be originated
110
     * from the same application. If not, a 400 HTTP exception will be raised.
111
     *
112
     * Note, this feature requires that the user client accepts cookie. Also, to use this feature,
113
     * forms submitted via POST method must contain a hidden input whose name is specified by [[csrfParam]].
114
     * You may use [[\yii\helpers\Html::beginForm()]] to generate his hidden input.
115
     *
116
     * In JavaScript, you may get the values of [[csrfParam]] and [[csrfToken]] via `yii.getCsrfParam()` and
117
     * `yii.getCsrfToken()`, respectively. The [[\yii\web\YiiAsset]] asset must be registered.
118
     * You also need to include CSRF meta tags in your pages by using [[\yii\helpers\Html::csrfMetaTags()]].
119
     *
120
     * @see Controller::enableCsrfValidation
121
     * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery
122
     */
123
    public $enableCsrfValidation = true;
124
    /**
125
     * @var string the name of the token used to prevent CSRF. Defaults to '_csrf'.
126
     * This property is used only when [[enableCsrfValidation]] is true.
127
     */
128
    public $csrfParam = '_csrf';
129
    /**
130
     * @var array the configuration for creating the CSRF [[Cookie|cookie]]. This property is used only when
131
     * both [[enableCsrfValidation]] and [[enableCsrfCookie]] are true.
132
     */
133
    public $csrfCookie = ['httpOnly' => true];
134
    /**
135
     * @var bool whether to use cookie to persist CSRF token. If false, CSRF token will be stored
136
     * in session under the name of [[csrfParam]]. Note that while storing CSRF tokens in session increases
137
     * security, it requires starting a session for every page, which will degrade your site performance.
138
     */
139
    public $enableCsrfCookie = true;
140
    /**
141
     * @var bool whether cookies should be validated to ensure they are not tampered. Defaults to true.
142
     */
143
    public $enableCookieValidation = true;
144
    /**
145
     * @var string a secret key used for cookie validation. This property must be set if [[enableCookieValidation]] is true.
146
     */
147
    public $cookieValidationKey;
148
    /**
149
     * @var string the name of the POST parameter that is used to indicate if a request is a PUT, PATCH or DELETE
150
     * request tunneled through POST. Defaults to '_method'.
151
     * @see getMethod()
152
     * @see getBodyParams()
153
     */
154
    public $methodParam = '_method';
155
    /**
156
     * @var array the parsers for converting the raw HTTP request body into [[bodyParams]].
157
     * The array keys are the request `Content-Types`, and the array values are the
158
     * corresponding configurations for [[Yii::createObject|creating the parser objects]].
159
     * A parser must implement the [[RequestParserInterface]].
160
     *
161
     * To enable parsing for JSON requests you can use the [[JsonParser]] class like in the following example:
162
     *
163
     * ```
164
     * [
165
     *     'application/json' => 'yii\web\JsonParser',
166
     * ]
167
     * ```
168
     *
169
     * To register a parser for parsing all request types you can use `'*'` as the array key.
170
     * This one will be used as a fallback in case no other types match.
171
     *
172
     * @see getBodyParams()
173
     */
174
    public $parsers = [];
175
    /**
176
     * @var array the configuration for trusted security related headers.
177
     *
178
     * An array key is an IPv4 or IPv6 IP address in CIDR notation for matching a client.
179
     *
180
     * An array value is a list of headers to trust. These will be matched against
181
     * [[secureHeaders]] to determine which headers are allowed to be sent by a specified host.
182
     * The case of the header names must be the same as specified in [[secureHeaders]].
183
     *
184
     * For example, to trust all headers listed in [[secureHeaders]] for IP addresses
185
     * in range `192.168.0.0-192.168.0.254` write the following:
186
     *
187
     * ```php
188
     * [
189
     *     '192.168.0.0/24',
190
     * ]
191
     * ```
192
     *
193
     * To trust just the `X-Forwarded-For` header from `10.0.0.1`, use:
194
     *
195
     * ```
196
     * [
197
     *     '10.0.0.1' => ['X-Forwarded-For']
198
     * ]
199
     * ```
200
     *
201
     * Default is to trust all headers except those listed in [[secureHeaders]] from all hosts.
202
     * Matches are tried in order and searching is stopped when IP matches.
203
     *
204
     * > Info: Matching is performed using [[IpValidator]].
205
     * See [[IpValidator::::setRanges()|IpValidator::setRanges()]]
206
     * and [[IpValidator::networks]] for advanced matching.
207
     *
208
     * @see $secureHeaders
209
     * @since 2.0.13
210
     */
211
    public $trustedHosts = [];
212
    /**
213
     * @var array lists of headers that are, by default, subject to the trusted host configuration.
214
     * These headers will be filtered unless explicitly allowed in [[trustedHosts]].
215
     * The match of header names is case-insensitive.
216
     * @see https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
217
     * @see $trustedHosts
218
     * @since 2.0.13
219
     */
220
    public $secureHeaders = [
221
        // Common:
222
        'X-Forwarded-For',
223
        'X-Forwarded-Host',
224
        'X-Forwarded-Proto',
225
226
        // Microsoft:
227
        'Front-End-Https',
228
        'X-Rewrite-Url',
229
    ];
230
    /**
231
     * @var string[] List of headers where proxies store the real client IP.
232
     * It's not advisable to put insecure headers here.
233
     * The match of header names is case-insensitive.
234
     * @see $trustedHosts
235
     * @see $secureHeaders
236
     * @since 2.0.13
237
     */
238
    public $ipHeaders = [
239
        'X-Forwarded-For', // Common
240
    ];
241
    /**
242
     * @var array list of headers to check for determining whether the connection is made via HTTPS.
243
     * The array keys are header names and the array value is a list of header values that indicate a secure connection.
244
     * The match of header names and values is case-insensitive.
245
     * It's not advisable to put insecure headers here.
246
     * @see $trustedHosts
247
     * @see $secureHeaders
248
     * @since 2.0.13
249
     */
250
    public $secureProtocolHeaders = [
251
        'X-Forwarded-Proto' => ['https'], // Common
252
        'Front-End-Https' => ['on'], // Microsoft
253
    ];
254
255
    /**
256
     * @var CookieCollection Collection of request cookies.
257
     */
258
    private $_cookies;
259
    /**
260
     * @var HeaderCollection Collection of request headers.
261
     */
262
    private $_headers;
263
264
265
    /**
266
     * Resolves the current request into a route and the associated parameters.
267
     * @return array the first element is the route, and the second is the associated parameters.
268
     * @throws NotFoundHttpException if the request cannot be resolved.
269
     */
270 1
    public function resolve()
271
    {
272 1
        $result = Yii::$app->getUrlManager()->parseRequest($this);
273 1
        if ($result !== false) {
274 1
            list($route, $params) = $result;
275 1
            if ($this->_queryParams === null) {
276 1
                $_GET = $params + $_GET; // preserve numeric keys
277
            } else {
278 1
                $this->_queryParams = $params + $this->_queryParams;
279
            }
280
281 1
            return [$route, $this->getQueryParams()];
282
        }
283
284
        throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
285
    }
286
287
    /**
288
     * Filters headers according to the [[trustedHosts]].
289
     * @param HeaderCollection $headerCollection
290
     * @since 2.0.13
291
     */
292 168
    protected function filterHeaders(HeaderCollection $headerCollection)
293
    {
294
        // do not trust any of the [[secureHeaders]] by default
295 168
        $trustedHeaders = [];
296
297
        // check if the client is a trusted host
298 168
        if (!empty($this->trustedHosts)) {
299 25
            $validator = $this->getIpValidator();
300 25
            $ip = $this->getRemoteIP();
301 25
            foreach ($this->trustedHosts as $cidr => $headers) {
302 25
                if (!is_array($headers)) {
303 25
                    $cidr = $headers;
304 25
                    $headers = $this->secureHeaders;
305
                }
306 25
                $validator->setRanges($cidr);
307 25
                if ($validator->validate($ip)) {
308 5
                    $trustedHeaders = $headers;
309 25
                    break;
310
                }
311
            }
312
        }
313
314
        // filter all secure headers unless they are trusted
315 168
        foreach ($this->secureHeaders as $secureHeader) {
316 168
            if (!in_array($secureHeader, $trustedHeaders)) {
317 168
                $headerCollection->remove($secureHeader);
318
            }
319
        }
320 168
    }
321
322
    /**
323
     * Creates instance of [[IpValidator]].
324
     * You can override this method to adjust validator or implement different matching strategy.
325
     *
326
     * @return IpValidator
327
     * @since 2.0.13
328
     */
329 25
    protected function getIpValidator()
330
    {
331 25
        return new IpValidator();
332
    }
333
334
    /**
335
     * Returns the header collection.
336
     * The header collection contains incoming HTTP headers.
337
     * @return HeaderCollection the header collection
338
     */
339 168
    public function getHeaders()
340
    {
341 168
        if ($this->_headers === null) {
342 168
            $this->_headers = new HeaderCollection();
343 168
            if (function_exists('getallheaders')) {
344
                $headers = getallheaders();
345
                foreach ($headers as $name => $value) {
346
                    $this->_headers->add($name, $value);
347
                }
348 168
            } elseif (function_exists('http_get_request_headers')) {
349
                $headers = http_get_request_headers();
350
                foreach ($headers as $name => $value) {
351
                    $this->_headers->add($name, $value);
352
                }
353
            } else {
354 168
                foreach ($_SERVER as $name => $value) {
355 164
                    if (strncmp($name, 'HTTP_', 5) === 0) {
356 42
                        $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
357 164
                        $this->_headers->add($name, $value);
358
                    }
359
                }
360
            }
361 168
            $this->filterHeaders($this->_headers);
362
        }
363
364 168
        return $this->_headers;
365
    }
366
367
    /**
368
     * Returns the method of the current request (e.g. GET, POST, HEAD, PUT, PATCH, DELETE).
369
     * @return string request method, such as GET, POST, HEAD, PUT, PATCH, DELETE.
370
     * The value returned is turned into upper case.
371
     */
372 36
    public function getMethod()
373
    {
374
        if (
375 36
            isset($_POST[$this->methodParam])
376
            // Never allow to downgrade request from WRITE methods (POST, PATCH, DELETE, etc)
377
            // to read methods (GET, HEAD, OPTIONS) for security reasons.
378 36
            && !in_array(strtoupper($_POST[$this->methodParam]), ['GET', 'HEAD', 'OPTIONS'], true)
379
        ) {
380 6
            return strtoupper($_POST[$this->methodParam]);
381
        }
382
383 34
        if ($this->headers->has('X-Http-Method-Override')) {
384 1
            return strtoupper($this->headers->get('X-Http-Method-Override'));
385
        }
386
387 33
        if (isset($_SERVER['REQUEST_METHOD'])) {
388 9
            return strtoupper($_SERVER['REQUEST_METHOD']);
389
        }
390
391 25
        return 'GET';
392
    }
393
394
    /**
395
     * Returns whether this is a GET request.
396
     * @return bool whether this is a GET request.
397
     */
398 2
    public function getIsGet()
399
    {
400 2
        return $this->getMethod() === 'GET';
401
    }
402
403
    /**
404
     * Returns whether this is an OPTIONS request.
405
     * @return bool whether this is a OPTIONS request.
406
     */
407 2
    public function getIsOptions()
408
    {
409 2
        return $this->getMethod() === 'OPTIONS';
410
    }
411
412
    /**
413
     * Returns whether this is a HEAD request.
414
     * @return bool whether this is a HEAD request.
415
     */
416 11
    public function getIsHead()
417
    {
418 11
        return $this->getMethod() === 'HEAD';
419
    }
420
421
    /**
422
     * Returns whether this is a POST request.
423
     * @return bool whether this is a POST request.
424
     */
425
    public function getIsPost()
426
    {
427
        return $this->getMethod() === 'POST';
428
    }
429
430
    /**
431
     * Returns whether this is a DELETE request.
432
     * @return bool whether this is a DELETE request.
433
     */
434
    public function getIsDelete()
435
    {
436
        return $this->getMethod() === 'DELETE';
437
    }
438
439
    /**
440
     * Returns whether this is a PUT request.
441
     * @return bool whether this is a PUT request.
442
     */
443
    public function getIsPut()
444
    {
445
        return $this->getMethod() === 'PUT';
446
    }
447
448
    /**
449
     * Returns whether this is a PATCH request.
450
     * @return bool whether this is a PATCH request.
451
     */
452
    public function getIsPatch()
453
    {
454
        return $this->getMethod() === 'PATCH';
455
    }
456
457
    /**
458
     * Returns whether this is an AJAX (XMLHttpRequest) request.
459
     *
460
     * Note that jQuery doesn't set the header in case of cross domain
461
     * requests: https://stackoverflow.com/questions/8163703/cross-domain-ajax-doesnt-send-x-requested-with-header
462
     *
463
     * @return bool whether this is an AJAX (XMLHttpRequest) request.
464
     */
465 15
    public function getIsAjax()
466
    {
467 15
        return $this->headers->get('X-Requested-With') === 'XMLHttpRequest';
468
    }
469
470
    /**
471
     * Returns whether this is a PJAX request.
472
     * @return bool whether this is a PJAX request
473
     */
474 3
    public function getIsPjax()
475
    {
476 3
        return $this->getIsAjax() && $this->headers->has('X-Pjax');
477
    }
478
479
    /**
480
     * Returns whether this is an Adobe Flash or Flex request.
481
     * @return bool whether this is an Adobe Flash or Adobe Flex request.
482
     */
483
    public function getIsFlash()
484
    {
485
        $userAgent = $this->headers->get('User-Agent', '');
486
        return stripos($userAgent, 'Shockwave') !== false
487
            || stripos($userAgent, 'Flash') !== false;
488
    }
489
490
    private $_rawBody;
491
492
    /**
493
     * Returns the raw HTTP request body.
494
     * @return string the request body
495
     */
496 3
    public function getRawBody()
497
    {
498 3
        if ($this->_rawBody === null) {
499 3
            $this->_rawBody = file_get_contents('php://input');
500
        }
501
502 3
        return $this->_rawBody;
503
    }
504
505
    /**
506
     * Sets the raw HTTP request body, this method is mainly used by test scripts to simulate raw HTTP requests.
507
     * @param string $rawBody the request body
508
     */
509
    public function setRawBody($rawBody)
510
    {
511
        $this->_rawBody = $rawBody;
512
    }
513
514
    private $_bodyParams;
515
516
    /**
517
     * Returns the request parameters given in the request body.
518
     *
519
     * Request parameters are determined using the parsers configured in [[parsers]] property.
520
     * If no parsers are configured for the current [[contentType]] it uses the PHP function `mb_parse_str()`
521
     * to parse the [[rawBody|request body]].
522
     * @return array the request parameters given in the request body.
523
     * @throws \yii\base\InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]].
524
     * @see getMethod()
525
     * @see getBodyParam()
526
     * @see setBodyParams()
527
     */
528 7
    public function getBodyParams()
529
    {
530 7
        if ($this->_bodyParams === null) {
531 4
            if (isset($_POST[$this->methodParam])) {
532 1
                $this->_bodyParams = $_POST;
533 1
                unset($this->_bodyParams[$this->methodParam]);
534 1
                return $this->_bodyParams;
535
            }
536
537 3
            $rawContentType = $this->getContentType();
538 3
            if (($pos = strpos($rawContentType, ';')) !== false) {
539
                // e.g. text/html; charset=UTF-8
540
                $contentType = substr($rawContentType, 0, $pos);
541
            } else {
542 3
                $contentType = $rawContentType;
543
            }
544
545 3
            if (isset($this->parsers[$contentType])) {
546
                $parser = Yii::createObject($this->parsers[$contentType]);
547
                if (!($parser instanceof RequestParserInterface)) {
548
                    throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
549
                }
550
                $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
551 3
            } elseif (isset($this->parsers['*'])) {
552
                $parser = Yii::createObject($this->parsers['*']);
553
                if (!($parser instanceof RequestParserInterface)) {
554
                    throw new InvalidConfigException('The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface.');
555
                }
556
                $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
557 3
            } elseif ($this->getMethod() === 'POST') {
558
                // PHP has already parsed the body so we have all params in $_POST
559
                $this->_bodyParams = $_POST;
560
            } else {
561 3
                $this->_bodyParams = [];
562 3
                mb_parse_str($this->getRawBody(), $this->_bodyParams);
563
            }
564
        }
565
566 7
        return $this->_bodyParams;
567
    }
568
569
    /**
570
     * Sets the request body parameters.
571
     * @param array $values the request body parameters (name-value pairs)
572
     * @see getBodyParam()
573
     * @see getBodyParams()
574
     */
575 6
    public function setBodyParams($values)
576
    {
577 6
        $this->_bodyParams = $values;
578 6
    }
579
580
    /**
581
     * Returns the named request body parameter value.
582
     * If the parameter does not exist, the second parameter passed to this method will be returned.
583
     * @param string $name the parameter name
584
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
585
     * @return mixed the parameter value
586
     * @see getBodyParams()
587
     * @see setBodyParams()
588
     */
589 7
    public function getBodyParam($name, $defaultValue = null)
590
    {
591 7
        $params = $this->getBodyParams();
592
593 7
        if (is_object($params)) {
594
            // unable to use `ArrayHelper::getValue()` due to different dots in key logic and lack of exception handling
595
            try {
596 1
                return $params->{$name};
597 1
            } catch (\Exception $e) {
0 ignored issues
show
Unused Code introduced by
catch (\Exception $e) { ...return $defaultValue; } does not seem to be 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...
598 1
                return $defaultValue;
599
            }
600
        }
601
602 7
        return isset($params[$name]) ? $params[$name] : $defaultValue;
603
    }
604
605
    /**
606
     * Returns POST parameter with a given name. If name isn't specified, returns an array of all POST parameters.
607
     *
608
     * @param string $name the parameter name
609
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
610
     * @return array|mixed
611
     */
612
    public function post($name = null, $defaultValue = null)
613
    {
614
        if ($name === null) {
615
            return $this->getBodyParams();
616
        }
617
618
        return $this->getBodyParam($name, $defaultValue);
619
    }
620
621
    private $_queryParams;
622
623
    /**
624
     * Returns the request parameters given in the [[queryString]].
625
     *
626
     * This method will return the contents of `$_GET` if params where not explicitly set.
627
     * @return array the request GET parameter values.
628
     * @see setQueryParams()
629
     */
630 36
    public function getQueryParams()
631
    {
632 36
        if ($this->_queryParams === null) {
633 29
            return $_GET;
634
        }
635
636 9
        return $this->_queryParams;
637
    }
638
639
    /**
640
     * Sets the request [[queryString]] parameters.
641
     * @param array $values the request query parameters (name-value pairs)
642
     * @see getQueryParam()
643
     * @see getQueryParams()
644
     */
645 9
    public function setQueryParams($values)
646
    {
647 9
        $this->_queryParams = $values;
648 9
    }
649
650
    /**
651
     * Returns GET parameter with a given name. If name isn't specified, returns an array of all GET parameters.
652
     *
653
     * @param string $name the parameter name
654
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
655
     * @return array|mixed
656
     */
657 24
    public function get($name = null, $defaultValue = null)
658
    {
659 24
        if ($name === null) {
660
            return $this->getQueryParams();
661
        }
662
663 24
        return $this->getQueryParam($name, $defaultValue);
664
    }
665
666
    /**
667
     * Returns the named GET parameter value.
668
     * If the GET parameter does not exist, the second parameter passed to this method will be returned.
669
     * @param string $name the GET parameter name.
670
     * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
671
     * @return mixed the GET parameter value
672
     * @see getBodyParam()
673
     */
674 27
    public function getQueryParam($name, $defaultValue = null)
675
    {
676 27
        $params = $this->getQueryParams();
677
678 27
        return isset($params[$name]) ? $params[$name] : $defaultValue;
679
    }
680
681
    private $_hostInfo;
682
    private $_hostName;
683
684
    /**
685
     * Returns the schema and host part of the current request URL.
686
     *
687
     * The returned URL does not have an ending slash.
688
     *
689
     * By default this value is based on the user request information. This method will
690
     * return the value of `$_SERVER['HTTP_HOST']` if it is available or `$_SERVER['SERVER_NAME']` if not.
691
     * You may want to check out the [PHP documentation](http://php.net/manual/en/reserved.variables.server.php)
692
     * for more information on these variables.
693
     *
694
     * You may explicitly specify it by setting the [[setHostInfo()|hostInfo]] property.
695
     *
696
     * > Warning: Dependent on the server configuration this information may not be
697
     * > reliable and [may be faked by the user sending the HTTP request](https://www.acunetix.com/vulnerabilities/web/host-header-attack).
698
     * > If the webserver is configured to serve the same site independent of the value of
699
     * > the `Host` header, this value is not reliable. In such situations you should either
700
     * > fix your webserver configuration or explicitly set the value by setting the [[setHostInfo()|hostInfo]] property.
701
     * > If you don't have access to the server configuration, you can setup [[\yii\filters\HostControl]] filter at
702
     * > application level in order to protect against such kind of attack.
703
     *
704
     * @property string|null schema and hostname part (with port number if needed) of the request URL
705
     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
706
     * See [[getHostInfo()]] for security related notes on this property.
707
     * @return string|null schema and hostname part (with port number if needed) of the request URL
708
     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
709
     * @see setHostInfo()
710
     */
711 30
    public function getHostInfo()
712
    {
713 30
        if ($this->_hostInfo === null) {
714 26
            $secure = $this->getIsSecureConnection();
715 26
            $http = $secure ? 'https' : 'http';
716
717 26
            if ($this->headers->has('X-Forwarded-Host')) {
718 2
                $this->_hostInfo = $http . '://' . trim(explode(',', $this->headers->get('X-Forwarded-Host'))[0]);
719 24
            } elseif ($this->headers->has('Host')) {
720 9
                $this->_hostInfo = $http . '://' . $this->headers->get('Host');
721 15
            } elseif (isset($_SERVER['SERVER_NAME'])) {
722 1
                $this->_hostInfo = $http . '://' . $_SERVER['SERVER_NAME'];
723 1
                $port = $secure ? $this->getSecurePort() : $this->getPort();
724 1
                if (($port !== 80 && !$secure) || ($port !== 443 && $secure)) {
725
                    $this->_hostInfo .= ':' . $port;
726
                }
727
            }
728
        }
729
730 30
        return $this->_hostInfo;
731
    }
732
733
    /**
734
     * Sets the schema and host part of the application URL.
735
     * This setter is provided in case the schema and hostname cannot be determined
736
     * on certain Web servers.
737
     * @param string|null $value the schema and host part of the application URL. The trailing slashes will be removed.
738
     * @see getHostInfo() for security related notes on this property.
739
     */
740 58
    public function setHostInfo($value)
741
    {
742 58
        $this->_hostName = null;
743 58
        $this->_hostInfo = $value === null ? null : rtrim($value, '/');
744 58
    }
745
746
    /**
747
     * Returns the host part of the current request URL.
748
     * Value is calculated from current [[getHostInfo()|hostInfo]] property.
749
     *
750
     * > Warning: The content of this value may not be reliable, dependent on the server
751
     * > configuration. Please refer to [[getHostInfo()]] for more information.
752
     *
753
     * @return string|null hostname part of the request URL (e.g. `www.yiiframework.com`)
754
     * @see getHostInfo()
755
     * @since 2.0.10
756
     */
757 17
    public function getHostName()
758
    {
759 17
        if ($this->_hostName === null) {
760 17
            $this->_hostName = parse_url($this->getHostInfo(), PHP_URL_HOST);
761
        }
762
763 17
        return $this->_hostName;
764
    }
765
766
    private $_baseUrl;
767
768
    /**
769
     * Returns the relative URL for the application.
770
     * This is similar to [[scriptUrl]] except that it does not include the script file name,
771
     * and the ending slashes are removed.
772
     * @return string the relative URL for the application
773
     * @see setScriptUrl()
774
     */
775 318
    public function getBaseUrl()
776
    {
777 318
        if ($this->_baseUrl === null) {
778 317
            $this->_baseUrl = rtrim(dirname($this->getScriptUrl()), '\\/');
779
        }
780
781 318
        return $this->_baseUrl;
782
    }
783
784
    /**
785
     * Sets the relative URL for the application.
786
     * By default the URL is determined based on the entry script URL.
787
     * This setter is provided in case you want to change this behavior.
788
     * @param string $value the relative URL for the application
789
     */
790 1
    public function setBaseUrl($value)
791
    {
792 1
        $this->_baseUrl = $value;
793 1
    }
794
795
    private $_scriptUrl;
796
797
    /**
798
     * Returns the relative URL of the entry script.
799
     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
800
     * @return string the relative URL of the entry script.
801
     * @throws InvalidConfigException if unable to determine the entry script URL
802
     */
803 319
    public function getScriptUrl()
804
    {
805 319
        if ($this->_scriptUrl === null) {
806 1
            $scriptFile = $this->getScriptFile();
807
            $scriptName = basename($scriptFile);
808
            if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $scriptName) {
809
                $this->_scriptUrl = $_SERVER['SCRIPT_NAME'];
810
            } elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $scriptName) {
811
                $this->_scriptUrl = $_SERVER['PHP_SELF'];
812
            } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $scriptName) {
813
                $this->_scriptUrl = $_SERVER['ORIG_SCRIPT_NAME'];
814
            } elseif (isset($_SERVER['PHP_SELF']) && ($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) {
815
                $this->_scriptUrl = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName;
816
            } elseif (!empty($_SERVER['DOCUMENT_ROOT']) && strpos($scriptFile, $_SERVER['DOCUMENT_ROOT']) === 0) {
817
                $this->_scriptUrl = str_replace([$_SERVER['DOCUMENT_ROOT'], '\\'], ['', '/'], $scriptFile);
818
            } else {
819
                throw new InvalidConfigException('Unable to determine the entry script URL.');
820
            }
821
        }
822
823 318
        return $this->_scriptUrl;
824
    }
825
826
    /**
827
     * Sets the relative URL for the application entry script.
828
     * This setter is provided in case the entry script URL cannot be determined
829
     * on certain Web servers.
830
     * @param string $value the relative URL for the application entry script.
831
     */
832 329
    public function setScriptUrl($value)
833
    {
834 329
        $this->_scriptUrl = $value === null ? null : '/' . trim($value, '/');
835 329
    }
836
837
    private $_scriptFile;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
838
839
    /**
840
     * Returns the entry script file path.
841
     * The default implementation will simply return `$_SERVER['SCRIPT_FILENAME']`.
842
     * @return string the entry script file path
843
     * @throws InvalidConfigException
844
     */
845 320
    public function getScriptFile()
846
    {
847 320
        if (isset($this->_scriptFile)) {
848 296
            return $this->_scriptFile;
849
        }
850
851 24
        if (isset($_SERVER['SCRIPT_FILENAME'])) {
852 22
            return $_SERVER['SCRIPT_FILENAME'];
853
        }
854
855 2
        throw new InvalidConfigException('Unable to determine the entry script file path.');
856
    }
857
858
    /**
859
     * Sets the entry script file path.
860
     * The entry script file path normally can be obtained from `$_SERVER['SCRIPT_FILENAME']`.
861
     * If your server configuration does not return the correct value, you may configure
862
     * this property to make it right.
863
     * @param string $value the entry script file path.
864
     */
865 296
    public function setScriptFile($value)
866
    {
867 296
        $this->_scriptFile = $value;
868 296
    }
869
870
    private $_pathInfo;
871
872
    /**
873
     * Returns the path info of the currently requested URL.
874
     * A path info refers to the part that is after the entry script and before the question mark (query string).
875
     * The starting and ending slashes are both removed.
876
     * @return string part of the request URL that is after the entry script and before the question mark.
877
     * Note, the returned path info is already URL-decoded.
878
     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
879
     */
880 19
    public function getPathInfo()
881
    {
882 19
        if ($this->_pathInfo === null) {
883
            $this->_pathInfo = $this->resolvePathInfo();
884
        }
885
886 19
        return $this->_pathInfo;
887
    }
888
889
    /**
890
     * Sets the path info of the current request.
891
     * This method is mainly provided for testing purpose.
892
     * @param string $value the path info of the current request
893
     */
894 20
    public function setPathInfo($value)
895
    {
896 20
        $this->_pathInfo = $value === null ? null : ltrim($value, '/');
897 20
    }
898
899
    /**
900
     * Resolves the path info part of the currently requested URL.
901
     * A path info refers to the part that is after the entry script and before the question mark (query string).
902
     * The starting slashes are both removed (ending slashes will be kept).
903
     * @return string part of the request URL that is after the entry script and before the question mark.
904
     * Note, the returned path info is decoded.
905
     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
906
     */
907
    protected function resolvePathInfo()
908
    {
909
        $pathInfo = $this->getUrl();
910
911
        if (($pos = strpos($pathInfo, '?')) !== false) {
912
            $pathInfo = substr($pathInfo, 0, $pos);
913
        }
914
915
        $pathInfo = urldecode($pathInfo);
916
917
        // try to encode in UTF8 if not so
918
        // http://w3.org/International/questions/qa-forms-utf-8.html
919
        if (!preg_match('%^(?:
920
            [\x09\x0A\x0D\x20-\x7E]              # ASCII
921
            | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
922
            | \xE0[\xA0-\xBF][\x80-\xBF]         # excluding overlongs
923
            | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
924
            | \xED[\x80-\x9F][\x80-\xBF]         # excluding surrogates
925
            | \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
926
            | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
927
            | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
928
            )*$%xs', $pathInfo)
929
        ) {
930
            $pathInfo = $this->utf8Encode($pathInfo);
931
        }
932
933
        $scriptUrl = $this->getScriptUrl();
934
        $baseUrl = $this->getBaseUrl();
935
        if (strpos($pathInfo, $scriptUrl) === 0) {
936
            $pathInfo = substr($pathInfo, strlen($scriptUrl));
937
        } elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) {
938
            $pathInfo = substr($pathInfo, strlen($baseUrl));
939
        } elseif (isset($_SERVER['PHP_SELF']) && strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) {
940
            $pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl));
941
        } else {
942
            throw new InvalidConfigException('Unable to determine the path info of the current request.');
943
        }
944
945
        if (strncmp($pathInfo, '/', 1) === 0) {
946
            $pathInfo = substr($pathInfo, 1);
947
        }
948
949
        return (string) $pathInfo;
950
    }
951
952
    /**
953
     * Encodes an ISO-8859-1 string to UTF-8
954
     * @param string $s
955
     * @return string the UTF-8 translation of `s`.
956
     * @see https://github.com/symfony/polyfill-php72/blob/master/Php72.php#L24
957
     */
958
    private function utf8Encode($s)
959
    {
960
        $s .= $s;
961
        $len = \strlen($s);
962
        for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) {
963
            switch (true) {
964
                case $s[$i] < "\x80": $s[$j] = $s[$i]; break;
965
                case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break;
966
                default: $s[$j] = "\xC3"; $s[++$j] = \chr(\ord($s[$i]) - 64); break;
967
            }
968
        }
969
        return substr($s, 0, $j);
970
    }
971
972
    /**
973
     * Returns the currently requested absolute URL.
974
     * This is a shortcut to the concatenation of [[hostInfo]] and [[url]].
975
     * @return string the currently requested absolute URL.
976
     */
977
    public function getAbsoluteUrl()
978
    {
979
        return $this->getHostInfo() . $this->getUrl();
980
    }
981
982
    private $_url;
983
984
    /**
985
     * Returns the currently requested relative URL.
986
     * This refers to the portion of the URL that is after the [[hostInfo]] part.
987
     * It includes the [[queryString]] part if any.
988
     * @return string the currently requested relative URL. Note that the URI returned may be URL-encoded depending on the client.
989
     * @throws InvalidConfigException if the URL cannot be determined due to unusual server configuration
990
     */
991 11
    public function getUrl()
992
    {
993 11
        if ($this->_url === null) {
994 3
            $this->_url = $this->resolveRequestUri();
995
        }
996
997 11
        return $this->_url;
998
    }
999
1000
    /**
1001
     * Sets the currently requested relative URL.
1002
     * The URI must refer to the portion that is after [[hostInfo]].
1003
     * Note that the URI should be URL-encoded.
1004
     * @param string $value the request URI to be set
1005
     */
1006 24
    public function setUrl($value)
1007
    {
1008 24
        $this->_url = $value;
1009 24
    }
1010
1011
    /**
1012
     * Resolves the request URI portion for the currently requested URL.
1013
     * This refers to the portion that is after the [[hostInfo]] part. It includes the [[queryString]] part if any.
1014
     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
1015
     * @return string|bool the request URI portion for the currently requested URL.
1016
     * Note that the URI returned may be URL-encoded depending on the client.
1017
     * @throws InvalidConfigException if the request URI cannot be determined due to unusual server configuration
1018
     */
1019 3
    protected function resolveRequestUri()
1020
    {
1021 3
        if ($this->headers->has('X-Rewrite-Url')) { // IIS
1022
            $requestUri = $this->headers->get('X-Rewrite-Url');
1023 3
        } elseif (isset($_SERVER['REQUEST_URI'])) {
1024 3
            $requestUri = $_SERVER['REQUEST_URI'];
1025 3
            if ($requestUri !== '' && $requestUri[0] !== '/') {
1026 3
                $requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri);
1027
            }
1028
        } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0 CGI
1029
            $requestUri = $_SERVER['ORIG_PATH_INFO'];
1030
            if (!empty($_SERVER['QUERY_STRING'])) {
1031
                $requestUri .= '?' . $_SERVER['QUERY_STRING'];
1032
            }
1033
        } else {
1034
            throw new InvalidConfigException('Unable to determine the request URI.');
1035
        }
1036
1037 3
        return $requestUri;
1038
    }
1039
1040
    /**
1041
     * Returns part of the request URL that is after the question mark.
1042
     * @return string part of the request URL that is after the question mark
1043
     */
1044
    public function getQueryString()
1045
    {
1046
        return isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
1047
    }
1048
1049
    /**
1050
     * Return if the request is sent via secure channel (https).
1051
     * @return bool if the request is sent via secure channel (https)
1052
     */
1053 43
    public function getIsSecureConnection()
1054
    {
1055 43
        if (isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'], 'on') === 0 || $_SERVER['HTTPS'] == 1)) {
1056 2
            return true;
1057
        }
1058 41
        foreach ($this->secureProtocolHeaders as $header => $values) {
1059 41
            if (($headerValue = $this->headers->get($header, null)) !== null) {
1060 2
                foreach ($values as $value) {
1061 2
                    if (strcasecmp($headerValue, $value) === 0) {
1062 41
                        return true;
1063
                    }
1064
                }
1065
            }
1066
        }
1067
1068 39
        return false;
1069
    }
1070
1071
    /**
1072
     * Returns the server name.
1073
     * @return string server name, null if not available
1074
     */
1075 1
    public function getServerName()
1076
    {
1077 1
        return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null;
1078
    }
1079
1080
    /**
1081
     * Returns the server port number.
1082
     * @return int|null server port number, null if not available
1083
     */
1084 2
    public function getServerPort()
1085
    {
1086 2
        return isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : null;
1087
    }
1088
1089
    /**
1090
     * Returns the URL referrer.
1091
     * @return string|null URL referrer, null if not available
1092
     */
1093
    public function getReferrer()
1094
    {
1095
        return $this->headers->get('Referer');
1096
    }
1097
1098
    /**
1099
     * Returns the URL origin of a CORS request.
1100
     *
1101
     * The return value is taken from the `Origin` [[getHeaders()|header]] sent by the browser.
1102
     *
1103
     * Note that the origin request header indicates where a fetch originates from.
1104
     * It doesn't include any path information, but only the server name.
1105
     * It is sent with a CORS requests, as well as with POST requests.
1106
     * It is similar to the referer header, but, unlike this header, it doesn't disclose the whole path.
1107
     * Please refer to <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin> for more information.
1108
     *
1109
     * @return string|null URL origin of a CORS request, `null` if not available.
1110
     * @see getHeaders()
1111
     * @since 2.0.13
1112
     */
1113 1
    public function getOrigin()
1114
    {
1115 1
        return $this->getHeaders()->get('origin');
1116
    }
1117
1118
    /**
1119
     * Returns the user agent.
1120
     * @return string|null user agent, null if not available
1121
     */
1122
    public function getUserAgent()
1123
    {
1124
        return $this->headers->get('User-Agent');
1125
    }
1126
1127
    /**
1128
     * Returns the user IP address.
1129
     * The IP is determined using headers and / or `$_SERVER` variables.
1130
     * @return string|null user IP address, null if not available
1131
     */
1132 55
    public function getUserIP()
1133
    {
1134 55
        foreach ($this->ipHeaders as $ipHeader) {
1135 55
            if ($this->headers->has($ipHeader)) {
1136 55
                return trim(explode(',', $this->headers->get($ipHeader))[0]);
1137
            }
1138
        }
1139
1140 54
        return $this->getRemoteIP();
1141
    }
1142
1143
    /**
1144
     * Returns the user host name.
1145
     * The HOST is determined using headers and / or `$_SERVER` variables.
1146
     * @return string|null user host name, null if not available
1147
     */
1148
    public function getUserHost()
1149
    {
1150
        foreach ($this->ipHeaders as $ipHeader) {
1151
            if ($this->headers->has($ipHeader)) {
1152
                return gethostbyaddr(trim(explode(',', $this->headers->get($ipHeader))[0]));
1153
            }
1154
        }
1155
1156
        return $this->getRemoteHost();
1157
    }
1158
1159
    /**
1160
     * Returns the IP on the other end of this connection.
1161
     * This is always the next hop, any headers are ignored.
1162
     * @return string|null remote IP address, `null` if not available.
1163
     * @since 2.0.13
1164
     */
1165 76
    public function getRemoteIP()
1166
    {
1167 76
        return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
1168
    }
1169
1170
    /**
1171
     * Returns the host name of the other end of this connection.
1172
     * This is always the next hop, any headers are ignored.
1173
     * @return string|null remote host name, `null` if not available
1174
     * @see getUserHost()
1175
     * @see getRemoteIP()
1176
     * @since 2.0.13
1177
     */
1178
    public function getRemoteHost()
1179
    {
1180
        return isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : null;
1181
    }
1182
1183
    /**
1184
     * @return string|null the username sent via HTTP authentication, `null` if the username is not given
1185
     * @see getAuthCredentials() to get both username and password in one call
1186
     */
1187 9
    public function getAuthUser()
1188
    {
1189 9
        return $this->getAuthCredentials()[0];
1190
    }
1191
1192
    /**
1193
     * @return string|null the password sent via HTTP authentication, `null` if the password is not given
1194
     * @see getAuthCredentials() to get both username and password in one call
1195
     */
1196 9
    public function getAuthPassword()
1197
    {
1198 9
        return $this->getAuthCredentials()[1];
1199
    }
1200
1201
    /**
1202
     * @return array that contains exactly two elements:
1203
     * - 0: the username sent via HTTP authentication, `null` if the username is not given
1204
     * - 1: the password sent via HTTP authentication, `null` if the password is not given
1205
     * @see getAuthUser() to get only username
1206
     * @see getAuthPassword() to get only password
1207
     * @since 2.0.13
1208
     */
1209 34
    public function getAuthCredentials()
1210
    {
1211 34
        $username = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
1212 34
        $password = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
1213 34
        if ($username !== null || $password !== null) {
1214 16
            return [$username, $password];
1215
        }
1216
1217
        /*
1218
         * Apache with php-cgi does not pass HTTP Basic authentication to PHP by default.
1219
         * To make it work, add the following line to to your .htaccess file:
1220
         *
1221
         * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
1222
         */
1223 18
        $auth_token = $this->getHeaders()->get('HTTP_AUTHORIZATION') ?: $this->getHeaders()->get('REDIRECT_HTTP_AUTHORIZATION');
1224 18
        if ($auth_token !== null && strncasecmp($auth_token, 'basic', 5) === 0) {
1225
            $parts = array_map(function ($value) {
1226 18
                return strlen($value) === 0 ? null : $value;
1227 18
            }, explode(':', base64_decode(mb_substr($auth_token, 6)), 2));
1228
1229 18
            if (count($parts) < 2) {
1230 2
                return [$parts[0], null];
1231
            }
1232
1233 16
            return $parts;
1234
        }
1235
1236
        return [null, null];
1237
    }
1238
1239
    private $_port;
1240
1241
    /**
1242
     * Returns the port to use for insecure requests.
1243
     * Defaults to 80, or the port specified by the server if the current
1244
     * request is insecure.
1245
     * @return int port number for insecure requests.
1246
     * @see setPort()
1247
     */
1248 1
    public function getPort()
1249
    {
1250 1
        if ($this->_port === null) {
1251 1
            $serverPort = $this->getServerPort();
1252 1
            $this->_port = !$this->getIsSecureConnection() && $serverPort !== null ? $serverPort : 80;
1253
        }
1254
1255 1
        return $this->_port;
1256
    }
1257
1258
    /**
1259
     * Sets the port to use for insecure requests.
1260
     * This setter is provided in case a custom port is necessary for certain
1261
     * server configurations.
1262
     * @param int $value port number.
1263
     */
1264
    public function setPort($value)
1265
    {
1266
        if ($value != $this->_port) {
1267
            $this->_port = (int) $value;
1268
            $this->_hostInfo = null;
1269
        }
1270
    }
1271
1272
    private $_securePort;
1273
1274
    /**
1275
     * Returns the port to use for secure requests.
1276
     * Defaults to 443, or the port specified by the server if the current
1277
     * request is secure.
1278
     * @return int port number for secure requests.
1279
     * @see setSecurePort()
1280
     */
1281
    public function getSecurePort()
1282
    {
1283
        if ($this->_securePort === null) {
1284
            $serverPort = $this->getServerPort();
1285
            $this->_securePort = $this->getIsSecureConnection() && $serverPort !== null ? $serverPort : 443;
1286
        }
1287
1288
        return $this->_securePort;
1289
    }
1290
1291
    /**
1292
     * Sets the port to use for secure requests.
1293
     * This setter is provided in case a custom port is necessary for certain
1294
     * server configurations.
1295
     * @param int $value port number.
1296
     */
1297
    public function setSecurePort($value)
1298
    {
1299
        if ($value != $this->_securePort) {
1300
            $this->_securePort = (int) $value;
1301
            $this->_hostInfo = null;
1302
        }
1303
    }
1304
1305
    private $_contentTypes;
1306
1307
    /**
1308
     * Returns the content types acceptable by the end user.
1309
     *
1310
     * This is determined by the `Accept` HTTP header. For example,
1311
     *
1312
     * ```php
1313
     * $_SERVER['HTTP_ACCEPT'] = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1314
     * $types = $request->getAcceptableContentTypes();
1315
     * print_r($types);
1316
     * // displays:
1317
     * // [
1318
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1319
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1320
     * //           'text/plain' => ['q' => 0.5],
1321
     * // ]
1322
     * ```
1323
     *
1324
     * @return array the content types ordered by the quality score. Types with the highest scores
1325
     * will be returned first. The array keys are the content types, while the array values
1326
     * are the corresponding quality score and other parameters as given in the header.
1327
     */
1328 3
    public function getAcceptableContentTypes()
1329
    {
1330 3
        if ($this->_contentTypes === null) {
1331 3
            if ($this->headers->get('Accept') !== null) {
1332 2
                $this->_contentTypes = $this->parseAcceptHeader($this->headers->get('Accept'));
0 ignored issues
show
Bug introduced by
It seems like $this->headers->get('Accept') targeting yii\web\HeaderCollection::get() can also be of type array; however, yii\web\Request::parseAcceptHeader() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1333
            } else {
1334 2
                $this->_contentTypes = [];
1335
            }
1336
        }
1337
1338 3
        return $this->_contentTypes;
1339
    }
1340
1341
    /**
1342
     * Sets the acceptable content types.
1343
     * Please refer to [[getAcceptableContentTypes()]] on the format of the parameter.
1344
     * @param array $value the content types that are acceptable by the end user. They should
1345
     * be ordered by the preference level.
1346
     * @see getAcceptableContentTypes()
1347
     * @see parseAcceptHeader()
1348
     */
1349
    public function setAcceptableContentTypes($value)
1350
    {
1351
        $this->_contentTypes = $value;
1352
    }
1353
1354
    /**
1355
     * Returns request content-type
1356
     * The Content-Type header field indicates the MIME type of the data
1357
     * contained in [[getRawBody()]] or, in the case of the HEAD method, the
1358
     * media type that would have been sent had the request been a GET.
1359
     * For the MIME-types the user expects in response, see [[acceptableContentTypes]].
1360
     * @return string request content-type. Null is returned if this information is not available.
1361
     * @link https://tools.ietf.org/html/rfc2616#section-14.17
1362
     * HTTP 1.1 header field definitions
1363
     */
1364 3
    public function getContentType()
1365
    {
1366 3
        if (isset($_SERVER['CONTENT_TYPE'])) {
1367
            return $_SERVER['CONTENT_TYPE'];
1368
        }
1369
1370
        //fix bug https://bugs.php.net/bug.php?id=66606
1371 3
        return $this->headers->get('Content-Type');
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->headers->get('Content-Type'); of type string|array adds the type array to the return on line 1371 which is incompatible with the return type documented by yii\web\Request::getContentType of type string.
Loading history...
1372
    }
1373
1374
    private $_languages;
1375
1376
    /**
1377
     * Returns the languages acceptable by the end user.
1378
     * This is determined by the `Accept-Language` HTTP header.
1379
     * @return array the languages ordered by the preference level. The first element
1380
     * represents the most preferred language.
1381
     */
1382 2
    public function getAcceptableLanguages()
1383
    {
1384 2
        if ($this->_languages === null) {
1385 1
            if ($this->headers->has('Accept-Language')) {
1386
                $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') targeting yii\web\HeaderCollection::get() can also be of type array; however, yii\web\Request::parseAcceptHeader() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1387
            } else {
1388 1
                $this->_languages = [];
1389
            }
1390
        }
1391
1392 2
        return $this->_languages;
1393
    }
1394
1395
    /**
1396
     * @param array $value the languages that are acceptable by the end user. They should
1397
     * be ordered by the preference level.
1398
     */
1399 1
    public function setAcceptableLanguages($value)
1400
    {
1401 1
        $this->_languages = $value;
1402 1
    }
1403
1404
    /**
1405
     * Parses the given `Accept` (or `Accept-Language`) header.
1406
     *
1407
     * This method will return the acceptable values with their quality scores and the corresponding parameters
1408
     * as specified in the given `Accept` header. The array keys of the return value are the acceptable values,
1409
     * while the array values consisting of the corresponding quality scores and parameters. The acceptable
1410
     * values with the highest quality scores will be returned first. For example,
1411
     *
1412
     * ```php
1413
     * $header = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1414
     * $accepts = $request->parseAcceptHeader($header);
1415
     * print_r($accepts);
1416
     * // displays:
1417
     * // [
1418
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1419
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1420
     * //           'text/plain' => ['q' => 0.5],
1421
     * // ]
1422
     * ```
1423
     *
1424
     * @param string $header the header to be parsed
1425
     * @return array the acceptable values ordered by their quality score. The values with the highest scores
1426
     * will be returned first.
1427
     */
1428 3
    public function parseAcceptHeader($header)
1429
    {
1430 3
        $accepts = [];
1431 3
        foreach (explode(',', $header) as $i => $part) {
1432 3
            $params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY);
1433 3
            if (empty($params)) {
1434 1
                continue;
1435
            }
1436
            $values = [
1437 3
                'q' => [$i, array_shift($params), 1],
1438
            ];
1439 3
            foreach ($params as $param) {
1440 2
                if (strpos($param, '=') !== false) {
1441 2
                    list($key, $value) = explode('=', $param, 2);
1442 2
                    if ($key === 'q') {
1443 2
                        $values['q'][2] = (float) $value;
1444
                    } else {
1445 2
                        $values[$key] = $value;
1446
                    }
1447
                } else {
1448 2
                    $values[] = $param;
1449
                }
1450
            }
1451 3
            $accepts[] = $values;
1452
        }
1453
1454 3
        usort($accepts, function ($a, $b) {
1455 3
            $a = $a['q']; // index, name, q
1456 3
            $b = $b['q'];
1457 3
            if ($a[2] > $b[2]) {
1458 2
                return -1;
1459
            }
1460
1461 2
            if ($a[2] < $b[2]) {
1462 1
                return 1;
1463
            }
1464
1465 2
            if ($a[1] === $b[1]) {
1466
                return $a[0] > $b[0] ? 1 : -1;
1467
            }
1468
1469 2
            if ($a[1] === '*/*') {
1470
                return 1;
1471
            }
1472
1473 2
            if ($b[1] === '*/*') {
1474
                return -1;
1475
            }
1476
1477 2
            $wa = $a[1][strlen($a[1]) - 1] === '*';
1478 2
            $wb = $b[1][strlen($b[1]) - 1] === '*';
1479 2
            if ($wa xor $wb) {
1480
                return $wa ? 1 : -1;
1481
            }
1482
1483 2
            return $a[0] > $b[0] ? 1 : -1;
1484 3
        });
1485
1486 3
        $result = [];
1487 3
        foreach ($accepts as $accept) {
1488 3
            $name = $accept['q'][1];
1489 3
            $accept['q'] = $accept['q'][2];
1490 3
            $result[$name] = $accept;
1491
        }
1492
1493 3
        return $result;
1494
    }
1495
1496
    /**
1497
     * Returns the user-preferred language that should be used by this application.
1498
     * The language resolution is based on the user preferred languages and the languages
1499
     * supported by the application. The method will try to find the best match.
1500
     * @param array $languages a list of the languages supported by the application. If this is empty, the current
1501
     * application language will be returned without further processing.
1502
     * @return string the language that the application should use.
1503
     */
1504 1
    public function getPreferredLanguage(array $languages = [])
1505
    {
1506 1
        if (empty($languages)) {
1507 1
            return Yii::$app->language;
1508
        }
1509 1
        foreach ($this->getAcceptableLanguages() as $acceptableLanguage) {
1510 1
            $acceptableLanguage = str_replace('_', '-', strtolower($acceptableLanguage));
1511 1
            foreach ($languages as $language) {
1512 1
                $normalizedLanguage = str_replace('_', '-', strtolower($language));
1513
1514
                if (
1515 1
                    $normalizedLanguage === $acceptableLanguage // en-us==en-us
1516 1
                    || strpos($acceptableLanguage, $normalizedLanguage . '-') === 0 // en==en-us
1517 1
                    || strpos($normalizedLanguage, $acceptableLanguage . '-') === 0 // en-us==en
1518
                ) {
1519 1
                    return $language;
1520
                }
1521
            }
1522
        }
1523
1524 1
        return reset($languages);
1525
    }
1526
1527
    /**
1528
     * Gets the Etags.
1529
     *
1530
     * @return array The entity tags
1531
     */
1532
    public function getETags()
1533
    {
1534
        if ($this->headers->has('If-None-Match')) {
1535
            return preg_split('/[\s,]+/', str_replace('-gzip', '', $this->headers->get('If-None-Match')), -1, PREG_SPLIT_NO_EMPTY);
1536
        }
1537
1538
        return [];
1539
    }
1540
1541
    /**
1542
     * Returns the cookie collection.
1543
     *
1544
     * Through the returned cookie collection, you may access a cookie using the following syntax:
1545
     *
1546
     * ```php
1547
     * $cookie = $request->cookies['name']
1548
     * if ($cookie !== null) {
1549
     *     $value = $cookie->value;
1550
     * }
1551
     *
1552
     * // alternatively
1553
     * $value = $request->cookies->getValue('name');
1554
     * ```
1555
     *
1556
     * @return CookieCollection the cookie collection.
1557
     */
1558 68
    public function getCookies()
1559
    {
1560 68
        if ($this->_cookies === null) {
1561 68
            $this->_cookies = new CookieCollection($this->loadCookies(), [
1562 67
                'readOnly' => true,
1563
            ]);
1564
        }
1565
1566 67
        return $this->_cookies;
1567
    }
1568
1569
    /**
1570
     * Converts `$_COOKIE` into an array of [[Cookie]].
1571
     * @return array the cookies obtained from request
1572
     * @throws InvalidConfigException if [[cookieValidationKey]] is not set when [[enableCookieValidation]] is true
1573
     */
1574 68
    protected function loadCookies()
1575
    {
1576 68
        $cookies = [];
1577 68
        if ($this->enableCookieValidation) {
1578 67
            if ($this->cookieValidationKey == '') {
1579 1
                throw new InvalidConfigException(get_class($this) . '::cookieValidationKey must be configured with a secret key.');
1580
            }
1581 66
            foreach ($_COOKIE as $name => $value) {
1582
                if (!is_string($value)) {
1583
                    continue;
1584
                }
1585
                $data = Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey);
1586
                if ($data === false) {
1587
                    continue;
1588
                }
1589
                $data = @unserialize($data);
1590
                if (is_array($data) && isset($data[0], $data[1]) && $data[0] === $name) {
1591
                    $cookies[$name] = Yii::createObject([
1592 66
                        'class' => 'yii\web\Cookie',
1593
                        'name' => $name,
1594
                        'value' => $data[1],
1595
                        'expire' => null,
1596
                    ]);
1597
                }
1598
            }
1599
        } else {
1600 1
            foreach ($_COOKIE as $name => $value) {
1601 1
                $cookies[$name] = Yii::createObject([
1602 1
                    'class' => 'yii\web\Cookie',
1603 1
                    'name' => $name,
1604 1
                    'value' => $value,
1605
                    'expire' => null,
1606
                ]);
1607
            }
1608
        }
1609
1610 67
        return $cookies;
1611
    }
1612
1613
    private $_csrfToken;
1614
1615
    /**
1616
     * Returns the token used to perform CSRF validation.
1617
     *
1618
     * This token is generated in a way to prevent [BREACH attacks](http://breachattack.com/). It may be passed
1619
     * along via a hidden field of an HTML form or an HTTP header value to support CSRF validation.
1620
     * @param bool $regenerate whether to regenerate CSRF token. When this parameter is true, each time
1621
     * this method is called, a new CSRF token will be generated and persisted (in session or cookie).
1622
     * @return string the token used to perform CSRF validation.
1623
     */
1624 74
    public function getCsrfToken($regenerate = false)
1625
    {
1626 74
        if ($this->_csrfToken === null || $regenerate) {
1627 74
            $token = $this->loadCsrfToken();
1628 73
            if ($regenerate || empty($token)) {
1629 70
                $token = $this->generateCsrfToken();
1630
            }
1631 73
            $this->_csrfToken = Yii::$app->security->maskToken($token);
1632
        }
1633
1634 73
        return $this->_csrfToken;
1635
    }
1636
1637
    /**
1638
     * Loads the CSRF token from cookie or session.
1639
     * @return string the CSRF token loaded from cookie or session. Null is returned if the cookie or session
1640
     * does not have CSRF token.
1641
     */
1642 74
    protected function loadCsrfToken()
1643
    {
1644 74
        if ($this->enableCsrfCookie) {
1645 70
            return $this->getCookies()->getValue($this->csrfParam);
1646
        }
1647
1648 4
        return Yii::$app->getSession()->get($this->csrfParam);
0 ignored issues
show
Bug introduced by
The method getSession does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1649
    }
1650
1651
    /**
1652
     * Generates an unmasked random token used to perform CSRF validation.
1653
     * @return string the random token for CSRF validation.
1654
     */
1655 70
    protected function generateCsrfToken()
1656
    {
1657 70
        $token = Yii::$app->getSecurity()->generateRandomString();
1658 70
        if ($this->enableCsrfCookie) {
1659 69
            $cookie = $this->createCsrfCookie($token);
1660 69
            Yii::$app->getResponse()->getCookies()->add($cookie);
1661
        } else {
1662 1
            Yii::$app->getSession()->set($this->csrfParam, $token);
0 ignored issues
show
Bug introduced by
The method getSession does only exist in yii\web\Application, but not in yii\console\Application.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1663
        }
1664
1665 70
        return $token;
1666
    }
1667
1668
    /**
1669
     * @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent.
1670
     */
1671 3
    public function getCsrfTokenFromHeader()
1672
    {
1673 3
        return $this->headers->get(static::CSRF_HEADER);
1674
    }
1675
1676
    /**
1677
     * Creates a cookie with a randomly generated CSRF token.
1678
     * Initial values specified in [[csrfCookie]] will be applied to the generated cookie.
1679
     * @param string $token the CSRF token
1680
     * @return Cookie the generated cookie
1681
     * @see enableCsrfValidation
1682
     */
1683 69
    protected function createCsrfCookie($token)
1684
    {
1685 69
        $options = $this->csrfCookie;
1686 69
        return Yii::createObject(array_merge($options, [
1687 69
            'class' => 'yii\web\Cookie',
1688 69
            'name' => $this->csrfParam,
1689 69
            'value' => $token,
1690
        ]));
1691
    }
1692
1693
    /**
1694
     * Performs the CSRF validation.
1695
     *
1696
     * This method will validate the user-provided CSRF token by comparing it with the one stored in cookie or session.
1697
     * This method is mainly called in [[Controller::beforeAction()]].
1698
     *
1699
     * Note that the method will NOT perform CSRF validation if [[enableCsrfValidation]] is false or the HTTP method
1700
     * is among GET, HEAD or OPTIONS.
1701
     *
1702
     * @param string $clientSuppliedToken the user-provided CSRF token to be validated. If null, the token will be retrieved from
1703
     * the [[csrfParam]] POST field or HTTP header.
1704
     * This parameter is available since version 2.0.4.
1705
     * @return bool whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true.
1706
     */
1707 7
    public function validateCsrfToken($clientSuppliedToken = null)
1708
    {
1709 7
        $method = $this->getMethod();
1710
        // only validate CSRF token on non-"safe" methods https://tools.ietf.org/html/rfc2616#section-9.1.1
1711 7
        if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
1712 6
            return true;
1713
        }
1714
1715 4
        $trueToken = $this->getCsrfToken();
1716
1717 4
        if ($clientSuppliedToken !== null) {
1718 2
            return $this->validateCsrfTokenInternal($clientSuppliedToken, $trueToken);
1719
        }
1720
1721 3
        return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
1722 3
            || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
0 ignored issues
show
Bug introduced by
It seems like $this->getCsrfTokenFromHeader() targeting yii\web\Request::getCsrfTokenFromHeader() can also be of type array; however, yii\web\Request::validateCsrfTokenInternal() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1723
    }
1724
1725
    /**
1726
     * Validates CSRF token.
1727
     *
1728
     * @param string $clientSuppliedToken The masked client-supplied token.
1729
     * @param string $trueToken The masked true token.
1730
     * @return bool
1731
     */
1732 4
    private function validateCsrfTokenInternal($clientSuppliedToken, $trueToken)
1733
    {
1734 4
        if (!is_string($clientSuppliedToken)) {
1735 3
            return false;
1736
        }
1737
1738 4
        $security = Yii::$app->security;
1739
1740 4
        return $security->compareString($security->unmaskToken($clientSuppliedToken), $security->unmaskToken($trueToken));
1741
    }
1742
}
1743