Completed
Push — 2.1 ( c952e8...98ed49 )
by Carsten
10:00
created

Request::getScriptUrl()   C

Complexity

Conditions 12
Paths 7

Size

Total Lines 22
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 79.7587

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 5.8703
c 0
b 0
f 0
ccs 4
cts 18
cp 0.2222
cc 12
eloc 17
nc 7
nop 0
crap 79.7587

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 9
    public function getMethod()
230
    {
231 9
        if (isset($_POST[$this->methodParam])) {
232 1
            return strtoupper($_POST[$this->methodParam]);
233
        }
234
235 8
        if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
236
            return strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']);
237
        }
238
239 8
        if (isset($_SERVER['REQUEST_METHOD'])) {
240 2
            return strtoupper($_SERVER['REQUEST_METHOD']);
241
        }
242
243 7
        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
    public function getIsHead()
269
    {
270
        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
    public function getBodyParams()
380
    {
381
        if ($this->_bodyParams === null) {
382
            if (isset($_POST[$this->methodParam])) {
383
                $this->_bodyParams = $_POST;
384
                unset($this->_bodyParams[$this->methodParam]);
385
                return $this->_bodyParams;
386
            }
387
388
            $contentType = $this->getContentType();
389
            if (($pos = strpos($contentType, ';')) !== false) {
390
                // e.g. application/json; charset=UTF-8
391
                $contentType = substr($contentType, 0, $pos);
392
            }
393
394
            if (isset($this->parsers[$contentType])) {
395
                $parser = Yii::createObject($this->parsers[$contentType]);
396
                if (!($parser instanceof RequestParserInterface)) {
397
                    throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
398
                }
399
                $this->_bodyParams = $parser->parse($this->getRawBody(), $contentType);
400
            } elseif (isset($this->parsers['*'])) {
401
                $parser = Yii::createObject($this->parsers['*']);
402
                if (!($parser instanceof RequestParserInterface)) {
403
                    throw new InvalidConfigException("The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
404
                }
405
                $this->_bodyParams = $parser->parse($this->getRawBody(), $contentType);
406
            } elseif ($this->getMethod() === 'POST') {
407
                // PHP has already parsed the body so we have all params in $_POST
408
                $this->_bodyParams = $_POST;
409
            } else {
410
                $this->_bodyParams = [];
411
                mb_parse_str($this->getRawBody(), $this->_bodyParams);
412
            }
413
        }
414
415
        return $this->_bodyParams;
416
    }
417
418
    /**
419
     * Sets the request body parameters.
420
     * @param array $values the request body parameters (name-value pairs)
421
     * @see getBodyParam()
422
     * @see getBodyParams()
423
     */
424
    public function setBodyParams($values)
425
    {
426
        $this->_bodyParams = $values;
427
    }
428
429
    /**
430
     * Returns the named request body parameter value.
431
     * If the parameter does not exist, the second parameter passed to this method will be returned.
432
     * @param string $name the parameter name
433
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
434
     * @return mixed the parameter value
435
     * @see getBodyParams()
436
     * @see setBodyParams()
437
     */
438
    public function getBodyParam($name, $defaultValue = null)
439
    {
440
        $params = $this->getBodyParams();
441
442
        return isset($params[$name]) ? $params[$name] : $defaultValue;
443
    }
444
445
    /**
446
     * Returns POST parameter with a given name. If name isn't specified, returns an array of all POST parameters.
447
     *
448
     * @param string $name the parameter name
449
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
450
     * @return array|mixed
451
     */
452
    public function post($name = null, $defaultValue = null)
453
    {
454
        if ($name === null) {
455
            return $this->getBodyParams();
456
        } else {
457
            return $this->getBodyParam($name, $defaultValue);
458
        }
459
    }
460
461
    private $_queryParams;
462
463
    /**
464
     * Returns the request parameters given in the [[queryString]].
465
     *
466
     * This method will return the contents of `$_GET` if params where not explicitly set.
467
     * @return array the request GET parameter values.
468
     * @see setQueryParams()
469
     */
470 13
    public function getQueryParams()
471
    {
472 13
        if ($this->_queryParams === null) {
473 12
            return $_GET;
474
        }
475
476 2
        return $this->_queryParams;
477
    }
478
479
    /**
480
     * Sets the request [[queryString]] parameters.
481
     * @param array $values the request query parameters (name-value pairs)
482
     * @see getQueryParam()
483
     * @see getQueryParams()
484
     */
485 2
    public function setQueryParams($values)
486
    {
487 2
        $this->_queryParams = $values;
488 2
    }
489
490
    /**
491
     * Returns GET parameter with a given name. If name isn't specified, returns an array of all GET parameters.
492
     *
493
     * @param string $name the parameter name
494
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
495
     * @return array|mixed
496
     */
497 5
    public function get($name = null, $defaultValue = null)
498
    {
499 5
        if ($name === null) {
500
            return $this->getQueryParams();
501
        } else {
502 5
            return $this->getQueryParam($name, $defaultValue);
503
        }
504
    }
505
506
    /**
507
     * Returns the named GET parameter value.
508
     * If the GET parameter does not exist, the second parameter passed to this method will be returned.
509
     * @param string $name the GET parameter name.
510
     * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
511
     * @return mixed the GET parameter value
512
     * @see getBodyParam()
513
     */
514 6
    public function getQueryParam($name, $defaultValue = null)
515
    {
516 6
        $params = $this->getQueryParams();
517
518 6
        return isset($params[$name]) ? $params[$name] : $defaultValue;
519
    }
520
521
    private $_hostInfo;
522
523
    /**
524
     * Returns the schema and host part of the current request URL.
525
     * The returned URL does not have an ending slash.
526
     * By default this is determined based on the user request information.
527
     * You may explicitly specify it by setting the [[setHostInfo()|hostInfo]] property.
528
     * @return string schema and hostname part (with port number if needed) of the request URL (e.g. `http://www.yiiframework.com`),
529
     * null if can't be obtained from `$_SERVER` and wasn't set.
530
     * @see setHostInfo()
531
     */
532 4
    public function getHostInfo()
533
    {
534 4
        if ($this->_hostInfo === null) {
535 2
            $secure = $this->getIsSecureConnection();
536 2
            $http = $secure ? 'https' : 'http';
537 2
            if (isset($_SERVER['HTTP_HOST'])) {
538
                $this->_hostInfo = $http . '://' . $_SERVER['HTTP_HOST'];
539 2
            } elseif (isset($_SERVER['SERVER_NAME'])) {
540
                $this->_hostInfo = $http . '://' . $_SERVER['SERVER_NAME'];
541
                $port = $secure ? $this->getSecurePort() : $this->getPort();
542
                if (($port !== 80 && !$secure) || ($port !== 443 && $secure)) {
543
                    $this->_hostInfo .= ':' . $port;
544
                }
545
            }
546 2
        }
547
548 4
        return $this->_hostInfo;
549
    }
550
551
    /**
552
     * Sets the schema and host part of the application URL.
553
     * This setter is provided in case the schema and hostname cannot be determined
554
     * on certain Web servers.
555
     * @param string $value the schema and host part of the application URL. The trailing slashes will be removed.
556
     */
557 18
    public function setHostInfo($value)
558
    {
559 18
        $this->_hostInfo = $value === null ? null : rtrim($value, '/');
560 18
    }
561
562
    private $_baseUrl;
563
564
    /**
565
     * Returns the relative URL for the application.
566
     * This is similar to [[scriptUrl]] except that it does not include the script file name,
567
     * and the ending slashes are removed.
568
     * @return string the relative URL for the application
569
     * @see setScriptUrl()
570
     */
571 96
    public function getBaseUrl()
572
    {
573 96
        if ($this->_baseUrl === null) {
574 95
            $this->_baseUrl = rtrim(dirname($this->getScriptUrl()), '\\/');
575 95
        }
576
577 96
        return $this->_baseUrl;
578
    }
579
580
    /**
581
     * Sets the relative URL for the application.
582
     * By default the URL is determined based on the entry script URL.
583
     * This setter is provided in case you want to change this behavior.
584
     * @param string $value the relative URL for the application
585
     */
586 1
    public function setBaseUrl($value)
587
    {
588 1
        $this->_baseUrl = $value;
589 1
    }
590
591
    private $_scriptUrl;
592
593
    /**
594
     * Returns the relative URL of the entry script.
595
     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
596
     * @return string the relative URL of the entry script.
597
     * @throws InvalidConfigException if unable to determine the entry script URL
598
     */
599 97
    public function getScriptUrl()
600
    {
601 97
        if ($this->_scriptUrl === null) {
602 1
            $scriptFile = $this->getScriptFile();
603
            $scriptName = basename($scriptFile);
604
            if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $scriptName) {
605
                $this->_scriptUrl = $_SERVER['SCRIPT_NAME'];
606
            } elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $scriptName) {
607
                $this->_scriptUrl = $_SERVER['PHP_SELF'];
608
            } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $scriptName) {
609
                $this->_scriptUrl = $_SERVER['ORIG_SCRIPT_NAME'];
610
            } elseif (isset($_SERVER['PHP_SELF']) && ($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) {
611
                $this->_scriptUrl = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName;
612
            } elseif (!empty($_SERVER['DOCUMENT_ROOT']) && strpos($scriptFile, $_SERVER['DOCUMENT_ROOT']) === 0) {
613
                $this->_scriptUrl = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $scriptFile));
614
            } else {
615
                throw new InvalidConfigException('Unable to determine the entry script URL.');
616
            }
