Completed
Push — fix-failing-htmltest-csrf-and-... ( 9e9972 )
by
unknown
11:59
created

Request::getQueryParam()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
ccs 3
cts 3
cp 1
cc 2
eloc 3
nc 2
nop 2
crap 2
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
13
/**
14
 * The web Request class represents an HTTP request
15
 *
16
 * It encapsulates the $_SERVER variable and resolves its inconsistency among different Web servers.
17
 * Also it provides an interface to retrieve request parameters from $_POST, $_GET, $_COOKIES and REST
18
 * parameters sent via other HTTP methods like PUT or DELETE.
19
 *
20
 * Request is configured as an application component in [[\yii\web\Application]] by default.
21
 * You can access that instance via `Yii::$app->request`.
22
 *
23
 * For more details and usage information on Request, see the [guide article on requests](guide:runtime-requests).
24
 *
25
 * @property string $absoluteUrl The currently requested absolute URL. This property is read-only.
26
 * @property array $acceptableContentTypes The content types ordered by the quality score. Types with the
27
 * highest scores will be returned first. The array keys are the content types, while the array values are the
28
 * corresponding quality score and other parameters as given in the header.
29
 * @property array $acceptableLanguages The languages ordered by the preference level. The first element
30
 * represents the most preferred language.
31
 * @property string|null $authPassword The password sent via HTTP authentication, null if the password is not
32
 * given. This property is read-only.
33
 * @property string|null $authUser The username sent via HTTP authentication, null if the username is not
34
 * given. This property is read-only.
35
 * @property string $baseUrl The relative URL for the application.
36
 * @property array $bodyParams The request parameters given in the request body.
37
 * @property string $contentType Request content-type. Null is returned if this information is not available.
38
 * This property is read-only.
39
 * @property CookieCollection $cookies The cookie collection. This property is read-only.
40
 * @property string $csrfToken The token used to perform CSRF validation. This property is read-only.
41
 * @property string $csrfTokenFromHeader The CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned
42
 * if no such header is sent. This property is read-only.
43
 * @property array $eTags The entity tags. This property is read-only.
44
 * @property HeaderCollection $headers The header collection. This property is read-only.
45
 * @property string|null $hostInfo Schema and hostname part (with port number if needed) of the request URL
46
 * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set. See
47
 * [[getHostInfo()]] for security related notes on this property.
48
 * @property string|null $hostName Hostname part of the request URL (e.g. `www.yiiframework.com`). This
49
 * property is read-only.
50
 * @property bool $isAjax Whether this is an AJAX (XMLHttpRequest) request. This property is read-only.
51
 * @property bool $isDelete Whether this is a DELETE request. This property is read-only.
52
 * @property bool $isFlash Whether this is an Adobe Flash or Adobe Flex request. This property is read-only.
53
 * @property bool $isGet Whether this is a GET request. This property is read-only.
54
 * @property bool $isHead Whether this is a HEAD request. This property is read-only.
55
 * @property bool $isOptions Whether this is a OPTIONS request. This property is read-only.
56
 * @property bool $isPatch Whether this is a PATCH request. This property is read-only.
57
 * @property bool $isPjax Whether this is a PJAX request. This property is read-only.
58
 * @property bool $isPost Whether this is a POST request. This property is read-only.
59
 * @property bool $isPut Whether this is a PUT request. This property is read-only.
60
 * @property bool $isSecureConnection If the request is sent via secure channel (https). This property is
61
 * read-only.
62
 * @property string $method Request method, such as GET, POST, HEAD, PUT, PATCH, DELETE. The value returned is
63
 * turned into upper case. This property is read-only.
64
 * @property string $pathInfo Part of the request URL that is after the entry script and before the question
65
 * mark. Note, the returned path info is already URL-decoded.
66
 * @property int $port Port number for insecure requests.
67
 * @property array $queryParams The request GET parameter values.
68
 * @property string $queryString Part of the request URL that is after the question mark. This property is
69
 * read-only.
70
 * @property string $rawBody The request body.
71
 * @property string|null $referrer URL referrer, null if not available. This property is read-only.
72
 * @property string|null $origin URL origin, null if not available. This property is read-only.
73
 * @property string $scriptFile The entry script file path.
74
 * @property string $scriptUrl The relative URL of the entry script.
75
 * @property int $securePort Port number for secure requests.
76
 * @property string $serverName Server name, null if not available. This property is read-only.
77
 * @property int|null $serverPort Server port number, null if not available. This property is read-only.
78
 * @property string $url The currently requested relative URL. Note that the URI returned may be URL-encoded
79
 * depending on the client.
80
 * @property string|null $userAgent User agent, null if not available. This property is read-only.
81
 * @property string|null $userHost User host name, null if not available. This property is read-only.
82
 * @property string|null $userIP User IP address, null if not available. This property is read-only.
83
 *
84
 * @author Qiang Xue <[email protected]>
85
 * @since 2.0
86
 */
