Completed
Push — master ( 5a9669...ea2c47 )
by Dmitry
12:22
created

Request::getAuthCredentials()   D

Complexity

Conditions 10
Paths 28

Size

Total Lines 29
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 10.0454

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 12
cts 13
cp 0.9231
rs 4.8196
c 0
b 0
f 0
cc 10
eloc 14
nc 28
nop 0
crap 10.0454

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\web;
9
10
use Yii;
11
use yii\base\InvalidConfigException;
12
use yii\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 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. See
48
 * [[getHostInfo()]] for security related notes on this property.
49
 * @property string|null $hostName Hostname part of the request URL (e.g. `www.yiiframework.com`). This
50
 * property is read-only.
51
 * @property bool $isAjax Whether this is an AJAX (XMLHttpRequest) request. This property is read-only.
52
 * @property bool $isDelete Whether this is a DELETE request. This property is read-only.
53
 * @property bool $isFlash Whether this is an Adobe Flash or Adobe Flex request. This property is 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|null $origin URL origin, null if not available. This property is read-only.
74
 * @property string $scriptFile The entry script file path.
75
 * @property string $scriptUrl The relative URL of the entry script.
76
 * @property int $securePort Port number for secure requests.
77
 * @property string $serverName Server name, null if not available. This property is read-only.
78
 * @property int|null $serverPort Server port number, null if not available. This property is read-only.
79
 * @property string $url The currently requested relative URL. Note that the URI returned may be URL-encoded
80
 * depending on the client.
81
 * @property string|null $userAgent User agent, null if not available. This property is read-only.
82
 * @property string|null $userHost User host name, null if not available. This property is read-only.
83
 * @property string|null $userIP User IP address, null if not available. This property is read-only.
84
 *
85
 * @author Qiang Xue <[email protected]>
86
 * @since 2.0
87
 * @SuppressWarnings(PHPMD.SuperGlobals)
88
 */