617
        }
618
619 96
        return $this->_scriptUrl;
620
    }
621
622
    /**
623
     * Sets the relative URL for the application entry script.
624
     * This setter is provided in case the entry script URL cannot be determined
625
     * on certain Web servers.
626
     * @param string $value the relative URL for the application entry script.
627
     */
628 101
    public function setScriptUrl($value)
629
    {
630 101
        $this->_scriptUrl = $value === null ? null : '/' . trim($value, '/');
631 101
    }
632
633
    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...
634
635
    /**
636
     * Returns the entry script file path.
637
     * The default implementation will simply return `$_SERVER['SCRIPT_FILENAME']`.
638
     * @return string the entry script file path
639
     * @throws InvalidConfigException
640
     */
641 98
    public function getScriptFile()
642
    {
643 98
        if (isset($this->_scriptFile)) {
644 88
            return $this->_scriptFile;
645 10
        } elseif (isset($_SERVER['SCRIPT_FILENAME'])) {
646 8
            return $_SERVER['SCRIPT_FILENAME'];
647
        } else {
648 2
            throw new InvalidConfigException('Unable to determine the entry script file path.');
649
        }
650
    }
651
652
    /**
653
     * Sets the entry script file path.
654
     * The entry script file path normally can be obtained from `$_SERVER['SCRIPT_FILENAME']`.
655
     * If your server configuration does not return the correct value, you may configure
656
     * this property to make it right.
657
     * @param string $value the entry script file path.
658
     */
