Completed
Pull Request — master (#15420)
by Leandro
40:45 queued 36:01
created

Request::createCsrfCookie()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

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

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1291
            } else {
1292 1
                $this->_contentTypes = [];
1293
            }
1294
        }
1295
1296 2
        return $this->_contentTypes;
1297
    }
1298
1299
    /**
1300
     * Sets the acceptable content types.
1301
     * Please refer to [[getAcceptableContentTypes()]] on the format of the parameter.
1302
     * @param array $value the content types that are acceptable by the end user. They should
1303
     * be ordered by the preference level.
1304
     * @see getAcceptableContentTypes()
1305
     * @see parseAcceptHeader()
1306
     */
1307
    public function setAcceptableContentTypes($value)
1308
    {
1309
        $this->_contentTypes = $value;
1310
    }
1311
1312
    /**
1313
     * Returns request content-type
1314
     * The Content-Type header field indicates the MIME type of the data
1315
     * contained in [[getRawBody()]] or, in the case of the HEAD method, the
1316
     * media type that would have been sent had the request been a GET.
1317
     * For the MIME-types the user expects in response, see [[acceptableContentTypes]].
1318
     * @return string request content-type. Null is returned if this information is not available.
1319
     * @link https://tools.ietf.org/html/rfc2616#section-14.17
1320
     * HTTP 1.1 header field definitions
1321
     */
1322
    public function getContentType()
1323
    {
1324
        if (isset($_SERVER['CONTENT_TYPE'])) {
1325
            return $_SERVER['CONTENT_TYPE'];
1326
        }
1327
1328
        //fix bug https://bugs.php.net/bug.php?id=66606
1329
        return $this->headers->get('Content-Type');
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->headers->get('Content-Type'); of type string|array adds the type array to the return on line 1329 which is incompatible with the return type documented by yii\web\Request::getContentType of type string.
Loading history...
1330
    }
1331
1332
    private $_languages;
1333
1334
    /**
1335
     * Returns the languages acceptable by the end user.
1336
     * This is determined by the `Accept-Language` HTTP header.
1337
     * @return array the languages ordered by the preference level. The first element
1338
     * represents the most preferred language.
1339
     */
1340 1
    public function getAcceptableLanguages()
1341
    {
1342 1
        if ($this->_languages === null) {
1343
            if ($this->headers->has('Accept-Language')) {
1344
                $this->_languages = array_keys($this->parseAcceptHeader($this->headers->get('Accept-Language')));
0 ignored issues
show
Bug introduced by
It seems like $this->headers->get('Accept-Language') targeting yii\web\HeaderCollection::get() can also be of type array; however, yii\web\Request::parseAcceptHeader() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1345
            } else {
1346
                $this->_languages = [];
1347
            }
1348
        }
1349
1350 1
        return $this->_languages;
1351
    }
1352
1353
    /**
1354
     * @param array $value the languages that are acceptable by the end user. They should
1355
     * be ordered by the preference level.
1356
     */
1357 1
    public function setAcceptableLanguages($value)
1358
    {
1359 1
        $this->_languages = $value;
1360 1
    }
1361
1362
    /**
1363
     * Parses the given `Accept` (or `Accept-Language`) header.
1364
     *
1365
     * This method will return the acceptable values with their quality scores and the corresponding parameters
1366
     * as specified in the given `Accept` header. The array keys of the return value are the acceptable values,
1367
     * while the array values consisting of the corresponding quality scores and parameters. The acceptable
1368
     * values with the highest quality scores will be returned first. For example,
1369
     *
1370
     * ```php
1371
     * $header = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1372
     * $accepts = $request->parseAcceptHeader($header);
1373
     * print_r($accepts);
1374
     * // displays:
1375
     * // [
1376
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1377
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1378
     * //           'text/plain' => ['q' => 0.5],
1379
     * // ]
1380
     * ```
1381
     *
1382
     * @param string $header the header to be parsed
1383
     * @return array the acceptable values ordered by their quality score. The values with the highest scores
1384
     * will be returned first.
1385
     */
1386 3
    public function parseAcceptHeader($header)