89
class Request extends \yii\base\Request
90
{
91
    /**
92
     * The name of the HTTP header for sending CSRF token.
93
     */
94
    const CSRF_HEADER = 'X-CSRF-Token';
95
    /**
96
     * The length of the CSRF token mask.
97
     * @deprecated 2.0.12 The mask length is now equal to the token length.
98
     */
99
    const CSRF_MASK_LENGTH = 8;
100
101
    /**
102
     * @var bool whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to true.
103
     * When CSRF validation is enabled, forms submitted to an Yii Web application must be originated
104
     * from the same application. If not, a 400 HTTP exception will be raised.
105
     *
106
     * Note, this feature requires that the user client accepts cookie. Also, to use this feature,
107
     * forms submitted via POST method must contain a hidden input whose name is specified by [[csrfParam]].
108
     * You may use [[\yii\helpers\Html::beginForm()]] to generate his hidden input.
109
     *
110
     * In JavaScript, you may get the values of [[csrfParam]] and [[csrfToken]] via `yii.getCsrfParam()` and
111
     * `yii.getCsrfToken()`, respectively. The [[\yii\web\YiiAsset]] asset must be registered.
112
     * You also need to include CSRF meta tags in your pages by using [[\yii\helpers\Html::csrfMetaTags()]].
113
     *
114
     * @see Controller::enableCsrfValidation
115
     * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery
116
     */
117
    public $enableCsrfValidation = true;
118
    /**
119
     * @var string the name of the token used to prevent CSRF. Defaults to '_csrf'.
120
     * This property is used only when [[enableCsrfValidation]] is true.
121
     */
122
    public $csrfParam = '_csrf';
123
    /**
124
     * @var array the configuration for creating the CSRF [[Cookie|cookie]]. This property is used only when
125
     * both [[enableCsrfValidation]] and [[enableCsrfCookie]] are true.
126
     */
127
    public $csrfCookie = ['httpOnly' => true];
128
    /**
129
     * @var bool whether to use cookie to persist CSRF token. If false, CSRF token will be stored
130
     * in session under the name of [[csrfParam]]. Note that while storing CSRF tokens in session increases
131
     * security, it requires starting a session for every page, which will degrade your site performance.
132
     */
133
    public $enableCsrfCookie = true;
134
    /**
135
     * @var bool whether cookies should be validated to ensure they are not tampered. Defaults to true.
136
     */
137
    public $enableCookieValidation = true;
138
    /**
139
     * @var string a secret key used for cookie validation. This property must be set if [[enableCookieValidation]] is true.
140
     */
141
    public $cookieValidationKey;
142
    /**
143
     * @var string the name of the POST parameter that is used to indicate if a request is a PUT, PATCH or DELETE
144
     * request tunneled through POST. Defaults to '_method'.
145
     * @see getMethod()
146
     * @see getBodyParams()
147
     */
148
    public $methodParam = '_method';
149
    /**
150
     * @var array the parsers for converting the raw HTTP request body into [[bodyParams]].
151
     * The array keys are the request `Content-Types`, and the array values are the
152
     * corresponding configurations for [[Yii::createObject|creating the parser objects]].
153
     * A parser must implement the [[RequestParserInterface]].
154
     *
155
     * To enable parsing for JSON requests you can use the [[JsonParser]] class like in the following example:
156
     *
157
     * ```
158
     * [
159
     *     'application/json' => 'yii\web\JsonParser',
160
     * ]
161
     * ```
162
     *
163
     * To register a parser for parsing all request types you can use `'*'` as the array key.
164
     * This one will be used as a fallback in case no other types match.
165
     *
166
     * @see getBodyParams()
167
     */
168
    public $parsers = [];
169
    /**
170
     * @var array the configuration for trusted security related headers.
171
     *
172
     * An array key is an IPv4 or IPv6 IP address in CIDR notation for matching a client.
173
     *
174
     * An array value is a list of headers to trust. These will be matched against
175
     * [[secureHeaders]] to determine which headers are allowed to be sent by a specified host.
176
     * The case of the header names must be the same as specified in [[secureHeaders]].
177
     *
178
     * For example, to trust all headers listed in [[secureHeaders]] for IP addresses
179
     * in range `192.168.0.0-192.168.0.254` write the following:
180
     *
181
     * ```php
182
     * [
183
     *     '192.168.0.0/24',
184
     * ]
185
     * ```
186
     *
187
     * To trust just the `X-Forwarded-For` header from `10.0.0.1`, use:
188
     *
189
     * ```
190
     * [
191
     *     '10.0.0.1' => ['X-Forwarded-For']
192
     * ]
193
     * ```
194
     *
195
     * Default is to trust all headers except those listed in [[secureHeaders]] from all hosts.
196
     * Matches are tried in order and searching is stopped when IP matches.
197
     * 
198
     * > Info: Matching is performed using [[IpValidator]].
199
     *   See [[IpValidator::::setRanges()|IpValidator::setRanges()]]
200
     *   and [[IpValidator::networks]] for advanced matching.
201
     * 
202
     * @see $secureHeaders
203
     * @since 2.0.13
204
     */
205
    public $trustedHosts = [];
206
    /**
207
     * @var array lists of headers that are, by default, subject to the trusted host configuration.
208
     * These headers will be filtered unless explicitly allowed in [[trustedHosts]].
209
     * The match of header names is case-insensitive.
210
     * @see https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
211
     * @see $trustedHosts
212
     * @since 2.0.13
213
     */
214
    public $secureHeaders = [
215
        'X-Forwarded-For',
216
        'X-Forwarded-Host',
217
        'X-Forwarded-Proto',
218
        'Front-End-Https',
219
        'X-Rewrite-Url',
220
    ];
221
    /**
222
     * @var string[] List of headers where proxies store the real client IP.
223
     * It's not advisable to put insecure headers here.
224
     * The match of header names is case-insensitive.
225
     * @see $trustedHosts
226
     * @see $secureHeaders
227
     * @since 2.0.13
228
     */
229
    public $ipHeaders = [
230
        'X-Forwarded-For',
231
    ];
232
    /**
233
     * @var array list of headers to check for determining whether the connection is made via HTTPS.
234
     * The array keys are header names and the array value is a list of header values that indicate a secure connection.
235
     * The match of header names and values is case-insensitive.
236
     * It's not advisable to put insecure headers here.
237
     * @see $trustedHosts
238
     * @see $secureHeaders
239
     * @since 2.0.13
240
     */
241
    public $secureProtocolHeaders = [
242
        'X-Forwarded-Proto' => ['https'],
243
        'Front-End-Https' => ['on'],
244
    ];
245
    /**
246
     * @var CookieCollection Collection of request cookies.
247
     */
248
    private $_cookies;
249
    /**
250
     * @var HeaderCollection Collection of request headers.
251
     */
252
    private $_headers;
253
254
255
    /**
256
     * Resolves the current request into a route and the associated parameters.
257
     * @return array the first element is the route, and the second is the associated parameters.
258
     * @throws NotFoundHttpException if the request cannot be resolved.
259
     */
260 1
    public function resolve()
261
    {
262 1
        $result = Yii::$app->getUrlManager()->parseRequest($this);
263 1
        if ($result !== false) {
264 1
            list($route, $params) = $result;
265 1
            if ($this->_queryParams === null) {
266 1
                $_GET = $params + $_GET; // preserve numeric keys
267
            } else {
268 1
                $this->_queryParams = $params + $this->_queryParams;
269
            }
270
271 1
            return [$route, $this->getQueryParams()];
272
        }
273
274
        throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
275
    }
276
277
    /**
278
     * Filters headers according to the [[trustedHosts]].
279
     * @param HeaderCollection $headerCollection
280
     * @since 2.0.13
281
     */
282 129
    protected function filterHeaders(HeaderCollection $headerCollection)
283
    {
284
        // do not trust any of the [[secureHeaders]] by default
285 129
        $trustedHeaders = [];
286
287
        // check if the client is a trusted host
288 129
        if (!empty($this->trustedHosts)) {
289 19
            $validator = $this->getIpValidator();
290 19
            $ip = $this->getRemoteIP();
291 19
            foreach ($this->trustedHosts as $cidr => $headers) {
292 19
                if (!is_array($headers)) {
293 19
                    $cidr = $headers;
294 19
                    $headers = $this->secureHeaders;
295
                }
296 19
                $validator->setRanges($cidr);
297 19
                if ($validator->validate($ip)) {
298 3
                    $trustedHeaders = $headers;
299 19
                    break;
300
                }
301
            }
302
        }
303
304
        // filter all secure headers unless they are trusted
305 129
        foreach ($this->secureHeaders as $secureHeader) {
306 129
            if (!in_array($secureHeader, $trustedHeaders)) {
307 129
                $headerCollection->remove($secureHeader);
308
            }
309
        }
310 129
    }
311
312
    /**
313
     * Creates instance of [[IpValidator]].
314
     * You can override this method to adjust validator or implement different matching strategy.
315
     *
316
     * @return IpValidator
317
     * @since 2.0.13
318
     */
319 19
    protected function getIpValidator()
320
    {
321 19
        return new IpValidator();
322
    }
323
324
    /**
325
     * Returns the header collection.
326
     * The header collection contains incoming HTTP headers.
327
     * @return HeaderCollection the header collection
328
     */
329 129
    public function getHeaders()
330
    {
331 129
        if ($this->_headers === null) {
332 129
            $this->_headers = new HeaderCollection();
333 129
            if (function_exists('getallheaders')) {
334
                $headers = getallheaders();
335
                foreach ($headers as $name => $value) {
336
                    $this->_headers->add($name, $value);
337
                }
338 129
            } elseif (function_exists('http_get_request_headers')) {
339
                $headers = http_get_request_headers();
340
                foreach ($headers as $name => $value) {
341
                    $this->_headers->add($name, $value);
342
                }
343
            } else {
344 129
                foreach ($_SERVER as $name => $value) {
345 126
                    if (strncmp($name, 'HTTP_', 5) === 0) {
346 37
                        $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
347 126
                        $this->_headers->add($name, $value);
348
                    }
349
                }
350
            }
351 129
            $this->filterHeaders($this->_headers);
352
        }
353
354 129
        return $this->_headers;
355
    }
356
357
    /**
358
     * Returns the method of the current request (e.g. GET, POST, HEAD, PUT, PATCH, DELETE).
359
     * @return string request method, such as GET, POST, HEAD, PUT, PATCH, DELETE.
360
     * The value returned is turned into upper case.
361
     */
362 22
    public function getMethod()
363
    {
364 22
        if (isset($_POST[$this->methodParam])) {
365 4
            return strtoupper($_POST[$this->methodParam]);
366
        }
367
368 19
        if ($this->headers->has('X-Http-Method-Override')) {
369 1
            return strtoupper($this->headers->get('X-Http-Method-Override'));
370
        }
371
372 18
        if (isset($_SERVER['REQUEST_METHOD'])) {
373 3
            return strtoupper($_SERVER['REQUEST_METHOD']);
374
        }
375
376 16
        return 'GET';
377
    }
378
379
    /**
380
     * Returns whether this is a GET request.
381
     * @return bool whether this is a GET request.
382
     */
383 2
    public function getIsGet()
384
    {
385 2
        return $this->getMethod() === 'GET';
386
    }
387
388
    /**
389
     * Returns whether this is an OPTIONS request.
390
     * @return bool whether this is a OPTIONS request.
391
     */
392
    public function getIsOptions()
393
    {
394
        return $this->getMethod() === 'OPTIONS';
395
    }
396
397
    /**
398
     * Returns whether this is a HEAD request.
399
     * @return bool whether this is a HEAD request.
400
     */
401 9
    public function getIsHead()
402
    {
403 9
        return $this->getMethod() === 'HEAD';
404
    }
405
406
    /**
407
     * Returns whether this is a POST request.
408
     * @return bool whether this is a POST request.
409
     */
410
    public function getIsPost()
411
    {
412
        return $this->getMethod() === 'POST';
413
    }
414
415
    /**
416
     * Returns whether this is a DELETE request.
417
     * @return bool whether this is a DELETE request.
418
     */
419
    public function getIsDelete()
420
    {
421
        return $this->getMethod() === 'DELETE';
422
    }
423
424
    /**
425
     * Returns whether this is a PUT request.
426
     * @return bool whether this is a PUT request.
427
     */
428
    public function getIsPut()
429
    {
430
        return $this->getMethod() === 'PUT';
431
    }
432
433
    /**
434
     * Returns whether this is a PATCH request.
435
     * @return bool whether this is a PATCH request.
436
     */
437
    public function getIsPatch()
438
    {
439
        return $this->getMethod() === 'PATCH';
440
    }
441
442
    /**
443
     * Returns whether this is an AJAX (XMLHttpRequest) request.
444
     *
445
     * Note that jQuery doesn't set the header in case of cross domain
446
     * requests: https://stackoverflow.com/questions/8163703/cross-domain-ajax-doesnt-send-x-requested-with-header
447
     *
448
     * @return bool whether this is an AJAX (XMLHttpRequest) request.
449
     */
450 14
    public function getIsAjax()
451
    {
452 14
        return $this->headers->get('X-Requested-With') === 'XMLHttpRequest';
453
    }
454
455
    /**
456
     * Returns whether this is a PJAX request.
457
     * @return bool whether this is a PJAX request
458
     */
459 3
    public function getIsPjax()
460
    {
461 3
        return $this->getIsAjax() && $this->headers->has('X-Pjax');
462
    }
463
464
    /**
465
     * Returns whether this is an Adobe Flash or Flex request.
466
     * @return bool whether this is an Adobe Flash or Adobe Flex request.
467
     */
468
    public function getIsFlash()
469
    {
470
        $userAgent = $this->headers->get('User-Agent', '');
471
        return stripos($userAgent, 'Shockwave') !== false
472
            || stripos($userAgent, 'Flash') !== false;
473
    }
474
475
    private $_rawBody;
476
477
    /**
478
     * Returns the raw HTTP request body.
479
     * @return string the request body
480
     */
481
    public function getRawBody()
482
    {
483
        if ($this->_rawBody === null) {
484
            $this->_rawBody = file_get_contents('php://input');
485
        }
486
487
        return $this->_rawBody;
488
    }
489
490
    /**
491
     * Sets the raw HTTP request body, this method is mainly used by test scripts to simulate raw HTTP requests.
492
     * @param string $rawBody the request body
493
     */
494
    public function setRawBody($rawBody)
495
    {
496
        $this->_rawBody = $rawBody;
497
    }
498
499
    private $_bodyParams;
500
501
    /**
502
     * Returns the request parameters given in the request body.
503
     *
504
     * Request parameters are determined using the parsers configured in [[parsers]] property.
505
     * If no parsers are configured for the current [[contentType]] it uses the PHP function `mb_parse_str()`
506
     * to parse the [[rawBody|request body]].
507
     * @return array the request parameters given in the request body.
508
     * @throws \yii\base\InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]].
509
     * @see getMethod()
510
     * @see getBodyParam()
511
     * @see setBodyParams()
512
     */
513 3
    public function getBodyParams()
514
    {
515 3
        if ($this->_bodyParams === null) {
516 1
            if (isset($_POST[$this->methodParam])) {
517 1
                $this->_bodyParams = $_POST;
518 1
                unset($this->_bodyParams[$this->methodParam]);
519 1
                return $this->_bodyParams;
520
            }
521
522
            $rawContentType = $this->getContentType();
523
            if (($pos = strpos($rawContentType, ';')) !== false) {
524
                // e.g. text/html; charset=UTF-8
525
                $contentType = substr($rawContentType, 0, $pos);
526
            } else {
527
                $contentType = $rawContentType;
528
            }
529
530
            if (isset($this->parsers[$contentType])) {
531
                $parser = Yii::createObject($this->parsers[$contentType]);
532
                if (!($parser instanceof RequestParserInterface)) {
533
                    throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
534
                }
535
                $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
536
            } elseif (isset($this->parsers['*'])) {
537
                $parser = Yii::createObject($this->parsers['*']);
538
                if (!($parser instanceof RequestParserInterface)) {
539
                    throw new InvalidConfigException('The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface.');
540
                }
541
                $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
542
            } elseif ($this->getMethod() === 'POST') {
543
                // PHP has already parsed the body so we have all params in $_POST
544
                $this->_bodyParams = $_POST;
545
            } else {
546
                $this->_bodyParams = [];
547
                mb_parse_str($this->getRawBody(), $this->_bodyParams);
548
            }
549
        }
550
551 3
        return $this->_bodyParams;
552
    }
553
554
    /**
555
     * Sets the request body parameters.
556
     * @param array $values the request body parameters (name-value pairs)
557
     * @see getBodyParam()
558
     * @see getBodyParams()
559
     */
560 2
    public function setBodyParams($values)
561
    {
562 2
        $this->_bodyParams = $values;
563 2
    }
564
565
    /**
566
     * Returns the named request body parameter value.
567
     * If the parameter does not exist, the second parameter passed to this method will be returned.
568
     * @param string $name the parameter name
569
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
570
     * @return mixed the parameter value
571
     * @see getBodyParams()
572
     * @see setBodyParams()
573
     */
574 3
    public function getBodyParam($name, $defaultValue = null)
575
    {
576 3
        $params = $this->getBodyParams();
577
578 3
        return isset($params[$name]) ? $params[$name] : $defaultValue;
579
    }
580
581
    /**
582
     * Returns POST parameter with a given name. If name isn't specified, returns an array of all POST parameters.
583
     *
584
     * @param string $name the parameter name
585
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
586
     * @return array|mixed
587
     */
588
    public function post($name = null, $defaultValue = null)
589
    {
590
        if ($name === null) {
591
            return $this->getBodyParams();
592
        }
593
594
        return $this->getBodyParam($name, $defaultValue);
595
    }
596
597
    private $_queryParams;
598
599
    /**
600
     * Returns the request parameters given in the [[queryString]].
601
     *
602
     * This method will return the contents of `$_GET` if params where not explicitly set.
603
     * @return array the request GET parameter values.
604
     * @see setQueryParams()
605
     */
606 31
    public function getQueryParams()
607
    {
608 31
        if ($this->_queryParams === null) {
609 26
            return $_GET;
610
        }
611
612 7
        return $this->_queryParams;
613
    }
614
615
    /**
616
     * Sets the request [[queryString]] parameters.
617
     * @param array $values the request query parameters (name-value pairs)
618
     * @see getQueryParam()
619
     * @see getQueryParams()
620
     */
621 7
    public function setQueryParams($values)
622
    {
623 7
        $this->_queryParams = $values;
624 7
    }
625
626
    /**
627
     * Returns GET parameter with a given name. If name isn't specified, returns an array of all GET parameters.
628
     *
629
     * @param string $name the parameter name
630
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
631
     * @return array|mixed
632
     */
633 19
    public function get($name = null, $defaultValue = null)
634
    {
635 19
        if ($name === null) {
636
            return $this->getQueryParams();
637
        }
638
639 19
        return $this->getQueryParam($name, $defaultValue);
640
    }
641
642
    /**
643
     * Returns the named GET parameter value.
644
     * If the GET parameter does not exist, the second parameter passed to this method will be returned.
645
     * @param string $name the GET parameter name.
646
     * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
647
     * @return mixed the GET parameter value
648
     * @see getBodyParam()
649
     */
650 22
    public function getQueryParam($name, $defaultValue = null)
651
    {
652 22
        $params = $this->getQueryParams();
653
654 22
        return isset($params[$name]) ? $params[$name] : $defaultValue;
655
    }
656
657
    private $_hostInfo;
658
    private $_hostName;
659
660
    /**
661
     * Returns the schema and host part of the current request URL.
662
     *
663
     * The returned URL does not have an ending slash.
664
     *
665
     * By default this value is based on the user request information. This method will
666
     * return the value of `$_SERVER['HTTP_HOST']` if it is available or `$_SERVER['SERVER_NAME']` if not.
667
     * You may want to check out the [PHP documentation](http://php.net/manual/en/reserved.variables.server.php)
668
     * for more information on these variables.
669
     *
670
     * You may explicitly specify it by setting the [[setHostInfo()|hostInfo]] property.
671
     *
672
     * > Warning: Dependent on the server configuration this information may not be
673
     * > reliable and [may be faked by the user sending the HTTP request](https://www.acunetix.com/vulnerabilities/web/host-header-attack).
674
     * > If the webserver is configured to serve the same site independent of the value of
675
     * > the `Host` header, this value is not reliable. In such situations you should either
676
     * > fix your webserver configuration or explicitly set the value by setting the [[setHostInfo()|hostInfo]] property.
677
     * > If you don't have access to the server configuration, you can setup [[\yii\filters\HostControl]] filter at
678
     * > application level in order to protect against such kind of attack.
679
     *
680
     * @property string|null schema and hostname part (with port number if needed) of the request URL
681
     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
682
     * See [[getHostInfo()]] for security related notes on this property.
683
     * @return string|null schema and hostname part (with port number if needed) of the request URL
684
     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
685
     * @see setHostInfo()
686
     */
687 24
    public function getHostInfo()
688
    {
689 24
        if ($this->_hostInfo === null) {
690 20
            $secure = $this->getIsSecureConnection();
691 20
            $http = $secure ? 'https' : 'http';
692 20
            if ($this->headers->has('Host')) {
693 7
                $this->_hostInfo = $http . '://' . $this->headers->get('Host');
694 13
            } elseif (isset($_SERVER['SERVER_NAME'])) {
695
                $this->_hostInfo = $http . '://' . $_SERVER['SERVER_NAME'];
696
                $port = $secure ? $this->getSecurePort() : $this->getPort();
697
                if (($port !== 80 && !$secure) || ($port !== 443 && $secure)) {
698
                    $this->_hostInfo .= ':' . $port;
699
                }
700
            }
701
        }
702
703 24
        return $this->_hostInfo;
704
    }
705
706
    /**
707
     * Sets the schema and host part of the application URL.
708
     * This setter is provided in case the schema and hostname cannot be determined
709
     * on certain Web servers.
710
     * @param string|null $value the schema and host part of the application URL. The trailing slashes will be removed.
711
     * @see getHostInfo() for security related notes on this property.
712
     */
713 57
    public function setHostInfo($value)
714
    {
715 57
        $this->_hostName = null;
716 57
        $this->_hostInfo = $value === null ? null : rtrim($value, '/');
717 57
    }
718
719
    /**
720
     * Returns the host part of the current request URL.
721
     * Value is calculated from current [[getHostInfo()|hostInfo]] property.
722
     *
723
     * > Warning: The content of this value may not be reliable, dependent on the server
724
     * > configuration. Please refer to [[getHostInfo()]] for more information.
725
     *
726
     * @return string|null hostname part of the request URL (e.g. `www.yiiframework.com`)
727
     * @see getHostInfo()
728
     * @since 2.0.10
729
     */
730 11
    public function getHostName()
731
    {
732 11
        if ($this->_hostName === null) {
733 11
            $this->_hostName = parse_url($this->getHostInfo(), PHP_URL_HOST);
734
        }
735
736 11
        return $this->_hostName;
737
    }
738
739
    private $_baseUrl;
740
741
    /**
742
     * Returns the relative URL for the application.
743
     * This is similar to [[scriptUrl]] except that it does not include the script file name,
744
     * and the ending slashes are removed.
745
     * @return string the relative URL for the application
746
     * @see setScriptUrl()
747
     */
748 272
    public function getBaseUrl()
749
    {
750 272
        if ($this->_baseUrl === null) {
751 271
            $this->_baseUrl = rtrim(dirname($this->getScriptUrl()), '\\/');
752
        }
753
754 272
        return $this->_baseUrl;
755
    }
756
757
    /**
758
     * Sets the relative URL for the application.
759
     * By default the URL is determined based on the entry script URL.
760
     * This setter is provided in case you want to change this behavior.
761
     * @param string $value the relative URL for the application
762
     */
763 1
    public function setBaseUrl($value)
764
    {
765 1
        $this->_baseUrl = $value;
766 1
    }
767
768
    private $_scriptUrl;
769
770
    /**
771
     * Returns the relative URL of the entry script.
772
     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
773
     * @return string the relative URL of the entry script.
774
     * @throws InvalidConfigException if unable to determine the entry script URL
775
     */
776 273
    public function getScriptUrl()
777
    {
778 273
        if ($this->_scriptUrl === null) {
779 1
            $scriptFile = $this->getScriptFile();
780
            $scriptName = basename($scriptFile);
781
            if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $scriptName) {
782
                $this->_scriptUrl = $_SERVER['SCRIPT_NAME'];
783
            } elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $scriptName) {
784
                $this->_scriptUrl = $_SERVER['PHP_SELF'];
785
            } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $scriptName) {
786
                $this->_scriptUrl = $_SERVER['ORIG_SCRIPT_NAME'];
787
            } elseif (isset($_SERVER['PHP_SELF']) && ($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) {
788
                $this->_scriptUrl = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName;
789
            } elseif (!empty($_SERVER['DOCUMENT_ROOT']) && strpos($scriptFile, $_SERVER['DOCUMENT_ROOT']) === 0) {
790
                $this->_scriptUrl = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $scriptFile));