659 88
    public function setScriptFile($value)
660
    {
661 88
        $this->_scriptFile = $value;
662 88
    }
663
664
    private $_pathInfo;
665
666
    /**
667
     * Returns the path info of the currently requested URL.
668
     * A path info refers to the part that is after the entry script and before the question mark (query string).
669
     * The starting and ending slashes are both removed.
670
     * @return string part of the request URL that is after the entry script and before the question mark.
671
     * Note, the returned path info is already URL-decoded.
672
     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
673
     */
674 7
    public function getPathInfo()
675
    {
676 7
        if ($this->_pathInfo === null) {
677
            $this->_pathInfo = $this->resolvePathInfo();
678
        }
679
680 7
        return $this->_pathInfo;
681
    }
682
683
    /**
684
     * Sets the path info of the current request.
685
     * This method is mainly provided for testing purpose.
686
     * @param string $value the path info of the current request
687
     */
688 7
    public function setPathInfo($value)
689
    {
690 7
        $this->_pathInfo = $value === null ? null : ltrim($value, '/');
691 7
    }
692
693
    /**
694
     * Resolves the path info part of the currently requested URL.
695
     * A path info refers to the part that is after the entry script and before the question mark (query string).
696
     * The starting slashes are both removed (ending slashes will be kept).
697
     * @return string part of the request URL that is after the entry script and before the question mark.
698
     * Note, the returned path info is decoded.
699
     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
700
     */
701
    protected function resolvePathInfo()
702
    {
703
        $pathInfo = $this->getUrl();
704
705
        if (($pos = strpos($pathInfo, '?')) !== false) {
706
            $pathInfo = substr($pathInfo, 0, $pos);
707
        }
708
709
        $pathInfo = urldecode($pathInfo);
710
711
        // try to encode in UTF8 if not so
712
        // http://w3.org/International/questions/qa-forms-utf-8.html
713
        if (!preg_match('%^(?:
714
            [\x09\x0A\x0D\x20-\x7E]              # ASCII
715
            | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
716
            | \xE0[\xA0-\xBF][\x80-\xBF]         # excluding overlongs
717
            | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
718
            | \xED[\x80-\x9F][\x80-\xBF]         # excluding surrogates
719
            | \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
720
            | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
721
            | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
722
            )*$%xs', $pathInfo)
723
        ) {
724
            $pathInfo = utf8_encode($pathInfo);
725
        }
726
727
        $scriptUrl = $this->getScriptUrl();
728
        $baseUrl = $this->getBaseUrl();
729
        if (strpos($pathInfo, $scriptUrl) === 0) {
730
            $pathInfo = substr($pathInfo, strlen($scriptUrl));
731
        } elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) {
732
            $pathInfo = substr($pathInfo, strlen($baseUrl));
733
        } elseif (isset($_SERVER['PHP_SELF']) && strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) {
734
            $pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl));
735
        } else {
736
            throw new InvalidConfigException('Unable to determine the path info of the current request.');
737
        }
738
739
        if (substr($pathInfo, 0, 1) === '/') {
740
            $pathInfo = substr($pathInfo, 1);
741
        }
742
743
        return (string) $pathInfo;
744
    }
