Completed
Push — security-enhancements ( 371440 )
by Alexander
08:04
created

Request::getIsPatch()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

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