791
            } else {
792
                throw new InvalidConfigException('Unable to determine the entry script URL.');
793
            }
794
        }
795
796 272
        return $this->_scriptUrl;
797
    }
798
799
    /**
800
     * Sets the relative URL for the application entry script.
801
     * This setter is provided in case the entry script URL cannot be determined
802
     * on certain Web servers.
803
     * @param string $value the relative URL for the application entry script.
804
     */
805 283
    public function setScriptUrl($value)
806
    {
807 283
        $this->_scriptUrl = $value === null ? null : '/' . trim($value, '/');
808 283
    }
809
810
    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...
811
812
    /**
813
     * Returns the entry script file path.
814
     * The default implementation will simply return `$_SERVER['SCRIPT_FILENAME']`.
815
     * @return string the entry script file path
816
     * @throws InvalidConfigException
817
     */
818 274
    public function getScriptFile()
819
    {
820 274
        if (isset($this->_scriptFile)) {
821 252
            return $this->_scriptFile;
822
        }
823
824 22
        if (isset($_SERVER['SCRIPT_FILENAME'])) {
825 20
            return $_SERVER['SCRIPT_FILENAME'];
826
        }
827
828 2
        throw new InvalidConfigException('Unable to determine the entry script file path.');
829
    }
830
831
    /**
832
     * Sets the entry script file path.
833
     * The entry script file path normally can be obtained from `$_SERVER['SCRIPT_FILENAME']`.
834
     * If your server configuration does not return the correct value, you may configure
835
     * this property to make it right.
836
     * @param string $value the entry script file path.
837
     */