745
746
    /**
747
     * Returns the currently requested absolute URL.
748
     * This is a shortcut to the concatenation of [[hostInfo]] and [[url]].
749
     * @return string the currently requested absolute URL.
750
     */
751
    public function getAbsoluteUrl()
752
    {
753
        return $this->getHostInfo() . $this->getUrl();
754
    }
755
756
    private $_url;
757
758
    /**
759
     * Returns the currently requested relative URL.
760
     * This refers to the portion of the URL that is after the [[hostInfo]] part.
761
     * It includes the [[queryString]] part if any.
762
     * @return string the currently requested relative URL. Note that the URI returned is URL-encoded.
763
     * @throws InvalidConfigException if the URL cannot be determined due to unusual server configuration
764
     */
765 7
    public function getUrl()
766
    {
767 7
        if ($this->_url === null) {
768
            $this->_url = $this->resolveRequestUri();
769
        }
770
771 7
        return $this->_url;
772
    }
773
774
    /**
775
     * Sets the currently requested relative URL.
776
     * The URI must refer to the portion that is after [[hostInfo]].
777
     * Note that the URI should be URL-encoded.
778
     * @param string $value the request URI to be set
779
     */
780 15
    public function setUrl($value)
781
    {
782 15
        $this->_url = $value;
783 15
    }
784
785
    /**
786
     * Resolves the request URI portion for the currently requested URL.
787
     * This refers to the portion that is after the [[hostInfo]] part. It includes the [[queryString]] part if any.
788
     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
789
     * @return string|boolean the request URI portion for the currently requested URL.
790
     * Note that the URI returned is URL-encoded.
791
     * @throws InvalidConfigException if the request URI cannot be determined due to unusual server configuration
792
     */
793
    protected function resolveRequestUri()
794
    {
795
        if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { // IIS
796
            $requestUri = $_SERVER['HTTP_X_REWRITE_URL'];
797
        } elseif (isset($_SERVER['REQUEST_URI'])) {
798
            $requestUri = $_SERVER['REQUEST_URI'];
799
            if ($requestUri !== '' && $requestUri[0] !== '/') {
800
                $requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri);
801
            }
802
        } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0 CGI
803
            $requestUri = $_SERVER['ORIG_PATH_INFO'];
804
            if (!empty($_SERVER['QUERY_STRING'])) {
805
                $requestUri .= '?' . $_SERVER['QUERY_STRING'];
806
            }
807
        } else {
808
            throw new InvalidConfigException('Unable to determine the request URI.');
809
        }
810
811
        return $requestUri;
812
    }
813
814
    /**
815
     * Returns part of the request URL that is after the question mark.
816
     * @return string part of the request URL that is after the question mark
817
     */
818
    public function getQueryString()
819
    {
820
        return isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
821
    }
822
823
    /**
824
     * Return if the request is sent via secure channel (https).
825
     * @return boolean if the request is sent via secure channel (https)
826
     */
827 2
    public function getIsSecureConnection()
828
    {
829 2
        return isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'], 'on') === 0 || $_SERVER['HTTPS'] == 1)
830 2
            || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0;
831
    }
832
833
    /**
834
     * Returns the server name.
835
     * @return string server name, null if not available
836
     */
837 1
    public function getServerName()
838
    {
839 1
        return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null;
840
    }
841
842
    /**
843
     * Returns the server port number.
844
     * @return integer|null server port number, null if not available
845
     */
846 1
    public function getServerPort()
847
    {
848 1
        return isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : null;
849
    }
850
851
    /**
852
     * Returns the URL referrer.
853
     * @return string|null URL referrer, null if not available
854
     */
855
    public function getReferrer()
856
    {
857
        return isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null;
858
    }
859
860
    /**
861
     * Returns the user agent.
862
     * @return string|null user agent, null if not available
863
     */
864
    public function getUserAgent()
865
    {
866
        return isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null;
867
    }
868
869
    /**
870
     * Returns the user IP address.
871
     * @return string|null user IP address, null if not available
872
     */
873 13
    public function getUserIP()
874
    {
875 13
        return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
876
    }
877
878
    /**
879
     * Returns the user host name.
880
     * @return string|null user host name, null if not available
881
     */
882
    public function getUserHost()
883
    {
884
        return isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : null;
885
    }
886
887
    /**
888
     * @return string|null the username sent via HTTP authentication, null if the username is not given
889
     */
890 10
    public function getAuthUser()
891
    {
892 10
        return isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
893
    }
894
895
    /**
896
     * @return string|null the password sent via HTTP authentication, null if the password is not given
897
     */
898 10
    public function getAuthPassword()
899
    {
900 10
        return isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
901
    }
902
903
    private $_port;
