Completed
Push — 2.1-master-merge ( 240673 )
by Alexander
13:45
created

Request::filterHeaders()   C

Complexity

Conditions 8
Paths 30

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 8

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 5.3846
c 0
b 0
f 0
ccs 16
cts 16
cp 1
cc 8
eloc 16
nc 30
nop 1
crap 8
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 Psr\Http\Message\RequestInterface;
11
use Psr\Http\Message\StreamInterface;
12
use Psr\Http\Message\UriInterface;
13
use Yii;
14
use yii\base\InvalidConfigException;
15
use yii\di\Instance;
16
use yii\http\Cookie;
17
use yii\http\CookieCollection;
18
use yii\http\FileStream;
19
use yii\http\MessageTrait;
20
use yii\http\Uri;
21
22
/**
23
 * The web Request class represents an HTTP request.
24
 *
25
 * It encapsulates the $_SERVER variable and resolves its inconsistency among different Web servers.
26
 * Also it provides an interface to retrieve request parameters from $_POST, $_GET, $_COOKIES and REST
27
 * parameters sent via other HTTP methods like PUT or DELETE.
28
 *
29
 * Request is configured as an application component in [[\yii\web\Application]] by default.
30
 * You can access that instance via `Yii::$app->request`.
31
 *
32
 * For more details and usage information on Request, see the [guide article on requests](guide:runtime-requests).
33
 *
34
 * @property string $absoluteUrl The currently requested absolute URL. This property is read-only.
35
 * @property array $acceptableContentTypes The content types ordered by the quality score. Types with the
36
 * highest scores will be returned first. The array keys are the content types, while the array values are the
37
 * corresponding quality score and other parameters as given in the header.
38
 * @property array $acceptableLanguages The languages ordered by the preference level. The first element
39
 * represents the most preferred language.
40
 * @property string|null $authPassword The password sent via HTTP authentication, null if the password is not
41
 * given. This property is read-only.
42
 * @property string|null $authUser The username sent via HTTP authentication, null if the username is not
43
 * given. This property is read-only.
44
 * @property string $baseUrl The relative URL for the application.
45
 * @property array $bodyParams The request parameters given in the request body.
46
 * @property string $contentType Request content-type. Null is returned if this information is not available.
47
 * This property is read-only.
48
 * @property CookieCollection $cookies The cookie collection. This property is read-only.
49
 * @property string $csrfToken The token used to perform CSRF validation. This property is read-only.
50
 * @property string $csrfTokenFromHeader The CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned
51
 * if no such header is sent. This property is read-only.
52
 * @property array $eTags The entity tags. This property is read-only.
53
 * @property string|null $hostInfo Schema and hostname part (with port number if needed) of the request URL
54
 * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set. See
55
 * [[getHostInfo()]] for security related notes on this property.
56
 * @property string|null $hostName Hostname part of the request URL (e.g. `www.yiiframework.com`). This
57
 * property is read-only.
58
 * @property bool $isAjax Whether this is an AJAX (XMLHttpRequest) request. This property is read-only.
59
 * @property bool $isDelete Whether this is a DELETE request. This property is read-only.
60
 * @property bool $isFlash Whether this is an Adobe Flash or Adobe Flex request. This property is read-only.
61
 * @property bool $isGet Whether this is a GET request. This property is read-only.
62
 * @property bool $isHead Whether this is a HEAD request. This property is read-only.
63
 * @property bool $isOptions Whether this is a OPTIONS request. This property is read-only.
64
 * @property bool $isPatch Whether this is a PATCH request. This property is read-only.
65
 * @property bool $isPjax Whether this is a PJAX request. This property is read-only.
66
 * @property bool $isPost Whether this is a POST request. This property is read-only.
67
 * @property bool $isPut Whether this is a PUT request. This property is read-only.
68
 * @property bool $isSecureConnection If the request is sent via secure channel (https). This property is
69
 * read-only.
70
 * @property string $method Request method, such as GET, POST, HEAD, PUT, PATCH, DELETE. The value returned is
71
 * turned into upper case.
72
 * @property UriInterface $uri the URI instance.
73
 * @property mixed $requestTarget the message's request target.
74
 * @property string $pathInfo Part of the request URL that is after the entry script and before the question
75
 * mark. Note, the returned path info is already URL-decoded.
76
 * @property int $port Port number for insecure requests.
77
 * @property array $queryParams The request GET parameter values.
78
 * @property string $queryString Part of the request URL that is after the question mark. This property is
79
 * read-only.
80
 * @property string $rawBody The request body.
81
 * @property string|null $referrer URL referrer, null if not available. This property is read-only.
82
 * @property string|null $origin URL origin, null if not available. This property is read-only.
83
 * @property string $scriptFile The entry script file path.
84
 * @property string $scriptUrl The relative URL of the entry script.
85
 * @property int $securePort Port number for secure requests.
86
 * @property string $serverName Server name, null if not available. This property is read-only.
87
 * @property int|null $serverPort Server port number, null if not available. This property is read-only.
88
 * @property string $url The currently requested relative URL. Note that the URI returned may be URL-encoded
89
 * depending on the client.
90
 * @property string|null $userAgent User agent, null if not available. This property is read-only.
91
 * @property string|null $userHost User host name, null if not available. This property is read-only.
92
 * @property string|null $userIP User IP address, null if not available. This property is read-only.
93
 *
94
 * @author Qiang Xue <[email protected]>
95
 * @since 2.0
96
 * @SuppressWarnings(PHPMD.SuperGlobals)
97
 */
