Completed
Push — 2.1 ( 68dd3d...1f97e0 )
by Alexander
11:18
created

Request::getCsrfToken()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 6
cts 6
cp 1
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 6
nc 3
nop 1
crap 5
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 $scriptFile The entry script file path.
73
 * @property string $scriptUrl The relative URL of the entry script.
74
 * @property int $securePort Port number for secure requests.
75
 * @property string $serverName Server name, null if not available. This property is read-only.
76
 * @property int|null $serverPort Server port number, null if not available. This property is read-only.
77
 * @property string $url The currently requested relative URL. Note that the URI returned may be URL-encoded depending on the client.
78
 * @property string|null $userAgent User agent, null if not available. This property is read-only.
79
 * @property string|null $userHost User host name, null if not available. This property is read-only.
80
 * @property string|null $userIP User IP address, null if not available. This property is read-only.
81
 *
82
 * @author Qiang Xue <[email protected]>
83
 * @since 2.0
84
 */
85
class Request extends \yii\base\Request
86
{
87
    /**
88
     * The name of the HTTP header for sending CSRF token.
89
     */
90
    const CSRF_HEADER = 'X-CSRF-Token';
91
    /**
92
     * The length of the CSRF token mask.
93
     * @deprecated 2.0.12 The mask length is now equal to the token length.
94
     */
95
    const CSRF_MASK_LENGTH = 8;
96
97
    /**
98
     * @var bool whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to true.
99
     * When CSRF validation is enabled, forms submitted to an Yii Web application must be originated
100
     * from the same application. If not, a 400 HTTP exception will be raised.
101
     *
102
     * Note, this feature requires that the user client accepts cookie. Also, to use this feature,
103
     * forms submitted via POST method must contain a hidden input whose name is specified by [[csrfParam]].
104
     * You may use [[\yii\helpers\Html::beginForm()]] to generate his hidden input.
105
     *
106
     * In JavaScript, you may get the values of [[csrfParam]] and [[csrfToken]] via `yii.getCsrfParam()` and
107
     * `yii.getCsrfToken()`, respectively. The [[\yii\web\YiiAsset]] asset must be registered.
108
     * You also need to include CSRF meta tags in your pages by using [[\yii\helpers\Html::csrfMetaTags()]].
109
     *
110
     * @see Controller::enableCsrfValidation
111
     * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery
112
     */
113
    public $enableCsrfValidation = true;
114
    /**
115
     * @var string the name of the token used to prevent CSRF. Defaults to '_csrf'.
116
     * This property is used only when [[enableCsrfValidation]] is true.
117
     */
118
    public $csrfParam = '_csrf';
119
    /**
120
     * @var array the configuration for creating the CSRF [[Cookie|cookie]]. This property is used only when
121
     * both [[enableCsrfValidation]] and [[enableCsrfCookie]] are true.
122
     */
123
    public $csrfCookie = ['httpOnly' => true];
124
    /**
125
     * @var bool whether to use cookie to persist CSRF token. If false, CSRF token will be stored
126
     * in session under the name of [[csrfParam]]. Note that while storing CSRF tokens in session increases
127
     * security, it requires starting a session for every page, which will degrade your site performance.
128
     */
129
    public $enableCsrfCookie = true;
130
    /**
131
     * @var bool whether cookies should be validated to ensure they are not tampered. Defaults to true.
132
     */
133
    public $enableCookieValidation = true;
134
    /**
135
     * @var string a secret key used for cookie validation. This property must be set if [[enableCookieValidation]] is true.
136
     */
137
    public $cookieValidationKey;
138
    /**
139
     * @var string the name of the POST parameter that is used to indicate if a request is a PUT, PATCH or DELETE
140
     * request tunneled through POST. Defaults to '_method'.
141
     * @see getMethod()
142
     * @see getBodyParams()
143
     */
144
    public $methodParam = '_method';
145
    /**
146
     * @var array the parsers for converting the raw HTTP request body into [[bodyParams]].
147
     * The array keys are the request `Content-Types`, and the array values are the
148
     * corresponding configurations for [[Yii::createObject|creating the parser objects]].
149
     * A parser must implement the [[RequestParserInterface]].
150
     *
151
     * To enable parsing for JSON requests you can use the [[JsonParser]] class like in the following example:
152
     *
153
     * ```
154
     * [
155
     *     'application/json' => \yii\web\JsonParser::class,
156
     * ]
157
     * ```
158
     *
159
     * To register a parser for parsing all request types you can use `'*'` as the array key.
160
     * This one will be used as a fallback in case no other types match.
161
     *
162
     * @see getBodyParams()
163
     */
164
    public $parsers = [];
165
166
    /**
167
     * @var CookieCollection Collection of request cookies.
168
     */
169
    private $_cookies;
170
    /**
171
     * @var HeaderCollection Collection of request headers.
172
     */
173
    private $_headers;
174
175
176
    /**
177
     * Resolves the current request into a route and the associated parameters.
178
     * @return array the first element is the route, and the second is the associated parameters.
179
     * @throws NotFoundHttpException if the request cannot be resolved.
180
     */
181 1
    public function resolve()
182
    {
183 1
        $result = Yii::$app->getUrlManager()->parseRequest($this);
184 1
        if ($result !== false) {
185 1
            list($route, $params) = $result;
186 1
            if ($this->_queryParams === null) {
187 1
                $_GET = $params + $_GET; // preserve numeric keys
188
            } else {
189 1
                $this->_queryParams = $params + $this->_queryParams;
190
            }
191 1
            return [$route, $this->getQueryParams()];
192
        }
193
194
        throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
195
    }
196
197
    /**
198
     * Returns the header collection.
199
     * The header collection contains incoming HTTP headers.
200
     * @return HeaderCollection the header collection
201
     */
202 10
    public function getHeaders()
203
    {
204 10
        if ($this->_headers === null) {
205 10
            $this->_headers = new HeaderCollection;
206 10
            if (function_exists('getallheaders')) {
207
                $headers = getallheaders();
208 10
            } elseif (function_exists('http_get_request_headers')) {
209
                $headers = http_get_request_headers();
210
            } else {
211 10
                foreach ($_SERVER as $name => $value) {
212 10
                    if (strncmp($name, 'HTTP_', 5) === 0) {
213 1
                        $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
214 1
                        $this->_headers->add($name, $value);
215
                    }
216
                }
217
218 10
                return $this->_headers;
219
            }
220
            foreach ($headers as $name => $value) {
221
                $this->_headers->add($name, $value);
222
            }
223
        }
224
225 10
        return $this->_headers;
226
    }
227
228
    /**
229
     * Returns the method of the current request (e.g. GET, POST, HEAD, PUT, PATCH, DELETE).
230
     * @return string request method, such as GET, POST, HEAD, PUT, PATCH, DELETE.
231
     * The value returned is turned into upper case.
232
     */
233 20
    public function getMethod()
234
    {
235 20
        if (isset($_POST[$this->methodParam])) {
236 4
            return strtoupper($_POST[$this->methodParam]);
237
        }
238
239 17
        if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
240
            return strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
241
        }
242
243 17
        if (isset($_SERVER['REQUEST_METHOD'])) {
244 2
            return strtoupper($_SERVER['REQUEST_METHOD']);
245
        }
246
247 16
        return 'GET';
248
    }