904
905
    /**
906
     * Returns the port to use for insecure requests.
907
     * Defaults to 80, or the port specified by the server if the current
908
     * request is insecure.
909
     * @return integer port number for insecure requests.
910
     * @see setPort()
911
     */
912
    public function getPort()
913
    {
914
        if ($this->_port === null) {
915
            $this->_port = !$this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : 80;
916
        }
917
918
        return $this->_port;
919
    }
920
921
    /**
922
     * Sets the port to use for insecure requests.
923
     * This setter is provided in case a custom port is necessary for certain
924
     * server configurations.
925
     * @param integer $value port number.
926
     */
927
    public function setPort($value)
928
    {
929
        if ($value != $this->_port) {
930
            $this->_port = (int) $value;
931
            $this->_hostInfo = null;
932
        }
933
    }
934
935
    private $_securePort;
936
937
    /**
938
     * Returns the port to use for secure requests.
939
     * Defaults to 443, or the port specified by the server if the current
940
     * request is secure.
941
     * @return integer port number for secure requests.
942
     * @see setSecurePort()
943
     */
944
    public function getSecurePort()
945
    {
946
        if ($this->_securePort === null) {
947
            $this->_securePort = $this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : 443;
948
        }
949
950
        return $this->_securePort;
951
    }
952
953
    /**
954
     * Sets the port to use for secure requests.
955
     * This setter is provided in case a custom port is necessary for certain
956
     * server configurations.
957
     * @param integer $value port number.
958
     */
959
    public function setSecurePort($value)
960
    {
961
        if ($value != $this->_securePort) {
962
            $this->_securePort = (int) $value;
963
            $this->_hostInfo = null;
964
        }
965
    }
966
967
    private $_contentTypes;
968
969
    /**
970
     * Returns the content types acceptable by the end user.
971
     * This is determined by the `Accept` HTTP header. For example,
972
     *
973
     * ```php
974
     * $_SERVER['HTTP_ACCEPT'] = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
975
     * $types = $request->getAcceptableContentTypes();
976
     * print_r($types);
977
     * // displays:
978
     * // [
979
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
980
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
981
     * //           'text/plain' => ['q' => 0.5],
982
     * // ]
983
     * ```
984
     *
985
     * @return array the content types ordered by the quality score. Types with the highest scores
986
     * will be returned first. The array keys are the content types, while the array values
987
     * are the corresponding quality score and other parameters as given in the header.
988
     */
989 2
    public function getAcceptableContentTypes()
990
    {
991 2
        if ($this->_contentTypes === null) {
992 2
            if (isset($_SERVER['HTTP_ACCEPT'])) {
993 2
                $this->_contentTypes = $this->parseAcceptHeader($_SERVER['HTTP_ACCEPT']);
994 2
            } else {
995 1
                $this->_contentTypes = [];
996
            }
997 2
        }
998
999 2
        return $this->_contentTypes;
1000
    }
1001
1002
    /**
1003
     * Sets the acceptable content types.
1004
     * Please refer to [[getAcceptableContentTypes()]] on the format of the parameter.
1005
     * @param array $value the content types that are acceptable by the end user. They should
1006
     * be ordered by the preference level.
1007
     * @see getAcceptableContentTypes()
1008
     * @see parseAcceptHeader()
1009
     */
1010
    public function setAcceptableContentTypes($value)
1011
    {
1012
        $this->_contentTypes = $value;
1013
    }
1014
1015
    /**
1016
     * Returns request content-type
1017
     * The Content-Type header field indicates the MIME type of the data
1018
     * contained in [[getRawBody()]] or, in the case of the HEAD method, the
1019
     * media type that would have been sent had the request been a GET.
1020
     * For the MIME-types the user expects in response, see [[acceptableContentTypes]].
1021
     * @return string request content-type. Null is returned if this information is not available.
1022
     * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
1023
     * HTTP 1.1 header field definitions
1024
     */
1025
    public function getContentType()
1026
    {
1027
        if (isset($_SERVER['CONTENT_TYPE'])) {
1028
            return $_SERVER['CONTENT_TYPE'];
1029
        } elseif (isset($_SERVER['HTTP_CONTENT_TYPE'])) {
1030
            //fix bug https://bugs.php.net/bug.php?id=66606
1031
            return $_SERVER['HTTP_CONTENT_TYPE'];
1032
        }
1033
1034
        return null;
1035
    }
1036
1037
    private $_languages;
1038
1039
    /**
1040
     * Returns the languages acceptable by the end user.
1041
     * This is determined by the `Accept-Language` HTTP header.
1042
     * @return array the languages ordered by the preference level. The first element
1043
     * represents the most preferred language.
1044
     */
1045 1
    public function getAcceptableLanguages()