87
class Request extends \yii\base\Request
88
{
89
    /**
90
     * The name of the HTTP header for sending CSRF token.
91
     */
92
    const CSRF_HEADER = 'X-CSRF-Token';
93
    /**
94
     * The length of the CSRF token mask.
95
     * @deprecated 2.0.12 The mask length is now equal to the token length.
96
     */
97
    const CSRF_MASK_LENGTH = 8;
98
99
    /**
100
     * @var bool whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to true.
101
     * When CSRF validation is enabled, forms submitted to an Yii Web application must be originated
102
     * from the same application. If not, a 400 HTTP exception will be raised.
103
     *
104
     * Note, this feature requires that the user client accepts cookie. Also, to use this feature,
105
     * forms submitted via POST method must contain a hidden input whose name is specified by [[csrfParam]].
106
     * You may use [[\yii\helpers\Html::beginForm()]] to generate his hidden input.
107
     *
108
     * In JavaScript, you may get the values of [[csrfParam]] and [[csrfToken]] via `yii.getCsrfParam()` and
109
     * `yii.getCsrfToken()`, respectively. The [[\yii\web\YiiAsset]] asset must be registered.
110
     * You also need to include CSRF meta tags in your pages by using [[\yii\helpers\Html::csrfMetaTags()]].
111
     *
112
     * @see Controller::enableCsrfValidation
113
     * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery
114
     */
115
    public $enableCsrfValidation = true;
116
    /**
117
     * @var string the name of the token used to prevent CSRF. Defaults to '_csrf'.
118
     * This property is used only when [[enableCsrfValidation]] is true.
119
     */
120
    public $csrfParam = '_csrf';
121
    /**
122
     * @var array the configuration for creating the CSRF [[Cookie|cookie]]. This property is used only when
123
     * both [[enableCsrfValidation]] and [[enableCsrfCookie]] are true.
124
     */
125
    public $csrfCookie = ['httpOnly' => true];
126
    /**
127
     * @var bool whether to use cookie to persist CSRF token. If false, CSRF token will be stored
128
     * in session under the name of [[csrfParam]]. Note that while storing CSRF tokens in session increases
129
     * security, it requires starting a session for every page, which will degrade your site performance.
130
     */
131
    public $enableCsrfCookie = true;
132
    /**
133
     * @var bool whether cookies should be validated to ensure they are not tampered. Defaults to true.
134
     */
135
    public $enableCookieValidation = true;
136
    /**
137
     * @var string a secret key used for cookie validation. This property must be set if [[enableCookieValidation]] is true.
138
     */
139
    public $cookieValidationKey;
140
    /**
141
     * @var string the name of the POST parameter that is used to indicate if a request is a PUT, PATCH or DELETE
142
     * request tunneled through POST. Defaults to '_method'.
143
     * @see getMethod()
144
     * @see getBodyParams()
145
     */
146
    public $methodParam = '_method';
147
    /**
148
     * @var array the parsers for converting the raw HTTP request body into [[bodyParams]].
149
     * The array keys are the request `Content-Types`, and the array values are the
150
     * corresponding configurations for [[Yii::createObject|creating the parser objects]].
151
     * A parser must implement the [[RequestParserInterface]].
152
     *
153
     * To enable parsing for JSON requests you can use the [[JsonParser]] class like in the following example:
154
     *
155
     * ```
156
     * [
157
     *     'application/json' => \yii\web\JsonParser::class,
158
     * ]
159
     * ```
160
     *
161
     * To register a parser for parsing all request types you can use `'*'` as the array key.
162
     * This one will be used as a fallback in case no other types match.
163
     *
164
     * @see getBodyParams()
165
     */
166
    public $parsers = [];
167
168
    /**
169
     * @var CookieCollection Collection of request cookies.
170
     */
171
    private $_cookies;
172
    /**
173
     * @var HeaderCollection Collection of request headers.
174
     */
175
    private $_headers;
176
177
178
    /**
179
     * Resolves the current request into a route and the associated parameters.
180
     * @return array the first element is the route, and the second is the associated parameters.
181
     * @throws NotFoundHttpException if the request cannot be resolved.
182
     */
183 1
    public function resolve()
184
    {
185 1
        $result = Yii::$app->getUrlManager()->parseRequest($this);
186 1
        if ($result !== false) {
187 1
            [$route, $params] = $result;
0 ignored issues
show
Bug introduced by
The variable $route does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $params does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
188 1
            if ($this->_queryParams === null) {
189 1
                $_GET = $params + $_GET; // preserve numeric keys
190
            } else {
191 1
                $this->_queryParams = $params + $this->_queryParams;
192
            }
193 1
            return [$route, $this->getQueryParams()];
194
        }
195
196
        throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
197
    }
198
199
    /**
200
     * Returns the header collection.
201
     * The header collection contains incoming HTTP headers.
202
     * @return HeaderCollection the header collection
203
     */
204 11
    public function getHeaders()
205
    {
206 11
        if ($this->_headers === null) {
207 11
            $this->_headers = new HeaderCollection();
208 11
            if (function_exists('getallheaders')) {
209
                $headers = getallheaders();
210 11
            } elseif (function_exists('http_get_request_headers')) {
211
                $headers = http_get_request_headers();
212
            } else {
213 11
                foreach ($_SERVER as $name => $value) {
214 11
                    if (strncmp($name, 'HTTP_', 5) === 0) {
215 2
                        $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
216 11
                        $this->_headers->add($name, $value);
217
                    }
218
                }
219
220 11
                return $this->_headers;
221
            }
222
            foreach ($headers as $name => $value) {
223
                $this->_headers->add($name, $value);
224
            }
225
        }
226
227 10
        return $this->_headers;
228
    }
229
230
    /**
231
     * Returns the method of the current request (e.g. GET, POST, HEAD, PUT, PATCH, DELETE).
232
     * @return string request method, such as GET, POST, HEAD, PUT, PATCH, DELETE.
233
     * The value returned is turned into upper case.
234
     */
235 20
    public function getMethod()
236
    {
237 20
        if (isset($_POST[$this->methodParam])) {
238 4
            return strtoupper($_POST[$this->methodParam]);
239
        }
240
241 17
        if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
242
            return strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
243
        }
244
245 17
        if (isset($_SERVER['REQUEST_METHOD'])) {
246 2
            return strtoupper($_SERVER['REQUEST_METHOD']);
247
        }
248
249 16
        return 'GET';
250
    }
251
252
    /**
253
     * Returns whether this is a GET request.
254
     * @return bool whether this is a GET request.
255
     */
256 2
    public function getIsGet()
257
    {
258 2
        return $this->getMethod() === 'GET';
259
    }
260
261
    /**
262
     * Returns whether this is an OPTIONS request.
263
     * @return bool whether this is a OPTIONS request.
264
     */
265
    public function getIsOptions()
266
    {
267
        return $this->getMethod() === 'OPTIONS';
268
    }
269
270
    /**
271
     * Returns whether this is a HEAD request.
272
     * @return bool whether this is a HEAD request.
273
     */
274 9
    public function getIsHead()
275
    {
276 9
        return $this->getMethod() === 'HEAD';
277
    }
278
279
    /**
280
     * Returns whether this is a POST request.
281
     * @return bool whether this is a POST request.
282
     */
283
    public function getIsPost()
284
    {
285
        return $this->getMethod() === 'POST';
286
    }
287
288
    /**
289
     * Returns whether this is a DELETE request.
290
     * @return bool whether this is a DELETE request.
291
     */
292
    public function getIsDelete()
293
    {
294
        return $this->getMethod() === 'DELETE';
295
    }
296
297
    /**
298
     * Returns whether this is a PUT request.
299
     * @return bool whether this is a PUT request.
300
     */
301
    public function getIsPut()
302
    {
303
        return $this->getMethod() === 'PUT';
304
    }
305
306
    /**
307
     * Returns whether this is a PATCH request.
308
     * @return bool whether this is a PATCH request.
309
     */
310
    public function getIsPatch()
311
    {
312
        return $this->getMethod() === 'PATCH';
313
    }
314
315
    /**
316
     * Returns whether this is an AJAX (XMLHttpRequest) request.
317
     *
318
     * Note that jQuery doesn't set the header in case of cross domain
319
     * requests: https://stackoverflow.com/questions/8163703/cross-domain-ajax-doesnt-send-x-requested-with-header
320
     *
321
     * @return bool whether this is an AJAX (XMLHttpRequest) request.
322
     */
323 10
    public function getIsAjax()
324
    {
325 10
        return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
326
    }
327
328
    /**
329
     * Returns whether this is a PJAX request
330
     * @return bool whether this is a PJAX request
331
     */