249
250
    /**
251
     * Returns whether this is a GET request.
252
     * @return bool whether this is a GET request.
253
     */
254 2
    public function getIsGet()
255
    {
256 2
        return $this->getMethod() === 'GET';
257
    }
258
259
    /**
260
     * Returns whether this is an OPTIONS request.
261
     * @return bool whether this is a OPTIONS request.
262
     */
263
    public function getIsOptions()
264
    {
265
        return $this->getMethod() === 'OPTIONS';
266
    }
267
268
    /**
269
     * Returns whether this is a HEAD request.
270
     * @return bool whether this is a HEAD request.
271
     */
272 9
    public function getIsHead()
273
    {
274 9
        return $this->getMethod() === 'HEAD';
275
    }
276
277
    /**
278
     * Returns whether this is a POST request.
279
     * @return bool whether this is a POST request.
280
     */
281
    public function getIsPost()
282
    {
283
        return $this->getMethod() === 'POST';
284
    }
285
286
    /**
287
     * Returns whether this is a DELETE request.
288
     * @return bool whether this is a DELETE request.
289
     */
290
    public function getIsDelete()
291
    {
292
        return $this->getMethod() === 'DELETE';
293
    }
294
295
    /**
296
     * Returns whether this is a PUT request.
297
     * @return bool whether this is a PUT request.
298
     */
299
    public function getIsPut()
300
    {
301
        return $this->getMethod() === 'PUT';
302
    }
303
304
    /**
305
     * Returns whether this is a PATCH request.
306
     * @return bool whether this is a PATCH request.
307
     */
308
    public function getIsPatch()
309
    {
310
        return $this->getMethod() === 'PATCH';
311
    }
312
313
    /**
314
     * Returns whether this is an AJAX (XMLHttpRequest) request.
315
     *
316
     * Note that jQuery doesn't set the header in case of cross domain
317
     * requests: https://stackoverflow.com/questions/8163703/cross-domain-ajax-doesnt-send-x-requested-with-header
318
     *
319
     * @return bool whether this is an AJAX (XMLHttpRequest) request.
320
     */
321 10
    public function getIsAjax()
322
    {
323 10
        return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
324
    }
325
326
    /**
327
     * Returns whether this is a PJAX request
328
     * @return bool whether this is a PJAX request
329
     */
330 1
    public function getIsPjax()
331
    {
332 1
        return $this->getIsAjax() && !empty($_SERVER['HTTP_X_PJAX']);
333
    }
334
335
    /**
336
     * Returns whether this is an Adobe Flash or Flex request.
337
     * @return bool whether this is an Adobe Flash or Adobe Flex request.
338
     */
339
    public function getIsFlash()
340
    {
341
        return isset($_SERVER['HTTP_USER_AGENT']) &&
342
            (stripos($_SERVER['HTTP_USER_AGENT'], 'Shockwave') !== false || stripos($_SERVER['HTTP_USER_AGENT'], 'Flash') !== false);
343
    }
344
345
    private $_rawBody;
346
347
    /**
348
     * Returns the raw HTTP request body.
349
     * @return string the request body
350
     */
351
    public function getRawBody()
352
    {
353
        if ($this->_rawBody === null) {
354
            $this->_rawBody = file_get_contents('php://input');
355
        }
356
357
        return $this->_rawBody;
358
    }
359
360
    /**
361
     * Sets the raw HTTP request body, this method is mainly used by test scripts to simulate raw HTTP requests.
362
     * @param string $rawBody the request body
363
     */
364
    public function setRawBody($rawBody)
365
    {
366
        $this->_rawBody = $rawBody;
367
    }
368
369
    private $_bodyParams;
370
371
    /**
372
     * Returns the request parameters given in the request body.
373
     *
374
     * Request parameters are determined using the parsers configured in [[parsers]] property.
375
     * If no parsers are configured for the current [[contentType]] it uses the PHP function `mb_parse_str()`
376
     * to parse the [[rawBody|request body]].
377
     * @return array the request parameters given in the request body.
378
     * @throws \yii\base\InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]].
379
     * @see getMethod()
380
     * @see getBodyParam()
381
     * @see setBodyParams()
382
     */
383 3
    public function getBodyParams()