838 252
    public function setScriptFile($value)
839
    {
840 252
        $this->_scriptFile = $value;
841 252
    }
842
843
    private $_pathInfo;
844
845
    /**
846
     * Returns the path info of the currently requested URL.
847
     * A path info refers to the part that is after the entry script and before the question mark (query string).
848
     * The starting and ending slashes are both removed.
849
     * @return string part of the request URL that is after the entry script and before the question mark.
850
     * Note, the returned path info is already URL-decoded.
851
     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
852
     */
853 18
    public function getPathInfo()
854
    {
855 18
        if ($this->_pathInfo === null) {
856
            $this->_pathInfo = $this->resolvePathInfo();
857
        }
858
859 18
        return $this->_pathInfo;
860
    }
861
862
    /**
863
     * Sets the path info of the current request.
864
     * This method is mainly provided for testing purpose.
865
     * @param string $value the path info of the current request
866
     */
867 19
    public function setPathInfo($value)
868
    {
869 19
        $this->_pathInfo = $value === null ? null : ltrim($value, '/');
870 19
    }
871
872
    /**
873
     * Resolves the path info part of the currently requested URL.
874
     * A path info refers to the part that is after the entry script and before the question mark (query string).
875
     * The starting slashes are both removed (ending slashes will be kept).
876
     * @return string part of the request URL that is after the entry script and before the question mark.
877
     * Note, the returned path info is decoded.
878
     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
879
     */