332 1
    public function getIsPjax()
333
    {
334 1
        return $this->getIsAjax() && !empty($_SERVER['HTTP_X_PJAX']);
335
    }
336
337
    /**
338
     * Returns whether this is an Adobe Flash or Flex request.
339
     * @return bool whether this is an Adobe Flash or Adobe Flex request.
340
     */
341
    public function getIsFlash()
342
    {
343
        return isset($_SERVER['HTTP_USER_AGENT']) &&
344
            (stripos($_SERVER['HTTP_USER_AGENT'], 'Shockwave') !== false || stripos($_SERVER['HTTP_USER_AGENT'], 'Flash') !== false);
345
    }
346
347
    private $_rawBody;
348
349
    /**
350
     * Returns the raw HTTP request body.
351
     * @return string the request body
352
     */
353
    public function getRawBody()
354
    {
355
        if ($this->_rawBody === null) {
356
            $this->_rawBody = file_get_contents('php://input');
357
        }
358
359
        return $this->_rawBody;
360
    }
361
362
    /**
363
     * Sets the raw HTTP request body, this method is mainly used by test scripts to simulate raw HTTP requests.
364
     * @param string $rawBody the request body
365
     */
366
    public function setRawBody($rawBody)
367
    {
368
        $this->_rawBody = $rawBody;
369
    }
370
371
    private $_bodyParams;
372
373
    /**
374
     * Returns the request parameters given in the request body.
375
     *
376
     * Request parameters are determined using the parsers configured in [[parsers]] property.
377
     * If no parsers are configured for the current [[contentType]] it uses the PHP function `mb_parse_str()`
378
     * to parse the [[rawBody|request body]].
379
     * @return array the request parameters given in the request body.
380
     * @throws \yii\base\InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]].
381
     * @see getMethod()
382
     * @see getBodyParam()
383
     * @see setBodyParams()
384
     */
385 3
    public function getBodyParams()
386
    {
387 3
        if ($this->_bodyParams === null) {
388 1
            if (isset($_POST[$this->methodParam])) {
389 1
                $this->_bodyParams = $_POST;
390 1
                unset($this->_bodyParams[$this->methodParam]);
391 1
                return $this->_bodyParams;
392
            }
393
394
            $rawContentType = $this->getContentType();
395
            if (($pos = strpos($rawContentType, ';')) !== false) {
396
                // e.g. text/html; charset=UTF-8
397
                $contentType = substr($rawContentType, 0, $pos);
398
            } else {
399
                $contentType = $rawContentType;
400
            }
401
402
            if (isset($this->parsers[$contentType])) {
403
                $parser = Yii::createObject($this->parsers[$contentType]);
404
                if (!($parser instanceof RequestParserInterface)) {
405
                    throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
406
                }
407
                $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
408
            } elseif (isset($this->parsers['*'])) {
409
                $parser = Yii::createObject($this->parsers['*']);
410
                if (!($parser instanceof RequestParserInterface)) {
411
                    throw new InvalidConfigException('The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface.');
412
                }
413
                $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
414
            } elseif ($this->getMethod() === 'POST') {
415
                // PHP has already parsed the body so we have all params in $_POST
416
                $this->_bodyParams = $_POST;
417
            } else {
418
                $this->_bodyParams = [];
419
                mb_parse_str($this->getRawBody(), $this->_bodyParams);
420
            }
421
        }
422
423 3
        return $this->_bodyParams;
424
    }
425
426
    /**
427
     * Sets the request body parameters.
428
     * @param array $values the request body parameters (name-value pairs)
429
     * @see getBodyParam()
430
     * @see getBodyParams()
431
     */
432 2
    public function setBodyParams($values)
433
    {
434 2
        $this->_bodyParams = $values;
435 2
    }
436
437
    /**
438
     * Returns the named request body parameter value.
439
     * If the parameter does not exist, the second parameter passed to this method will be returned.
440
     * @param string $name the parameter name
441
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
442
     * @return mixed the parameter value
443
     * @see getBodyParams()
444
     * @see setBodyParams()
445
     */
446 3
    public function getBodyParam($name, $defaultValue = null)
447
    {
448 3
        $params = $this->getBodyParams();
449
450 3
        return isset($params[$name]) ? $params[$name] : $defaultValue;
451
    }
452
453
    /**
454
     * Returns POST parameter with a given name. If name isn't specified, returns an array of all POST parameters.
455
     *
456
     * @param string $name the parameter name
457
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
458
     * @return array|mixed
459
     */
460
    public function post($name = null, $defaultValue = null)
461
    {
462
        if ($name === null) {
463
            return $this->getBodyParams();
464
        }
465
466
        return $this->getBodyParam($name, $defaultValue);
467
    }
468
469
    private $_queryParams;
470
471
    /**
472
     * Returns the request parameters given in the [[queryString]].
473
     *
474
     * This method will return the contents of `$_GET` if params where not explicitly set.
475
     * @return array the request GET parameter values.
476
     * @see setQueryParams()
477
     */
478 26
    public function getQueryParams()
479
    {
480 26
        if ($this->_queryParams === null) {
481 21
            return $_GET;
482
        }
483
484 7
        return $this->_queryParams;
485
    }
486
487
    /**
488
     * Sets the request [[queryString]] parameters.
489
     * @param array $values the request query parameters (name-value pairs)
490
     * @see getQueryParam()
491
     * @see getQueryParams()
492
     */
493 7
    public function setQueryParams($values)
494
    {
495 7
        $this->_queryParams = $values;
496 7
    }
497
498
    /**
499
     * Returns GET parameter with a given name. If name isn't specified, returns an array of all GET parameters.
500
     *
501
     * @param string $name the parameter name
502
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
503
     * @return array|mixed
504
     */
505 14
    public function get($name = null, $defaultValue = null)
506
    {
507 14
        if ($name === null) {
508
            return $this->getQueryParams();
509
        }
510
511 14
        return $this->getQueryParam($name, $defaultValue);
512
    }
513
514
    /**
515
     * Returns the named GET parameter value.
516
     * If the GET parameter does not exist, the second parameter passed to this method will be returned.
517
     * @param string $name the GET parameter name.
518
     * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
519
     * @return mixed the GET parameter value
520
     * @see getBodyParam()
521
     */
522 17
    public function getQueryParam($name, $defaultValue = null)
523
    {
524 17
        $params = $this->getQueryParams();
525
526 17
        return isset($params[$name]) ? $params[$name] : $defaultValue;
527
    }
528
529
    private $_hostInfo;
530
    private $_hostName;