1387
    {
1388 3
        $accepts = [];
1389 3
        foreach (explode(',', $header) as $i => $part) {
1390 3
            $params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY);
1391 3
            if (empty($params)) {
1392 1
                continue;
1393
            }
1394
            $values = [
1395 3
                'q' => [$i, array_shift($params), 1],
1396
            ];
1397 3
            foreach ($params as $param) {
1398 2
                if (strpos($param, '=') !== false) {
1399 2
                    list($key, $value) = explode('=', $param, 2);
1400 2
                    if ($key === 'q') {
1401 2
                        $values['q'][2] = (float) $value;
1402
                    } else {
1403 2
                        $values[$key] = $value;
1404
                    }
1405
                } else {
1406 2
                    $values[] = $param;
1407
                }
1408
            }
1409 3
            $accepts[] = $values;
1410
        }
1411
1412 3
        usort($accepts, function ($a, $b) {
1413 3
            $a = $a['q']; // index, name, q
1414 3
            $b = $b['q'];
1415 3
            if ($a[2] > $b[2]) {
1416 2
                return -1;
1417
            }
1418
1419 2
            if ($a[2] < $b[2]) {
1420 1
                return 1;
1421
            }
1422
1423 2
            if ($a[1] === $b[1]) {
1424
                return $a[0] > $b[0] ? 1 : -1;
1425
            }
1426
1427 2
            if ($a[1] === '*/*') {
1428
                return 1;
1429
            }
1430
1431 2
            if ($b[1] === '*/*') {
1432
                return -1;
1433
            }
1434
1435 2
            $wa = $a[1][strlen($a[1]) - 1] === '*';
1436 2
            $wb = $b[1][strlen($b[1]) - 1] === '*';
1437 2
            if ($wa xor $wb) {
1438
                return $wa ? 1 : -1;
1439
            }
1440
1441 2
            return $a[0] > $b[0] ? 1 : -1;
1442 3
        });
1443
1444 3
        $result = [];
1445 3
        foreach ($accepts as $accept) {
1446 3
            $name = $accept['q'][1];
1447 3
            $accept['q'] = $accept['q'][2];
1448 3
            $result[$name] = $accept;
1449
        }
1450
1451 3
        return $result;
1452
    }
1453
1454
    /**
1455
     * Returns the user-preferred language that should be used by this application.
1456
     * The language resolution is based on the user preferred languages and the languages
1457
     * supported by the application. The method will try to find the best match.
1458
     * @param array $languages a list of the languages supported by the application. If this is empty, the current
1459
     * application language will be returned without further processing.
1460
     * @return string the language that the application should use.
1461
     */
1462 1
    public function getPreferredLanguage(array $languages = [])
1463
    {
1464 1
        if (empty($languages)) {
1465 1
            return Yii::$app->language;
1466
        }
1467 1
        foreach ($this->getAcceptableLanguages() as $acceptableLanguage) {
1468 1
            $acceptableLanguage = str_replace('_', '-', strtolower($acceptableLanguage));
1469 1
            foreach ($languages as $language) {
1470 1
                $normalizedLanguage = str_replace('_', '-', strtolower($language));
1471
1472
                if (
1473 1
                    $normalizedLanguage === $acceptableLanguage // en-us==en-us
1474 1
                    || strpos($acceptableLanguage, $normalizedLanguage . '-') === 0 // en==en-us
1475 1
                    || strpos($normalizedLanguage, $acceptableLanguage . '-') === 0 // en-us==en
1476
                ) {
1477 1
                    return $language;
1478
                }
1479
            }
1480
        }
1481
1482 1
        return reset($languages);
1483
    }
1484
1485
    /**
1486
     * Gets the Etags.
1487
     *
1488
     * @return array The entity tags
1489
     */
1490
    public function getETags()
1491
    {
1492
        if ($this->headers->has('If-None-Match')) {
1493
            return preg_split('/[\s,]+/', str_replace('-gzip', '', $this->headers->get('If-None-Match')), -1, PREG_SPLIT_NO_EMPTY);
1494
        }
1495
1496
        return [];
1497
    }