384
    {
385 3
        if ($this->_bodyParams === null) {
386 1
            if (isset($_POST[$this->methodParam])) {
387 1
                $this->_bodyParams = $_POST;
388 1
                unset($this->_bodyParams[$this->methodParam]);
389 1
                return $this->_bodyParams;
390
            }
391
392
            $rawContentType = $this->getContentType();
393
            if (($pos = strpos($rawContentType, ';')) !== false) {
394
                // e.g. application/json; charset=UTF-8
395
                $contentType = substr($rawContentType, 0, $pos);
396
            } else {
397
                $contentType = $rawContentType;
398
            }
399
400
            if (isset($this->parsers[$contentType])) {
401
                $parser = Yii::createObject($this->parsers[$contentType]);
402
                if (!($parser instanceof RequestParserInterface)) {
403
                    throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
404
                }
405
                $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
406
            } elseif (isset($this->parsers['*'])) {
407
                $parser = Yii::createObject($this->parsers['*']);
408
                if (!($parser instanceof RequestParserInterface)) {
409
                    throw new InvalidConfigException("The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
410
                }
411
                $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
412
            } elseif ($this->getMethod() === 'POST') {
413
                // PHP has already parsed the body so we have all params in $_POST
414
                $this->_bodyParams = $_POST;
415
            } else {
416
                $this->_bodyParams = [];
417
                mb_parse_str($this->getRawBody(), $this->_bodyParams);
418
            }
419
        }
420
421 3
        return $this->_bodyParams;
422
    }
423
424
    /**
425
     * Sets the request body parameters.
426
     * @param array $values the request body parameters (name-value pairs)
427
     * @see getBodyParam()
428
     * @see getBodyParams()
429
     */
430 2
    public function setBodyParams($values)
431
    {
432 2
        $this->_bodyParams = $values;
433 2
    }
434
435
    /**
436
     * Returns the named request body parameter value.
437
     * If the parameter does not exist, the second parameter passed to this method will be returned.
438
     * @param string $name the parameter name
439
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
440
     * @return mixed the parameter value
441
     * @see getBodyParams()
442
     * @see setBodyParams()
443
     */
444 3
    public function getBodyParam($name, $defaultValue = null)
445
    {
446 3
        $params = $this->getBodyParams();
447
448 3
        return isset($params[$name]) ? $params[$name] : $defaultValue;
449
    }
450
451
    /**
452
     * Returns POST parameter with a given name. If name isn't specified, returns an array of all POST parameters.
453
     *
454
     * @param string $name the parameter name
455
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
456
     * @return array|mixed
457
     */
458
    public function post($name = null, $defaultValue = null)
459
    {
460
        if ($name === null) {
461
            return $this->getBodyParams();
462
        }
463
464
        return $this->getBodyParam($name, $defaultValue);
465
    }
466
467
    private $_queryParams;
468
469
    /**
470
     * Returns the request parameters given in the [[queryString]].
471
     *
472
     * This method will return the contents of `$_GET` if params where not explicitly set.
473
     * @return array the request GET parameter values.
474
     * @see setQueryParams()
475
     */
476 24
    public function getQueryParams()
477
    {
478 24
        if ($this->_queryParams === null) {
479 20
            return $_GET;
480
        }
481
482 6
        return $this->_queryParams;
483
    }
484
485
    /**
486
     * Sets the request [[queryString]] parameters.
487
     * @param array $values the request query parameters (name-value pairs)
488
     * @see getQueryParam()
489
     * @see getQueryParams()
490
     */
491 6
    public function setQueryParams($values)
492
    {
493 6
        $this->_queryParams = $values;
494 6
    }
495
496
    /**
497
     * Returns GET parameter with a given name. If name isn't specified, returns an array of all GET parameters.
498
     *
499
     * @param string $name the parameter name
500
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
501
     * @return array|mixed
502
     */
503 14
    public function get($name = null, $defaultValue = null)
504
    {
505 14
        if ($name === null) {
506
            return $this->getQueryParams();
507
        }
508
509 14
        return $this->getQueryParam($name, $defaultValue);
510
    }
511
512
    /**
513
     * Returns the named GET parameter value.
514
     * If the GET parameter does not exist, the second parameter passed to this method will be returned.
515
     * @param string $name the GET parameter name.
516
     * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
517
     * @return mixed the GET parameter value
518
     * @see getBodyParam()
519
     */
520 17
    public function getQueryParam($name, $defaultValue = null)
521
    {
522 17
        $params = $this->getQueryParams();
523
524 17
        return isset($params[$name]) ? $params[$name] : $defaultValue;
525
    }
526
527
    private $_hostInfo;
528
    private $_hostName;
529
530
    /**
531
     * Returns the schema and host part of the current request URL.
532
     *
533
     * The returned URL does not have an ending slash.
534
     *
535
     * By default this value is based on the user request information. This method will
536
     * return the value of `$_SERVER['HTTP_HOST']` if it is available or `$_SERVER['SERVER_NAME']` if not.
537
     * You may want to check out the [PHP documentation](http://php.net/manual/en/reserved.variables.server.php)
538
     * for more information on these variables.
539
     *
540
     * You may explicitly specify it by setting the [[setHostInfo()|hostInfo]] property.
541
     *
542
     * > Warning: Dependent on the server configuration this information may not be
543
     * > reliable and [may be faked by the user sending the HTTP request](https://www.acunetix.com/vulnerabilities/web/host-header-attack).
544
     * > If the webserver is configured to serve the same site independent of the value of
545
     * > the `Host` header, this value is not reliable. In such situations you should either
546
     * > fix your webserver configuration or explicitly set the value by setting the [[setHostInfo()|hostInfo]] property.
547
     * > If you don't have access to the server configuration, you can setup [[\yii\filters\HostControl]] filter at
548
     * > application level in order to protect against such kind of attack.
549
     *
550
     * @property string|null schema and hostname part (with port number if needed) of the request URL
551
     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
552
     * See [[getHostInfo()]] for security related notes on this property.
553
     * @return string|null schema and hostname part (with port number if needed) of the request URL
554
     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
555
     * @see setHostInfo()
556
     */
557 24
    public function getHostInfo()
558
    {
559 24
        if ($this->_hostInfo === null) {
560 20
            $secure = $this->getIsSecureConnection();
561 20
            $http = $secure ? 'https' : 'http';
562 20
            if (isset($_SERVER['HTTP_HOST'])) {
563 7
                $this->_hostInfo = $http . '://' . $_SERVER['HTTP_HOST'];
564 13
            } elseif (isset($_SERVER['SERVER_NAME'])) {
565
                $this->_hostInfo = $http . '://' . $_SERVER['SERVER_NAME'];
566
                $port = $secure ? $this->getSecurePort() : $this->getPort();
567
                if (($port !== 80 && !$secure) || ($port !== 443 && $secure)) {
568
                    $this->_hostInfo .= ':' . $port;
569
                }
570
            }
571
        }
572
573 24
        return $this->_hostInfo;
574
    }
575
576
    /**
577
     * Sets the schema and host part of the application URL.
578
     * This setter is provided in case the schema and hostname cannot be determined
579
     * on certain Web servers.
580
     * @param string|null $value the schema and host part of the application URL. The trailing slashes will be removed.
581
     * @see getHostInfo() for security related notes on this property.
582
     */
583 37
    public function setHostInfo($value)
584
    {
585 37
        $this->_hostName = null;
586 37
        $this->_hostInfo = $value === null ? null : rtrim($value, '/');
587 37
    }
588
589
    /**
590
     * Returns the host part of the current request URL.
591
     * Value is calculated from current [[getHostInfo()|hostInfo]] property.
592
     *
593
     * > Warning: The content of this value may not be reliable, dependent on the server
594
     * > configuration. Please refer to [[getHostInfo()]] for more information.
595
     *
596
     * @return string|null hostname part of the request URL (e.g. `www.yiiframework.com`)
597
     * @see getHostInfo()
598
     * @since 2.0.10
599
     */
600 11
    public function getHostName()
601
    {
602 11
        if ($this->_hostName === null) {
603 11
            $this->_hostName = parse_url($this->getHostInfo(), PHP_URL_HOST);
604
        }
605
606 11
        return $this->_hostName;
607
    }
608
609
    private $_baseUrl;
610
611
    /**
612
     * Returns the relative URL for the application.
613
     * This is similar to [[scriptUrl]] except that it does not include the script file name,
614
     * and the ending slashes are removed.
615
     * @return string the relative URL for the application
616
     * @see setScriptUrl()
617
     */
618 198
    public function getBaseUrl()
619
    {
620 198
        if ($this->_baseUrl === null) {
621 197
            $this->_baseUrl = rtrim(dirname($this->getScriptUrl()), '\\/');
622
        }
623
624 198
        return $this->_baseUrl;
625
    }
626
627
    /**
628
     * Sets the relative URL for the application.
629
     * By default the URL is determined based on the entry script URL.
630
     * This setter is provided in case you want to change this behavior.
631
     * @param string $value the relative URL for the application
632
     */
633 1
    public function setBaseUrl($value)
634
    {
635 1
        $this->_baseUrl = $value;
636 1
    }
637
638
    private $_scriptUrl;
639
640
    /**
641
     * Returns the relative URL of the entry script.
642
     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
643
     * @return string the relative URL of the entry script.
644
     * @throws InvalidConfigException if unable to determine the entry script URL
645
     */
646 199
    public function getScriptUrl()
647
    {
648 199
        if ($this->_scriptUrl === null) {
649 1
            $scriptFile = $this->getScriptFile();
650
            $scriptName = basename($scriptFile);
651
            if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $scriptName) {
652
                $this->_scriptUrl = $_SERVER['SCRIPT_NAME'];
653
            } elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $scriptName) {
654
                $this->_scriptUrl = $_SERVER['PHP_SELF'];
655
            } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $scriptName) {
656
                $this->_scriptUrl = $_SERVER['ORIG_SCRIPT_NAME'];
657
            } elseif (isset($_SERVER['PHP_SELF']) && ($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) {
658
                $this->_scriptUrl = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName;
659
            } elseif (!empty($_SERVER['DOCUMENT_ROOT']) && strpos($scriptFile, $_SERVER['DOCUMENT_ROOT']) === 0) {
660
                $this->_scriptUrl = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $scriptFile));