531
532
    /**
533
     * Returns the schema and host part of the current request URL.
534
     *
535
     * The returned URL does not have an ending slash.
536
     *
537
     * By default this value is based on the user request information. This method will
538
     * return the value of `$_SERVER['HTTP_HOST']` if it is available or `$_SERVER['SERVER_NAME']` if not.
539
     * You may want to check out the [PHP documentation](http://php.net/manual/en/reserved.variables.server.php)
540
     * for more information on these variables.
541
     *
542
     * You may explicitly specify it by setting the [[setHostInfo()|hostInfo]] property.
543
     *
544
     * > Warning: Dependent on the server configuration this information may not be
545
     * > reliable and [may be faked by the user sending the HTTP request](https://www.acunetix.com/vulnerabilities/web/host-header-attack).
546
     * > If the webserver is configured to serve the same site independent of the value of
547
     * > the `Host` header, this value is not reliable. In such situations you should either
548
     * > fix your webserver configuration or explicitly set the value by setting the [[setHostInfo()|hostInfo]] property.
549
     * > If you don't have access to the server configuration, you can setup [[\yii\filters\HostControl]] filter at
550
     * > application level in order to protect against such kind of attack.
551
     *
552
     * @property string|null schema and hostname part (with port number if needed) of the request URL
553
     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
554
     * See [[getHostInfo()]] for security related notes on this property.
555
     * @return string|null schema and hostname part (with port number if needed) of the request URL
556
     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
557
     * @see setHostInfo()
558
     */
559 24
    public function getHostInfo()
560
    {
561 24
        if ($this->_hostInfo === null) {
562 20
            $secure = $this->getIsSecureConnection();
563 20
            $http = $secure ? 'https' : 'http';
564 20
            if (isset($_SERVER['HTTP_HOST'])) {
565 7
                $this->_hostInfo = $http . '://' . $_SERVER['HTTP_HOST'];
566 13
            } elseif (isset($_SERVER['SERVER_NAME'])) {
567
                $this->_hostInfo = $http . '://' . $_SERVER['SERVER_NAME'];
568
                $port = $secure ? $this->getSecurePort() : $this->getPort();
569
                if (($port !== 80 && !$secure) || ($port !== 443 && $secure)) {
570
                    $this->_hostInfo .= ':' . $port;
571
                }
572
            }
573
        }
574
575 24
        return $this->_hostInfo;
576
    }
577
578
    /**
579
     * Sets the schema and host part of the application URL.
580
     * This setter is provided in case the schema and hostname cannot be determined
581
     * on certain Web servers.
582
     * @param string|null $value the schema and host part of the application URL. The trailing slashes will be removed.
583
     * @see getHostInfo() for security related notes on this property.
584
     */
585 57
    public function setHostInfo($value)
586
    {
587 57
        $this->_hostName = null;
588 57
        $this->_hostInfo = $value === null ? null : rtrim($value, '/');
589 57
    }
590
591
    /**
592
     * Returns the host part of the current request URL.
593
     * Value is calculated from current [[getHostInfo()|hostInfo]] property.
594
     *
595
     * > Warning: The content of this value may not be reliable, dependent on the server
596
     * > configuration. Please refer to [[getHostInfo()]] for more information.
597
     *
598
     * @return string|null hostname part of the request URL (e.g. `www.yiiframework.com`)
599
     * @see getHostInfo()
600
     * @since 2.0.10
601
     */
602 11
    public function getHostName()
603
    {
604 11
        if ($this->_hostName === null) {
605 11
            $this->_hostName = parse_url($this->getHostInfo(), PHP_URL_HOST);
606
        }
607
608 11
        return $this->_hostName;
609
    }
610
611
    private $_baseUrl;
612
613
    /**
614
     * Returns the relative URL for the application.
615
     * This is similar to [[scriptUrl]] except that it does not include the script file name,
616
     * and the ending slashes are removed.
617
     * @return string the relative URL for the application
618
     * @see setScriptUrl()
619
     */
620 247
    public function getBaseUrl()
621
    {
622 247
        if ($this->_baseUrl === null) {
623 246
            $this->_baseUrl = rtrim(dirname($this->getScriptUrl()), '\\/');
624
        }
625
626 247
        return $this->_baseUrl;
627
    }
628
629
    /**
630
     * Sets the relative URL for the application.
631
     * By default the URL is determined based on the entry script URL.
632
     * This setter is provided in case you want to change this behavior.
633
     * @param string $value the relative URL for the application
634
     */
635 1
    public function setBaseUrl($value)
636
    {
637 1
        $this->_baseUrl = $value;
638 1
    }
639
640
    private $_scriptUrl;
641
642
    /**
643
     * Returns the relative URL of the entry script.
644
     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
645
     * @return string the relative URL of the entry script.
646
     * @throws InvalidConfigException if unable to determine the entry script URL
647
     */
648 248
    public function getScriptUrl()
649
    {
650 248
        if ($this->_scriptUrl === null) {
651 1
            $scriptFile = $this->getScriptFile();
652
            $scriptName = basename($scriptFile);
653
            if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $scriptName) {
654
                $this->_scriptUrl = $_SERVER['SCRIPT_NAME'];
655
            } elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $scriptName) {
656
                $this->_scriptUrl = $_SERVER['PHP_SELF'];
657
            } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $scriptName) {
658
                $this->_scriptUrl = $_SERVER['ORIG_SCRIPT_NAME'];
659
            } elseif (isset($_SERVER['PHP_SELF']) && ($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) {
660
                $this->_scriptUrl = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName;
661
            } elseif (!empty($_SERVER['DOCUMENT_ROOT']) && strpos($scriptFile, $_SERVER['DOCUMENT_ROOT']) === 0) {
662
                $this->_scriptUrl = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $scriptFile));
663
            } else {
664
                throw new InvalidConfigException('Unable to determine the entry script URL.');
665
            }
666
        }
667
668 247
        return $this->_scriptUrl;
669
    }
670
671
    /**
672
     * Sets the relative URL for the application entry script.
673
     * This setter is provided in case the entry script URL cannot be determined
674
     * on certain Web servers.
675
     * @param string $value the relative URL for the application entry script.
676
     */
677 258
    public function setScriptUrl($value)
678
    {
679 258
        $this->_scriptUrl = $value === null ? null : '/' . trim($value, '/');
680 258
    }
681
682
    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...
683
684
    /**
685
     * Returns the entry script file path.
686
     * The default implementation will simply return `$_SERVER['SCRIPT_FILENAME']`.
687
     * @return string the entry script file path
688
     * @throws InvalidConfigException
689
     */
690 249
    public function getScriptFile()
691
    {
692 249
        if (isset($this->_scriptFile)) {
693 227
            return $this->_scriptFile;
694
        }
695
696 22
        if (isset($_SERVER['SCRIPT_FILENAME'])) {
697 20
            return $_SERVER['SCRIPT_FILENAME'];
698
        }
699
700 2
        throw new InvalidConfigException('Unable to determine the entry script file path.');
701
    }