98
class Request extends \yii\base\Request implements RequestInterface
99
{
100
    use MessageTrait;
101
102
    /**
103
     * The name of the HTTP header for sending CSRF token.
104
     */
105
    const CSRF_HEADER = 'X-CSRF-Token';
106
    /**
107
     * The length of the CSRF token mask.
108
     * @deprecated 2.0.12 The mask length is now equal to the token length.
109
     */
110
    const CSRF_MASK_LENGTH = 8;
111
112
    /**
113
     * @var bool whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to true.
114
     * When CSRF validation is enabled, forms submitted to an Yii Web application must be originated
115
     * from the same application. If not, a 400 HTTP exception will be raised.
116
     *
117
     * Note, this feature requires that the user client accepts cookie. Also, to use this feature,
118
     * forms submitted via POST method must contain a hidden input whose name is specified by [[csrfParam]].
119
     * You may use [[\yii\helpers\Html::beginForm()]] to generate his hidden input.
120
     *
121
     * In JavaScript, you may get the values of [[csrfParam]] and [[csrfToken]] via `yii.getCsrfParam()` and
122
     * `yii.getCsrfToken()`, respectively. The [[\yii\web\YiiAsset]] asset must be registered.
123
     * You also need to include CSRF meta tags in your pages by using [[\yii\helpers\Html::csrfMetaTags()]].
124
     *
125
     * @see Controller::enableCsrfValidation
126
     * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery
127
     */
128
    public $enableCsrfValidation = true;
129
    /**
130
     * @var string the name of the token used to prevent CSRF. Defaults to '_csrf'.
131
     * This property is used only when [[enableCsrfValidation]] is true.
132
     */
133
    public $csrfParam = '_csrf';
134
    /**
135
     * @var array the configuration for creating the CSRF [[Cookie|cookie]]. This property is used only when
136
     * both [[enableCsrfValidation]] and [[enableCsrfCookie]] are true.
137
     */
138
    public $csrfCookie = ['httpOnly' => true];
139
    /**
140
     * @var bool whether to use cookie to persist CSRF token. If false, CSRF token will be stored
141
     * in session under the name of [[csrfParam]]. Note that while storing CSRF tokens in session increases
142
     * security, it requires starting a session for every page, which will degrade your site performance.
143
     */
144
    public $enableCsrfCookie = true;
145
    /**
146
     * @var bool whether cookies should be validated to ensure they are not tampered. Defaults to true.
147
     */
148
    public $enableCookieValidation = true;
149
    /**
150
     * @var string a secret key used for cookie validation. This property must be set if [[enableCookieValidation]] is true.
151
     */
152
    public $cookieValidationKey;
153
    /**
154
     * @var string the name of the POST parameter that is used to indicate if a request is a PUT, PATCH or DELETE
155
     * request tunneled through POST. Defaults to '_method'.
156
     * @see getMethod()
157
     * @see getBodyParams()
158
     */
159
    public $methodParam = '_method';
160
    /**
161
     * @var array the parsers for converting the raw HTTP request body into [[bodyParams]].
162
     * The array keys are the request `Content-Types`, and the array values are the
163
     * corresponding configurations for [[Yii::createObject|creating the parser objects]].
164
     * A parser must implement the [[RequestParserInterface]].
165
     *
166
     * To enable parsing for JSON requests you can use the [[JsonParser]] class like in the following example:
167
     *
168
     * ```
169
     * [
170
     *     'application/json' => \yii\web\JsonParser::class,
171
     * ]
172
     * ```
173
     *
174
     * To register a parser for parsing all request types you can use `'*'` as the array key.
175
     * This one will be used as a fallback in case no other types match.
176
     *
177
     * @see getBodyParams()
178
     */
179
    public $parsers = [];
180
    /**
181
     * @var array the configuration for trusted security related headers.
182
     *
183
     * An array key is a regular expression for matching a client (ususally a proxy) by
184
     * host name or ip address.
185
     *
186
     * An array value is a list of headers to trust. These will be matched against
187
     * [[secureHeaders]] to determine which headers are allowed to be sent by a specified host.
188
     * The case of the header names must be the same as specified in [[secureHeaders]].
189
     *
190
     * To specify a host for which you trust all headers listed in [[secureHeaders]] you can just specify
191
     * the regular expression as the array value.
192
     *
193
     * For example, to trust all headers listed in [[secureHeaders]]
194
     * from domains ending in '.trusted.com' use the following:
195
     *
196
     * ```php
197
     * [
198
     *     '/\.trusted\.com$/',
199
     * ]
200
     * ```
201
     *
202
     * To trust just the `x-forwarded-for` header from domains ending in `.partial.com` use:
203
     *
204
     * ```
205
     * [
206
     *     '/\.partial\.com$/' => ['X-Forwarded-For']
207
     * ]
208
     * ```
209
     *
210
     * Default is to trust all headers except those listed in [[secureHeaders]] from all hosts.
211
     * Matches are tried in order and searching is stopped when a host or IP matches.
212
     * @see $secureHeaders
213
     * @since 2.0.13
214
     */
215
    public $trustedHosts = [];
216
    /**
217
     * @var array lists of headers that are, by default, subject to the trusted host configuration.
218
     * These headers will be filtered unless explicitly allowed in [[$trustedHosts]].
219
     * The match of header names is case-insensitive.
220
     * @see https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
221
     * @see $trustedHosts
222
     * @since 2.0.13
223
     */
224
    public $secureHeaders = [
225
        'X-Forwarded-For',
226
        'X-Forwarded-Host',
227
        'X-Forwarded-Proto',
228
        'Front-End-Https',
229
        'X-Rewrite-Url',
230
    ];
231
    /**
232
     * @var string[] List of headers where proxies store the real client IP.
233
     * It's not advisable to put insecure headers here.
234
     * The match of header names is case-insensitive.
235
     * @see $trustedHosts
236
     * @see $secureHeaders
237
     * @since 2.0.13
238
     */
239
    public $ipHeaders = [
240
        'X-Forwarded-For',
241
    ];
242
    /**
243
     * @var array list of headers to check for determining whether the connection is made via HTTPS.
244
     * The array keys are header names and the array value is a list of header values that indicate a secure connection.
245
     * The match of header names and values is case-insensitive.
246
     * It's not advisable to put insecure headers here.
247
     * @see $trustedHosts
248
     * @see $secureHeaders
249
     * @since 2.0.13
250
     */
251
    public $secureProtocolHeaders = [
252
        'X-Forwarded-Proto' => ['https'],
253
        'Front-End-Https' => ['on'],
254
    ];
255
    /**
256
     * @var CookieCollection Collection of request cookies.
257
     */
258
    private $_cookies;
259
    /**
260
     * @var string the HTTP method of the request.
261
     */
262
    private $_method;
263
    /**
264
     * @var UriInterface the URI instance associated with request.
265
     */
266
    private $_uri;
267
    /**
268
     * @var mixed the message's request target.
269
     */
270
    private $_requestTarget;
271
272
273
    /**
274
     * Resolves the current request into a route and the associated parameters.
275
     * @return array the first element is the route, and the second is the associated parameters.
276
     * @throws NotFoundHttpException if the request cannot be resolved.
277
     */
278 1
    public function resolve()
279
    {
280 1
        $result = Yii::$app->getUrlManager()->parseRequest($this);
281 1
        if ($result !== false) {
282 1
            [$route, $params] = $result;
0 ignored issues
show
Bug introduced by
The variable $route does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $params does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
283 1
            if ($this->_queryParams === null) {
284 1
                $_GET = $params + $_GET; // preserve numeric keys
285
            } else {
286 1
                $this->_queryParams = $params + $this->_queryParams;
287
            }
288
289 1
            return [$route, $this->getQueryParams()];
290
        }
291
292
        throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
293
    }
294
295
    /**
296
     * Filters headers according to the [[trustedHosts]].
297
     * @param array $inputHeaders
298
     * @since 2.0.13
299
     * @return array
300
     */
301 103
    protected function filterHeaders(array $inputHeaders)
302
    {
303
        // do not trust any of the [[secureHeaders]] by default
304 103
        $trustedHeaders = [];
305
        // check if the client is a trusted host
306 103
        if (!empty($this->trustedHosts)) {
307 19
            $host = $this->getRemoteHost();
308 19
            $ip = $this->getRemoteIP();
309 19
            foreach ($this->trustedHosts as $hostRegex => $headers) {
310 19
                if (!is_array($headers)) {
311 19
                    $hostRegex = $headers;
312 19
                    $headers = $this->secureHeaders;
313
                }
314 19
                if (preg_match($hostRegex, $host) || preg_match($hostRegex, $ip)) {
315 6
                    $trustedHeaders = $headers;
316 19
                    break;
317
                }
318
            }
319
        }
320
        // filter all secure headers unless they are trusted
321 103
        foreach ($this->secureHeaders as $secureHeader) {
322 103
            if (!in_array($secureHeader, $trustedHeaders)) {
323 103
                unset($inputHeaders[$secureHeader]);
324
            }
325
        }
326
327 103
        return $inputHeaders;
328
    }
329
330
    /**
331
     * Returns default message's headers, which should be present once [[headerCollection]] is instantiated.
332
     * @return string[][] an associative array of the message's headers.
333
     */
334 103
    protected function defaultHeaders()
335
    {
336 103
        if (function_exists('getallheaders')) {
337
            $headers = getallheaders();
338 103
        } elseif (function_exists('http_get_request_headers')) {
339
            $headers = http_get_request_headers();
340
        } else {
341 103
            $headers = [];
342 103
            foreach ($_SERVER as $name => $value) {
343 100
                if (strncmp($name, 'HTTP_', 5) === 0) {
344 30
                    $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
345 100
                    $headers[$name] = $value;
346
                }
347
            }
348 103
            $headers = $this->filterHeaders($headers);
349
        }
350
351 103
        return $headers;
352
    }
353
354
    /**
355
     * {@inheritdoc}
356
     * @since 2.1.0
357
     */
358
    public function getRequestTarget()
359
    {
360
        if ($this->_requestTarget === null) {
361
            $this->_requestTarget = $this->getUri()->__toString();
362
        }
363
        return $this->_requestTarget;
364
    }
365
366
    /**
367
     * Specifies the message's request target
368
     * @param mixed $requestTarget the message's request target.
369
     * @since 2.1.0
370
     */
371
    public function setRequestTarget($requestTarget)
372
    {
373
        $this->_requestTarget = $requestTarget;
374
    }
375
376
    /**
377
     * {@inheritdoc}
378
     * @since 2.1.0
379
     */
380
    public function withRequestTarget($requestTarget)
381
    {
382
        if ($this->getRequestTarget() === $requestTarget) {
383
            return $this;
384
        }
385
386
        $newInstance = clone $this;
387
        $newInstance->setRequestTarget($requestTarget);
388
        return $newInstance;
389
    }
390
391
    /**
392
     * {@inheritdoc}
393
     */
394 24
    public function getMethod()
395
    {
396 24
        if ($this->_method === null) {
397 20
            if (isset($_POST[$this->methodParam])) {
398 1
                $this->_method = $_POST[$this->methodParam];
399 19
            } elseif ($this->hasHeader('x-http-method-override')) {
400 1
                $this->_method = $this->getHeaderLine('x-http-method-override');
401 18
            } elseif (isset($_SERVER['REQUEST_METHOD'])) {
402 2
                $this->_method = $_SERVER['REQUEST_METHOD'];
403
            } else {
404 17
                $this->_method = 'GET';
405
            }
406
        }
407 24
        return $this->_method;
408
    }
409
410
    /**
411
     * Specifies request HTTP method.
412
     * @param string $method case-sensitive HTTP method.
413
     * @since 2.1.0
414
     */
415 6
    public function setMethod($method)
416
    {
417 6
        $this->_method =  $method;
418 6
    }
419
420
    /**
421
     * {@inheritdoc}
422
     * @since 2.1.0
423
     */
424
    public function withMethod($method)
425
    {
426
        if ($this->getMethod() === $method) {
427
            return $this;
428
        }
429
430
        $newInstance = clone $this;
431
        $newInstance->setMethod($method);
432
        return $newInstance;
433
    }
434
435
    /**
436
     * {@inheritdoc}
437
     * @since 2.1.0
438
     */
439
    public function getUri()
440
    {
441
        if (!$this->_uri instanceof UriInterface) {
442
            if ($this->_uri === null) {
443
                $uri = new Uri(['string' => $this->getAbsoluteUrl()]);
444
            } elseif ($this->_uri instanceof \Closure) {
445
                $uri = call_user_func($this->_uri, $this);
446
            } else {
447
                $uri = $this->_uri;
448
            }
449
450
            $this->_uri = Instance::ensure($uri, UriInterface::class);
451
        }
452
        return $this->_uri;
453
    }
454
455
    /**
456
     * Specifies the URI instance.
457
     * @param UriInterface|\Closure|array $uri URI instance or its DI compatible configuration.
458
     * @since 2.1.0
459
     */
460
    public function setUri($uri)
461
    {
462
        $this->_uri = $uri;
0 ignored issues
show
Documentation Bug introduced by
It seems like $uri can also be of type object<Closure> or array. However, the property $_uri is declared as type object<Psr\Http\Message\UriInterface>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
463
    }
464
465
    /**
466
     * {@inheritdoc}
467
     * @since 2.1.0
468
     */
469
    public function withUri(UriInterface $uri, $preserveHost = false)
470
    {
471
        if ($this->getUri() === $uri) {
472
            return $this;
473
        }
474
475
        $newInstance = clone $this;
476
477
        $newInstance->setUri($uri);
478
        if (!$preserveHost) {
479
            return $newInstance->withHeader('host', $uri->getHost());
480
        }
481
        return $newInstance;
482
    }
483
484
    /**
485
     * Returns whether this is a GET request.
486
     * @return bool whether this is a GET request.
487
     */
488 2
    public function getIsGet()
489
    {
490 2
        return $this->getMethod() === 'GET';
491
    }
492
493
    /**
494
     * Returns whether this is an OPTIONS request.
495
     * @return bool whether this is a OPTIONS request.
496
     */
497
    public function getIsOptions()
498
    {
499
        return $this->getMethod() === 'OPTIONS';
500
    }
501
502
    /**
503
     * Returns whether this is a HEAD request.
504
     * @return bool whether this is a HEAD request.
505
     */
506 9
    public function getIsHead()
507
    {
508 9
        return $this->getMethod() === 'HEAD';
509
    }
510
511
    /**
512
     * Returns whether this is a POST request.
513
     * @return bool whether this is a POST request.
514
     */
515
    public function getIsPost()
516
    {
517
        return $this->getMethod() === 'POST';
518
    }
519
520
    /**
521
     * Returns whether this is a DELETE request.
522
     * @return bool whether this is a DELETE request.
523
     */
524
    public function getIsDelete()
525
    {
526
        return $this->getMethod() === 'DELETE';
527
    }
528
529
    /**
530
     * Returns whether this is a PUT request.
531
     * @return bool whether this is a PUT request.
532
     */
533
    public function getIsPut()
534
    {
535
        return $this->getMethod() === 'PUT';
536
    }
537
538
    /**
539
     * Returns whether this is a PATCH request.
540
     * @return bool whether this is a PATCH request.
541
     */
542
    public function getIsPatch()
543
    {
544
        return $this->getMethod() === 'PATCH';
545
    }
546
547
    /**
548
     * Returns whether this is an AJAX (XMLHttpRequest) request.
549
     *
550
     * Note that jQuery doesn't set the header in case of cross domain
551
     * requests: https://stackoverflow.com/questions/8163703/cross-domain-ajax-doesnt-send-x-requested-with-header
552
     *
553
     * @return bool whether this is an AJAX (XMLHttpRequest) request.
554
     */
555 14
    public function getIsAjax()
556
    {
557 14
        return $this->getHeaderLine('x-requested-with') === 'XMLHttpRequest';
558
    }
559
560
    /**
561
     * Returns whether this is a PJAX request.
562
     * @return bool whether this is a PJAX request
563
     */
564 3
    public function getIsPjax()
565
    {
566 3
        return $this->getIsAjax() && $this->hasHeader('x-pjax');
567
    }
568
569
    /**
570
     * Returns whether this is an Adobe Flash or Flex request.
571
     * @return bool whether this is an Adobe Flash or Adobe Flex request.
572
     */
573
    public function getIsFlash()
574
    {
575
        $userAgent = $this->getUserAgent();
576
        if ($userAgent === null) {
577
            return false;
578
        }
579
        return (stripos($userAgent, 'Shockwave') !== false || stripos($userAgent, 'Flash') !== false);
580
    }
581
582
    /**
583
     * Returns default message body to be used in case it is not explicitly set.
584
     * @return StreamInterface default body instance.
585
     */
586 1
    protected function defaultBody()
587
    {
588 1
        return new FileStream([
589 1
            'filename' => 'php://input',
590
            'mode' => 'r',
591
        ]);
592
    }
593
594
    private $_rawBody;
595
596
    /**
597
     * Returns the raw HTTP request body.
598
     * @return string the request body
599
     */
600 1
    public function getRawBody()
601
    {
602 1
        if ($this->_rawBody === null) {
603 1
            $this->_rawBody = $this->getBody()->__toString();
604
        }
605
606 1
        return $this->_rawBody;
607
    }
608
609
    /**
610
     * Sets the raw HTTP request body, this method is mainly used by test scripts to simulate raw HTTP requests.
611
     * @param string $rawBody the request body
612
     */
613
    public function setRawBody($rawBody)
614
    {
615
        $this->_rawBody = $rawBody;
616
    }
617
618
    private $_bodyParams;
619
620
    /**
621
     * Returns the request parameters given in the request body.
622
     *
623
     * Request parameters are determined using the parsers configured in [[parsers]] property.
624
     * If no parsers are configured for the current [[contentType]] it uses the PHP function `mb_parse_str()`
625
     * to parse the [[rawBody|request body]].
626
     * @return array the request parameters given in the request body.
627
     * @throws \yii\base\InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]].
628
     * @see getMethod()
629
     * @see getBodyParam()
630
     * @see setBodyParams()
631
     */
632 4
    public function getBodyParams()
633
    {
634 4
        if ($this->_bodyParams === null) {
635 2
            if (isset($_POST[$this->methodParam])) {
636
                $this->_bodyParams = $_POST;
637
                unset($this->_bodyParams[$this->methodParam]);
638
                return $this->_bodyParams;
639
            }
640
641 2
            $rawContentType = $this->getContentType();
642 2
            if (($pos = strpos($rawContentType, ';')) !== false) {
643
                // e.g. text/html; charset=UTF-8
644
                $contentType = substr($rawContentType, 0, $pos);
645
            } else {
646 2
                $contentType = $rawContentType;
647
            }
648
649 2
            if (isset($this->parsers[$contentType])) {
650
                $parser = Yii::createObject($this->parsers[$contentType]);
651
                if (!($parser instanceof RequestParserInterface)) {
652
                    throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
653
                }
654
                $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
655 2
            } elseif (isset($this->parsers['*'])) {
656
                $parser = Yii::createObject($this->parsers['*']);
657
                if (!($parser instanceof RequestParserInterface)) {
658
                    throw new InvalidConfigException('The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface.');
659
                }
660
                $this->_bodyParams = $parser->parse($this->getRawBody(), $rawContentType);
661 2
            } elseif ($this->getMethod() === 'POST') {
662
                // PHP has already parsed the body so we have all params in $_POST
663 1
                $this->_bodyParams = $_POST;
664
            } else {
665 1
                $this->_bodyParams = [];
666 1
                mb_parse_str($this->getRawBody(), $this->_bodyParams);
667
            }
668
        }
669
670 4
        return $this->_bodyParams;
671
    }
672
673
    /**
674
     * Sets the request body parameters.
675
     * @param array $values the request body parameters (name-value pairs)
676
     * @see getBodyParam()
677
     * @see getBodyParams()
678
     */
679 3
    public function setBodyParams($values)
680
    {
681 3
        $this->_bodyParams = $values;
682 3
    }
683
684
    /**
685
     * Returns the named request body parameter value.
686
     * If the parameter does not exist, the second parameter passed to this method will be returned.
687
     * @param string $name the parameter name
688
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
689
     * @return mixed the parameter value
690
     * @see getBodyParams()
691
     * @see setBodyParams()
692
     */
693 4
    public function getBodyParam($name, $defaultValue = null)
694
    {
695 4
        $params = $this->getBodyParams();
696
697 4
        return isset($params[$name]) ? $params[$name] : $defaultValue;
698
    }
699
700
    /**
701
     * Returns POST parameter with a given name. If name isn't specified, returns an array of all POST parameters.
702
     *
703
     * @param string $name the parameter name
704
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
705
     * @return array|mixed
706
     */
707
    public function post($name = null, $defaultValue = null)
708
    {
709
        if ($name === null) {
710
            return $this->getBodyParams();
711
        }
712
713
        return $this->getBodyParam($name, $defaultValue);
714
    }
715
716
    private $_queryParams;
717
718
    /**
719
     * Returns the request parameters given in the [[queryString]].
720
     *
721
     * This method will return the contents of `$_GET` if params where not explicitly set.
722
     * @return array the request GET parameter values.
723
     * @see setQueryParams()
724
     */
725 29
    public function getQueryParams()
726
    {
727 29
        if ($this->_queryParams === null) {
728 23
            return $_GET;
729
        }
730
731 8
        return $this->_queryParams;
732
    }
733
734
    /**
735
     * Sets the request [[queryString]] parameters.
736
     * @param array $values the request query parameters (name-value pairs)
737
     * @see getQueryParam()
738
     * @see getQueryParams()
739
     */
740 8
    public function setQueryParams($values)
741
    {
742 8
        $this->_queryParams = $values;
743 8
    }
744
745
    /**
746
     * Returns GET parameter with a given name. If name isn't specified, returns an array of all GET parameters.
747
     *
748
     * @param string $name the parameter name
749
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
750
     * @return array|mixed
751
     */
752 15
    public function get($name = null, $defaultValue = null)
753
    {
754 15
        if ($name === null) {
755
            return $this->getQueryParams();
756
        }
757
758 15
        return $this->getQueryParam($name, $defaultValue);
759
    }
760
761
    /**
762
     * Returns the named GET parameter value.
763
     * If the GET parameter does not exist, the second parameter passed to this method will be returned.
764
     * @param string $name the GET parameter name.
765
     * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
766
     * @return mixed the GET parameter value
767
     * @see getBodyParam()
768
     */
769 20
    public function getQueryParam($name, $defaultValue = null)
770
    {
771 20
        $params = $this->getQueryParams();
772
773 20
        return isset($params[$name]) ? $params[$name] : $defaultValue;
774
    }
775
776
    private $_hostInfo;
777
    private $_hostName;
778
779
    /**
780
     * Returns the schema and host part of the current request URL.
781
     *
782
     * The returned URL does not have an ending slash.
783
     *
784
     * By default this value is based on the user request information. This method will
785
     * return the value of `$_SERVER['HTTP_HOST']` if it is available or `$_SERVER['SERVER_NAME']` if not.
786
     * You may want to check out the [PHP documentation](http://php.net/manual/en/reserved.variables.server.php)
787
     * for more information on these variables.
788
     *
789
     * You may explicitly specify it by setting the [[setHostInfo()|hostInfo]] property.
790
     *
791
     * > Warning: Dependent on the server configuration this information may not be
792
     * > reliable and [may be faked by the user sending the HTTP request](https://www.acunetix.com/vulnerabilities/web/host-header-attack).
793
     * > If the webserver is configured to serve the same site independent of the value of
794
     * > the `Host` header, this value is not reliable. In such situations you should either
795
     * > fix your webserver configuration or explicitly set the value by setting the [[setHostInfo()|hostInfo]] property.
796
     * > If you don't have access to the server configuration, you can setup [[\yii\filters\HostControl]] filter at
797
     * > application level in order to protect against such kind of attack.
798
     *
799
     * @property string|null schema and hostname part (with port number if needed) of the request URL
800
     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
801
     * See [[getHostInfo()]] for security related notes on this property.
802
     * @return string|null schema and hostname part (with port number if needed) of the request URL
803
     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
804
     * @see setHostInfo()
805
     */
806 24
    public function getHostInfo()
807
    {
808 24
        if ($this->_hostInfo === null) {
809 20
            $secure = $this->getIsSecureConnection();
810 20
            $http = $secure ? 'https' : 'http';
811 20
            if ($this->hasHeader('Host')) {
812 7
                $this->_hostInfo = $http . '://' . $this->getHeaderLine('Host');
813 13
            } elseif (isset($_SERVER['SERVER_NAME'])) {
814
                $this->_hostInfo = $http . '://' . $_SERVER['SERVER_NAME'];
815
                $port = $secure ? $this->getSecurePort() : $this->getPort();
816
                if (($port !== 80 && !$secure) || ($port !== 443 && $secure)) {
817
                    $this->_hostInfo .= ':' . $port;
818
                }
819
            }
820
        }
821
822 24
        return $this->_hostInfo;
823
    }
824
825
    /**
826
     * Sets the schema and host part of the application URL.
827
     * This setter is provided in case the schema and hostname cannot be determined
828
     * on certain Web servers.
829
     * @param string|null $value the schema and host part of the application URL. The trailing slashes will be removed.
830
     * @see getHostInfo() for security related notes on this property.
831
     */
832 57
    public function setHostInfo($value)
833
    {
834 57
        $this->_hostName = null;
835 57
        $this->_hostInfo = $value === null ? null : rtrim($value, '/');
836 57
    }
837
838
    /**
839
     * Returns the host part of the current request URL.
840
     * Value is calculated from current [[getHostInfo()|hostInfo]] property.
841
     *
842
     * > Warning: The content of this value may not be reliable, dependent on the server
843
     * > configuration. Please refer to [[getHostInfo()]] for more information.
844
     *
845
     * @return string|null hostname part of the request URL (e.g. `www.yiiframework.com`)
846
     * @see getHostInfo()
847
     * @since 2.0.10
848
     */
849 11
    public function getHostName()
850
    {
851 11
        if ($this->_hostName === null) {
852 11
            $this->_hostName = parse_url($this->getHostInfo(), PHP_URL_HOST);
853
        }
854
855 11
        return $this->_hostName;
856
    }
857
858
    private $_baseUrl;
859
860
    /**
861
     * Returns the relative URL for the application.
862
     * This is similar to [[scriptUrl]] except that it does not include the script file name,
863
     * and the ending slashes are removed.
864
     * @return string the relative URL for the application
865
     * @see setScriptUrl()
866
     */
867 257
    public function getBaseUrl()
868
    {
869 257
        if ($this->_baseUrl === null) {
870 256
            $this->_baseUrl = rtrim(dirname($this->getScriptUrl()), '\\/');
871
        }
872
873 257
        return $this->_baseUrl;
874
    }
875
876
    /**
877
     * Sets the relative URL for the application.
878
     * By default the URL is determined based on the entry script URL.
879
     * This setter is provided in case you want to change this behavior.
880
     * @param string $value the relative URL for the application
881
     */
882 1
    public function setBaseUrl($value)
883
    {
884 1
        $this->_baseUrl = $value;
885 1
    }
886
887
    private $_scriptUrl;
888
889
    /**
890
     * Returns the relative URL of the entry script.
891
     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
892
     * @return string the relative URL of the entry script.
893
     * @throws InvalidConfigException if unable to determine the entry script URL
894
     */
895 258
    public function getScriptUrl()
896
    {
897 258
        if ($this->_scriptUrl === null) {
898 2
            $scriptFile = $this->getScriptFile();
899 1
            $scriptName = basename($scriptFile);
900 1
            if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $scriptName) {
901 1
                $this->_scriptUrl = $_SERVER['SCRIPT_NAME'];
902
            } elseif (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $scriptName) {
903
                $this->_scriptUrl = $_SERVER['PHP_SELF'];
904
            } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $scriptName) {
905
                $this->_scriptUrl = $_SERVER['ORIG_SCRIPT_NAME'];
906
            } elseif (isset($_SERVER['PHP_SELF']) && ($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) {
907
                $this->_scriptUrl = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName;
908
            } elseif (!empty($_SERVER['DOCUMENT_ROOT']) && strpos($scriptFile, $_SERVER['DOCUMENT_ROOT']) === 0) {
909
                $this->_scriptUrl = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $scriptFile));
910
            } else {
911
                throw new InvalidConfigException('Unable to determine the entry script URL.');
912
            }
