Completed
Pull Request — 2.1 (#12704)
by Robert
08:59
created

Request::validateCsrfTokenInternal()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3.0067

Importance

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

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

Let’s take a look at an example:

class A
{
    public function foo() { }
}

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

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

Available Fixes

  1. Add an additional type-check:

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

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1319
        }
1320 25
        return $token;
1321
    }
1322
1323
    /**
1324
     * Returns the XOR result of two strings.
1325
     * If the two strings are of different lengths, the shorter one will be padded to the length of the longer one.
1326
     * @param string $token1
1327
     * @param string $token2
1328
     * @return string the XOR result
1329
     */
1330 27
    private function xorTokens($token1, $token2)
1331
    {
1332 27
        $n1 = StringHelper::byteLength($token1);
1333 27
        $n2 = StringHelper::byteLength($token2);
1334 27
        if ($n1 > $n2) {
1335 27
            $token2 = str_pad($token2, $n1, $token2);
1336 27
        } elseif ($n1 < $n2) {
1337 3
            $token1 = str_pad($token1, $n2, $n1 === 0 ? ' ' : $token1);
1338 3
        }
1339
1340 27
        return $token1 ^ $token2;
1341
    }
1342
1343
    /**
1344
     * @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent.
1345
     */
1346 3
    public function getCsrfTokenFromHeader()
1347
    {
1348 3
        $key = 'HTTP_' . str_replace('-', '_', strtoupper(static::CSRF_HEADER));
1349 3
        return isset($_SERVER[$key]) ? $_SERVER[$key] : null;
1350
    }
1351
1352
    /**
1353
     * Creates a cookie with a randomly generated CSRF token.
1354
     * Initial values specified in [[csrfCookie]] will be applied to the generated cookie.
1355
     * @param string $token the CSRF token
1356
     * @return Cookie the generated cookie
1357
     * @see enableCsrfValidation
1358
     */
1359 24
    protected function createCsrfCookie($token)
1360
    {
1361 24
        $options = $this->csrfCookie;
1362 24
        $options['name'] = $this->csrfParam;
1363 24
        $options['value'] = $token;
1364 24
        return new Cookie($options);
1365
    }
1366
1367
    /**
1368
     * Performs the CSRF validation.
1369
     *
1370
     * This method will validate the user-provided CSRF token by comparing it with the one stored in cookie or session.
1371
     * This method is mainly called in [[Controller::beforeAction()]].
1372
     *
1373
     * Note that the method will NOT perform CSRF validation if [[enableCsrfValidation]] is false or the HTTP method
1374
     * is among GET, HEAD or OPTIONS.
1375
     *
1376
     * @param string $token the user-provided CSRF token to be validated. If null, the token will be retrieved from
1377
     * the [[csrfParam]] POST field or HTTP header.
1378
     * This parameter is available since version 2.0.4.
1379
     * @return boolean whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true.
1380
     */
1381 3
    public function validateCsrfToken($token = null)
1382
    {
1383 3
        $method = $this->getMethod();
1384
        // only validate CSRF token on non-"safe" methods http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
1385 3
        if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
1386 3
            return true;
1387
        }
1388
1389 3
        $trueToken = $this->loadCsrfToken();
1390
1391 3
        if ($token !== null) {
1392 1
            return $this->validateCsrfTokenInternal($token, $trueToken);
1393
        } else {
1394 3
            return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
1395 3
                || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
1396
        }
1397
    }
1398
1399
    /**
1400
     * Validates CSRF token
1401
     *
1402
     * @param string $token
1403
     * @param string $trueToken
1404
     * @return boolean
1405
     */
1406 3
    private function validateCsrfTokenInternal($token, $trueToken)
1407
    {
1408 3
        if (!is_string($token)) {
1409 3
            return false;
1410
        }
1411
1412 3
        $token = base64_decode(str_replace('.', '+', $token));
1413 3
        $n = StringHelper::byteLength($token);
1414 3
        if ($n <= static::CSRF_MASK_LENGTH) {
1415
            return false;
1416
        }
1417 3
        $mask = StringHelper::byteSubstr($token, 0, static::CSRF_MASK_LENGTH);
1418 3
        $token = StringHelper::byteSubstr($token, static::CSRF_MASK_LENGTH, $n - static::CSRF_MASK_LENGTH);
1419 3
        $token = $this->xorTokens($mask, $token);
1420
1421 3
        return $token === $trueToken;
1422
    }
1423
}
1424