702
703
    /**
704
     * Sets the entry script file path.
705
     * The entry script file path normally can be obtained from `$_SERVER['SCRIPT_FILENAME']`.
706
     * If your server configuration does not return the correct value, you may configure
707
     * this property to make it right.
708
     * @param string $value the entry script file path.
709
     */
710 227
    public function setScriptFile($value)
711
    {
712 227
        $this->_scriptFile = $value;
713 227
    }
714
715
    private $_pathInfo;
716
717
    /**
718
     * Returns the path info of the currently requested URL.
719
     * A path info refers to the part that is after the entry script and before the question mark (query string).
720
     * The starting and ending slashes are both removed.
721
     * @return string part of the request URL that is after the entry script and before the question mark.
722
     * Note, the returned path info is already URL-decoded.
723
     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
724
     */
725 18
    public function getPathInfo()
726
    {
727 18
        if ($this->_pathInfo === null) {
728
            $this->_pathInfo = $this->resolvePathInfo();
729
        }
730
731 18
        return $this->_pathInfo;
732
    }
733
734
    /**
735
     * Sets the path info of the current request.
736
     * This method is mainly provided for testing purpose.
737
     * @param string $value the path info of the current request
738
     */
739 19
    public function setPathInfo($value)
740
    {
741 19
        $this->_pathInfo = $value === null ? null : ltrim($value, '/');
742 19
    }
743
744
    /**
745
     * Resolves the path info part of the currently requested URL.
746
     * A path info refers to the part that is after the entry script and before the question mark (query string).
747
     * The starting slashes are both removed (ending slashes will be kept).
748
     * @return string part of the request URL that is after the entry script and before the question mark.
749
     * Note, the returned path info is decoded.
750
     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
751
     */
752
    protected function resolvePathInfo()
753
    {
754
        $pathInfo = $this->getUrl();
755
756
        if (($pos = strpos($pathInfo, '?')) !== false) {
757
            $pathInfo = substr($pathInfo, 0, $pos);
758
        }
759
760
        $pathInfo = urldecode($pathInfo);
761
762
        // try to encode in UTF8 if not so
763
        // http://w3.org/International/questions/qa-forms-utf-8.html
764
        if (!preg_match('%^(?:
765
            [\x09\x0A\x0D\x20-\x7E]              # ASCII
766
            | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
767
            | \xE0[\xA0-\xBF][\x80-\xBF]         # excluding overlongs
768
            | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
769
            | \xED[\x80-\x9F][\x80-\xBF]         # excluding surrogates
770
            | \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
771
            | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
772
            | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
773
            )*$%xs', $pathInfo)
774
        ) {
775
            $pathInfo = utf8_encode($pathInfo);
776
        }
777
778
        $scriptUrl = $this->getScriptUrl();
779
        $baseUrl = $this->getBaseUrl();
780
        if (strpos($pathInfo, $scriptUrl) === 0) {
781
            $pathInfo = substr($pathInfo, strlen($scriptUrl));
782
        } elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) {
783
            $pathInfo = substr($pathInfo, strlen($baseUrl));
784
        } elseif (isset($_SERVER['PHP_SELF']) && strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) {
785
            $pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl));
786
        } else {
787
            throw new InvalidConfigException('Unable to determine the path info of the current request.');
788
        }
789
790
        if (substr($pathInfo, 0, 1) === '/') {
791
            $pathInfo = substr($pathInfo, 1);
792
        }
793
794
        return (string) $pathInfo;
795
    }
796
797
    /**
798
     * Returns the currently requested absolute URL.
799
     * This is a shortcut to the concatenation of [[hostInfo]] and [[url]].
800
     * @return string the currently requested absolute URL.
801
     */
802
    public function getAbsoluteUrl()
803
    {
804
        return $this->getHostInfo() . $this->getUrl();
805
    }
806
807
    private $_url;
808
809
    /**
810
     * Returns the currently requested relative URL.
811
     * This refers to the portion of the URL that is after the [[hostInfo]] part.
812
     * It includes the [[queryString]] part if any.
813
     * @return string the currently requested relative URL. Note that the URI returned may be URL-encoded depending on the client.
814
     * @throws InvalidConfigException if the URL cannot be determined due to unusual server configuration
815
     */
816 10
    public function getUrl()
817
    {
818 10
        if ($this->_url === null) {
819 2
            $this->_url = $this->resolveRequestUri();
820
        }
821
822 10
        return $this->_url;
823
    }
824
825
    /**
826
     * Sets the currently requested relative URL.
827
     * The URI must refer to the portion that is after [[hostInfo]].
828
     * Note that the URI should be URL-encoded.
829
     * @param string $value the request URI to be set
830
     */
831 24
    public function setUrl($value)
832
    {
833 24
        $this->_url = $value;
834 24
    }
835
836
    /**
837
     * Resolves the request URI portion for the currently requested URL.
838
     * This refers to the portion that is after the [[hostInfo]] part. It includes the [[queryString]] part if any.
839
     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
840
     * @return string|bool the request URI portion for the currently requested URL.
841
     * Note that the URI returned may be URL-encoded depending on the client.
842
     * @throws InvalidConfigException if the request URI cannot be determined due to unusual server configuration
843
     */
844 2
    protected function resolveRequestUri()
845
    {
846 2
        if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { // IIS
847
            $requestUri = $_SERVER['HTTP_X_REWRITE_URL'];
848 2
        } elseif (isset($_SERVER['REQUEST_URI'])) {
849 2
            $requestUri = $_SERVER['REQUEST_URI'];
850 2
            if ($requestUri !== '' && $requestUri[0] !== '/') {
851 2
                $requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri);
852
            }
853
        } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0 CGI
854
            $requestUri = $_SERVER['ORIG_PATH_INFO'];
855
            if (!empty($_SERVER['QUERY_STRING'])) {
856
                $requestUri .= '?' . $_SERVER['QUERY_STRING'];
857
            }
858
        } else {
859
            throw new InvalidConfigException('Unable to determine the request URI.');
860
        }
861
862 2
        return $requestUri;
863
    }
864
865
    /**
866
     * Returns part of the request URL that is after the question mark.
867
     * @return string part of the request URL that is after the question mark
868
     */
869
    public function getQueryString()
870
    {
871
        return isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
872
    }
873
874
    /**
875
     * Return if the request is sent via secure channel (https).
876
     * @return bool if the request is sent via secure channel (https)
877
     */
878 20
    public function getIsSecureConnection()
879
    {
880 20
        return isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'], 'on') === 0 || $_SERVER['HTTPS'] == 1)
881 20
            || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0;
882
    }
883
884
    /**
885
     * Returns the server name.
886
     * @return string server name, null if not available
887
     */
888 1
    public function getServerName()
889
    {
890 1
        return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null;
891
    }