913
        }
914
915 257
        return $this->_scriptUrl;
916
    }
917
918
    /**
919
     * Sets the relative URL for the application entry script.
920
     * This setter is provided in case the entry script URL cannot be determined
921
     * on certain Web servers.
922
     * @param string $value the relative URL for the application entry script.
923
     */
924 268
    public function setScriptUrl($value)
925
    {
926 268
        $this->_scriptUrl = $value === null ? null : '/' . trim($value, '/');
927 268
    }
928
929
    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...
930
931
    /**
932
     * Returns the entry script file path.
933
     * The default implementation will simply return `$_SERVER['SCRIPT_FILENAME']`.
934
     * @return string the entry script file path
935
     * @throws InvalidConfigException
936
     */
937 259
    public function getScriptFile()
938
    {
939 259
        if (isset($this->_scriptFile)) {
940 237
            return $this->_scriptFile;
941
        }
942
943 23
        if (isset($_SERVER['SCRIPT_FILENAME'])) {
944 21
            return $_SERVER['SCRIPT_FILENAME'];
945
        }
946
947 2
        throw new InvalidConfigException('Unable to determine the entry script file path.');
948
    }
949
950
    /**
951
     * Sets the entry script file path.
952
     * The entry script file path normally can be obtained from `$_SERVER['SCRIPT_FILENAME']`.
953
     * If your server configuration does not return the correct value, you may configure
954
     * this property to make it right.
955
     * @param string $value the entry script file path.
956
     */