880
    protected function resolvePathInfo()
881
    {
882
        $pathInfo = $this->getUrl();
883
884
        if (($pos = strpos($pathInfo, '?')) !== false) {
885
            $pathInfo = substr($pathInfo, 0, $pos);
886
        }
887
888
        $pathInfo = urldecode($pathInfo);
889
890
        // try to encode in UTF8 if not so
891
        // http://w3.org/International/questions/qa-forms-utf-8.html
892
        if (!preg_match('%^(?:
893
            [\x09\x0A\x0D\x20-\x7E]              # ASCII
894
            | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
895
            | \xE0[\xA0-\xBF][\x80-\xBF]         # excluding overlongs
896
            | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
897
            | \xED[\x80-\x9F][\x80-\xBF]         # excluding surrogates
898
            | \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
899
            | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
900
            | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
901
            )*$%xs', $pathInfo)
902
        ) {
903
            $pathInfo = utf8_encode($pathInfo);
904
        }
905
906
        $scriptUrl = $this->getScriptUrl();
907
        $baseUrl = $this->getBaseUrl();
908
        if (strpos($pathInfo, $scriptUrl) === 0) {
909
            $pathInfo = substr($pathInfo, strlen($scriptUrl));
910
        } elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) {
911
            $pathInfo = substr($pathInfo, strlen($baseUrl));
912
        } elseif (isset($_SERVER['PHP_SELF']) && strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) {
913
            $pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl));
914
        } else {
915
            throw new InvalidConfigException('Unable to determine the path info of the current request.');
916
        }
917
918
        if (substr($pathInfo, 0, 1) === '/') {
919
            $pathInfo = substr($pathInfo, 1);
920
        }
921
922
        return (string) $pathInfo;
923
    }
924
925
    /**
926
     * Returns the currently requested absolute URL.
927
     * This is a shortcut to the concatenation of [[hostInfo]] and [[url]].
928
     * @return string the currently requested absolute URL.
929
     */
930
    public function getAbsoluteUrl()
931
    {
932
        return $this->getHostInfo() . $this->getUrl();
933
    }
934
935
    private $_url;
936
937
    /**
938
     * Returns the currently requested relative URL.
939
     * This refers to the portion of the URL that is after the [[hostInfo]] part.
940
     * It includes the [[queryString]] part if any.
941
     * @return string the currently requested relative URL. Note that the URI returned may be URL-encoded depending on the client.
942
     * @throws InvalidConfigException if the URL cannot be determined due to unusual server configuration
943
     */
944 11
    public function getUrl()
945
    {
946 11
        if ($this->_url === null) {
947 3
            $this->_url = $this->resolveRequestUri();
948
        }
949
950 11
        return $this->_url;
951
    }
952
953
    /**
954
     * Sets the currently requested relative URL.
955
     * The URI must refer to the portion that is after [[hostInfo]].
956
     * Note that the URI should be URL-encoded.
957
     * @param string $value the request URI to be set
958
     */
959 24
    public function setUrl($value)
960
    {
961 24
        $this->_url = $value;
962 24
    }
963
964
    /**
965
     * Resolves the request URI portion for the currently requested URL.
966
     * This refers to the portion that is after the [[hostInfo]] part. It includes the [[queryString]] part if any.
967
     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
968
     * @return string|bool the request URI portion for the currently requested URL.
969
     * Note that the URI returned may be URL-encoded depending on the client.
970
     * @throws InvalidConfigException if the request URI cannot be determined due to unusual server configuration
971
     */
972 3
    protected function resolveRequestUri()
973
    {
974 3
        if ($this->headers->has('X-Rewrite-Url')) { // IIS
975
            $requestUri = $this->headers->get('X-Rewrite-Url');
976 3
        } elseif (isset($_SERVER['REQUEST_URI'])) {
977 3
            $requestUri = $_SERVER['REQUEST_URI'];
978 3
            if ($requestUri !== '' && $requestUri[0] !== '/') {
979 3
                $requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri);
980
            }
981
        } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0 CGI
982
            $requestUri = $_SERVER['ORIG_PATH_INFO'];
983
            if (!empty($_SERVER['QUERY_STRING'])) {
984
                $requestUri .= '?' . $_SERVER['QUERY_STRING'];
985
            }
986
        } else {
987
            throw new InvalidConfigException('Unable to determine the request URI.');
988
        }
989
990 3
        return $requestUri;
991
    }
992
993
    /**
994
     * Returns part of the request URL that is after the question mark.
995
     * @return string part of the request URL that is after the question mark
996
     */
997
    public function getQueryString()
998
    {
999
        return isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
1000
    }
1001
1002
    /**
1003
     * Return if the request is sent via secure channel (https).
1004
     * @return bool if the request is sent via secure channel (https)
1005
     */
1006 37
    public function getIsSecureConnection()
1007
    {
1008 37
        if (isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'], 'on') === 0 || $_SERVER['HTTPS'] == 1)) {
1009 2
            return true;
1010
        }
1011 35
        foreach ($this->secureProtocolHeaders as $header => $values) {
1012 35
            if (($headerValue = $this->headers->get($header, null)) !== null) {
1013 2
                foreach ($values as $value) {
1014 2
                    if (strcasecmp($headerValue, $value) === 0) {
1015 35
                        return true;
1016
                    }
1017
                }
1018
            }
1019
        }
1020
1021 33
        return false;
1022
    }
1023
1024
    /**
1025
     * Returns the server name.
1026
     * @return string server name, null if not available
1027
     */
1028 1
    public function getServerName()
1029
    {
1030 1
        return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null;
1031
    }
1032
1033
    /**
1034
     * Returns the server port number.
1035
     * @return int|null server port number, null if not available
1036
     */
1037 1
    public function getServerPort()
1038
    {
1039 1
        return isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : null;
1040
    }
1041
1042
    /**
1043
     * Returns the URL referrer.
1044
     * @return string|null URL referrer, null if not available
1045
     */
1046
    public function getReferrer()
1047
    {
1048
        return $this->headers->get('Referer');
1049
    }
1050
1051
    /**
1052
     * Returns the URL origin of a CORS request.
1053
     *
1054
     * The return value is taken from the `Origin` [[getHeaders()|header]] sent by the browser.
1055
     *
1056
     * Note that the origin request header indicates where a fetch originates from.
1057
     * It doesn't include any path information, but only the server name.
1058
     * It is sent with a CORS requests, as well as with POST requests.
1059
     * It is similar to the referer header, but, unlike this header, it doesn't disclose the whole path.
1060
     * Please refer to <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin> for more information.
1061
     *
1062
     * @return string|null URL origin of a CORS request, `null` if not available.
1063
     * @see getHeaders()
1064
     * @since 2.0.13
1065
     */