892
893
    /**
894
     * Returns the server port number.
895
     * @return int|null server port number, null if not available
896
     */
897 1
    public function getServerPort()
898
    {
899 1
        return isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : null;
900
    }
901
902
    /**
903
     * Returns the URL referrer.
904
     * @return string|null URL referrer, null if not available
905
     */
906
    public function getReferrer()
907
    {
908
        return isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
909
    }
910
911
    /**
912
     * Returns the URL origin of a CORS request.
913
     *
914
     * The return value is taken from the `Origin` [[getHeaders()|header]] sent by the browser.
915
     *
916
     * Note that the origin request header indicates where a fetch originates from.
917
     * It doesn't include any path information, but only the server name.
918
     * It is sent with a CORS requests, as well as with POST requests.
919
     * It is similar to the referer header, but, unlike this header, it doesn't disclose the whole path.
920
     * Please refer to <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin> for more information.
921
     *
922
     * @return string|null URL origin of a CORS request, `null` if not available.
923
     * @see getHeaders()
924
     * @since 2.0.13
925
     */
926 1
    public function getOrigin()
927
    {
928 1
        return $this->getHeaders()->get('origin');
929
    }
930
931
    /**
932
     * Returns the user agent.
933
     * @return string|null user agent, null if not available
934
     */
935
    public function getUserAgent()
936
    {
937
        return isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null;
938
    }
939
940
    /**
941
     * Returns the user IP address.
942
     * @return string|null user IP address, null if not available
943
     */
944 27
    public function getUserIP()
945
    {
946 27
        return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
947
    }
948
949
    /**
950
     * Returns the user host name.
951
     * @return string|null user host name, null if not available
952
     */
953
    public function getUserHost()
954
    {
955
        return isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : null;
956
    }
957
958
    /**
959
     * @return string|null the username sent via HTTP authentication, null if the username is not given
960
     */
961 10
    public function getAuthUser()
962
    {
963 10
        return isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
964
    }
965
966
    /**
967
     * @return string|null the password sent via HTTP authentication, null if the password is not given
968
     */
969 10
    public function getAuthPassword()
970
    {
971 10
        return isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
972
    }
973
974
    private $_port;
975
976
    /**
977
     * Returns the port to use for insecure requests.
978
     * Defaults to 80, or the port specified by the server if the current
979
     * request is insecure.
980
     * @return int port number for insecure requests.
981
     * @see setPort()
982
     */
983
    public function getPort()
984
    {
985
        if ($this->_port === null) {
986
            $this->_port = !$this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : 80;
987
        }
988
989
        return $this->_port;
990
    }
991
992
    /**
993
     * Sets the port to use for insecure requests.
994
     * This setter is provided in case a custom port is necessary for certain
995
     * server configurations.
996
     * @param int $value port number.
997
     */
998
    public function setPort($value)
999
    {
1000
        if ($value != $this->_port) {
1001
            $this->_port = (int) $value;
1002
            $this->_hostInfo = null;
1003
        }
1004
    }
1005
1006
    private $_securePort;
1007
1008
    /**
1009
     * Returns the port to use for secure requests.
1010
     * Defaults to 443, or the port specified by the server if the current
1011
     * request is secure.
1012
     * @return int port number for secure requests.
1013
     * @see setSecurePort()
1014
     */
1015
    public function getSecurePort()
1016
    {
1017
        if ($this->_securePort === null) {
1018
            $this->_securePort = $this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : 443;
1019
        }
1020
1021
        return $this->_securePort;
1022
    }
1023
1024
    /**
1025
     * Sets the port to use for secure requests.
1026
     * This setter is provided in case a custom port is necessary for certain
1027
     * server configurations.
1028
     * @param int $value port number.
1029
     */
1030
    public function setSecurePort($value)
1031
    {
1032
        if ($value != $this->_securePort) {
1033
            $this->_securePort = (int) $value;
1034
            $this->_hostInfo = null;
1035
        }
1036
    }
1037
1038
    private $_contentTypes;
1039
1040
    /**
1041
     * Returns the content types acceptable by the end user.
1042
     * This is determined by the `Accept` HTTP header. For example,
1043
     *
1044
     * ```php
1045
     * $_SERVER['HTTP_ACCEPT'] = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1046
     * $types = $request->getAcceptableContentTypes();
1047
     * print_r($types);
1048
     * // displays:
1049
     * // [
1050
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1051
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1052
     * //           'text/plain' => ['q' => 0.5],
1053
     * // ]
1054
     * ```
1055
     *
1056
     * @return array the content types ordered by the quality score. Types with the highest scores
1057
     * will be returned first. The array keys are the content types, while the array values
1058
     * are the corresponding quality score and other parameters as given in the header.
1059
     */
1060 2
    public function getAcceptableContentTypes()
1061
    {
1062 2
        if ($this->_contentTypes === null) {
1063 2
            if (isset($_SERVER['HTTP_ACCEPT'])) {
1064 2
                $this->_contentTypes = $this->parseAcceptHeader($_SERVER['HTTP_ACCEPT']);
1065
            } else {
1066 1
                $this->_contentTypes = [];
1067
            }
1068
        }
1069
1070 2
        return $this->_contentTypes;
1071
    }
1072
1073
    /**
1074
     * Sets the acceptable content types.
1075
     * Please refer to [[getAcceptableContentTypes()]] on the format of the parameter.
1076
     * @param array $value the content types that are acceptable by the end user. They should
1077
     * be ordered by the preference level.
1078
     * @see getAcceptableContentTypes()
1079
     * @see parseAcceptHeader()
1080
     */
1081
    public function setAcceptableContentTypes($value)
1082
    {
1083
        $this->_contentTypes = $value;
1084
    }
1085
1086
    /**
1087
     * Returns request content-type
1088
     * The Content-Type header field indicates the MIME type of the data
1089
     * contained in [[getRawBody()]] or, in the case of the HEAD method, the
1090
     * media type that would have been sent had the request been a GET.
1091
     * For the MIME-types the user expects in response, see [[acceptableContentTypes]].
1092
     * @return string request content-type. Null is returned if this information is not available.
1093
     * @link https://tools.ietf.org/html/rfc2616#section-14.17
1094
     * HTTP 1.1 header field definitions
1095
     */
1096
    public function getContentType()
1097
    {
1098
        if (isset($_SERVER['CONTENT_TYPE'])) {
1099
            return $_SERVER['CONTENT_TYPE'];
1100
        }
1101
1102
        if (isset($_SERVER['HTTP_CONTENT_TYPE'])) {
1103
            //fix bug https://bugs.php.net/bug.php?id=66606
1104
            return $_SERVER['HTTP_CONTENT_TYPE'];
1105
        }
1106
1107
        return null;
1108
    }
1109
1110
    private $_languages;