957 237
    public function setScriptFile($value)
958
    {
959 237
        $this->_scriptFile = $value;
960 237
    }
961
962
    private $_pathInfo;
963
964
    /**
965
     * Returns the path info of the currently requested URL.
966
     * A path info refers to the part that is after the entry script and before the question mark (query string).
967
     * The starting and ending slashes are both removed.
968
     * @return string part of the request URL that is after the entry script and before the question mark.
969
     * Note, the returned path info is already URL-decoded.
970
     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
971
     */
972 18
    public function getPathInfo()
973
    {
974 18
        if ($this->_pathInfo === null) {
975
            $this->_pathInfo = $this->resolvePathInfo();
976
        }
977
978 18
        return $this->_pathInfo;
979
    }
980
981
    /**
982
     * Sets the path info of the current request.
983
     * This method is mainly provided for testing purpose.
984
     * @param string $value the path info of the current request
985
     */
986 19
    public function setPathInfo($value)
987
    {
988 19
        $this->_pathInfo = $value === null ? null : ltrim($value, '/');
989 19
    }
990
991
    /**
992
     * Resolves the path info part of the currently requested URL.
993
     * A path info refers to the part that is after the entry script and before the question mark (query string).
994
     * The starting slashes are both removed (ending slashes will be kept).
995
     * @return string part of the request URL that is after the entry script and before the question mark.
996
     * Note, the returned path info is decoded.
997
     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
998
     */