661
            } else {
662
                throw new InvalidConfigException('Unable to determine the entry script URL.');
663
            }
664
        }
665
666 198
        return $this->_scriptUrl;
667
    }
668
669
    /**
670
     * Sets the relative URL for the application entry script.
671
     * This setter is provided in case the entry script URL cannot be determined
672
     * on certain Web servers.
673
     * @param string $value the relative URL for the application entry script.
674
     */
675 203
    public function setScriptUrl($value)
676
    {
677 203
        $this->_scriptUrl = $value === null ? null : '/' . trim($value, '/');
678 203
    }
679
680
    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...
681
682
    /**
683
     * Returns the entry script file path.
684
     * The default implementation will simply return `$_SERVER['SCRIPT_FILENAME']`.
685
     * @return string the entry script file path
686
     * @throws InvalidConfigException
687
     */
688 200
    public function getScriptFile()
689
    {
690 200
        if (isset($this->_scriptFile)) {
691 187
            return $this->_scriptFile;
692
        }
693
694 13
        if (isset($_SERVER['SCRIPT_FILENAME'])) {
695 11
            return $_SERVER['SCRIPT_FILENAME'];
696
        }
697
698 2
        throw new InvalidConfigException('Unable to determine the entry script file path.');
699
    }
700
701
    /**
702
     * Sets the entry script file path.
703
     * The entry script file path normally can be obtained from `$_SERVER['SCRIPT_FILENAME']`.
704
     * If your server configuration does not return the correct value, you may configure
705
     * this property to make it right.
706
     * @param string $value the entry script file path.
707
     */
708 187
    public function setScriptFile($value)
709
    {
710 187
        $this->_scriptFile = $value;
711 187
    }
712
713
    private $_pathInfo;
714
715
    /**
716
     * Returns the path info of the currently requested URL.
717
     * A path info refers to the part that is after the entry script and before the question mark (query string).
718
     * The starting and ending slashes are both removed.
719
     * @return string part of the request URL that is after the entry script and before the question mark.
720
     * Note, the returned path info is already URL-decoded.
721
     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
722
     */
723 18
    public function getPathInfo()
724
    {
725 18
        if ($this->_pathInfo === null) {
726
            $this->_pathInfo = $this->resolvePathInfo();
727
        }
728
729 18
        return $this->_pathInfo;
730
    }
731
732
    /**
733
     * Sets the path info of the current request.
734
     * This method is mainly provided for testing purpose.
735
     * @param string $value the path info of the current request
736
     */
737 19
    public function setPathInfo($value)
738
    {
739 19
        $this->_pathInfo = $value === null ? null : ltrim($value, '/');
740 19
    }
