Completed
Push — master ( b4f1a5...4c1600 )
by Carsten
80:08 queued 71:13
created

framework/web/Request.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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