999
    protected function resolvePathInfo()
1000
    {
1001
        $pathInfo = $this->getUrl();
1002
1003
        if (($pos = strpos($pathInfo, '?')) !== false) {
1004
            $pathInfo = substr($pathInfo, 0, $pos);
1005
        }
1006
1007
        $pathInfo = urldecode($pathInfo);
1008
1009
        // try to encode in UTF8 if not so
1010
        // http://w3.org/International/questions/qa-forms-utf-8.html
1011
        if (!preg_match('%^(?:
1012
            [\x09\x0A\x0D\x20-\x7E]              # ASCII
1013
            | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
1014
            | \xE0[\xA0-\xBF][\x80-\xBF]         # excluding overlongs
1015
            | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
1016
            | \xED[\x80-\x9F][\x80-\xBF]         # excluding surrogates
1017
            | \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
1018
            | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
1019
            | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
1020
            )*$%xs', $pathInfo)
1021
        ) {
1022
            $pathInfo = utf8_encode($pathInfo);
1023
        }
1024
1025
        $scriptUrl = $this->getScriptUrl();
1026
        $baseUrl = $this->getBaseUrl();
1027
        if (strpos($pathInfo, $scriptUrl) === 0) {
1028
            $pathInfo = substr($pathInfo, strlen($scriptUrl));
1029
        } elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) {
1030
            $pathInfo = substr($pathInfo, strlen($baseUrl));
1031
        } elseif (isset($_SERVER['PHP_SELF']) && strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) {
1032
            $pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl));
1033
        } else {
1034
            throw new InvalidConfigException('Unable to determine the path info of the current request.');
1035
        }
1036
1037
        if (substr($pathInfo, 0, 1) === '/') {
1038
            $pathInfo = substr($pathInfo, 1);
1039
        }
1040
1041
        return (string) $pathInfo;
1042
    }
1043
1044
    /**
1045
     * Returns the currently requested absolute URL.
1046
     * This is a shortcut to the concatenation of [[hostInfo]] and [[url]].
1047
     * @return string the currently requested absolute URL.
1048
     */
1049
    public function getAbsoluteUrl()
1050
    {
1051
        return $this->getHostInfo() . $this->getUrl();
1052
    }
1053
1054
    private $_url;
1055
1056
    /**
1057
     * Returns the currently requested relative URL.
1058
     * This refers to the portion of the URL that is after the [[hostInfo]] part.
1059
     * It includes the [[queryString]] part if any.
1060
     * @return string the currently requested relative URL. Note that the URI returned may be URL-encoded depending on the client.
1061
     * @throws InvalidConfigException if the URL cannot be determined due to unusual server configuration
1062
     */
1063 11
    public function getUrl()
1064
    {
1065 11
        if ($this->_url === null) {
1066 3
            $this->_url = $this->resolveRequestUri();
1067
        }
1068
1069 11
        return $this->_url;
1070
    }
1071
1072
    /**
1073
     * Sets the currently requested relative URL.
1074
     * The URI must refer to the portion that is after [[hostInfo]].
1075
     * Note that the URI should be URL-encoded.
1076
     * @param string $value the request URI to be set
1077
     */
1078 24
    public function setUrl($value)
1079
    {
1080 24
        $this->_url = $value;
1081 24
    }
1082
1083
    /**
1084
     * Resolves the request URI portion for the currently requested URL.
1085
     * This refers to the portion that is after the [[hostInfo]] part. It includes the [[queryString]] part if any.
1086
     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
1087
     * @return string|bool the request URI portion for the currently requested URL.
1088
     * Note that the URI returned may be URL-encoded depending on the client.
1089
     * @throws InvalidConfigException if the request URI cannot be determined due to unusual server configuration
1090
     */