1046
    {
1047 1
        if ($this->_languages === null) {
1048
            if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
1049
                $this->_languages = array_keys($this->parseAcceptHeader($_SERVER['HTTP_ACCEPT_LANGUAGE']));
1050
            } else {
1051
                $this->_languages = [];
1052
            }
1053
        }
1054
1055 1
        return $this->_languages;
1056
    }
1057
1058
    /**
1059
     * @param array $value the languages that are acceptable by the end user. They should
1060
     * be ordered by the preference level.
1061
     */
1062 1
    public function setAcceptableLanguages($value)
1063
    {
1064 1
        $this->_languages = $value;
1065 1
    }
1066
1067
    /**
1068
     * Parses the given `Accept` (or `Accept-Language`) header.
1069
     *
1070
     * This method will return the acceptable values with their quality scores and the corresponding parameters
1071
     * as specified in the given `Accept` header. The array keys of the return value are the acceptable values,
1072
     * while the array values consisting of the corresponding quality scores and parameters. The acceptable
1073
     * values with the highest quality scores will be returned first. For example,
1074
     *
1075
     * ```php
1076
     * $header = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1077
     * $accepts = $request->parseAcceptHeader($header);
1078
     * print_r($accepts);
1079
     * // displays:
1080
     * // [
1081
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1082
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1083
     * //           'text/plain' => ['q' => 0.5],
1084
     * // ]
1085
     * ```
1086
     *
1087
     * @param string $header the header to be parsed
1088
     * @return array the acceptable values ordered by their quality score. The values with the highest scores
1089
     * will be returned first.
1090
     */
1091 3
    public function parseAcceptHeader($header)
1092
    {
1093 3
        $accepts = [];
1094 3
        foreach (explode(',', $header) as $i => $part) {
1095 3
            $params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY);
1096 3
            if (empty($params)) {
1097 1
                continue;
1098
            }
1099
            $values = [
1100 3
                'q' => [$i, array_shift($params), 1],
1101 3
            ];
1102 3
            foreach ($params as $param) {
1103 2
                if (strpos($param, '=') !== false) {
1104 2
                    list($key, $value) = explode('=', $param, 2);
1105 2
                    if ($key === 'q') {
1106 2
                        $values['q'][2] = (double) $value;
1107 2
                    } else {
1108 1
                        $values[$key] = $value;
1109
                    }
1110 2
                } else {
1111 1
                    $values[] = $param;
1112
                }
1113 3
            }
1114 3
            $accepts[] = $values;
1115 3
        }
1116
1117 3
        usort($accepts, function ($a, $b) {
1118 3
            $a = $a['q']; // index, name, q
1119 3
            $b = $b['q'];
1120 3
            if ($a[2] > $b[2]) {
1121 1
                return -1;
1122 3
            } elseif ($a[2] < $b[2]) {
1123 2
                return 1;
1124 2
            } elseif ($a[1] === $b[1]) {
1125
                return $a[0] > $b[0] ? 1 : -1;
1126 2
            } elseif ($a[1] === '*/*') {
1127
                return 1;
1128 2
            } elseif ($b[1] === '*/*') {
1129
                return -1;
1130
            } else {
1131 2
                $wa = $a[1][strlen($a[1]) - 1] === '*';
1132 2
                $wb = $b[1][strlen($b[1]) - 1] === '*';
1133 2
                if ($wa xor $wb) {
1134
                    return $wa ? 1 : -1;
1135
                } else {
1136 2
                    return $a[0] > $b[0] ? 1 : -1;
1137
                }
1138
            }
1139 3
        });
1140
1141 3
        $result = [];
1142 3
        foreach ($accepts as $accept) {
1143 3
            $name = $accept['q'][1];
1144 3
            $accept['q'] = $accept['q'][2];
1145 3
            $result[$name] = $accept;
1146 3
        }
1147
1148 3
        return $result;
1149
    }
1150
1151
    /**
1152
     * Returns the user-preferred language that should be used by this application.
1153
     * The language resolution is based on the user preferred languages and the languages
1154
     * supported by the application. The method will try to find the best match.
1155
     * @param array $languages a list of the languages supported by the application. If this is empty, the current
1156
     * application language will be returned without further processing.
1157
     * @return string the language that the application should use.
1158
     */
1159 1
    public function getPreferredLanguage(array $languages = [])
1160
    {
1161 1
        if (empty($languages)) {
1162 1
            return Yii::$app->language;
1163
        }
1164 1
        foreach ($this->getAcceptableLanguages() as $acceptableLanguage) {
1165 1
            $acceptableLanguage = str_replace('_', '-', strtolower($acceptableLanguage));
1166 1
            foreach ($languages as $language) {
1167 1
                $normalizedLanguage = str_replace('_', '-', strtolower($language));
1168
1169 1
                if ($normalizedLanguage === $acceptableLanguage || // en-us==en-us
1170 1
                    strpos($acceptableLanguage, $normalizedLanguage . '-') === 0 || // en==en-us
1171 1
                    strpos($normalizedLanguage, $acceptableLanguage . '-') === 0) { // en-us==en
1172
1173 1
                    return $language;
1174
                }
1175 1
            }
1176 1
        }
1177
1178 1
        return reset($languages);
1179
    }