741
742
    /**
743
     * Resolves the path info part of the currently requested URL.
744
     * A path info refers to the part that is after the entry script and before the question mark (query string).
745
     * The starting slashes are both removed (ending slashes will be kept).
746
     * @return string part of the request URL that is after the entry script and before the question mark.
747
     * Note, the returned path info is decoded.
748
     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
749
     */
750
    protected function resolvePathInfo()
751
    {
752
        $pathInfo = $this->getUrl();
753
754
        if (($pos = strpos($pathInfo, '?')) !== false) {
755
            $pathInfo = substr($pathInfo, 0, $pos);
756
        }
757
758
        $pathInfo = urldecode($pathInfo);
759
760
        // try to encode in UTF8 if not so
761
        // http://w3.org/International/questions/qa-forms-utf-8.html
762
        if (!preg_match('%^(?:
763
            [\x09\x0A\x0D\x20-\x7E]              # ASCII
764
            | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
765
            | \xE0[\xA0-\xBF][\x80-\xBF]         # excluding overlongs
766
            | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
767
            | \xED[\x80-\x9F][\x80-\xBF]         # excluding surrogates
768
            | \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
769
            | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
770
            | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
771
            )*$%xs', $pathInfo)
772
        ) {
773
            $pathInfo = utf8_encode($pathInfo);
774
        }
775
776
        $scriptUrl = $this->getScriptUrl();
777
        $baseUrl = $this->getBaseUrl();
778
        if (strpos($pathInfo, $scriptUrl) === 0) {
779
            $pathInfo = substr($pathInfo, strlen($scriptUrl));
780
        } elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) {
781
            $pathInfo = substr($pathInfo, strlen($baseUrl));
782
        } elseif (isset($_SERVER['PHP_SELF']) && strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) {
783
            $pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl));
784
        } else {
785
            throw new InvalidConfigException('Unable to determine the path info of the current request.');
786
        }
787
788
        if (substr($pathInfo, 0, 1) === '/') {
789
            $pathInfo = substr($pathInfo, 1);
790
        }
791
792
        return (string) $pathInfo;
793
    }
794
795
    /**
796
     * Returns the currently requested absolute URL.
797
     * This is a shortcut to the concatenation of [[hostInfo]] and [[url]].
798
     * @return string the currently requested absolute URL.
799
     */
800
    public function getAbsoluteUrl()
801
    {
802
        return $this->getHostInfo() . $this->getUrl();
803
    }
804
805
    private $_url;
806
807
    /**
808
     * Returns the currently requested relative URL.
809
     * This refers to the portion of the URL that is after the [[hostInfo]] part.
810
     * It includes the [[queryString]] part if any.
811
     * @return string the currently requested relative URL. Note that the URI returned may be URL-encoded depending on the client.
812
     * @throws InvalidConfigException if the URL cannot be determined due to unusual server configuration
813
     */
814 9
    public function getUrl()
815
    {
816 9
        if ($this->_url === null) {
817 2
            $this->_url = $this->resolveRequestUri();
818
        }
819
820 9
        return $this->_url;
821
    }
822
823
    /**
824
     * Sets the currently requested relative URL.
825
     * The URI must refer to the portion that is after [[hostInfo]].
826
     * Note that the URI should be URL-encoded.
827
     * @param string $value the request URI to be set
828
     */
829 9
    public function setUrl($value)
830
    {
831 9
        $this->_url = $value;
832 9
    }
833
834
    /**
835
     * Resolves the request URI portion for the currently requested URL.
836
     * This refers to the portion that is after the [[hostInfo]] part. It includes the [[queryString]] part if any.
837
     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
838
     * @return string|bool the request URI portion for the currently requested URL.
839
     * Note that the URI returned may be URL-encoded depending on the client.
840
     * @throws InvalidConfigException if the request URI cannot be determined due to unusual server configuration
841
     */
842 2
    protected function resolveRequestUri()
843
    {
844 2
        if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { // IIS
845
            $requestUri = $_SERVER['HTTP_X_REWRITE_URL'];
846 2
        } elseif (isset($_SERVER['REQUEST_URI'])) {
847 2
            $requestUri = $_SERVER['REQUEST_URI'];
848 2
            if ($requestUri !== '' && $requestUri[0] !== '/') {
849 2
                $requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri);
850
            }
851
        } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0 CGI
852
            $requestUri = $_SERVER['ORIG_PATH_INFO'];
853
            if (!empty($_SERVER['QUERY_STRING'])) {
854
                $requestUri .= '?' . $_SERVER['QUERY_STRING'];
855
            }
856
        } else {
857
            throw new InvalidConfigException('Unable to determine the request URI.');
858
        }
859
860 2
        return $requestUri;
861
    }
862
863
    /**
864
     * Returns part of the request URL that is after the question mark.
865
     * @return string part of the request URL that is after the question mark
866
     */
867
    public function getQueryString()
868
    {
869
        return isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
870
    }
871
872
    /**
873
     * Return if the request is sent via secure channel (https).
874
     * @return bool if the request is sent via secure channel (https)
875
     */
876 20
    public function getIsSecureConnection()
877
    {
878 20
        return isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'], 'on') === 0 || $_SERVER['HTTPS'] == 1)
879 20
            || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0;
880
    }
881
882
    /**
883
     * Returns the server name.
884
     * @return string server name, null if not available
885
     */
886 1
    public function getServerName()
887
    {
888 1
        return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null;
889
    }
890
891
    /**
892
     * Returns the server port number.
893
     * @return int|null server port number, null if not available
894
     */
895 1
    public function getServerPort()
896
    {
897 1
        return isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : null;
898
    }
899
900
    /**
901
     * Returns the URL referrer.
902
     * @return string|null URL referrer, null if not available
903
     */
904
    public function getReferrer()
905
    {
906
        return isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
907
    }
908
909
    /**
910
     * Returns the user agent.
911
     * @return string|null user agent, null if not available
912
     */
913
    public function getUserAgent()
914
    {
915
        return isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null;
916
    }
917
918
    /**
919
     * Returns the user IP address.
920
     * @return string|null user IP address, null if not available
921
     */