1091 3
    protected function resolveRequestUri()
1092
    {
1093 3
        if ($this->hasHeader('x-rewrite-url')) { // IIS
1094
            $requestUri = $this->getHeaderLine('x-rewrite-url');
1095 3
        } elseif (isset($_SERVER['REQUEST_URI'])) {
1096 3
            $requestUri = $_SERVER['REQUEST_URI'];
1097 3
            if ($requestUri !== '' && $requestUri[0] !== '/') {
1098 3
                $requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri);
1099
            }
1100
        } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0 CGI
1101
            $requestUri = $_SERVER['ORIG_PATH_INFO'];
1102
            if (!empty($_SERVER['QUERY_STRING'])) {
1103
                $requestUri .= '?' . $_SERVER['QUERY_STRING'];
1104
            }
1105
        } else {
1106
            throw new InvalidConfigException('Unable to determine the request URI.');
1107
        }
1108
1109 3
        return $requestUri;
1110
    }
1111
1112
    /**
1113
     * Returns part of the request URL that is after the question mark.
1114
     * @return string part of the request URL that is after the question mark
1115
     */
1116
    public function getQueryString()
1117
    {
1118
        return isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '';
1119
    }
1120
1121
    /**
1122
     * Return if the request is sent via secure channel (https).
1123
     * @return bool if the request is sent via secure channel (https)
1124
     */
1125 37
    public function getIsSecureConnection()
1126
    {
1127 37
        return isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'], 'on') === 0 || $_SERVER['HTTPS'] == 1)
1128 37
            || strcasecmp($this->getHeaderLine('x-forwarded-proto'), 'https') === 0;
1129
    }
1130
1131
    /**
1132
     * Returns the server name.
1133
     * @return string server name, null if not available
1134
     */
1135 1
    public function getServerName()
1136
    {
1137 1
        return isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null;
1138
    }
1139
1140
    /**
1141
     * Returns the server port number.
1142
     * @return int|null server port number, null if not available
1143
     */
1144 1
    public function getServerPort()
1145
    {
1146 1
        return isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : null;
1147
    }
1148
1149
    /**
1150
     * Returns the URL referrer.
1151
     * @return string|null URL referrer, null if not available
1152
     */
1153
    public function getReferrer()
1154
    {
1155
        if (!$this->hasHeader('Referer')) {
1156
            return null;
1157
        }
1158
        return $this->getHeaderLine('Referer');
1159
    }
1160
1161
    /**
1162
     * Returns the URL origin of a CORS request.
1163
     *
1164
     * The return value is taken from the `Origin` [[getHeaders()|header]] sent by the browser.
1165
     *
1166
     * Note that the origin request header indicates where a fetch originates from.
1167
     * It doesn't include any path information, but only the server name.
1168
     * It is sent with a CORS requests, as well as with POST requests.
1169
     * It is similar to the referer header, but, unlike this header, it doesn't disclose the whole path.
1170
     * Please refer to <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin> for more information.
1171
     *
1172
     * @return string|null URL origin of a CORS request, `null` if not available.
1173
     * @see getHeaders()
1174
     * @since 2.0.13
1175
     */
1176 1
    public function getOrigin()
1177
    {
1178 1
        return $this->getHeaderLine('origin');
1179
    }
1180
1181
    /**
1182
     * Returns the user agent.
1183
     * @return string|null user agent, null if not available
1184
     */
1185
    public function getUserAgent()
1186
    {
1187
        if (!$this->hasHeader('User-Agent')) {
1188
            return null;
1189
        }
1190
        return $this->getHeaderLine('User-Agent');
1191
    }
1192
1193
    /**
1194
     * Returns the user IP address.
1195
     * The IP is determined using headers and / or `$_SERVER` variables.
1196
     * @return string|null user IP address, null if not available
1197
     */
1198 32
    public function getUserIP()
1199
    {
1200 32
        foreach ($this->ipHeaders as $ipHeader) {
1201 32
            if ($this->hasHeader($ipHeader)) {
1202 32
                return trim(explode(',', $this->getHeaderLine($ipHeader))[0]);
1203
            }
1204
        }
1205
1206 30
        return $this->getRemoteIP();
1207
    }
1208
1209
    /**
1210
     * Returns the user host name.
1211
     * The HOST is determined using headers and / or `$_SERVER` variables.
1212
     * @return string|null user host name, null if not available
1213
     */
1214
    public function getUserHost()
1215
    {
1216
        foreach ($this->ipHeaders as $ipHeader) {
1217
            if ($this->hasHeader($ipHeader)) {
1218
                return gethostbyaddr(trim(explode(',', $this->getHeaderLine($ipHeader))[0]));
1219
            }
1220
        }
1221
1222
        return $this->getRemoteHost();
1223
    }
1224
1225
    /**
1226
     * Returns the IP on the other end of this connection.
1227
     * This is always the next hop, any headers are ignored.
1228
     * @return string|null remote IP address, `null` if not available.
1229
     * @since 2.0.13
1230
     */
1231 47
    public function getRemoteIP()
1232
    {
1233 47
        return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
1234
    }
1235
1236
    /**
1237
     * Returns the host name of the other end of this connection.
1238
     * This is always the next hop, any headers are ignored.
1239
     * @return string|null remote host name, `null` if not available
1240
     * @see getUserHost()
1241
     * @see getRemoteIP()
1242
     * @since 2.0.13
1243
     */
1244 19
    public function getRemoteHost()
1245
    {
1246 19
        return isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : null;
1247
    }
1248
1249
    /**
1250
     * @return string|null the username sent via HTTP authentication, null if the username is not given
1251
     */
1252 10
    public function getAuthUser()
1253
    {
1254 10
        return isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null;
1255
    }
1256
1257
    /**
1258
     * @return string|null the password sent via HTTP authentication, null if the password is not given
1259
     */
1260 10
    public function getAuthPassword()
1261
    {
1262 10
        return isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null;
1263
    }
1264
1265
    private $_port;
1266
1267
    /**
1268
     * Returns the port to use for insecure requests.
1269
     * Defaults to 80, or the port specified by the server if the current
1270
     * request is insecure.
1271
     * @return int port number for insecure requests.
1272
     * @see setPort()
1273
     */
1274
    public function getPort()
1275
    {
1276
        if ($this->_port === null) {
1277
            $this->_port = !$this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : 80;
1278
        }
1279
1280
        return $this->_port;
1281
    }
1282
1283
    /**
1284
     * Sets the port to use for insecure requests.
1285
     * This setter is provided in case a custom port is necessary for certain
1286
     * server configurations.
1287
     * @param int $value port number.
1288
     */
1289
    public function setPort($value)
1290
    {
1291
        if ($value != $this->_port) {
1292
            $this->_port = (int) $value;
1293
            $this->_hostInfo = null;
1294
        }
1295
    }
1296
1297
    private $_securePort;
1298
1299
    /**
1300
     * Returns the port to use for secure requests.
1301
     * Defaults to 443, or the port specified by the server if the current
1302
     * request is secure.
1303
     * @return int port number for secure requests.
1304
     * @see setSecurePort()
1305
     */
1306
    public function getSecurePort()
1307
    {
1308
        if ($this->_securePort === null) {
1309
            $this->_securePort = $this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : 443;
1310
        }
1311
1312
        return $this->_securePort;
1313
    }
1314
1315
    /**
1316
     * Sets the port to use for secure requests.
1317
     * This setter is provided in case a custom port is necessary for certain
1318
     * server configurations.
1319
     * @param int $value port number.
1320
     */
1321
    public function setSecurePort($value)
1322
    {
1323
        if ($value != $this->_securePort) {
1324
            $this->_securePort = (int) $value;
1325
            $this->_hostInfo = null;
1326
        }
1327
    }
1328
1329
    private $_contentTypes;