1180
1181
    /**
1182
     * Gets the Etags.
1183
     *
1184
     * @return array The entity tags
1185
     */
1186
    public function getETags()
1187
    {
1188
        if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) {
1189
            return preg_split('/[\s,]+/', str_replace('-gzip', '', $_SERVER['HTTP_IF_NONE_MATCH']), -1, PREG_SPLIT_NO_EMPTY);
1190
        } else {
1191
            return [];
1192
        }
1193
    }
1194
1195
    /**
1196
     * Returns the cookie collection.
1197
     * Through the returned cookie collection, you may access a cookie using the following syntax:
1198
     *
1199
     * ```php
1200
     * $cookie = $request->cookies['name']
1201
     * if ($cookie !== null) {
1202
     *     $value = $cookie->value;
1203
     * }
1204
     *
1205
     * // alternatively
1206
     * $value = $request->cookies->getValue('name');
1207
     * ```
1208
     *
1209
     * @return CookieCollection the cookie collection.
1210
     */
1211 21
    public function getCookies()
1212
    {
1213 21
        if ($this->_cookies === null) {
1214 21
            $this->_cookies = new CookieCollection($this->loadCookies(), [
1215 21
                'readOnly' => true,
1216 21
            ]);
1217 21
        }
1218
1219 21
        return $this->_cookies;
1220
    }
1221
1222
    /**
1223
     * Converts `$_COOKIE` into an array of [[Cookie]].
1224
     * @return array the cookies obtained from request
1225
     * @throws InvalidConfigException if [[cookieValidationKey]] is not set when [[enableCookieValidation]] is true
1226
     */
1227 21
    protected function loadCookies()
1228
    {
1229 21
        $cookies = [];
1230 21
        if ($this->enableCookieValidation) {
1231 21
            if ($this->cookieValidationKey == '') {
1232
                throw new InvalidConfigException(get_class($this) . '::cookieValidationKey must be configured with a secret key.');
1233
            }
1234 21
            foreach ($_COOKIE as $name => $value) {
1235
                if (!is_string($value)) {
1236
                    continue;
1237
                }
1238
                $data = Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey);
1239
                if ($data === false) {
1240
                    continue;
1241
                }
1242
                $data = @unserialize($data);
1243
                if (is_array($data) && isset($data[0], $data[1]) && $data[0] === $name) {
1244
                    $cookies[$name] = new Cookie([
1245
                        'name' => $name,
1246
                        'value' => $data[1],
1247
                        'expire' => null,
1248
                    ]);
1249
                }
1250 21
            }
1251 21
        } else {
1252
            foreach ($_COOKIE as $name => $value) {
1253
                $cookies[$name] = new Cookie([
1254
                    'name' => $name,
1255
                    'value' => $value,
1256
                    'expire' => null,
1257
                ]);
1258
            }
1259
        }
1260
1261 21
        return $cookies;
1262
    }
1263
1264
    private $_csrfToken;
1265
1266
    /**
1267
     * Returns the token used to perform CSRF validation.
1268
     *
1269
     * This token is generated in a way to prevent [BREACH attacks](http://breachattack.com/). It may be passed
1270
     * along via a hidden field of an HTML form or an HTTP header value to support CSRF validation.
1271
     * @param boolean $regenerate whether to regenerate CSRF token. When this parameter is true, each time
1272
     * this method is called, a new CSRF token will be generated and persisted (in session or cookie).
1273
     * @return string the token used to perform CSRF validation.
1274
     */
1275 22
    public function getCsrfToken($regenerate = false)
1276
    {
1277 22
        if ($this->_csrfToken === null || $regenerate) {
1278 22
            if ($regenerate || ($token = $this->loadCsrfToken()) === null) {
1279 22
                $token = $this->generateCsrfToken();
1280 22
            }
1281
            // the mask doesn't need to be very random
1282 22
            $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.';
1283 22
            $mask = substr(str_shuffle(str_repeat($chars, 5)), 0, static::CSRF_MASK_LENGTH);
1284
            // The + sign may be decoded as blank space later, which will fail the validation
1285 22
            $this->_csrfToken = str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask)));
1286 22
        }
1287
1288 22
        return $this->_csrfToken;
1289
    }
1290
1291
    /**
1292
     * Loads the CSRF token from cookie or session.
1293
     * @return string the CSRF token loaded from cookie or session. Null is returned if the cookie or session
1294
     * does not have CSRF token.
1295
     */