1111
1112
    /**
1113
     * Returns the languages acceptable by the end user.
1114
     * This is determined by the `Accept-Language` HTTP header.
1115
     * @return array the languages ordered by the preference level. The first element
1116
     * represents the most preferred language.
1117
     */
1118 1
    public function getAcceptableLanguages()
1119
    {
1120 1
        if ($this->_languages === null) {
1121
            if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
1122
                $this->_languages = array_keys($this->parseAcceptHeader($_SERVER['HTTP_ACCEPT_LANGUAGE']));
1123
            } else {
1124
                $this->_languages = [];
1125
            }
1126
        }
1127
1128 1
        return $this->_languages;
1129
    }
1130
1131
    /**
1132
     * @param array $value the languages that are acceptable by the end user. They should
1133
     * be ordered by the preference level.
1134
     */
1135 1
    public function setAcceptableLanguages($value)
1136
    {
1137 1
        $this->_languages = $value;
1138 1
    }
1139
1140
    /**
1141
     * Parses the given `Accept` (or `Accept-Language`) header.
1142
     *
1143
     * This method will return the acceptable values with their quality scores and the corresponding parameters
1144
     * as specified in the given `Accept` header. The array keys of the return value are the acceptable values,
1145
     * while the array values consisting of the corresponding quality scores and parameters. The acceptable
1146
     * values with the highest quality scores will be returned first. For example,
1147
     *
1148
     * ```php
1149
     * $header = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1150
     * $accepts = $request->parseAcceptHeader($header);
1151
     * print_r($accepts);
1152
     * // displays:
1153
     * // [
1154
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1155
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1156
     * //           'text/plain' => ['q' => 0.5],
1157
     * // ]
1158
     * ```
1159
     *
1160
     * @param string $header the header to be parsed
1161
     * @return array the acceptable values ordered by their quality score. The values with the highest scores
1162
     * will be returned first.
1163
     */
1164 3
    public function parseAcceptHeader($header)
1165
    {
1166 3
        $accepts = [];
1167 3
        foreach (explode(',', $header) as $i => $part) {
1168 3
            $params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY);
1169 3
            if (empty($params)) {
1170 1
                continue;
1171
            }
1172
            $values = [
1173 3
                'q' => [$i, array_shift($params), 1],
1174
            ];
1175 3
            foreach ($params as $param) {
1176 2
                if (strpos($param, '=') !== false) {
1177 2
                    [$key, $value] = explode('=', $param, 2);
0 ignored issues
show
Bug introduced by
The variable $key does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $value does not exist. Did you mean $values?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
1178 2
                    if ($key === 'q') {
1179 2
                        $values['q'][2] = (float) $value;
0 ignored issues
show
Bug introduced by
The variable $value does not exist. Did you mean $values?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
1180
                    } else {
1181 2
                        $values[$key] = $value;
0 ignored issues
show
Bug introduced by
The variable $value does not exist. Did you mean $values?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
1182
                    }
1183
                } else {
1184 2
                    $values[] = $param;
1185
                }
1186
            }
1187 3
            $accepts[] = $values;
1188
        }
1189
1190 3
        usort($accepts, function ($a, $b) {
1191 3
            $a = $a['q']; // index, name, q
1192 3
            $b = $b['q'];
1193 3
            if ($a[2] > $b[2]) {
1194 2
                return -1;
1195
            }
1196
1197 2
            if ($a[2] < $b[2]) {
1198 1
                return 1;
1199
            }
1200
1201 2
            if ($a[1] === $b[1]) {
1202
                return $a[0] > $b[0] ? 1 : -1;
1203
            }
1204
1205 2
            if ($a[1] === '*/*') {
1206
                return 1;
1207
            }
1208
1209 2
            if ($b[1] === '*/*') {
1210
                return -1;
1211
            }
1212
1213 2
            $wa = $a[1][strlen($a[1]) - 1] === '*';
1214 2
            $wb = $b[1][strlen($b[1]) - 1] === '*';
1215 2
            if ($wa xor $wb) {
1216
                return $wa ? 1 : -1;
1217
            }
1218
1219 2
            return $a[0] > $b[0] ? 1 : -1;
1220 3
        });
1221
1222 3
        $result = [];
1223 3
        foreach ($accepts as $accept) {
1224 3
            $name = $accept['q'][1];
1225 3
            $accept['q'] = $accept['q'][2];
1226 3
            $result[$name] = $accept;
1227
        }
1228
1229 3
        return $result;
1230
    }
1231
1232
    /**
1233
     * Returns the user-preferred language that should be used by this application.
1234
     * The language resolution is based on the user preferred languages and the languages
1235
     * supported by the application. The method will try to find the best match.
1236
     * @param array $languages a list of the languages supported by the application. If this is empty, the current
1237
     * application language will be returned without further processing.
1238
     * @return string the language that the application should use.
1239
     */
1240 1
    public function getPreferredLanguage(array $languages = [])
1241
    {
1242 1
        if (empty($languages)) {
1243 1
            return Yii::$app->language;
1244
        }
1245 1
        foreach ($this->getAcceptableLanguages() as $acceptableLanguage) {
1246 1
            $acceptableLanguage = str_replace('_', '-', strtolower($acceptableLanguage));
1247 1
            foreach ($languages as $language) {
1248 1
                $normalizedLanguage = str_replace('_', '-', strtolower($language));
1249
1250
                if (
1251 1
                    $normalizedLanguage === $acceptableLanguage // en-us==en-us
1252 1
                    || strpos($acceptableLanguage, $normalizedLanguage . '-') === 0 // en==en-us
1253 1
                    || strpos($normalizedLanguage, $acceptableLanguage . '-') === 0 // en-us==en
1254
                ) {
1255 1
                    return $language;
1256
                }
1257
            }
1258
        }
1259
1260 1
        return reset($languages);
1261
    }
1262
1263
    /**
1264
     * Gets the Etags.
1265
     *
1266
     * @return array The entity tags
1267
     */
1268
    public function getETags()
1269
    {
1270
        if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
1271
            return preg_split('/[\s,]+/', str_replace('-gzip', '', $_SERVER['HTTP_IF_NONE_MATCH']), -1, PREG_SPLIT_NO_EMPTY);
1272
        }
1273
1274
        return [];
1275
    }
1276
1277
    /**
1278
     * Returns the cookie collection.
1279
     * Through the returned cookie collection, you may access a cookie using the following syntax:
1280
     *
1281
     * ```php
1282
     * $cookie = $request->cookies['name']
1283
     * if ($cookie !== null) {
1284
     *     $value = $cookie->value;
1285
     * }
1286
     *
1287
     * // alternatively
1288
     * $value = $request->cookies->getValue('name');
1289
     * ```
1290
     *
1291
     * @return CookieCollection the cookie collection.
1292
     */
1293 31
    public function getCookies()