1330
1331
    /**
1332
     * Returns the content types acceptable by the end user.
1333
     *
1334
     * This is determined by the `Accept` HTTP header. For example,
1335
     *
1336
     * ```php
1337
     * $_SERVER['HTTP_ACCEPT'] = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1338
     * $types = $request->getAcceptableContentTypes();
1339
     * print_r($types);
1340
     * // displays:
1341
     * // [
1342
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1343
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1344
     * //           'text/plain' => ['q' => 0.5],
1345
     * // ]
1346
     * ```
1347
     *
1348
     * @return array the content types ordered by the quality score. Types with the highest scores
1349
     * will be returned first. The array keys are the content types, while the array values
1350
     * are the corresponding quality score and other parameters as given in the header.
1351
     */
1352 3
    public function getAcceptableContentTypes()
1353
    {
1354 3
        if ($this->_contentTypes === null) {
1355 2
            if ($this->hasHeader('Accept')) {
1356 2
                $this->_contentTypes = $this->parseAcceptHeader($this->getHeaderLine('Accept'));
1357
            } else {
1358 1
                $this->_contentTypes = [];
1359
            }
1360
        }
1361
1362 3
        return $this->_contentTypes;
1363
    }
1364
1365
    /**
1366
     * Sets the acceptable content types.
1367
     * Please refer to [[getAcceptableContentTypes()]] on the format of the parameter.
1368
     * @param array $value the content types that are acceptable by the end user. They should
1369
     * be ordered by the preference level.
1370
     * @see getAcceptableContentTypes()
1371
     * @see parseAcceptHeader()
1372
     */
1373 1
    public function setAcceptableContentTypes($value)
1374
    {
1375 1
        $this->_contentTypes = $value;
1376 1
    }
1377
1378
    /**
1379
     * Returns request content-type
1380
     * The Content-Type header field indicates the MIME type of the data
1381
     * contained in [[getRawBody()]] or, in the case of the HEAD method, the
1382
     * media type that would have been sent had the request been a GET.
1383
     * For the MIME-types the user expects in response, see [[acceptableContentTypes]].
1384
     * @return string request content-type. Empty string is returned if this information is not available.
1385
     * @link https://tools.ietf.org/html/rfc2616#section-14.17
1386
     * HTTP 1.1 header field definitions
1387
     */
1388 2
    public function getContentType()
1389
    {
1390 2
        return $this->getHeaderLine('Content-Type');
1391
    }
1392
1393
    private $_languages;
1394
1395
    /**
1396
     * Returns the languages acceptable by the end user.
1397
     * This is determined by the `Accept-Language` HTTP header.
1398
     * @return array the languages ordered by the preference level. The first element
1399
     * represents the most preferred language.
1400
     */
1401 1
    public function getAcceptableLanguages()
1402
    {
1403 1
        if ($this->_languages === null) {
1404
            if ($this->hasHeader('Accept-Language')) {
1405
                $this->_languages = array_keys($this->parseAcceptHeader($this->getHeaderLine('Accept-Language')));
1406
            } else {
1407
                $this->_languages = [];
1408
            }
1409
        }
1410
1411 1
        return $this->_languages;
1412
    }
1413
1414
    /**
1415
     * @param array $value the languages that are acceptable by the end user. They should
1416
     * be ordered by the preference level.
1417
     */
1418 1
    public function setAcceptableLanguages($value)
1419
    {
1420 1
        $this->_languages = $value;
1421 1
    }
1422
1423
    /**
1424
     * Parses the given `Accept` (or `Accept-Language`) header.
1425
     *
1426
     * This method will return the acceptable values with their quality scores and the corresponding parameters
1427
     * as specified in the given `Accept` header. The array keys of the return value are the acceptable values,
1428
     * while the array values consisting of the corresponding quality scores and parameters. The acceptable
1429
     * values with the highest quality scores will be returned first. For example,
1430
     *
1431
     * ```php
1432
     * $header = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1433
     * $accepts = $request->parseAcceptHeader($header);
1434
     * print_r($accepts);
1435
     * // displays:
1436
     * // [
1437
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1438
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1439
     * //           'text/plain' => ['q' => 0.5],
1440
     * // ]
1441
     * ```
1442
     *
1443
     * @param string $header the header to be parsed
1444
     * @return array the acceptable values ordered by their quality score. The values with the highest scores
1445
     * will be returned first.
1446
     */
1447 3
    public function parseAcceptHeader($header)
1448
    {
1449 3
        $accepts = [];
1450 3
        foreach (explode(',', $header) as $i => $part) {
1451 3
            $params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY);
1452 3
            if (empty($params)) {
1453 1
                continue;
1454
            }
1455
            $values = [
1456 3
                'q' => [$i, array_shift($params), 1],
1457
            ];
1458 3
            foreach ($params as $param) {
1459 2
                if (strpos($param, '=') !== false) {
1460 2
                    [$key, $value] = explode('=', $param, 2);
0 ignored issues
show
Bug introduced by
The variable $key does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $value does not exist. Did you mean $values?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
1461 2
                    if ($key === 'q') {
1462 2
                        $values['q'][2] = (float) $value;
0 ignored issues
show
Bug introduced by
The variable $value does not exist. Did you mean $values?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
1463
                    } else {
1464 2
                        $values[$key] = $value;
0 ignored issues
show
Bug introduced by
The variable $value does not exist. Did you mean $values?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
1465
                    }
1466
                } else {
1467 2
                    $values[] = $param;
1468
                }
1469
            }
1470 3
            $accepts[] = $values;
1471
        }
1472
1473 3
        usort($accepts, function ($a, $b) {
1474 3
            $a = $a['q']; // index, name, q
1475 3
            $b = $b['q'];
1476 3
            if ($a[2] > $b[2]) {
1477 2
                return -1;
1478
            }
1479
1480 2
            if ($a[2] < $b[2]) {
1481 1
                return 1;
1482
            }
1483
1484 2
            if ($a[1] === $b[1]) {
1485
                return $a[0] > $b[0] ? 1 : -1;
1486
            }
1487
1488 2
            if ($a[1] === '*/*') {
1489
                return 1;
1490
            }
1491
1492 2
            if ($b[1] === '*/*') {
1493
                return -1;
1494
            }
1495
1496 2
            $wa = $a[1][strlen($a[1]) - 1] === '*';
1497 2
            $wb = $b[1][strlen($b[1]) - 1] === '*';
1498 2
            if ($wa xor $wb) {
1499
                return $wa ? 1 : -1;
1500
            }
1501
1502 2
            return $a[0] > $b[0] ? 1 : -1;
1503 3
        });
1504
1505 3
        $result = [];
1506 3
        foreach ($accepts as $accept) {
1507 3
            $name = $accept['q'][1];
1508 3
            $accept['q'] = $accept['q'][2];
1509 3
            $result[$name] = $accept;
1510
        }
1511
1512 3
        return $result;
1513
    }
1514
1515
    /**
1516
     * Returns the user-preferred language that should be used by this application.
1517
     * The language resolution is based on the user preferred languages and the languages
1518
     * supported by the application. The method will try to find the best match.
1519
     * @param array $languages a list of the languages supported by the application. If this is empty, the current
1520
     * application language will be returned without further processing.
1521
     * @return string the language that the application should use.
1522
     */
1523 1
    public function getPreferredLanguage(array $languages = [])
1524
    {
1525 1
        if (empty($languages)) {
1526 1
            return Yii::$app->language;
1527
        }
1528 1
        foreach ($this->getAcceptableLanguages() as $acceptableLanguage) {
1529 1
            $acceptableLanguage = str_replace('_', '-', strtolower($acceptableLanguage));
1530 1
            foreach ($languages as $language) {
1531 1
                $normalizedLanguage = str_replace('_', '-', strtolower($language));
1532
1533
                if (
1534 1
                    $normalizedLanguage === $acceptableLanguage // en-us==en-us
1535 1
                    || strpos($acceptableLanguage, $normalizedLanguage . '-') === 0 // en==en-us
1536 1
                    || strpos($normalizedLanguage, $acceptableLanguage . '-') === 0 // en-us==en
1537
                ) {
1538 1
                    return $language;
1539
                }
1540
            }
1541
        }
1542
1543 1
        return reset($languages);
1544
    }