1498
1499
    /**
1500
     * Returns the cookie collection.
1501
     *
1502
     * Through the returned cookie collection, you may access a cookie using the following syntax:
1503
     *
1504
     * ```php
1505
     * $cookie = $request->cookies['name']
1506
     * if ($cookie !== null) {
1507
     *     $value = $cookie->value;
1508
     * }
1509
     *
1510
     * // alternatively
1511
     * $value = $request->cookies->getValue('name');
1512
     * ```
1513
     *
1514
     * @return CookieCollection the cookie collection.
1515
     */
1516 35
    public function getCookies()
1517
    {
1518 35
        if ($this->_cookies === null) {
1519 35
            $this->_cookies = new CookieCollection($this->loadCookies(), [
1520 34
                'readOnly' => true,
1521
            ]);
1522
        }
1523
1524 34
        return $this->_cookies;
1525
    }
1526
1527
    /**
1528
     * Converts `$_COOKIE` into an array of [[Cookie]].
1529
     * @return array the cookies obtained from request
1530
     * @throws InvalidConfigException if [[cookieValidationKey]] is not set when [[enableCookieValidation]] is true
1531
     */
1532 35
    protected function loadCookies()
1533
    {
1534 35
        $cookies = [];
1535 35
        if ($this->enableCookieValidation) {
1536 34
            if ($this->cookieValidationKey == '') {
1537 1
                throw new InvalidConfigException(get_class($this) . '::cookieValidationKey must be configured with a secret key.');
1538
            }
1539 33
            foreach ($_COOKIE as $name => $value) {
1540
                if (!is_string($value)) {
1541
                    continue;
1542
                }
1543
                $data = Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey);
1544
                if ($data === false) {
1545
                    continue;
1546
                }
1547
                $data = @unserialize($data);
1548
                if (is_array($data) && isset($data[0], $data[1]) && $data[0] === $name) {
1549
                    $cookies[$name] = Yii::createObject([
1550 33
                        'class' => 'yii\web\Cookie',
1551
                        'name' => $name,
1552
                        'value' => $data[1],
1553
                        'expire' => null,
1554
                    ]);
1555
                }
1556
            }
1557
        } else {
1558 1
            foreach ($_COOKIE as $name => $value) {
1559 1
                $cookies[$name] = Yii::createObject([
1560 1
                    'class' => 'yii\web\Cookie',
1561 1
                    'name' => $name,
1562 1
                    'value' => $value,
1563
                    'expire' => null,
1564
                ]);
1565
            }
1566
        }
1567
1568 34
        return $cookies;
1569
    }
1570
1571
    private $_csrfToken;
1572
1573
    /**
1574
     * Returns the token used to perform CSRF validation.
1575
     *
1576
     * This token is generated in a way to prevent [BREACH attacks](http://breachattack.com/). It may be passed
1577
     * along via a hidden field of an HTML form or an HTTP header value to support CSRF validation.
1578
     * @param bool $regenerate whether to regenerate CSRF token. When this parameter is true, each time
1579
     * this method is called, a new CSRF token will be generated and persisted (in session or cookie).
1580
     * @return string the token used to perform CSRF validation.
1581
     */
1582 39
    public function getCsrfToken($regenerate = false)
1583
    {
1584 39
        if ($this->_csrfToken === null || $regenerate) {
1585 39
            $token = $this->loadCsrfToken();
1586 38
            if ($regenerate || empty($token)) {
1587 36
                $token = $this->generateCsrfToken();
1588
            }
1589 38
            $this->_csrfToken = Yii::$app->security->maskToken($token);
1590
        }
1591
1592 38
        return $this->_csrfToken;
1593
    }
1594
1595
    /**
1596
     * Loads the CSRF token from cookie or session.
1597
     * @return string the CSRF token loaded from cookie or session. Null is returned if the cookie or session
1598
     * does not have CSRF token.
1599
     */
1600 39
    protected function loadCsrfToken()
1601
    {
1602 39
        if ($this->enableCsrfCookie) {
1603 35
            return $this->getCookies()->getValue($this->csrfParam);
1604
        }
1605
1606 4
        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...
1607
    }