1294
    {
1295 31
        if ($this->_cookies === null) {
1296 31
            $this->_cookies = new CookieCollection($this->loadCookies(), [
1297 31
                'readOnly' => true,
1298
            ]);
1299
        }
1300
1301 31
        return $this->_cookies;
1302
    }
1303
1304
    /**
1305
     * Converts `$_COOKIE` into an array of [[Cookie]].
1306
     * @return array the cookies obtained from request
1307
     * @throws InvalidConfigException if [[cookieValidationKey]] is not set when [[enableCookieValidation]] is true
1308
     */
1309 31
    protected function loadCookies()
1310
    {
1311 31
        $cookies = [];
1312 31
        if ($this->enableCookieValidation) {
1313 30
            if ($this->cookieValidationKey == '') {
1314
                throw new InvalidConfigException(get_class($this) . '::cookieValidationKey must be configured with a secret key.');
1315
            }
1316 30
            foreach ($_COOKIE as $name => $value) {
1317
                if (!is_string($value)) {
1318
                    continue;
1319
                }
1320
                $data = Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey);
1321
                if ($data === false) {
1322
                    continue;
1323
                }
1324
                $data = @unserialize($data);
1325
                if (is_array($data) && isset($data[0], $data[1]) && $data[0] === $name) {
1326
                    $cookies[$name] = new Cookie([
1327 30
                        'name' => $name,
1328
                        'value' => $data[1],
1329
                        'expire' => null,
1330
                    ]);
1331
                }
1332
            }
1333
        } else {
1334 1
            foreach ($_COOKIE as $name => $value) {
1335
                $cookies[$name] = new Cookie([
1336
                    'name' => $name,
1337
                    'value' => $value,
1338
                    'expire' => null,
1339
                ]);
1340
            }
1341
        }
1342
1343 31
        return $cookies;
1344
    }
1345
1346
    private $_csrfToken;
1347
1348
    /**
1349
     * Returns the token used to perform CSRF validation.
1350
     *
1351
     * This token is generated in a way to prevent [BREACH attacks](http://breachattack.com/). It may be passed
1352
     * along via a hidden field of an HTML form or an HTTP header value to support CSRF validation.
1353
     * @param bool $regenerate whether to regenerate CSRF token. When this parameter is true, each time
1354
     * this method is called, a new CSRF token will be generated and persisted (in session or cookie).
1355
     * @return string the token used to perform CSRF validation.
1356
     */
1357 34
    public function getCsrfToken($regenerate = false)
1358
    {
1359 34
        if ($this->_csrfToken === null || $regenerate) {
1360 34
            if ($regenerate || ($token = $this->loadCsrfToken()) === null) {
1361 33
                $token = $this->generateCsrfToken();
1362
            }
1363 34
            $this->_csrfToken = Yii::$app->security->maskToken($token);
1364
        }
1365
1366 34
        return $this->_csrfToken;
1367
    }
1368
1369
    /**
1370
     * Loads the CSRF token from cookie or session.
1371
     * @return string the CSRF token loaded from cookie or session. Null is returned if the cookie or session
1372
     * does not have CSRF token.
1373
     */
1374 34
    protected function loadCsrfToken()
1375
    {
1376 34
        if ($this->enableCsrfCookie) {
1377 31
            return $this->getCookies()->getValue($this->csrfParam);
1378
        }
1379 3
        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...
1380
    }
1381
1382
    /**
1383
     * Generates an unmasked random token used to perform CSRF validation.
1384
     * @return string the random token for CSRF validation.
1385
     */
1386 33
    protected function generateCsrfToken()
1387
    {
1388 33
        $token = Yii::$app->getSecurity()->generateRandomKey();
1389 33
        if ($this->enableCsrfCookie) {
1390 31
            $cookie = $this->createCsrfCookie($token);
1391 31
            Yii::$app->getResponse()->getCookies()->add($cookie);
1392
        } else {
1393 2
            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...
1394
        }
1395 33
        return $token;
1396
    }
1397
1398
    /**
1399
     * @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent.
1400
     */
1401 3
    public function getCsrfTokenFromHeader()
1402
    {
1403 3
        return $this->headers->get(static::CSRF_HEADER);
1404
    }
1405
1406
    /**
1407
     * Creates a cookie with a randomly generated CSRF token.
1408
     * Initial values specified in [[csrfCookie]] will be applied to the generated cookie.
1409
     * @param string $token the CSRF token
1410
     * @return Cookie the generated cookie
1411
     * @see enableCsrfValidation
1412
     */
1413 31
    protected function createCsrfCookie($token)
1414
    {
1415 31
        $options = $this->csrfCookie;
1416 31
        $options['name'] = $this->csrfParam;
1417 31
        $options['value'] = $token;
1418 31
        return new Cookie($options);
1419
    }
1420
1421
    /**
1422
     * Performs the CSRF validation.
1423
     *
1424
     * This method will validate the user-provided CSRF token by comparing it with the one stored in cookie or session.
1425
     * This method is mainly called in [[Controller::beforeAction()]].
1426
     *
1427
     * Note that the method will NOT perform CSRF validation if [[enableCsrfValidation]] is false or the HTTP method
1428
     * is among GET, HEAD or OPTIONS.
1429
     *
1430
     * @param string $clientSuppliedToken the user-provided CSRF token to be validated. If null, the token will be retrieved from
1431
     * the [[csrfParam]] POST field or HTTP header.
1432
     * This parameter is available since version 2.0.4.
1433
     * @return bool whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true.
1434
     */
1435 5
    public function validateCsrfToken($clientSuppliedToken = null)
1436
    {
1437 5
        $method = $this->getMethod();
1438
        // only validate CSRF token on non-"safe" methods https://tools.ietf.org/html/rfc2616#section-9.1.1
1439 5
        if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
1440 5
            return true;
1441
        }
1442
1443 3
        $trueToken = $this->getCsrfToken();
1444
1445 3
        if ($clientSuppliedToken !== null) {
1446 1
            return $this->validateCsrfTokenInternal($clientSuppliedToken, $trueToken);
1447
        }
1448
1449 3
        return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
1450 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...
1451
    }
1452
1453
    /**
1454
     * Validates CSRF token
1455
     *
1456
     * @param string $clientSuppliedToken The masked client-supplied token.
1457
     * @param string $trueToken The masked true token.
1458
     * @return bool
1459
     */
1460 3
    private function validateCsrfTokenInternal($clientSuppliedToken, $trueToken)
1461
    {
1462 3
        if (!is_string($clientSuppliedToken)) {
1463 3
            return false;
1464
        }
1465
1466 3
        $security = Yii::$app->security;
1467
1468 3
        return $security->unmaskToken($clientSuppliedToken) === $security->unmaskToken($trueToken);
1469
    }
1470
}
1471