1066 1
    public function getOrigin()
1067
    {
1068 1
        return $this->getHeaders()->get('origin');
1069
    }
1070
1071
    /**
1072
     * Returns the user agent.
1073
     * @return string|null user agent, null if not available
1074
     */
1075
    public function getUserAgent()
1076
    {
1077
        return $this->headers->get('User-Agent');
1078
    }
1079
1080
    /**
1081
     * Returns the user IP address.
1082
     * The IP is determined using headers and / or `$_SERVER` variables.
1083
     * @return string|null user IP address, null if not available
1084
     */
1085 44
    public function getUserIP()
1086
    {
1087 44
        foreach ($this->ipHeaders as $ipHeader) {
1088 44
            if ($this->headers->has($ipHeader)) {
1089 44
                return trim(explode(',', $this->headers->get($ipHeader))[0]);
1090
            }
1091
        }
1092
1093 43
        return $this->getRemoteIP();
1094
    }
1095
1096
    /**
1097
     * Returns the user host name.
1098
     * The HOST is determined using headers and / or `$_SERVER` variables.
1099
     * @return string|null user host name, null if not available
1100
     */
1101
    public function getUserHost()
1102
    {
1103
        foreach ($this->ipHeaders as $ipHeader) {
1104
            if ($this->headers->has($ipHeader)) {
1105
                return gethostbyaddr(trim(explode(',', $this->headers->get($ipHeader))[0]));
1106
            }
1107
        }
1108
1109
        return $this->getRemoteHost();
1110
    }
1111
1112
    /**
1113
     * Returns the IP on the other end of this connection.
1114
     * This is always the next hop, any headers are ignored.
1115
     * @return string|null remote IP address, `null` if not available.
1116
     * @since 2.0.13
1117
     */
1118 59
    public function getRemoteIP()
1119
    {
1120 59
        return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
1121
    }
1122
1123
    /**
1124
     * Returns the host name of the other end of this connection.
1125
     * This is always the next hop, any headers are ignored.
1126
     * @return string|null remote host name, `null` if not available
1127
     * @see getUserHost()
1128
     * @see getRemoteIP()
1129
     * @since 2.0.13
1130
     */
1131
    public function getRemoteHost()
1132
    {
1133
        return isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : null;
1134
    }
1135
1136
    /**
1137
     * @return string|null the username sent via HTTP authentication, `null` if the username is not given
1138
     * @see getAuthCredentials() to get both username and password in one call
1139
     */
1140 9
    public function getAuthUser()
1141
    {
1142 9
        return $this->getAuthCredentials()[0];
1143
    }
1144
1145
    /**
1146
     * @return string|null the password sent via HTTP authentication, `null` if the password is not given
1147
     * @see getAuthCredentials() to get both username and password in one call
1148
     */
1149 9
    public function getAuthPassword()
1150
    {
1151 9
        return $this->getAuthCredentials()[1];
1152
    }
1153
1154
    /**
1155
     * @return array that contains exactly two elements:
1156
     * - 0: the username sent via HTTP authentication, `null` if the username is not given
1157
     * - 1: the password sent via HTTP authentication, `null` if the password is not given
1158
     * @see getAuthUser() to get only username
1159
     * @see getAuthPassword() to get only password
1160
     * @since 2.0.13
1161
     */
1162 29
    public function getAuthCredentials()
1163
    {
1164 29
        $username = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
1165 29
        $password = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
1166 29
        if ($username !== null || $password !== null) {
1167 11
            return [$username, $password];
1168
        }
1169
1170
        /*
1171
         * Apache with php-cgi does not pass HTTP Basic authentication to PHP by default.
1172
         * To make it work, add the following line to to your .htaccess file:
1173
         *
1174
         * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
1175
         */
1176 18
        $auth_token = $this->getHeaders()->get('HTTP_AUTHORIZATION') ?: $this->getHeaders()->get('REDIRECT_HTTP_AUTHORIZATION');
1177 18
        if ($auth_token != null && strpos(strtolower($auth_token), 'basic') === 0) {
1178
            $parts = array_map(function ($value) {
1179 18
                return strlen($value) === 0 ? null : $value;
1180 18
            }, explode(':', base64_decode(mb_substr($auth_token, 6)), 2));
1181
1182 18
            if (count($parts) < 2) {
1183 2
                return [$parts[0], null];
1184
            }
1185
1186 16
            return $parts;
1187
        }
1188
1189
        return [null, null];
1190
    }
1191
1192
    private $_port;
1193
1194
    /**
1195
     * Returns the port to use for insecure requests.
1196
     * Defaults to 80, or the port specified by the server if the current
1197
     * request is insecure.
1198
     * @return int port number for insecure requests.
1199
     * @see setPort()
1200
     */
1201
    public function getPort()
1202
    {
1203
        if ($this->_port === null) {
1204
            $this->_port = !$this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : 80;
1205
        }
1206
1207
        return $this->_port;
1208
    }
1209
1210
    /**
1211
     * Sets the port to use for insecure requests.
1212
     * This setter is provided in case a custom port is necessary for certain
1213
     * server configurations.
1214
     * @param int $value port number.
1215
     */
1216
    public function setPort($value)
1217
    {
1218
        if ($value != $this->_port) {
1219
            $this->_port = (int) $value;
1220
            $this->_hostInfo = null;
1221
        }
1222
    }
1223
1224
    private $_securePort;
1225
1226
    /**
1227
     * Returns the port to use for secure requests.
1228
     * Defaults to 443, or the port specified by the server if the current
1229
     * request is secure.
1230
     * @return int port number for secure requests.
1231
     * @see setSecurePort()
1232
     */
1233
    public function getSecurePort()
1234
    {
1235
        if ($this->_securePort === null) {
1236
            $this->_securePort = $this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : 443;
1237
        }
1238
1239
        return $this->_securePort;
1240
    }
1241
1242
    /**
1243
     * Sets the port to use for secure requests.
1244
     * This setter is provided in case a custom port is necessary for certain
1245
     * server configurations.
1246
     * @param int $value port number.
1247
     */
1248
    public function setSecurePort($value)
1249
    {
1250
        if ($value != $this->_securePort) {
1251
            $this->_securePort = (int) $value;
1252
            $this->_hostInfo = null;
1253
        }
1254
    }
1255
1256
    private $_contentTypes;
1257
1258
    /**
1259
     * Returns the content types acceptable by the end user.
1260
     *
1261
     * This is determined by the `Accept` HTTP header. For example,
1262
     *
1263
     * ```php
1264
     * $_SERVER['HTTP_ACCEPT'] = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1265
     * $types = $request->getAcceptableContentTypes();
1266
     * print_r($types);
1267
     * // displays:
1268
     * // [
1269
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1270
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1271
     * //           'text/plain' => ['q' => 0.5],
1272
     * // ]
1273
     * ```
1274
     *
1275
     * @return array the content types ordered by the quality score. Types with the highest scores
1276
     * will be returned first. The array keys are the content types, while the array values
1277
     * are the corresponding quality score and other parameters as given in the header.
1278
     */
1279 2
    public function getAcceptableContentTypes()
1280
    {
1281 2
        if ($this->_contentTypes === null) {
1282 2
            if ($this->headers->get('Accept') !== null) {
1283 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...
1284
            } else {
1285 1
                $this->_contentTypes = [];
1286
            }
1287
        }
1288
1289 2
        return $this->_contentTypes;
1290
    }