1545
1546
    /**
1547
     * Gets the Etags.
1548
     *
1549
     * @return array The entity tags
1550
     */
1551
    public function getETags()
1552
    {
1553
        if ($this->hasHeader('if-none-match')) {
1554
            return preg_split('/[\s,]+/', str_replace('-gzip', '', $this->getHeaderLine('if-none-match')), -1, PREG_SPLIT_NO_EMPTY);
1555
        }
1556
1557
        return [];
1558
    }
1559
1560
    /**
1561
     * Returns the cookie collection.
1562
     *
1563
     * Through the returned cookie collection, you may access a cookie using the following syntax:
1564
     *
1565
     * ```php
1566
     * $cookie = $request->cookies['name']
1567
     * if ($cookie !== null) {
1568
     *     $value = $cookie->value;
1569
     * }
1570
     *
1571
     * // alternatively
1572
     * $value = $request->cookies->getValue('name');
1573
     * ```
1574
     *
1575
     * @return CookieCollection the cookie collection.
1576
     */
1577 33
    public function getCookies()
1578
    {
1579 33
        if ($this->_cookies === null) {
1580 33
            $this->_cookies = new CookieCollection($this->loadCookies(), [
1581 33
                'readOnly' => true,
1582
            ]);
1583
        }
1584
1585 33
        return $this->_cookies;
1586
    }
1587
1588
    /**
1589
     * Converts `$_COOKIE` into an array of [[Cookie]].
1590
     * @return array the cookies obtained from request
1591
     * @throws InvalidConfigException if [[cookieValidationKey]] is not set when [[enableCookieValidation]] is true
1592
     */
1593 33
    protected function loadCookies()
1594
    {
1595 33
        $cookies = [];
1596 33
        if ($this->enableCookieValidation) {
1597 32
            if ($this->cookieValidationKey == '') {
1598
                throw new InvalidConfigException(get_class($this) . '::cookieValidationKey must be configured with a secret key.');
1599
            }
1600 32
            foreach ($_COOKIE as $name => $value) {
1601
                if (!is_string($value)) {
1602
                    continue;
1603
                }
1604
                $data = Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey);
1605
                if ($data === false) {
1606
                    continue;
1607
                }
1608
                $data = @unserialize($data);
1609
                if (is_array($data) && isset($data[0], $data[1]) && $data[0] === $name) {
1610
                    $cookies[$name] = new Cookie([
1611 32
                        'name' => $name,
1612
                        'value' => $data[1],
1613
                        'expire' => null,
1614
                    ]);
1615
                }
1616
            }
1617
        } else {
1618 1
            foreach ($_COOKIE as $name => $value) {
1619
                $cookies[$name] = new Cookie([
1620
                    'name' => $name,
1621
                    'value' => $value,
1622
                    'expire' => null,
1623
                ]);
1624
            }
1625
        }
1626
1627 33
        return $cookies;
1628
    }
1629
1630
    private $_csrfToken;
1631
1632
    /**
1633
     * Returns the token used to perform CSRF validation.
1634
     *
1635
     * This token is generated in a way to prevent [BREACH attacks](http://breachattack.com/). It may be passed
1636
     * along via a hidden field of an HTML form or an HTTP header value to support CSRF validation.
1637
     * @param bool $regenerate whether to regenerate CSRF token. When this parameter is true, each time
1638
     * this method is called, a new CSRF token will be generated and persisted (in session or cookie).
1639
     * @return string the token used to perform CSRF validation.
1640
     */
1641 37
    public function getCsrfToken($regenerate = false)
1642
    {
1643 37
        if ($this->_csrfToken === null || $regenerate) {
1644 37
            if ($regenerate || ($token = $this->loadCsrfToken()) === null) {
1645 35
                $token = $this->generateCsrfToken();
1646
            }
1647 37
            $this->_csrfToken = Yii::$app->security->maskToken($token);
1648
        }
1649
1650 37
        return $this->_csrfToken;
1651
    }
1652
1653
    /**
1654
     * Loads the CSRF token from cookie or session.
1655
     * @return string the CSRF token loaded from cookie or session. Null is returned if the cookie or session
1656
     * does not have CSRF token.
1657
     */
1658 37
    protected function loadCsrfToken()
1659
    {
1660 37
        if ($this->enableCsrfCookie) {
1661 33
            return $this->getCookies()->getValue($this->csrfParam);
1662
        }
1663
1664 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...
1665
    }
1666
1667
    /**
1668
     * Generates an unmasked random token used to perform CSRF validation.
1669
     * @return string the random token for CSRF validation.
1670
     */
1671 35
    protected function generateCsrfToken()
1672
    {
1673 35
        $token = Yii::$app->getSecurity()->generateRandomString();
1674 35
        if ($this->enableCsrfCookie) {
1675 33
            $cookie = $this->createCsrfCookie($token);
1676 33
            Yii::$app->getResponse()->getCookies()->add($cookie);
1677
        } else {
1678 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...
1679
        }
1680
1681 35
        return $token;
1682
    }
1683
1684
    /**
1685
     * @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent.
1686
     */
1687 3
    public function getCsrfTokenFromHeader()
1688
    {
1689 3
        return $this->getHeaderLine(static::CSRF_HEADER);
1690
    }
1691
1692
    /**
1693
     * Creates a cookie with a randomly generated CSRF token.
1694
     * Initial values specified in [[csrfCookie]] will be applied to the generated cookie.
1695
     * @param string $token the CSRF token
1696
     * @return Cookie the generated cookie
1697
     * @see enableCsrfValidation
1698
     */
1699 33
    protected function createCsrfCookie($token)
1700
    {
1701 33
        $options = $this->csrfCookie;
1702 33
        $options['name'] = $this->csrfParam;
1703 33
        $options['value'] = $token;
1704 33
        return new Cookie($options);
1705
    }
1706
1707
    /**
1708
     * Performs the CSRF validation.
1709
     *
1710
     * This method will validate the user-provided CSRF token by comparing it with the one stored in cookie or session.
1711
     * This method is mainly called in [[Controller::beforeAction()]].
1712
     *
1713
     * Note that the method will NOT perform CSRF validation if [[enableCsrfValidation]] is false or the HTTP method
1714
     * is among GET, HEAD or OPTIONS.
1715
     *
1716
     * @param string $clientSuppliedToken the user-provided CSRF token to be validated. If null, the token will be retrieved from
1717
     * the [[csrfParam]] POST field or HTTP header.
1718
     * This parameter is available since version 2.0.4.
1719
     * @return bool whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true.
1720
     */
1721 5
    public function validateCsrfToken($clientSuppliedToken = null)
1722
    {
1723 5
        $method = $this->getMethod();
1724
        // only validate CSRF token on non-"safe" methods https://tools.ietf.org/html/rfc2616#section-9.1.1
1725 5
        if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
1726 5
            return true;
1727
        }
1728
1729
1730 3
        $trueToken = $this->getCsrfToken();
1731
1732 3
        if ($clientSuppliedToken !== null) {
1733 1
            return $this->validateCsrfTokenInternal($clientSuppliedToken, $trueToken);
1734
        }
1735
1736 3
        return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken)
1737 3
            || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
1738
    }
1739
1740
    /**
1741
     * Validates CSRF token.
1742
     *
1743
     * @param string $clientSuppliedToken The masked client-supplied token.
1744
     * @param string $trueToken The masked true token.
1745
     * @return bool
1746
     */
1747 3
    private function validateCsrfTokenInternal($clientSuppliedToken, $trueToken)
1748
    {
1749 3
        if (!is_string($clientSuppliedToken)) {
1750 3
            return false;
1751
        }
1752
1753 3
        $security = Yii::$app->security;
1754
1755 3
        return $security->unmaskToken($clientSuppliedToken) === $security->unmaskToken($trueToken);
1756
    }
1757
1758
    /**
1759
     * {@inheritdoc}
1760
     */
1761 1
    public function __clone()
1762
    {
1763 1
        parent::__clone();
1764
1765 1
        $this->cloneHttpMessageInternals();
1766
1767 1
        if (is_object($this->_cookies)) {
1768
            $this->_cookies = clone $this->_cookies;
1769
        }
1770 1
    }
1771
}
1772