922 18
    public function getUserIP()
923
    {
924 18
        return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
925
    }
926
927
    /**
928
     * Returns the user host name.
929
     * @return string|null user host name, null if not available
930
     */
931
    public function getUserHost()
932
    {
933
        return isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : null;
934
    }
935
936
    /**
937
     * @return string|null the username sent via HTTP authentication, null if the username is not given
938
     */
939 10
    public function getAuthUser()
940
    {
941 10
        return isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
942
    }
943
944
    /**
945
     * @return string|null the password sent via HTTP authentication, null if the password is not given
946
     */
947 10
    public function getAuthPassword()
948
    {
949 10
        return isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
950
    }
951
952
    private $_port;
953
954
    /**
955
     * Returns the port to use for insecure requests.
956
     * Defaults to 80, or the port specified by the server if the current
957
     * request is insecure.
958
     * @return int port number for insecure requests.
959
     * @see setPort()
960
     */
961
    public function getPort()
962
    {
963
        if ($this->_port === null) {
964
            $this->_port = !$this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : 80;
965
        }
966
967
        return $this->_port;
968
    }
969
970
    /**
971
     * Sets the port to use for insecure requests.
972
     * This setter is provided in case a custom port is necessary for certain
973
     * server configurations.
974
     * @param int $value port number.
975
     */
976
    public function setPort($value)
977
    {
978
        if ($value != $this->_port) {
979
            $this->_port = (int) $value;
980
            $this->_hostInfo = null;
981
        }
982
    }
983
984
    private $_securePort;
985
986
    /**
987
     * Returns the port to use for secure requests.
988
     * Defaults to 443, or the port specified by the server if the current
989
     * request is secure.
990
     * @return int port number for secure requests.
991
     * @see setSecurePort()
992
     */
993
    public function getSecurePort()
994
    {
995
        if ($this->_securePort === null) {
996
            $this->_securePort = $this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : 443;
997
        }
998
999
        return $this->_securePort;
1000
    }
1001
1002
    /**
1003
     * Sets the port to use for secure requests.
1004
     * This setter is provided in case a custom port is necessary for certain
1005
     * server configurations.
1006
     * @param int $value port number.
1007
     */
1008
    public function setSecurePort($value)
1009
    {
1010
        if ($value != $this->_securePort) {
1011
            $this->_securePort = (int) $value;
1012
            $this->_hostInfo = null;
1013
        }
1014
    }
1015
1016
    private $_contentTypes;
1017
1018
    /**
1019
     * Returns the content types acceptable by the end user.
1020
     * This is determined by the `Accept` HTTP header. For example,
1021
     *
1022
     * ```php
1023
     * $_SERVER['HTTP_ACCEPT'] = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1024
     * $types = $request->getAcceptableContentTypes();
1025
     * print_r($types);
1026
     * // displays:
1027
     * // [
1028
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1029
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1030
     * //           'text/plain' => ['q' => 0.5],
1031
     * // ]
1032
     * ```
1033
     *
1034
     * @return array the content types ordered by the quality score. Types with the highest scores
1035
     * will be returned first. The array keys are the content types, while the array values
1036
     * are the corresponding quality score and other parameters as given in the header.
1037
     */
1038 2
    public function getAcceptableContentTypes()
1039
    {
1040 2
        if ($this->_contentTypes === null) {
1041 2
            if (isset($_SERVER['HTTP_ACCEPT'])) {
1042 2
                $this->_contentTypes = $this->parseAcceptHeader($_SERVER['HTTP_ACCEPT']);
1043
            } else {
1044 1
                $this->_contentTypes = [];
1045
            }
1046
        }
1047
1048 2
        return $this->_contentTypes;
1049
    }
1050
1051
    /**
1052
     * Sets the acceptable content types.
1053
     * Please refer to [[getAcceptableContentTypes()]] on the format of the parameter.
1054
     * @param array $value the content types that are acceptable by the end user. They should
1055
     * be ordered by the preference level.
1056
     * @see getAcceptableContentTypes()
1057
     * @see parseAcceptHeader()
1058
     */
1059
    public function setAcceptableContentTypes($value)
1060
    {
1061
        $this->_contentTypes = $value;
1062
    }
1063
1064
    /**
1065
     * Returns request content-type
1066
     * The Content-Type header field indicates the MIME type of the data
1067
     * contained in [[getRawBody()]] or, in the case of the HEAD method, the
1068
     * media type that would have been sent had the request been a GET.
1069
     * For the MIME-types the user expects in response, see [[acceptableContentTypes]].
1070
     * @return string request content-type. Null is returned if this information is not available.
1071
     * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
1072
     * HTTP 1.1 header field definitions
1073
     */
1074
    public function getContentType()
1075
    {
1076
        if (isset($_SERVER['CONTENT_TYPE'])) {
1077
            return $_SERVER['CONTENT_TYPE'];
1078
        }
1079
1080
        if (isset($_SERVER['HTTP_CONTENT_TYPE'])) {
1081
            //fix bug https://bugs.php.net/bug.php?id=66606
1082
            return $_SERVER['HTTP_CONTENT_TYPE'];
1083
        }
1084
1085
        return null;
1086
    }
1087
1088
    private $_languages;
1089
1090
    /**
1091
     * Returns the languages acceptable by the end user.
1092
     * This is determined by the `Accept-Language` HTTP header.
1093
     * @return array the languages ordered by the preference level. The first element
1094
     * represents the most preferred language.
1095
     */
1096 1
    public function getAcceptableLanguages()
1097
    {
1098 1
        if ($this->_languages === null) {
1099
            if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
1100
                $this->_languages = array_keys($this->parseAcceptHeader($_SERVER['HTTP_ACCEPT_LANGUAGE']));
1101
            } else {
1102
                $this->_languages = [];
1103
            }
1104
        }
1105
1106 1
        return $this->_languages;
1107
    }
1108
1109
    /**
1110
     * @param array $value the languages that are acceptable by the end user. They should
1111
     * be ordered by the preference level.
1112
     */
1113 1
    public function setAcceptableLanguages($value)
1114
    {
1115 1
        $this->_languages = $value;
1116 1
    }