1291
1292
    /**
1293
     * Sets the acceptable content types.
1294
     * Please refer to [[getAcceptableContentTypes()]] on the format of the parameter.
1295
     * @param array $value the content types that are acceptable by the end user. They should
1296
     * be ordered by the preference level.
1297
     * @see getAcceptableContentTypes()
1298
     * @see parseAcceptHeader()
1299
     */
1300
    public function setAcceptableContentTypes($value)
1301
    {
1302
        $this->_contentTypes = $value;
1303
    }
1304
1305
    /**
1306
     * Returns request content-type
1307
     * The Content-Type header field indicates the MIME type of the data
1308
     * contained in [[getRawBody()]] or, in the case of the HEAD method, the
1309
     * media type that would have been sent had the request been a GET.
1310
     * For the MIME-types the user expects in response, see [[acceptableContentTypes]].
1311
     * @return string request content-type. Null is returned if this information is not available.
1312
     * @link https://tools.ietf.org/html/rfc2616#section-14.17
1313
     * HTTP 1.1 header field definitions
1314
     */
1315
    public function getContentType()
1316
    {
1317
        if (isset($_SERVER['CONTENT_TYPE'])) {
1318
            return $_SERVER['CONTENT_TYPE'];
1319
        }
1320
1321
        //fix bug https://bugs.php.net/bug.php?id=66606
1322
        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 1322 which is incompatible with the return type documented by yii\web\Request::getContentType of type string.
Loading history...
1323
    }
1324
1325
    private $_languages;
1326
1327
    /**
1328
     * Returns the languages acceptable by the end user.
1329
     * This is determined by the `Accept-Language` HTTP header.
1330
     * @return array the languages ordered by the preference level. The first element
1331
     * represents the most preferred language.
1332
     */
1333 1
    public function getAcceptableLanguages()
1334
    {
1335 1
        if ($this->_languages === null) {
1336
            if ($this->headers->has('Accept-Language')) {
1337
                $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...
1338
            } else {
1339
                $this->_languages = [];
1340
            }
1341
        }
1342
1343 1
        return $this->_languages;
1344
    }
1345
1346
    /**
1347
     * @param array $value the languages that are acceptable by the end user. They should
1348
     * be ordered by the preference level.
1349
     */
1350 1
    public function setAcceptableLanguages($value)
1351
    {
1352 1
        $this->_languages = $value;
1353 1
    }
1354
1355
    /**
1356
     * Parses the given `Accept` (or `Accept-Language`) header.
1357
     *
1358
     * This method will return the acceptable values with their quality scores and the corresponding parameters
1359
     * as specified in the given `Accept` header. The array keys of the return value are the acceptable values,
1360
     * while the array values consisting of the corresponding quality scores and parameters. The acceptable
1361
     * values with the highest quality scores will be returned first. For example,
1362
     *
1363
     * ```php
1364
     * $header = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1365
     * $accepts = $request->parseAcceptHeader($header);
1366
     * print_r($accepts);
1367
     * // displays:
1368
     * // [
1369
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1370
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1371
     * //           'text/plain' => ['q' => 0.5],
1372
     * // ]
1373
     * ```
1374
     *
1375
     * @param string $header the header to be parsed
1376
     * @return array the acceptable values ordered by their quality score. The values with the highest scores
1377
     * will be returned first.
1378
     */
1379 3
    public function parseAcceptHeader($header)
1380
    {
1381 3
        $accepts = [];
1382 3
        foreach (explode(',', $header) as $i => $part) {
1383 3
            $params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY);
1384 3
            if (empty($params)) {
1385 1
                continue;
1386
            }
1387
            $values = [
1388 3
                'q' => [$i, array_shift($params), 1],
1389
            ];
1390 3
            foreach ($params as $param) {
1391 2
                if (strpos($param, '=') !== false) {
1392 2
                    list($key, $value) = explode('=', $param, 2);
1393 2
                    if ($key === 'q') {
1394 2
                        $values['q'][2] = (float) $value;
1395
                    } else {
1396 2
                        $values[$key] = $value;
1397
                    }
1398
                } else {
1399 2
                    $values[] = $param;
1400
                }
1401
            }
1402 3
            $accepts[] = $values;
1403
        }
1404
1405 3
        usort($accepts, function ($a, $b) {
1406 3
            $a = $a['q']; // index, name, q
1407 3
            $b = $b['q'];
1408 3
            if ($a[2] > $b[2]) {
1409 2
                return -1;
1410
            }
1411
1412 2
            if ($a[2] < $b[2]) {
1413 1
                return 1;
1414
            }
1415
1416 2
            if ($a[1] === $b[1]) {
1417
                return $a[0] > $b[0] ? 1 : -1;
1418
            }
1419
1420 2
            if ($a[1] === '*/*') {
1421
                return 1;
1422
            }
1423
1424 2
            if ($b[1] === '*/*') {
1425
                return -1;
1426
            }
1427
1428 2
            $wa = $a[1][strlen($a[1]) - 1] === '*';
1429 2
            $wb = $b[1][strlen($b[1]) - 1] === '*';
1430 2
            if ($wa xor $wb) {
1431
                return $wa ? 1 : -1;
1432
            }
1433
1434 2
            return $a[0] > $b[0] ? 1 : -1;
1435 3
        });
1436
1437 3
        $result = [];
1438 3
        foreach ($accepts as $accept) {
1439 3
            $name = $accept['q'][1];
1440 3
            $accept['q'] = $accept['q'][2];
1441 3
            $result[$name] = $accept;
1442
        }
1443
1444 3
        return $result;
1445
    }
1446
1447
    /**
1448
     * Returns the user-preferred language that should be used by this application.
1449
     * The language resolution is based on the user preferred languages and the languages
1450
     * supported by the application. The method will try to find the best match.
1451
     * @param array $languages a list of the languages supported by the application. If this is empty, the current
1452
     * application language will be returned without further processing.
1453
     * @return string the language that the application should use.
1454
     */
1455 1
    public function getPreferredLanguage(array $languages = [])
1456
    {
1457 1
        if (empty($languages)) {
1458 1
            return Yii::$app->language;
1459
        }
1460 1
        foreach ($this->getAcceptableLanguages() as $acceptableLanguage) {
1461 1
            $acceptableLanguage = str_replace('_', '-', strtolower($acceptableLanguage));
1462 1
            foreach ($languages as $language) {
1463 1
                $normalizedLanguage = str_replace('_', '-', strtolower($language));
1464
1465
                if (
1466 1
                    $normalizedLanguage === $acceptableLanguage // en-us==en-us
1467 1
                    || strpos($acceptableLanguage, $normalizedLanguage . '-') === 0 // en==en-us
1468 1
                    || strpos($normalizedLanguage, $acceptableLanguage . '-') === 0 // en-us==en
1469
                ) {
1470 1
                    return $language;
1471
                }
1472
            }
1473
        }
1474
1475 1
        return reset($languages);
1476
    }
1477
1478
    /**
1479
     * Gets the Etags.
1480
     *
1481
     * @return array The entity tags
1482
     */
1483
    public function getETags()
1484
    {
1485
        if ($this->headers->has('If-None-Match')) {
1486
            return preg_split('/[\s,]+/', str_replace('-gzip', '', $this->headers->get('If-None-Match')), -1, PREG_SPLIT_NO_EMPTY);
1487
        }
1488
1489
        return [];
1490
    }