1608
1609
    /**
1610
     * Generates an unmasked random token used to perform CSRF validation.
1611
     * @return string the random token for CSRF validation.
1612
     */
1613 36
    protected function generateCsrfToken()
1614
    {
1615 36
        $token = Yii::$app->getSecurity()->generateRandomString();
1616 36
        if ($this->enableCsrfCookie) {
1617 34
            $cookie = $this->createCsrfCookie($token);
1618 34
            Yii::$app->getResponse()->getCookies()->add($cookie);
1619
        } else {
1620 2
            Yii::$app->getSession()->set($this->csrfParam, $token);
0 ignored issues
show
Bug introduced by
The method getSession does only exist in yii\web\Application, but not in yii\console\Application.

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

Let’s take a look at an example:

class A
{
    public function foo() { }
}

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

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

Available Fixes

  1. Add an additional type-check:

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

    function someFunction(B $x) { /** ... */ }
    
Loading history...
1621
        }
1622
1623 36
        return $token;
1624
    }
1625
1626
    /**
1627
     * @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent.
1628
     */
1629 3
    public function getCsrfTokenFromHeader()
1630
    {
1631 3
        return $this->headers->get(static::CSRF_HEADER);
1632
    }
1633
1634
    /**
1635
     * Creates a cookie with a randomly generated CSRF token.
1636
     * Initial values specified in [[csrfCookie]] will be applied to the generated cookie.
1637
     * @param string $token the CSRF token
1638
     * @return Cookie the generated cookie
1639
     * @see enableCsrfValidation
1640
     */
1641 34
    protected function createCsrfCookie($token)
1642
    {
1643 34
        $options = $this->csrfCookie;
1644 34
        return Yii::createObject(array_merge($options, [
1645 34
            'class' => 'yii\web\Cookie',
1646 34
            'name' => $this->csrfParam,
1647 34
            'value' => $token,
1648
        ]));
1649
    }
1650
1651
    /**
1652
     * Performs the CSRF validation.
1653
     *
1654
     * This method will validate the user-provided CSRF token by comparing it with the one stored in cookie or session.
1655
     * This method is mainly called in [[Controller::beforeAction()]].
1656
     *
1657
     * Note that the method will NOT perform CSRF validation if [[enableCsrfValidation]] is false or the HTTP method
1658
     * is among GET, HEAD or OPTIONS.
1659
     *
1660
     * @param string $clientSuppliedToken the user-provided CSRF token to be validated. If null, the token will be retrieved from
1661
     * the [[csrfParam]] POST field or HTTP header.
1662
     * This parameter is available since version 2.0.4.
1663
     * @return bool whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true.
1664
     */
1665 6
    public function validateCsrfToken($clientSuppliedToken = null)
1666
    {
1667 6
        $method = $this->getMethod();
1668
        // only validate CSRF token on non-"safe" methods https://tools.ietf.org/html/rfc2616#section-9.1.1
1669 6
        if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
1670 5
            return true;
1671
        }
1672
1673 4
        $trueToken = $this->getCsrfToken();
1674
1675 4
        if ($clientSuppliedToken !== null) {
1676 2
            return $this->validateCsrfTokenInternal($clientSuppliedToken, $trueToken);
1677
        }
1678
1679 3
        return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
1680 3
            || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
0 ignored issues
show
Bug introduced by
It seems like $this->getCsrfTokenFromHeader() targeting yii\web\Request::getCsrfTokenFromHeader() can also be of type array; however, yii\web\Request::validateCsrfTokenInternal() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1681
    }
1682
1683
    /**
1684
     * Validates CSRF token.
1685
     *
1686
     * @param string $clientSuppliedToken The masked client-supplied token.
1687
     * @param string $trueToken The masked true token.
1688
     * @return bool
1689
     */
1690 4
    private function validateCsrfTokenInternal($clientSuppliedToken, $trueToken)
1691
    {
1692 4
        if (!is_string($clientSuppliedToken)) {
1693 3
            return false;
1694
        }
1695
1696 4
        $security = Yii::$app->security;
1697
1698 4
        return $security->unmaskToken($clientSuppliedToken) === $security->unmaskToken($trueToken);
1699
    }
1700
}
1701