1296 22
    protected function loadCsrfToken()
1297
    {
1298 22
        if ($this->enableCsrfCookie) {
1299 21
            return $this->getCookies()->getValue($this->csrfParam);
1300
        } else {
1301 1
            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...
1302
        }
1303
    }
1304
1305
    /**
1306
     * Generates  an unmasked random token used to perform CSRF validation.
1307
     * @return string the random token for CSRF validation.
1308
     */
1309 22
    protected function generateCsrfToken()
1310
    {
1311 22
        $token = Yii::$app->getSecurity()->generateRandomString();
1312 22
        if ($this->enableCsrfCookie) {
1313 21
            $cookie = $this->createCsrfCookie($token);
1314 21
            Yii::$app->getResponse()->getCookies()->add($cookie);
1315 21
        } else {
1316 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...
1317
        }
1318 22
        return $token;
1319
    }
1320
1321
    /**
1322
     * Returns the XOR result of two strings.
1323
     * If the two strings are of different lengths, the shorter one will be padded to the length of the longer one.
1324
     * @param string $token1
1325
     * @param string $token2
1326
     * @return string the XOR result
1327
     */
1328 22
    private function xorTokens($token1, $token2)
1329
    {
1330 22
        $n1 = StringHelper::byteLength($token1);
1331 22
        $n2 = StringHelper::byteLength($token2);
1332 22
        if ($n1 > $n2) {
1333 22
            $token2 = str_pad($token2, $n1, $token2);
1334 22
        } elseif ($n1 < $n2) {
1335
            $token1 = str_pad($token1, $n2, $n1 === 0 ? ' ' : $token1);
1336
        }
1337
1338 22
        return $token1 ^ $token2;
1339
    }
1340
1341
    /**
1342
     * @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent.
1343
     */
1344
    public function getCsrfTokenFromHeader()
1345
    {
1346
        $key = 'HTTP_' . str_replace('-', '_', strtoupper(static::CSRF_HEADER));
1347
        return isset($_SERVER[$key]) ? $_SERVER[$key] : null;
1348
    }
1349
1350
    /**
1351
     * Creates a cookie with a randomly generated CSRF token.
1352
     * Initial values specified in [[csrfCookie]] will be applied to the generated cookie.
1353
     * @param string $token the CSRF token
1354
     * @return Cookie the generated cookie
1355
     * @see enableCsrfValidation
1356
     */
1357 21
    protected function createCsrfCookie($token)
1358
    {
1359 21
        $options = $this->csrfCookie;
1360 21
        $options['name'] = $this->csrfParam;
1361 21
        $options['value'] = $token;
1362 21
        return new Cookie($options);
1363
    }
1364
1365
    /**
1366
     * Performs the CSRF validation.
1367
     *
1368
     * This method will validate the user-provided CSRF token by comparing it with the one stored in cookie or session.
1369
     * This method is mainly called in [[Controller::beforeAction()]].
1370
     *
1371
     * Note that the method will NOT perform CSRF validation if [[enableCsrfValidation]] is false or the HTTP method
1372
     * is among GET, HEAD or OPTIONS.
1373
     *
1374
     * @param string $token the user-provided CSRF token to be validated. If null, the token will be retrieved from
1375
     * the [[csrfParam]] POST field or HTTP header.
1376
     * This parameter is available since version 2.0.4.
1377
     * @return boolean whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true.
1378
     */
1379 1
    public function validateCsrfToken($token = null)
1380
    {
1381 1
        $method = $this->getMethod();
1382
        // only validate CSRF token on non-"safe" methods http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1
1383 1
        if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
1384 1
            return true;
1385
        }
1386
1387
        $trueToken = $this->loadCsrfToken();
1388
1389
        if ($token !== null) {
1390
            return $this->validateCsrfTokenInternal($token, $trueToken);
1391
        } else {
1392
            return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
1393
                || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
1394
        }
1395
    }
1396
1397
    /**
1398
     * Validates CSRF token
1399
     *
1400
     * @param string $token
1401
     * @param string $trueToken
1402
     * @return boolean
1403
     */
1404
    private function validateCsrfTokenInternal($token, $trueToken)
1405
    {
1406
        $token = base64_decode(str_replace('.', '+', $token));
1407
        $n = StringHelper::byteLength($token);
1408
        if ($n <= static::CSRF_MASK_LENGTH) {
1409
            return false;
1410
        }
1411
        $mask = StringHelper::byteSubstr($token, 0, static::CSRF_MASK_LENGTH);
1412
        $token = StringHelper::byteSubstr($token, static::CSRF_MASK_LENGTH, $n - static::CSRF_MASK_LENGTH);
1413
        $token = $this->xorTokens($mask, $token);
1414
1415
        return $token === $trueToken;
1416
    }
1417
}
1418