1491
1492
    /**
1493
     * Returns the cookie collection.
1494
     *
1495
     * Through the returned cookie collection, you may access a cookie using the following syntax:
1496
     *
1497
     * ```php
1498
     * $cookie = $request->cookies['name']
1499
     * if ($cookie !== null) {
1500
     *     $value = $cookie->value;
1501
     * }
1502
     *
1503
     * // alternatively
1504
     * $value = $request->cookies->getValue('name');
1505
     * ```
1506
     *
1507
     * @return CookieCollection the cookie collection.
1508
     */
1509 33
    public function getCookies()
1510
    {
1511 33
        if ($this->_cookies === null) {
1512 33
            $this->_cookies = new CookieCollection($this->loadCookies(), [
1513 33
                'readOnly' => true,
1514
            ]);
1515
        }
1516
1517 33
        return $this->_cookies;
1518
    }
1519
1520
    /**
1521
     * Converts `$_COOKIE` into an array of [[Cookie]].
1522
     * @return array the cookies obtained from request
1523
     * @throws InvalidConfigException if [[cookieValidationKey]] is not set when [[enableCookieValidation]] is true
1524
     */
1525 33
    protected function loadCookies()
1526
    {
1527 33
        $cookies = [];
1528 33
        if ($this->enableCookieValidation) {
1529 33
            if ($this->cookieValidationKey == '') {
1530
                throw new InvalidConfigException(get_class($this) . '::cookieValidationKey must be configured with a secret key.');
1531
            }
1532 33
            foreach ($_COOKIE as $name => $value) {
1533
                if (!is_string($value)) {
1534
                    continue;
1535
                }
1536
                $data = Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey);
1537
                if ($data === false) {
1538
                    continue;
1539
                }
1540
                $data = @unserialize($data);
1541
                if (is_array($data) && isset($data[0], $data[1]) && $data[0] === $name) {
1542
                    $cookies[$name] = Yii::createObject([
1543 33
                        'class' => 'yii\web\Cookie',
1544
                        'name' => $name,
1545
                        'value' => $data[1],
1546
                        'expire' => null,
1547
                    ]);
1548
                }
1549
            }
1550
        } else {
1551
            foreach ($_COOKIE as $name => $value) {
1552
                $cookies[$name] = Yii::createObject([
1553
                    'class' => 'yii\web\Cookie',
1554
                    'name' => $name,
1555
                    'value' => $value,
1556
                    'expire' => null,
1557
                ]);
1558
            }
1559
        }
1560
1561 33
        return $cookies;
1562
    }
1563
1564
    private $_csrfToken;
1565
1566
    /**
1567
     * Returns the token used to perform CSRF validation.
1568
     *
1569
     * This token is generated in a way to prevent [BREACH attacks](http://breachattack.com/). It may be passed
1570
     * along via a hidden field of an HTML form or an HTTP header value to support CSRF validation.
1571
     * @param bool $regenerate whether to regenerate CSRF token. When this parameter is true, each time
1572
     * this method is called, a new CSRF token will be generated and persisted (in session or cookie).
1573
     * @return string the token used to perform CSRF validation.
1574
     */
1575 37
    public function getCsrfToken($regenerate = false)
1576
    {
1577 37
        if ($this->_csrfToken === null || $regenerate) {
1578 37
            if ($regenerate || ($token = $this->loadCsrfToken()) === null) {
1579 35
                $token = $this->generateCsrfToken();
1580
            }
1581 37
            $this->_csrfToken = Yii::$app->security->maskToken($token);
1582
        }
1583
1584 37
        return $this->_csrfToken;
1585
    }
1586
1587
    /**
1588
     * Loads the CSRF token from cookie or session.
1589
     * @return string the CSRF token loaded from cookie or session. Null is returned if the cookie or session
1590
     * does not have CSRF token.
1591
     */
1592 37
    protected function loadCsrfToken()
1593
    {
1594 37
        if ($this->enableCsrfCookie) {
1595 33
            return $this->getCookies()->getValue($this->csrfParam);
1596
        }
1597
1598 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...
1599
    }
1600
1601
    /**
1602
     * Generates an unmasked random token used to perform CSRF validation.
1603
     * @return string the random token for CSRF validation.
1604
     */
1605 35
    protected function generateCsrfToken()
1606
    {
1607 35
        $token = Yii::$app->getSecurity()->generateRandomString();
1608 35
        if ($this->enableCsrfCookie) {
1609 33
            $cookie = $this->createCsrfCookie($token);
1610 33
            Yii::$app->getResponse()->getCookies()->add($cookie);
1611
        } else {
1612 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...
1613
        }
1614
1615 35
        return $token;
1616
    }
1617
1618
    /**
1619
     * @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent.
1620
     */
1621 3
    public function getCsrfTokenFromHeader()
1622
    {
1623 3
        return $this->headers->get(static::CSRF_HEADER);
1624
    }
1625
1626
    /**
1627
     * Creates a cookie with a randomly generated CSRF token.
1628
     * Initial values specified in [[csrfCookie]] will be applied to the generated cookie.
1629
     * @param string $token the CSRF token
1630
     * @return Cookie the generated cookie
1631
     * @see enableCsrfValidation
1632
     */
1633 33
    protected function createCsrfCookie($token)
1634
    {
1635 33
        $options = $this->csrfCookie;
1636 33
        return Yii::createObject(array_merge($options, [
1637 33
            'class' => 'yii\web\Cookie',
1638 33
            'name' => $this->csrfParam,
1639 33
            'value' => $token,
1640
        ]));
1641
    }
1642
1643
    /**
1644
     * Performs the CSRF validation.
1645
     *
1646
     * This method will validate the user-provided CSRF token by comparing it with the one stored in cookie or session.
1647
     * This method is mainly called in [[Controller::beforeAction()]].
1648
     *
1649
     * Note that the method will NOT perform CSRF validation if [[enableCsrfValidation]] is false or the HTTP method
1650
     * is among GET, HEAD or OPTIONS.
1651
     *
1652
     * @param string $clientSuppliedToken the user-provided CSRF token to be validated. If null, the token will be retrieved from
1653
     * the [[csrfParam]] POST field or HTTP header.
1654
     * This parameter is available since version 2.0.4.
1655
     * @return bool whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true.
1656
     */
1657 5
    public function validateCsrfToken($clientSuppliedToken = null)
1658
    {
1659 5
        $method = $this->getMethod();
1660
        // only validate CSRF token on non-"safe" methods https://tools.ietf.org/html/rfc2616#section-9.1.1
1661 5
        if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
1662 5
            return true;
1663
        }
1664
1665 3
        $trueToken = $this->getCsrfToken();
1666
1667 3
        if ($clientSuppliedToken !== null) {
1668 1
            return $this->validateCsrfTokenInternal($clientSuppliedToken, $trueToken);
1669
        }
1670
1671 3
        return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
1672 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...
1673
    }
1674
1675
    /**
1676
     * Validates CSRF token.
1677
     *
1678
     * @param string $clientSuppliedToken The masked client-supplied token.
1679
     * @param string $trueToken The masked true token.
1680
     * @return bool
1681
     */
1682 3
    private function validateCsrfTokenInternal($clientSuppliedToken, $trueToken)
1683
    {
1684 3
        if (!is_string($clientSuppliedToken)) {
1685 3
            return false;
1686
        }
1687
1688 3
        $security = Yii::$app->security;
1689
1690 3
        return $security->unmaskToken($clientSuppliedToken) === $security->unmaskToken($trueToken);
1691
    }
1692
}
1693