Completed
Push — 2.1 ( 224aac...d335fd )
by Dmitry
54:21 queued 13:00
created

Request::getHostName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

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