1117
1118
    /**
1119
     * Parses the given `Accept` (or `Accept-Language`) header.
1120
     *
1121
     * This method will return the acceptable values with their quality scores and the corresponding parameters
1122
     * as specified in the given `Accept` header. The array keys of the return value are the acceptable values,
1123
     * while the array values consisting of the corresponding quality scores and parameters. The acceptable
1124
     * values with the highest quality scores will be returned first. For example,
1125
     *
1126
     * ```php
1127
     * $header = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1128
     * $accepts = $request->parseAcceptHeader($header);
1129
     * print_r($accepts);
1130
     * // displays:
1131
     * // [
1132
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1133
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1134
     * //           'text/plain' => ['q' => 0.5],
1135
     * // ]
1136
     * ```
1137
     *
1138
     * @param string $header the header to be parsed
1139
     * @return array the acceptable values ordered by their quality score. The values with the highest scores
1140
     * will be returned first.
1141
     */
1142 3
    public function parseAcceptHeader($header)
1143
    {
1144 3
        $accepts = [];
1145 3
        foreach (explode(',', $header) as $i => $part) {
1146 3
            $params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY);
1147 3
            if (empty($params)) {
1148 1
                continue;
1149
            }
1150
            $values = [
1151 3
                'q' => [$i, array_shift($params), 1],
1152
            ];
1153 3
            foreach ($params as $param) {
1154 2
                if (strpos($param, '=') !== false) {
1155 2
                    list($key, $value) = explode('=', $param, 2);
1156 2
                    if ($key === 'q') {
1157 2
                        $values['q'][2] = (double) $value;
1158
                    } else {
1159 1
                        $values[$key] = $value;
1160
                    }
1161
                } else {
1162 1
                    $values[] = $param;
1163
                }
1164
            }
1165 3
            $accepts[] = $values;
1166
        }
1167
1168 3
        usort($accepts, function ($a, $b) {
1169 3
            $a = $a['q']; // index, name, q
1170 3
            $b = $b['q'];
1171 3
            if ($a[2] > $b[2]) {
1172 2
                return -1;
1173
            }
1174
1175 2
            if ($a[2] < $b[2]) {
1176 1
                return 1;
1177
            }
1178
1179 2
            if ($a[1] === $b[1]) {
1180
                return $a[0] > $b[0] ? 1 : -1;
1181
            }
1182
1183 2
            if ($a[1] === '*/*') {
1184
                return 1;
1185
            }
1186
1187 2
            if ($b[1] === '*/*') {
1188
                return -1;
1189
            }
1190
1191 2
            $wa = $a[1][strlen($a[1]) - 1] === '*';
1192 2
            $wb = $b[1][strlen($b[1]) - 1] === '*';
1193 2
            if ($wa xor $wb) {
1194
                return $wa ? 1 : -1;
1195
            }
1196
1197 2
            return $a[0] > $b[0] ? 1 : -1;
1198 3
        });
1199
1200 3
        $result = [];
1201 3
        foreach ($accepts as $accept) {
1202 3
            $name = $accept['q'][1];
1203 3
            $accept['q'] = $accept['q'][2];
1204 3
            $result[$name] = $accept;
1205
        }
1206
1207 3
        return $result;
1208
    }
1209
1210
    /**
1211
     * Returns the user-preferred language that should be used by this application.
1212
     * The language resolution is based on the user preferred languages and the languages
1213
     * supported by the application. The method will try to find the best match.
1214
     * @param array $languages a list of the languages supported by the application. If this is empty, the current
1215
     * application language will be returned without further processing.
1216
     * @return string the language that the application should use.
1217
     */
1218 1
    public function getPreferredLanguage(array $languages = [])
1219
    {
1220 1
        if (empty($languages)) {
1221 1
            return Yii::$app->language;
1222
        }
1223 1
        foreach ($this->getAcceptableLanguages() as $acceptableLanguage) {
1224 1
            $acceptableLanguage = str_replace('_', '-', strtolower($acceptableLanguage));
1225 1
            foreach ($languages as $language) {
1226 1
                $normalizedLanguage = str_replace('_', '-', strtolower($language));
1227
1228 1
                if ($normalizedLanguage === $acceptableLanguage || // en-us==en-us
1229 1
                    strpos($acceptableLanguage, $normalizedLanguage . '-') === 0 || // en==en-us
1230 1
                    strpos($normalizedLanguage, $acceptableLanguage . '-') === 0) { // en-us==en
1231
1232 1
                    return $language;
1233
                }
1234
            }
1235
        }
1236
1237 1
        return reset($languages);
1238
    }
1239
1240
    /**
1241
     * Gets the Etags.
1242
     *
1243
     * @return array The entity tags
1244
     */
1245
    public function getETags()
1246
    {
1247
        if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
1248
            return preg_split('/[\s,]+/', str_replace('-gzip', '', $_SERVER['HTTP_IF_NONE_MATCH']), -1, PREG_SPLIT_NO_EMPTY);
1249
        }
1250
1251
        return [];
1252
    }
1253
1254
    /**
1255
     * Returns the cookie collection.
1256
     * Through the returned cookie collection, you may access a cookie using the following syntax:
1257
     *
1258
     * ```php
1259
     * $cookie = $request->cookies['name']
1260
     * if ($cookie !== null) {
1261
     *     $value = $cookie->value;
1262
     * }
1263
     *
1264
     * // alternatively
1265
     * $value = $request->cookies->getValue('name');
1266
     * ```
1267
     *
1268
     * @return CookieCollection the cookie collection.
1269
     */
1270 29
    public function getCookies()
1271
    {
1272 29
        if ($this->_cookies === null) {
1273 29
            $this->_cookies = new CookieCollection($this->loadCookies(), [
1274 29
                'readOnly' => true,
1275
            ]);
1276
        }
1277
1278 29
        return $this->_cookies;
1279
    }
1280
1281
    /**
1282
     * Converts `$_COOKIE` into an array of [[Cookie]].
1283
     * @return array the cookies obtained from request
1284
     * @throws InvalidConfigException if [[cookieValidationKey]] is not set when [[enableCookieValidation]] is true
1285
     */
1286 29
    protected function loadCookies()
1287
    {
1288 29
        $cookies = [];
1289 29
        if ($this->enableCookieValidation) {
1290 29
            if ($this->cookieValidationKey == '') {
1291
                throw new InvalidConfigException(get_class($this) . '::cookieValidationKey must be configured with a secret key.');
1292
            }
1293 29
            foreach ($_COOKIE as $name => $value) {
1294
                if (!is_string($value)) {
1295
                    continue;
1296
                }
1297
                $data = Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey);
1298
                if ($data === false) {
1299
                    continue;
1300
                }
1301
                $data = @unserialize($data);
1302
                if (is_array($data) && isset($data[0], $data[1]) && $data[0] === $name) {
1303
                    $cookies[$name] = new Cookie([
1304 29
                        'name' => $name,
1305
                        'value' => $data[1],
1306
                        'expire' => null,
1307
                    ]);
1308
                }
1309
            }
1310
        } else {
1311
            foreach ($_COOKIE as $name => $value) {
1312
                $cookies[$name] = new Cookie([
1313
                    'name' => $name,
1314
                    'value' => $value,
1315
                    'expire' => null,
1316
                ]);
1317
            }
1318
        }
1319
1320 29
        return $cookies;
1321
    }
1322
1323
    private $_csrfToken;
1324
1325
    /**
1326
     * Returns the token used to perform CSRF validation.
1327
     *
1328
     * This token is generated in a way to prevent [BREACH attacks](http://breachattack.com/). It may be passed
1329
     * along via a hidden field of an HTML form or an HTTP header value to support CSRF validation.
1330
     * @param bool $regenerate whether to regenerate CSRF token. When this parameter is true, each time
1331
     * this method is called, a new CSRF token will be generated and persisted (in session or cookie).
1332
     * @return string the token used to perform CSRF validation.
1333
     */
1334 32
    public function getCsrfToken($regenerate = false)
1335
    {
1336 32
        if ($this->_csrfToken === null || $regenerate) {
1337 32
            if ($regenerate || ($token = $this->loadCsrfToken()) === null) {
1338 31
                $token = $this->generateCsrfToken();
1339
            }
1340 32
            $this->_csrfToken = Yii::$app->security->maskToken($token);
1341
        }
1342
1343 32
        return $this->_csrfToken;
1344
    }
1345
1346
    /**
1347
     * Loads the CSRF token from cookie or session.
1348
     * @return string the CSRF token loaded from cookie or session. Null is returned if the cookie or session
1349
     * does not have CSRF token.
1350
     */
1351 32
    protected function loadCsrfToken()
1352
    {
1353 32
        if ($this->enableCsrfCookie) {
1354 29
            return $this->getCookies()->getValue($this->csrfParam);
1355
        }
1356 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...
1357
    }
1358
1359
    /**
1360
     * Generates an unmasked random token used to perform CSRF validation.
1361
     * @return string the random token for CSRF validation.
1362
     */
1363 31
    protected function generateCsrfToken()
1364
    {
1365 31
        $token = Yii::$app->getSecurity()->generateRandomKey();
1366 31
        if ($this->enableCsrfCookie) {
1367 29
            $cookie = $this->createCsrfCookie($token);
1368 29
            Yii::$app->getResponse()->getCookies()->add($cookie);
1369
        } else {
1370 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...
1371
        }
1372 31
        return $token;
1373
    }
1374
1375
    /**
1376
     * @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent.
1377
     */
1378 3
    public function getCsrfTokenFromHeader()
1379
    {
1380 3
        return $this->headers->get(static::CSRF_HEADER);
1381
    }
1382
1383
    /**
1384
     * Creates a cookie with a randomly generated CSRF token.
1385
     * Initial values specified in [[csrfCookie]] will be applied to the generated cookie.
1386
     * @param string $token the CSRF token
1387
     * @return Cookie the generated cookie
1388
     * @see enableCsrfValidation
1389
     */
1390 29
    protected function createCsrfCookie($token)
1391
    {
1392 29
        $options = $this->csrfCookie;
1393 29
        $options['name'] = $this->csrfParam;
1394 29
        $options['value'] = $token;
1395 29
        return new Cookie($options);
1396
    }
1397
1398
    /**
1399
     * Performs the CSRF validation.
1400
     *
1401
     * This method will validate the user-provided CSRF token by comparing it with the one stored in cookie or session.
1402
     * This method is mainly called in [[Controller::beforeAction()]].
1403
     *
1404
     * Note that the method will NOT perform CSRF validation if [[enableCsrfValidation]] is false or the HTTP method
1405
     * is among GET, HEAD or OPTIONS.
1406
     *
1407
     * @param string $clientSuppliedToken the user-provided CSRF token to be validated. If null, the token will be retrieved from
1408
     * the [[csrfParam]] POST field or HTTP header.
1409
     * This parameter is available since version 2.0.4.
1410
     * @return bool whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true.
1411
     */
1412 5
    public function validateCsrfToken($clientSuppliedToken = null)
1413
    {
1414 5
        $method = $this->getMethod();
1415
        // only validate CSRF token on non-"safe" methods http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
1416 5
        if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
1417 5
            return true;
1418
        }
1419
1420 3
        $trueToken = $this->getCsrfToken();
1421
1422 3
        if ($clientSuppliedToken !== null) {
1423 1
            return $this->validateCsrfTokenInternal($clientSuppliedToken, $trueToken);
1424
        }
1425
1426 3
        return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
1427 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...
1428
    }
1429
1430
    /**
1431
     * Validates CSRF token
1432
     *
1433
     * @param string $clientSuppliedToken The masked client-supplied token.
1434
     * @param string $trueToken The masked true token.
1435
     * @return bool
1436
     */
1437 3
    private function validateCsrfTokenInternal($clientSuppliedToken, $trueToken)
1438
    {
1439 3
        if (!is_string($clientSuppliedToken)) {
1440 3
            return false;
1441
        }
1442
1443 3
        $security = Yii::$app->security;
1444
1445 3
        return $security->unmaskToken($clientSuppliedToken) === $security->unmaskToken($trueToken);
1446
    }
1447
}
1448