Completed
Push — 2.1 ( 0cb910...fafa40 )
by
unknown
40:37 queued 36:49
created

Request   F

Complexity

Total Complexity 295

Size/Duplication

Total Lines 2137
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 16

Test Coverage

Coverage 69.59%

Importance

Changes 0
Metric Value
wmc 295
lcom 3
cbo 16
dl 0
loc 2137
ccs 444
cts 638
cp 0.6959
rs 0.6314
c 0
b 0
f 0

109 Methods

Rating   Name   Duplication   Size   Complexity  
A resolve() 0 16 3
C filterHeaders() 0 33 7
A getIpValidator() 0 4 1
B defaultHeaders() 0 18 5
A getRequestTarget() 0 7 2
A setRequestTarget() 0 4 1
A withRequestTarget() 0 10 2
A getMethod() 0 13 4
A setMethod() 0 4 1
A withMethod() 0 10 2
A getUri() 0 15 4
A setUri() 0 4 1
A withUri() 0 14 3
A getIsGet() 0 4 1
A getIsOptions() 0 4 1
A getIsPost() 0 4 1
A getIsDelete() 0 4 1
A getIsPut() 0 4 1
A getIsPatch() 0 4 1
A getIsAjax() 0 4 1
A getIsPjax() 0 4 2
A getIsFlash() 0 8 3
A defaultBody() 0 7 1
A getRawBody() 0 4 1
A setRawBody() 0 6 1
C getParsedBody() 0 48 13
A setParsedBody() 0 4 1
A withParsedBody() 0 6 1
A getParsedBodyParam() 0 15 4
A post() 0 8 2
A getQueryParams() 0 8 2
A setQueryParams() 0 4 1
A withQueryParams() 0 10 2
A get() 0 8 2
A getQueryParam() 0 6 2
A setServerParams() 0 4 1
A getServerParams() 0 7 2
A getServerParam() 0 8 2
A setCookieParams() 0 5 1
A getCookieParams() 0 7 2
A withCookieParams() 0 10 2
C getHostInfo() 0 21 11
A setHostInfo() 0 5 2
A getHostName() 0 8 2
A getBaseUrl() 0 8 2
A setBaseUrl() 0 4 1
C getScriptUrl() 0 23 12
A setScriptUrl() 0 4 2
A getScriptFile() 0 12 3
A setScriptFile() 0 4 1
A getPathInfo() 0 8 2
A setPathInfo() 0 4 2
D resolvePathInfo() 0 44 9
A getAbsoluteUrl() 0 4 1
A getUrl() 0 8 2
A setUrl() 0 4 1
C resolveRequestUri() 0 22 7
A getQueryString() 0 4 1
B getIsSecureConnection() 0 18 8
A getServerName() 0 4 1
A getServerPort() 0 5 2
A getReferrer() 0 7 2
A getOrigin() 0 4 1
A getUserAgent() 0 7 2
A getUserIP() 0 10 3
A getUserHost() 0 10 3
A getRemoteIP() 0 4 1
A getRemoteHost() 0 4 1
A getAuthUser() 0 4 1
A getAuthPassword() 0 4 1
C getAuthCredentials() 0 29 8
A getPort() 0 9 4
A setPort() 0 7 2
A getSecurePort() 0 9 4
A setSecurePort() 0 7 2
A getAcceptableContentTypes() 0 12 3
A setAcceptableContentTypes() 0 4 1
A getContentType() 0 4 1
A getAcceptableLanguages() 0 12 3
A setAcceptableLanguages() 0 4 1
C parseAcceptHeader() 0 67 16
C getPreferredLanguage() 0 22 7
A getETags() 0 8 2
A getCookies() 0 10 2
D loadCookies() 0 38 10
A getUploadedFiles() 0 10 3
A setUploadedFiles() 0 4 1
A withUploadedFiles() 0 6 1
A defaultUploadedFiles() 0 10 2
A populateUploadedFileRecursive() 0 18 3
A getUploadedFileByName() 0 8 2
A getUploadedFilesByName() 0 11 3
A findUploadedFiles() 0 7 2
A reduceUploadedFiles() 0 11 2
B getCsrfToken() 0 12 5
A loadCsrfToken() 0 8 2
A generateCsrfToken() 0 12 2
A getCsrfTokenFromHeader() 0 4 1
A createCsrfCookie() 0 9 1
B validateCsrfToken() 0 17 5
A validateCsrfTokenInternal() 0 10 2
A getAttributes() 0 7 2
A setAttributes() 0 4 1
A getAttribute() 0 9 2
A withAttribute() 0 13 3
A withoutAttribute() 0 13 2
A defaultAttributes() 0 4 1
A __clone() 0 10 2
A getIsHead() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Request often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Request, and based on these observations, apply Extract Interface, too.

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\ServerRequestInterface;
11
use Psr\Http\Message\StreamInterface;
12
use Psr\Http\Message\UploadedFileInterface;
13
use Psr\Http\Message\UriInterface;
14
use Yii;
15
use yii\base\InvalidConfigException;
16
use yii\di\Instance;
17
use yii\helpers\ArrayHelper;
18
use yii\http\Cookie;
19
use yii\http\CookieCollection;
20
use yii\http\FileStream;
21
use yii\http\MemoryStream;
22
use yii\http\MessageTrait;
23
use yii\http\UploadedFile;
24
use yii\http\Uri;
25
use yii\validators\IpValidator;
26
27
/**
28
 * The web Request class represents an HTTP request.
29
 *
30
 * It encapsulates the $_SERVER variable and resolves its inconsistency among different Web servers.
31
 * Also it provides an interface to retrieve request parameters from $_POST, $_GET, $_COOKIES and REST
32
 * parameters sent via other HTTP methods like PUT or DELETE.
33
 *
34
 * Request is configured as an application component in [[\yii\web\Application]] by default.
35
 * You can access that instance via `Yii::$app->request`.
36
 *
37
 * For more details and usage information on Request, see the [guide article on requests](guide:runtime-requests).
38
 *
39
 * @property string $absoluteUrl The currently requested absolute URL. This property is read-only.
40
 * @property array $acceptableContentTypes The content types ordered by the quality score. Types with the
41
 * highest scores will be returned first. The array keys are the content types, while the array values are the
42
 * corresponding quality score and other parameters as given in the header.
43
 * @property array $acceptableLanguages The languages ordered by the preference level. The first element
44
 * represents the most preferred language.
45
 * @property string|null $authPassword The password sent via HTTP authentication, null if the password is not
46
 * given. This property is read-only.
47
 * @property string|null $authUser The username sent via HTTP authentication, null if the username is not
48
 * given. This property is read-only.
49
 * @property string $baseUrl The relative URL for the application.
50
 * @property array $parsedBody The request parameters given in the request body.
51
 * @property string $contentType Request content-type. Null is returned if this information is not available.
52
 * This property is read-only.
53
 * @property CookieCollection $cookies The cookie collection. This property is read-only.
54
 * @property string $csrfToken The token used to perform CSRF validation. This property is read-only.
55
 * @property string $csrfTokenFromHeader The CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned
56
 * if no such header is sent. This property is read-only.
57
 * @property array $eTags The entity tags. This property is read-only.
58
 * @property string|null $hostInfo Schema and hostname part (with port number if needed) of the request URL
59
 * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set. See
60
 * [[getHostInfo()]] for security related notes on this property.
61
 * @property string|null $hostName Hostname part of the request URL (e.g. `www.yiiframework.com`). This
62
 * property is read-only.
63
 * @property bool $isAjax Whether this is an AJAX (XMLHttpRequest) request. This property is read-only.
64
 * @property bool $isDelete Whether this is a DELETE request. This property is read-only.
65
 * @property bool $isFlash Whether this is an Adobe Flash or Adobe Flex request. This property is read-only.
66
 * @property bool $isGet Whether this is a GET request. This property is read-only.
67
 * @property bool $isHead Whether this is a HEAD request. This property is read-only.
68
 * @property bool $isOptions Whether this is a OPTIONS request. This property is read-only.
69
 * @property bool $isPatch Whether this is a PATCH request. This property is read-only.
70
 * @property bool $isPjax Whether this is a PJAX request. This property is read-only.
71
 * @property bool $isPost Whether this is a POST request. This property is read-only.
72
 * @property bool $isPut Whether this is a PUT request. This property is read-only.
73
 * @property bool $isSecureConnection If the request is sent via secure channel (https). This property is
74
 * read-only.
75
 * @property string $method Request method, such as GET, POST, HEAD, PUT, PATCH, DELETE. The value returned is
76
 * turned into upper case.
77
 * @property UriInterface $uri the URI instance.
78
 * @property mixed $requestTarget the message's request target.
79
 * @property string $pathInfo Part of the request URL that is after the entry script and before the question
80
 * mark. Note, the returned path info is already URL-decoded.
81
 * @property int $port Port number for insecure requests.
82
 * @property array $queryParams The request GET parameter values.
83
 * @property string $queryString Part of the request URL that is after the question mark. This property is
84
 * read-only.
85
 * @property string $rawBody The request body.
86
 * @property string|null $referrer URL referrer, null if not available. This property is read-only.
87
 * @property string|null $origin URL origin, null if not available. This property is read-only.
88
 * @property string $scriptFile The entry script file path.
89
 * @property string $scriptUrl The relative URL of the entry script.
90
 * @property int $securePort Port number for secure requests.
91
 * @property string $serverName Server name, null if not available. This property is read-only.
92
 * @property int|null $serverPort Server port number, null if not available. This property is read-only.
93
 * @property string $url The currently requested relative URL. Note that the URI returned may be URL-encoded
94
 * depending on the client.
95
 * @property array $uploadedFiles Uploaded files for this request. See [[getUploadedFiles()]] for details.
96
 * @property string|null $userAgent User agent, null if not available. This property is read-only.
97
 * @property string|null $userHost User host name, null if not available. This property is read-only.
98
 * @property string|null $userIP User IP address, null if not available. This property is read-only.
99
 *
100
 * @author Qiang Xue <[email protected]>
101
 * @since 2.0
102
 * @SuppressWarnings(PHPMD.SuperGlobals)
103
 */
104
class Request extends \yii\base\Request implements ServerRequestInterface
105
{
106
    use MessageTrait;
107
108
    /**
109
     * The name of the HTTP header for sending CSRF token.
110
     */
111
    const CSRF_HEADER = 'X-CSRF-Token';
112
    /**
113
     * The length of the CSRF token mask.
114
     * @deprecated 2.0.12 The mask length is now equal to the token length.
115
     */
116
    const CSRF_MASK_LENGTH = 8;
117
118
    /**
119
     * @var bool whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to true.
120
     * When CSRF validation is enabled, forms submitted to an Yii Web application must be originated
121
     * from the same application. If not, a 400 HTTP exception will be raised.
122
     *
123
     * Note, this feature requires that the user client accepts cookie. Also, to use this feature,
124
     * forms submitted via POST method must contain a hidden input whose name is specified by [[csrfParam]].
125
     * You may use [[\yii\helpers\Html::beginForm()]] to generate his hidden input.
126
     *
127
     * In JavaScript, you may get the values of [[csrfParam]] and [[csrfToken]] via `yii.getCsrfParam()` and
128
     * `yii.getCsrfToken()`, respectively. The [[\yii\web\YiiAsset]] asset must be registered.
129
     * You also need to include CSRF meta tags in your pages by using [[\yii\helpers\Html::csrfMetaTags()]].
130
     *
131
     * @see Controller::enableCsrfValidation
132
     * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery
133
     */
134
    public $enableCsrfValidation = true;
135
    /**
136
     * @var string the name of the token used to prevent CSRF. Defaults to '_csrf'.
137
     * This property is used only when [[enableCsrfValidation]] is true.
138
     */
139
    public $csrfParam = '_csrf';
140
    /**
141
     * @var array the configuration for creating the CSRF [[Cookie|cookie]]. This property is used only when
142
     * both [[enableCsrfValidation]] and [[enableCsrfCookie]] are true.
143
     */
144
    public $csrfCookie = ['httpOnly' => true];
145
    /**
146
     * @var bool whether to use cookie to persist CSRF token. If false, CSRF token will be stored
147
     * in session under the name of [[csrfParam]]. Note that while storing CSRF tokens in session increases
148
     * security, it requires starting a session for every page, which will degrade your site performance.
149
     */
150
    public $enableCsrfCookie = true;
151
    /**
152
     * @var bool whether cookies should be validated to ensure they are not tampered. Defaults to true.
153
     */
154
    public $enableCookieValidation = true;
155
    /**
156
     * @var string a secret key used for cookie validation. This property must be set if [[enableCookieValidation]] is true.
157
     */
158
    public $cookieValidationKey;
159
    /**
160
     * @var string the name of the POST parameter that is used to indicate if a request is a PUT, PATCH or DELETE
161
     * request tunneled through POST. Defaults to '_method'.
162
     * @see getMethod()
163
     * @see getParsedBody()
164
     */
165
    public $methodParam = '_method';
166
    /**
167
     * @var array the parsers for converting the raw HTTP request body into [[bodyParams]].
168
     * The array keys are the request `Content-Types`, and the array values are the
169
     * corresponding configurations for [[Yii::createObject|creating the parser objects]].
170
     * A parser must implement the [[RequestParserInterface]].
171
     *
172
     * To enable parsing for JSON requests you can use the [[JsonParser]] class like in the following example:
173
     *
174
     * ```
175
     * [
176
     *     'application/json' => \yii\web\JsonParser::class,
177
     * ]
178
     * ```
179
     *
180
     * To register a parser for parsing all request types you can use `'*'` as the array key.
181
     * This one will be used as a fallback in case no other types match.
182
     *
183
     * @see getParsedBody()
184
     */
185
    public $parsers = [];
186
    /**
187
     * @var string name of the class to be used for uploaded file instantiation.
188
     * This class should implement [[UploadedFileInterface]].
189
     * @since 2.1.0
190
     */
191
    public $uploadedFileClass = UploadedFile::class;
192
    /**
193
     * @var array the configuration for trusted security related headers.
194
     *
195
     * An array key is an IPv4 or IPv6 IP address in CIDR notation for matching a client.
196
     *
197
     * An array value is a list of headers to trust. These will be matched against
198
     * [[secureHeaders]] to determine which headers are allowed to be sent by a specified host.
199
     * The case of the header names must be the same as specified in [[secureHeaders]].
200
     *
201
     * For example, to trust all headers listed in [[secureHeaders]] for IP addresses
202
     * in range `192.168.0.0-192.168.0.254` write the following:
203
     *
204
     * ```php
205
     * [
206
     *     '192.168.0.0/24',
207
     * ]
208
     * ```
209
     *
210
     * To trust just the `X-Forwarded-For` header from `10.0.0.1`, use:
211
     *
212
     * ```
213
     * [
214
     *     '10.0.0.1' => ['X-Forwarded-For']
215
     * ]
216
     * ```
217
     *
218
     * Default is to trust all headers except those listed in [[secureHeaders]] from all hosts.
219
     * Matches are tried in order and searching is stopped when IP matches.
220
     *
221
     * > Info: Matching is performed using [[IpValidator]].
222
     *   See [[IpValidator::::setRanges()|IpValidator::setRanges()]]
223
     *   and [[IpValidator::networks]] for advanced matching.
224
     *
225
     * @see $secureHeaders
226
     * @since 2.0.13
227
     */
228
    public $trustedHosts = [];
229
    /**
230
     * @var array lists of headers that are, by default, subject to the trusted host configuration.
231
     * These headers will be filtered unless explicitly allowed in [[trustedHosts]].
232
     * The match of header names is case-insensitive.
233
     * @see https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
234
     * @see $trustedHosts
235
     * @since 2.0.13
236
     */
237
    public $secureHeaders = [
238
        // Common:
239
        'X-Forwarded-For',
240
        'X-Forwarded-Host',
241
        'X-Forwarded-Proto',
242
243
        // Microsoft:
244
        'Front-End-Https',
245
        'X-Rewrite-Url',
246
    ];
247
    /**
248
     * @var string[] List of headers where proxies store the real client IP.
249
     * It's not advisable to put insecure headers here.
250
     * The match of header names is case-insensitive.
251
     * @see $trustedHosts
252
     * @see $secureHeaders
253
     * @since 2.0.13
254
     */
255
    public $ipHeaders = [
256
        'X-Forwarded-For', // Common
257
    ];
258
    /**
259
     * @var array list of headers to check for determining whether the connection is made via HTTPS.
260
     * The array keys are header names and the array value is a list of header values that indicate a secure connection.
261
     * The match of header names and values is case-insensitive.
262
     * It's not advisable to put insecure headers here.
263
     * @see $trustedHosts
264
     * @see $secureHeaders
265
     * @since 2.0.13
266
     */
267
    public $secureProtocolHeaders = [
268
        'X-Forwarded-Proto' => ['https'], // Common
269
        'Front-End-Https' => ['on'], // Microsoft
270
    ];
271
272
    /**
273
     * @var array attributes derived from the request.
274
     * @since 2.1.0
275
     */
276
    private $_attributes;
277
    /**
278
     * @var array server parameters.
279
     * @since 2.1.0
280
     */
281
    private $_serverParams;
282
    /**
283
     * @var array the cookies sent by the client to the server.
284
     * @since 2.1.0
285
     */
286
    private $_cookieParams;
287
    /**
288
     * @var CookieCollection Collection of request cookies.
289
     */
290
    private $_cookies;
291
    /**
292
     * @var string the HTTP method of the request.
293
     */
294
    private $_method;
295
    /**
296
     * @var UriInterface the URI instance associated with request.
297
     */
298
    private $_uri;
299
    /**
300
     * @var mixed the message's request target.
301
     */
302
    private $_requestTarget;
303
    /**
304
     * @var array uploaded files.
305
     * @since 2.1.0
306
     */
307
    private $_uploadedFiles;
308
309
310
    /**
311
     * Resolves the current request into a route and the associated parameters.
312
     * @return array the first element is the route, and the second is the associated parameters.
313
     * @throws NotFoundHttpException if the request cannot be resolved.
314
     */
315 1
    public function resolve()
316
    {
317 1
        $result = Yii::$app->getUrlManager()->parseRequest($this);
318 1
        if ($result !== false) {
319 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...
320 1
            if ($this->_queryParams === null) {
321 1
                $_GET = $params + $_GET; // preserve numeric keys
322
            } else {
323 1
                $this->_queryParams = $params + $this->_queryParams;
324
            }
325
326 1
            return [$route, $this->getQueryParams()];
327
        }
328
329
        throw new NotFoundHttpException(Yii::t('yii', 'Page not found.'));
330
    }
331
332
    /**
333
     * Filters headers according to the [[trustedHosts]].
334
     * @param array $rawHeaders
335
     * @return array filtered headers
336
     * @since 2.0.13
337
     */
338 142
    protected function filterHeaders($rawHeaders)
339
    {
340
        // do not trust any of the [[secureHeaders]] by default
341 142
        $trustedHeaders = [];
342
343
        // check if the client is a trusted host
344 142
        if (!empty($this->trustedHosts)) {
345 24
            $validator = $this->getIpValidator();
346 24
            $ip = $this->getRemoteIP();
347 24
            foreach ($this->trustedHosts as $cidr => $headers) {
348 24
                if (!is_array($headers)) {
349 24
                    $cidr = $headers;
350 24
                    $headers = $this->secureHeaders;
351
                }
352 24
                $validator->setRanges($cidr);
353 24
                if ($validator->validate($ip)) {
354 4
                    $trustedHeaders = $headers;
355 24
                    break;
356
                }
357
            }
358
        }
359
360 142
        $rawHeaders = array_change_key_case($rawHeaders, CASE_LOWER);
361
362
        // filter all secure headers unless they are trusted
363 142
        foreach ($this->secureHeaders as $secureHeader) {
364 142
            if (!in_array($secureHeader, $trustedHeaders)) {
365 142
                unset($rawHeaders[strtolower($secureHeader)]);
366
            }
367
        }
368
369 142
        return $rawHeaders;
370
    }
371
372
    /**
373
     * Creates instance of [[IpValidator]].
374
     * You can override this method to adjust validator or implement different matching strategy.
375
     *
376
     * @return IpValidator
377
     * @since 2.0.13
378
     */
379 24
    protected function getIpValidator()
380
    {
381 24
        return new IpValidator();
382
    }
383
384
    /**
385
     * Returns default message's headers, which should be present once [[headerCollection]] is instantiated.
386
     * @return string[][] an associative array of the message's headers.
387
     */
388 142
    protected function defaultHeaders()
389
    {
390 142
        if (function_exists('getallheaders')) {
391
            $headers = getallheaders();
392 142
        } elseif (function_exists('http_get_request_headers')) {
393
            $headers = http_get_request_headers();
394
        } else {
395 142
            $headers = [];
396 142
            foreach ($_SERVER as $name => $value) {
397 138
                if (strncmp($name, 'HTTP_', 5) === 0) {
398 33
                    $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
399 138
                    $headers[$name] = $value;
400
                }
401
            }
402
        }
403
404 142
        return $this->filterHeaders($headers);
405
    }
406
407
    /**
408
     * {@inheritdoc}
409
     * @since 2.1.0
410
     */
411
    public function getRequestTarget()
412
    {
413
        if ($this->_requestTarget === null) {
414
            $this->_requestTarget = $this->getUri()->__toString();
415
        }
416
        return $this->_requestTarget;
417
    }
418
419
    /**
420
     * Specifies the message's request target
421
     * @param mixed $requestTarget the message's request target.
422
     * @since 2.1.0
423
     */
424
    public function setRequestTarget($requestTarget)
425
    {
426
        $this->_requestTarget = $requestTarget;
427
    }
428
429
    /**
430
     * {@inheritdoc}
431
     * @since 2.1.0
432
     */
433
    public function withRequestTarget($requestTarget)
434
    {
435
        if ($this->getRequestTarget() === $requestTarget) {
436
            return $this;
437
        }
438
439
        $newInstance = clone $this;
440
        $newInstance->setRequestTarget($requestTarget);
441
        return $newInstance;
442
    }
443
444
    /**
445
     * {@inheritdoc}
446
     */
447 50
    public function getMethod()
448
    {
449 50
        if ($this->_method === null) {
450 41
            if (isset($_POST[$this->methodParam])) {
451 1
                $this->_method = $_POST[$this->methodParam];
452 40
            } elseif ($this->hasHeader('x-http-method-override')) {
453 1
                $this->_method = $this->getHeaderLine('x-http-method-override');
454
            } else {
455 39
                $this->_method = $this->getServerParam('REQUEST_METHOD', 'GET');
456
            }
457
        }
458 50
        return $this->_method;
459
    }
460
461
    /**
462
     * Specifies request HTTP method.
463
     * @param string $method case-sensitive HTTP method.
464
     * @since 2.1.0
465
     */
466 11
    public function setMethod($method)
467
    {
468 11
        $this->_method =  $method;
469 11
    }
470
471
    /**
472
     * {@inheritdoc}
473
     * @since 2.1.0
474
     */
475 1
    public function withMethod($method)
476
    {
477 1
        if ($this->getMethod() === $method) {
478
            return $this;
479
        }
480
481 1
        $newInstance = clone $this;
482 1
        $newInstance->setMethod($method);
483 1
        return $newInstance;
484
    }
485
486
    /**
487
     * {@inheritdoc}
488
     * @since 2.1.0
489
     */
490
    public function getUri()
491
    {
492
        if (!$this->_uri instanceof UriInterface) {
493
            if ($this->_uri === null) {
494
                $uri = new Uri(['string' => $this->getAbsoluteUrl()]);
495
            } elseif ($this->_uri instanceof \Closure) {
496
                $uri = call_user_func($this->_uri, $this);
497
            } else {
498
                $uri = $this->_uri;
499
            }
500
501
            $this->_uri = Instance::ensure($uri, UriInterface::class);
502
        }
503
        return $this->_uri;
504
    }
505
506
    /**
507
     * Specifies the URI instance.
508
     * @param UriInterface|\Closure|array $uri URI instance or its DI compatible configuration.
509
     * @since 2.1.0
510
     */
511
    public function setUri($uri)
512
    {
513
        $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...
514
    }
515
516
    /**
517
     * {@inheritdoc}
518
     * @since 2.1.0
519
     */
520
    public function withUri(UriInterface $uri, $preserveHost = false)
521
    {
522
        if ($this->getUri() === $uri) {
523
            return $this;
524
        }
525
526
        $newInstance = clone $this;
527
528
        $newInstance->setUri($uri);
529
        if (!$preserveHost) {
530
            return $newInstance->withHeader('host', $uri->getHost());
531
        }
532
        return $newInstance;
533
    }
534
535
    /**
536
     * Returns whether this is a GET request.
537
     * @return bool whether this is a GET request.
538
     */
539 2
    public function getIsGet()
540
    {
541 2
        return $this->getMethod() === 'GET';
542
    }
543
544
    /**
545
     * Returns whether this is an OPTIONS request.
546
     * @return bool whether this is a OPTIONS request.
547
     */
548 1
    public function getIsOptions()
549
    {
550 1
        return $this->getMethod() === 'OPTIONS';
551
    }
552
553
    /**
554
     * Returns whether this is a HEAD request.
555
     * @return bool whether this is a HEAD request.
556
     */
557
    public function getIsHead()
558
    {
559
        return $this->getMethod() === 'HEAD';
560
    }
561
562
    /**
563
     * Returns whether this is a POST request.
564
     * @return bool whether this is a POST request.
565
     */
566
    public function getIsPost()
567
    {
568
        return $this->getMethod() === 'POST';
569
    }
570
571
    /**
572
     * Returns whether this is a DELETE request.
573
     * @return bool whether this is a DELETE request.
574
     */
575
    public function getIsDelete()
576
    {
577
        return $this->getMethod() === 'DELETE';
578
    }
579
580
    /**
581
     * Returns whether this is a PUT request.
582
     * @return bool whether this is a PUT request.
583
     */
584
    public function getIsPut()
585
    {
586
        return $this->getMethod() === 'PUT';
587
    }
588
589
    /**
590
     * Returns whether this is a PATCH request.
591
     * @return bool whether this is a PATCH request.
592
     */
593
    public function getIsPatch()
594
    {
595
        return $this->getMethod() === 'PATCH';
596
    }
597
598
    /**
599
     * Returns whether this is an AJAX (XMLHttpRequest) request.
600
     *
601
     * Note that jQuery doesn't set the header in case of cross domain
602
     * requests: https://stackoverflow.com/questions/8163703/cross-domain-ajax-doesnt-send-x-requested-with-header
603
     *
604
     * @return bool whether this is an AJAX (XMLHttpRequest) request.
605
     */
606 15
    public function getIsAjax()
607
    {
608 15
        return $this->getHeaderLine('x-requested-with') === 'XMLHttpRequest';
609
    }
610
611
    /**
612
     * Returns whether this is a PJAX request
613
     * @return bool whether this is a PJAX request
614
     */
615 3
    public function getIsPjax()
616
    {
617 3
        return $this->getIsAjax() && $this->hasHeader('x-pjax');
618
    }
619
620
    /**
621
     * Returns whether this is an Adobe Flash or Flex request.
622
     * @return bool whether this is an Adobe Flash or Adobe Flex request.
623
     */
624
    public function getIsFlash()
625
    {
626
        $userAgent = $this->getUserAgent();
627
        if ($userAgent === null) {
628
            return false;
629
        }
630
        return (stripos($userAgent, 'Shockwave') !== false || stripos($userAgent, 'Flash') !== false);
631
    }
632
633
    /**
634
     * Returns default message body to be used in case it is not explicitly set.
635
     * @return StreamInterface default body instance.
636
     */
637
    protected function defaultBody()
638
    {
639
        return new FileStream([
640
            'filename' => 'php://input',
641
            'mode' => 'r',
642
        ]);
643
    }
644
645
    /**
646
     * Returns the raw HTTP request body.
647
     * @return string the request body
648
     */
649
    public function getRawBody()
650
    {
651
        return $this->getBody()->__toString();
652
    }
653
654
    /**
655
     * Sets the raw HTTP request body, this method is mainly used by test scripts to simulate raw HTTP requests.
656
     * @param string $rawBody the request body
657
     */
658 6
    public function setRawBody($rawBody)
659
    {
660 6
        $body = new MemoryStream();
661 6
        $body->write($rawBody);
662 6
        $this->setBody($body);
663 6
    }
664
665
    private $_parsedBody;
666
667
    /**
668
     * Returns the request parameters given in the request body.
669
     *
670
     * Request parameters are determined using the parsers configured in [[parsers]] property.
671
     * If no parsers are configured for the current [[contentType]] it uses the PHP function `mb_parse_str()`
672
     * to parse the [[rawBody|request body]].
673
     *
674
     * Since 2.1.0 body params also include result of [[getUploadedFiles()]].
675
     *
676
     * @return array the request parameters given in the request body.
677
     * @throws InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]].
678
     * @throws UnsupportedMediaTypeHttpException if unable to parse raw body.
679
     * @see getMethod()
680
     * @see getParsedBodyParam()
681
     * @see setParsedBody()
682
     */
683 12
    public function getParsedBody()
684
    {
685 12
        if ($this->_parsedBody === null) {
686 8
            if (isset($_POST[$this->methodParam])) {
687
                $this->_parsedBody = $_POST;
688
                unset($this->_parsedBody[$this->methodParam]);
689
                return $this->_parsedBody;
690
            }
691
692 8
            $contentType = $this->getContentType();
693 8
            if (($pos = strpos($contentType, ';')) !== false) {
694
                // e.g. text/html; charset=UTF-8
695 2
                $contentType = trim(substr($contentType, 0, $pos));
696
            }
697
698 8
            if (isset($this->parsers[$contentType])) {
699 2
                $parser = Yii::createObject($this->parsers[$contentType]);
700 2
                if (!($parser instanceof RequestParserInterface)) {
701
                    throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface.");
702
                }
703 2
                $this->_parsedBody = $parser->parse($this);
704 6
            } elseif (isset($this->parsers['*'])) {
705
                $parser = Yii::createObject($this->parsers['*']);
706
                if (!($parser instanceof RequestParserInterface)) {
707
                    throw new InvalidConfigException('The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface.');
708
                }
709
                $this->_parsedBody = $parser->parse($this);
710 6
            } elseif ($this->getMethod() === 'POST') {
711 6
                if ($contentType !== 'application/x-www-form-urlencoded' && $contentType !== 'multipart/form-data') {
712 1
                    throw new UnsupportedMediaTypeHttpException();
713
                }
714
                // PHP has already parsed the body so we have all params in $_POST
715 6
                $this->_parsedBody = $_POST;
716
717 6
                if ($contentType === 'multipart/form-data') {
718 6
                    $this->_parsedBody = ArrayHelper::merge($this->_parsedBody, $this->getUploadedFiles());
719
                }
720
            } else {
721 1
                if ($contentType !== 'application/x-www-form-urlencoded') {
722 1
                    throw new UnsupportedMediaTypeHttpException();
723
                }
724 1
                $this->_parsedBody = [];
725 1
                mb_parse_str($this->getBody()->__toString(), $this->_parsedBody);
726
            }
727
        }
728
729 12
        return $this->_parsedBody;
730
    }
731
732
    /**
733
     * Sets the request body parameters.
734
     * @param array $values the request body parameters (name-value pairs)
735
     * @see getParsedBodyParam()
736
     * @see getParsedBody()
737
     */
738 4
    public function setParsedBody($values)
739
    {
740 4
        $this->_parsedBody = $values;
741 4
    }
742
743
    /**
744
     * {@inheritdoc}
745
     * @since 2.1.0
746
     */
747
    public function withParsedBody($data)
748
    {
749
        $newInstance = clone $this;
750
        $newInstance->setParsedBody($data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 747 can also be of type null or object; however, yii\web\Request::setParsedBody() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

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

An additional type check may prevent trouble.

Loading history...
751
        return $newInstance;
752
    }
753
754
    /**
755
     * Returns the named request body parameter value.
756
     * If the parameter does not exist, the second parameter passed to this method will be returned.
757
     * @param string $name the parameter name
758
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
759
     * @return mixed the parameter value
760
     * @see getParsedBody()
761
     * @see setParsedBody()
762
     */
763 5
    public function getParsedBodyParam($name, $defaultValue = null)
764
    {
765 5
        $params = $this->getParsedBody();
766
767 5
        if (is_object($params)) {
768
            // unable to use `ArrayHelper::getValue()` due to different dots in key logic and lack of exception handling
769
            try {
770 1
                return $params->{$name};
771 1
            } catch (\Exception $e) {
0 ignored issues
show
Unused Code introduced by
catch (\Exception $e) { ...return $defaultValue; } does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
772 1
                return $defaultValue;
773
            }
774
        }
775
776 5
        return isset($params[$name]) ? $params[$name] : $defaultValue;
777
    }
778
779
    /**
780
     * Returns POST parameter with a given name. If name isn't specified, returns an array of all POST parameters.
781
     *
782
     * @param string $name the parameter name
783
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
784
     * @return array|mixed
785
     */
786
    public function post($name = null, $defaultValue = null)
787
    {
788
        if ($name === null) {
789
            return $this->getParsedBody();
790
        }
791
792
        return $this->getParsedBodyParam($name, $defaultValue);
793
    }
794
795
    private $_queryParams;
796
797
    /**
798
     * Returns the request parameters given in the [[queryString]].
799
     *
800
     * This method will return the contents of `$_GET` if params where not explicitly set.
801
     * @return array the request GET parameter values.
802
     * @see setQueryParams()
803
     */
804 18
    public function getQueryParams()
805
    {
806 18
        if ($this->_queryParams === null) {
807 14
            return $_GET;
808
        }
809
810 5
        return $this->_queryParams;
811
    }
812
813
    /**
814
     * Sets the request [[queryString]] parameters.
815
     * @param array $values the request query parameters (name-value pairs)
816
     * @see getQueryParam()
817
     * @see getQueryParams()
818
     */
819 5
    public function setQueryParams($values)
820
    {
821 5
        $this->_queryParams = $values;
822 5
    }
823
824
    /**
825
     * {@inheritdoc}
826
     */
827
    public function withQueryParams(array $query)
828
    {
829
        if ($this->getQueryParams() === $query) {
830
            return $this;
831
        }
832
833
        $newInstance = clone $this;
834
        $newInstance->setQueryParams($query);
835
        return $newInstance;
836
    }
837
838
    /**
839
     * Returns GET parameter with a given name. If name isn't specified, returns an array of all GET parameters.
840
     *
841
     * @param string $name the parameter name
842
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
843
     * @return array|mixed
844
     */
845 6
    public function get($name = null, $defaultValue = null)
846
    {
847 6
        if ($name === null) {
848
            return $this->getQueryParams();
849
        }
850
851 6
        return $this->getQueryParam($name, $defaultValue);
852
    }
853
854
    /**
855
     * Returns the named GET parameter value.
856
     * If the GET parameter does not exist, the second parameter passed to this method will be returned.
857
     * @param string $name the GET parameter name.
858
     * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
859
     * @return mixed the GET parameter value
860
     * @see getParsedBodyParam()
861
     */
862 9
    public function getQueryParam($name, $defaultValue = null)
863
    {
864 9
        $params = $this->getQueryParams();
865
866 9
        return isset($params[$name]) ? $params[$name] : $defaultValue;
867
    }
868
869
    /**
870
     * Sets the data related to the incoming request environment.
871
     * @param array $serverParams server parameters.
872
     * @since 2.1.0
873
     */
874 4
    public function setServerParams(array $serverParams)
875
    {
876 4
        $this->_serverParams = $serverParams;
877 4
    }
878
879
    /**
880
     * {@inheritdoc}
881
     * @since 2.1.0
882
     */
883 122
    public function getServerParams()
884
    {
885 122
        if ($this->_serverParams === null) {
886 119
            $this->_serverParams = $_SERVER;
887
        }
888 122
        return $this->_serverParams;
889
    }
890
891
    /**
892
     * Return the server environment parameter by name.
893
     * @param string $name parameter name.
894
     * @param mixed $default default value to return if the parameter does not exist.
895
     * @return mixed parameter value.
896
     * @since 2.1.0
897
     */
898 122
    public function getServerParam($name, $default = null)
899
    {
900 122
        $params = $this->getServerParams();
901 122
        if (!isset($params[$name])) {
902 102
            return $default;
903
        }
904 43
        return $params[$name];
905
    }
906
907
    /**
908
     * Specifies cookies.
909
     * @param array $cookies array of key/value pairs representing cookies.
910
     * @since 2.1.0
911
     */
912
    public function setCookieParams(array $cookies)
913
    {
914
        $this->_cookieParams = $cookies;
915
        $this->_cookies = null;
916
    }
917
918
    /**
919
     * {@inheritdoc}
920
     * @since 2.1.0
921
     */
922 44
    public function getCookieParams()
923
    {
924 44
        if ($this->_cookieParams === null) {
925 44
            $this->_cookieParams = $_COOKIE;
926
        }
927 44
        return $this->_cookieParams;
928
    }
929
930
    /**
931
     * {@inheritdoc}
932
     * @since 2.1.0
933
     */
934
    public function withCookieParams(array $cookies)
935
    {
936
        if ($this->getCookieParams() === $cookies) {
937
            return $this;
938
        }
939
940
        $newInstance = clone $this;
941
        $newInstance->setCookieParams($cookies);
942
        return $newInstance;
943
    }
944
945
    private $_hostInfo;
946
    private $_hostName;
947
948
    /**
949
     * Returns the schema and host part of the current request URL.
950
     *
951
     * The returned URL does not have an ending slash.
952
     *
953
     * By default this value is based on the user request information. This method will
954
     * return the value of `$_SERVER['HTTP_HOST']` if it is available or `$_SERVER['SERVER_NAME']` if not.
955
     * You may want to check out the [PHP documentation](http://php.net/manual/en/reserved.variables.server.php)
956
     * for more information on these variables.
957
     *
958
     * You may explicitly specify it by setting the [[setHostInfo()|hostInfo]] property.
959
     *
960
     * > Warning: Dependent on the server configuration this information may not be
961
     * > reliable and [may be faked by the user sending the HTTP request](https://www.acunetix.com/vulnerabilities/web/host-header-attack).
962
     * > If the webserver is configured to serve the same site independent of the value of
963
     * > the `Host` header, this value is not reliable. In such situations you should either
964
     * > fix your webserver configuration or explicitly set the value by setting the [[setHostInfo()|hostInfo]] property.
965
     * > If you don't have access to the server configuration, you can setup [[\yii\filters\HostControl]] filter at
966
     * > application level in order to protect against such kind of attack.
967
     *
968
     * @property string|null schema and hostname part (with port number if needed) of the request URL
969
     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
970
     * See [[getHostInfo()]] for security related notes on this property.
971
     * @return string|null schema and hostname part (with port number if needed) of the request URL
972
     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
973
     * @see setHostInfo()
974
     */
975 23
    public function getHostInfo()
976
    {
977 23
        if ($this->_hostInfo === null) {
978 19
            $secure = $this->getIsSecureConnection();
979 19
            $http = $secure ? 'https' : 'http';
980
981 19
            if ($this->hasHeader('X-Forwarded-Host')) {
982 1
                $this->_hostInfo = $http . '://' . $this->getHeaderLine('X-Forwarded-Host');
983 18
            } elseif ($this->hasHeader('Host')) {
984 9
                $this->_hostInfo = $http . '://' . $this->getHeaderLine('Host');
985 9
            } elseif (($serverName = $this->getServerParam('SERVER_NAME')) !== null) {
986 1
                $this->_hostInfo = $http . '://' . $serverName;
987 1
                $port = $secure ? $this->getSecurePort() : $this->getPort();
988 1
                if (($port !== 80 && !$secure) || ($port !== 443 && $secure)) {
989
                    $this->_hostInfo .= ':' . $port;
990
                }
991
            }
992
        }
993
994 23
        return $this->_hostInfo;
995
    }
996
997
    /**
998
     * Sets the schema and host part of the application URL.
999
     * This setter is provided in case the schema and hostname cannot be determined
1000
     * on certain Web servers.
1001
     * @param string|null $value the schema and host part of the application URL. The trailing slashes will be removed.
1002
     * @see getHostInfo() for security related notes on this property.
1003
     */
1004 47
    public function setHostInfo($value)
1005
    {
1006 47
        $this->_hostName = null;
1007 47
        $this->_hostInfo = $value === null ? null : rtrim($value, '/');
1008 47
    }
1009
1010
    /**
1011
     * Returns the host part of the current request URL.
1012
     * Value is calculated from current [[getHostInfo()|hostInfo]] property.
1013
     *
1014
     * > Warning: The content of this value may not be reliable, dependent on the server
1015
     * > configuration. Please refer to [[getHostInfo()]] for more information.
1016
     *
1017
     * @return string|null hostname part of the request URL (e.g. `www.yiiframework.com`)
1018
     * @see getHostInfo()
1019
     * @since 2.0.10
1020
     */
1021 16
    public function getHostName()
1022
    {
1023 16
        if ($this->_hostName === null) {
1024 16
            $this->_hostName = parse_url($this->getHostInfo(), PHP_URL_HOST);
1025
        }
1026
1027 16
        return $this->_hostName;
1028
    }
1029
1030
    private $_baseUrl;
1031
1032
    /**
1033
     * Returns the relative URL for the application.
1034
     * This is similar to [[scriptUrl]] except that it does not include the script file name,
1035
     * and the ending slashes are removed.
1036
     * @return string the relative URL for the application
1037
     * @see setScriptUrl()
1038
     */
1039 254
    public function getBaseUrl()
1040
    {
1041 254
        if ($this->_baseUrl === null) {
1042 253
            $this->_baseUrl = rtrim(dirname($this->getScriptUrl()), '\\/');
1043
        }
1044
1045 254
        return $this->_baseUrl;
1046
    }
1047
1048
    /**
1049
     * Sets the relative URL for the application.
1050
     * By default the URL is determined based on the entry script URL.
1051
     * This setter is provided in case you want to change this behavior.
1052
     * @param string $value the relative URL for the application
1053
     */
1054 1
    public function setBaseUrl($value)
1055
    {
1056 1
        $this->_baseUrl = $value;
1057 1
    }
1058
1059
    private $_scriptUrl;
1060
1061
    /**
1062
     * Returns the relative URL of the entry script.
1063
     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
1064
     * @return string the relative URL of the entry script.
1065
     * @throws InvalidConfigException if unable to determine the entry script URL
1066
     */
1067 255
    public function getScriptUrl()
1068
    {
1069 255
        if ($this->_scriptUrl === null) {
1070 2
            $scriptFile = $this->getScriptFile();
1071 1
            $scriptName = basename($scriptFile);
1072 1
            $serverParams = $this->getServerParams();
1073 1
            if (isset($serverParams['SCRIPT_NAME']) && basename($serverParams['SCRIPT_NAME']) === $scriptName) {
1074 1
                $this->_scriptUrl = $serverParams['SCRIPT_NAME'];
1075
            } elseif (isset($serverParams['PHP_SELF']) && basename($serverParams['PHP_SELF']) === $scriptName) {
1076
                $this->_scriptUrl = $serverParams['PHP_SELF'];
1077
            } elseif (isset($serverParams['ORIG_SCRIPT_NAME']) && basename($serverParams['ORIG_SCRIPT_NAME']) === $scriptName) {
1078
                $this->_scriptUrl = $serverParams['ORIG_SCRIPT_NAME'];
1079
            } elseif (isset($serverParams['PHP_SELF']) && ($pos = strpos($serverParams['PHP_SELF'], '/' . $scriptName)) !== false) {
1080
                $this->_scriptUrl = substr($serverParams['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName;
1081
            } elseif (!empty($serverParams['DOCUMENT_ROOT']) && strpos($scriptFile, $serverParams['DOCUMENT_ROOT']) === 0) {
1082
                $this->_scriptUrl = str_replace('\\', '/', str_replace($serverParams['DOCUMENT_ROOT'], '', $scriptFile));
1083
            } else {
1084
                throw new InvalidConfigException('Unable to determine the entry script URL.');
1085
            }
1086
        }
1087
1088 254
        return $this->_scriptUrl;
1089
    }
1090
1091
    /**
1092
     * Sets the relative URL for the application entry script.
1093
     * This setter is provided in case the entry script URL cannot be determined
1094
     * on certain Web servers.
1095
     * @param string $value the relative URL for the application entry script.
1096
     */
1097 265
    public function setScriptUrl($value)
1098
    {
1099 265
        $this->_scriptUrl = $value === null ? null : '/' . trim($value, '/');
1100 265
    }
1101
1102
    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...
1103
1104
    /**
1105
     * Returns the entry script file path.
1106
     * The default implementation will simply return `$_SERVER['SCRIPT_FILENAME']`.
1107
     * @return string the entry script file path
1108
     * @throws InvalidConfigException
1109
     */
1110 256
    public function getScriptFile()
1111
    {
1112 256
        if (isset($this->_scriptFile)) {
1113 244
            return $this->_scriptFile;
1114
        }
1115
1116 13
        if (($scriptFilename = $this->getServerParam('SCRIPT_FILENAME')) !== null) {
1117 11
            return $scriptFilename;
1118
        }
1119
1120 2
        throw new InvalidConfigException('Unable to determine the entry script file path.');
1121
    }
1122
1123
    /**
1124
     * Sets the entry script file path.
1125
     * The entry script file path normally can be obtained from `$_SERVER['SCRIPT_FILENAME']`.
1126
     * If your server configuration does not return the correct value, you may configure
1127
     * this property to make it right.
1128
     * @param string $value the entry script file path.
1129
     */
1130 244
    public function setScriptFile($value)
1131
    {
1132 244
        $this->_scriptFile = $value;
1133 244
    }
1134
1135
    private $_pathInfo;
1136
1137
    /**
1138
     * Returns the path info of the currently requested URL.
1139
     * A path info refers to the part that is after the entry script and before the question mark (query string).
1140
     * The starting and ending slashes are both removed.
1141
     * @return string part of the request URL that is after the entry script and before the question mark.
1142
     * Note, the returned path info is already URL-decoded.
1143
     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
1144
     */
1145 17
    public function getPathInfo()
1146
    {
1147 17
        if ($this->_pathInfo === null) {
1148
            $this->_pathInfo = $this->resolvePathInfo();
1149
        }
1150
1151 17
        return $this->_pathInfo;
1152
    }
1153
1154
    /**
1155
     * Sets the path info of the current request.
1156
     * This method is mainly provided for testing purpose.
1157
     * @param string $value the path info of the current request
1158
     */
1159 18
    public function setPathInfo($value)
1160
    {
1161 18
        $this->_pathInfo = $value === null ? null : ltrim($value, '/');
1162 18
    }
1163
1164
    /**
1165
     * Resolves the path info part of the currently requested URL.
1166
     * A path info refers to the part that is after the entry script and before the question mark (query string).
1167
     * The starting slashes are both removed (ending slashes will be kept).
1168
     * @return string part of the request URL that is after the entry script and before the question mark.
1169
     * Note, the returned path info is decoded.
1170
     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
1171
     */
1172
    protected function resolvePathInfo()
1173
    {
1174
        $pathInfo = $this->getUrl();
1175
1176
        if (($pos = strpos($pathInfo, '?')) !== false) {
1177
            $pathInfo = substr($pathInfo, 0, $pos);
1178
        }
1179
1180
        $pathInfo = urldecode($pathInfo);
1181
1182
        // try to encode in UTF8 if not so
1183
        // http://w3.org/International/questions/qa-forms-utf-8.html
1184
        if (!preg_match('%^(?:
1185
            [\x09\x0A\x0D\x20-\x7E]              # ASCII
1186
            | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
1187
            | \xE0[\xA0-\xBF][\x80-\xBF]         # excluding overlongs
1188
            | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
1189
            | \xED[\x80-\x9F][\x80-\xBF]         # excluding surrogates
1190
            | \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
1191
            | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
1192
            | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
1193
            )*$%xs', $pathInfo)
1194
        ) {
1195
            $pathInfo = utf8_encode($pathInfo);
1196
        }
1197
1198
        $scriptUrl = $this->getScriptUrl();
1199
        $baseUrl = $this->getBaseUrl();
1200
        if (strpos($pathInfo, $scriptUrl) === 0) {
1201
            $pathInfo = substr($pathInfo, strlen($scriptUrl));
1202
        } elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) {
1203
            $pathInfo = substr($pathInfo, strlen($baseUrl));
1204
        } elseif (($phpSelf = $this->getServerParam('PHP_SELF')) !== null && strpos($phpSelf, $scriptUrl) === 0) {
1205
            $pathInfo = substr($phpSelf, strlen($scriptUrl));
1206
        } else {
1207
            throw new InvalidConfigException('Unable to determine the path info of the current request.');
1208
        }
1209
1210
        if (substr($pathInfo, 0, 1) === '/') {
1211
            $pathInfo = substr($pathInfo, 1);
1212
        }
1213
1214
        return (string) $pathInfo;
1215
    }
1216
1217
    /**
1218
     * Returns the currently requested absolute URL.
1219
     * This is a shortcut to the concatenation of [[hostInfo]] and [[url]].
1220
     * @return string the currently requested absolute URL.
1221
     */
1222
    public function getAbsoluteUrl()
1223
    {
1224
        return $this->getHostInfo() . $this->getUrl();
1225
    }
1226
1227
    private $_url;
1228
1229
    /**
1230
     * Returns the currently requested relative URL.
1231
     * This refers to the portion of the URL that is after the [[hostInfo]] part.
1232
     * It includes the [[queryString]] part if any.
1233
     * @return string the currently requested relative URL. Note that the URI returned may be URL-encoded depending on the client.
1234
     * @throws InvalidConfigException if the URL cannot be determined due to unusual server configuration
1235
     */
1236 9
    public function getUrl()
1237
    {
1238 9
        if ($this->_url === null) {
1239 2
            $this->_url = $this->resolveRequestUri();
1240
        }
1241
1242 9
        return $this->_url;
1243
    }
1244
1245
    /**
1246
     * Sets the currently requested relative URL.
1247
     * The URI must refer to the portion that is after [[hostInfo]].
1248
     * Note that the URI should be URL-encoded.
1249
     * @param string $value the request URI to be set
1250
     */
1251 24
    public function setUrl($value)
1252
    {
1253 24
        $this->_url = $value;
1254 24
    }
1255
1256
    /**
1257
     * Resolves the request URI portion for the currently requested URL.
1258
     * This refers to the portion that is after the [[hostInfo]] part. It includes the [[queryString]] part if any.
1259
     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
1260
     * @return string|bool the request URI portion for the currently requested URL.
1261
     * Note that the URI returned may be URL-encoded depending on the client.
1262
     * @throws InvalidConfigException if the request URI cannot be determined due to unusual server configuration
1263
     */
1264 2
    protected function resolveRequestUri()
1265
    {
1266 2
        $serverParams = $this->getServerParams();
1267
1268 2
        if ($this->hasHeader('x-rewrite-url')) { // IIS
1269
            $requestUri = $this->getHeaderLine('x-rewrite-url');
1270 2
        } elseif (isset($serverParams['REQUEST_URI'])) {
1271 2
            $requestUri = $serverParams['REQUEST_URI'];
1272 2
            if ($requestUri !== '' && $requestUri[0] !== '/') {
1273 2
                $requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri);
1274
            }
1275
        } elseif (isset($serverParams['ORIG_PATH_INFO'])) { // IIS 5.0 CGI
1276
            $requestUri = $serverParams['ORIG_PATH_INFO'];
1277
            if (!empty($serverParams['QUERY_STRING'])) {
1278
                $requestUri .= '?' . $serverParams['QUERY_STRING'];
1279
            }
1280
        } else {
1281
            throw new InvalidConfigException('Unable to determine the request URI.');
1282
        }
1283
1284 2
        return $requestUri;
1285
    }
1286
1287
    /**
1288
     * Returns part of the request URL that is after the question mark.
1289
     * @return string part of the request URL that is after the question mark
1290
     */
1291
    public function getQueryString()
1292
    {
1293
        return $this->getServerParam('QUERY_STRING', '');
1294
    }
1295
1296
    /**
1297
     * Return if the request is sent via secure channel (https).
1298
     * @return bool if the request is sent via secure channel (https)
1299
     */
1300 36
    public function getIsSecureConnection()
1301
    {
1302 36
        $https = $this->getServerParam('HTTPS');
1303 36
        if ($https !== null && (strcasecmp($https, 'on') === 0 || $https == 1)) {
1304 2
            return true;
1305
        }
1306 34
        foreach ($this->secureProtocolHeaders as $header => $values) {
1307 34
            if ($this->hasHeader($header)) {
1308 2
                foreach ($values as $value) {
1309 2
                    if (strcasecmp($this->getHeaderLine($header), $value) === 0) {
1310 34
                        return true;
1311
                    }
1312
                }
1313
            }
1314
        }
1315
1316 32
        return false;
1317
    }
1318
1319
    /**
1320
     * Returns the server name.
1321
     * @return string server name, null if not available
1322
     */
1323 1
    public function getServerName()
1324
    {
1325 1
        return $this->getServerParam('SERVER_NAME');
1326
    }
1327
1328
    /**
1329
     * Returns the server port number.
1330
     * @return int|null server port number, null if not available
1331
     */
1332 2
    public function getServerPort()
1333
    {
1334 2
        $port = $this->getServerParam('SERVER_PORT');
1335 2
        return $port === null ? null : (int) $port;
1336
    }
1337
1338
    /**
1339
     * Returns the URL referrer.
1340
     * @return string|null URL referrer, null if not available
1341
     */
1342
    public function getReferrer()
1343
    {
1344
        if (!$this->hasHeader('Referer')) {
1345
            return null;
1346
        }
1347
        return $this->getHeaderLine('Referer');
1348
    }
1349
1350
    /**
1351
     * Returns the URL origin of a CORS request.
1352
     *
1353
     * The return value is taken from the `Origin` [[getHeaders()|header]] sent by the browser.
1354
     *
1355
     * Note that the origin request header indicates where a fetch originates from.
1356
     * It doesn't include any path information, but only the server name.
1357
     * It is sent with a CORS requests, as well as with POST requests.
1358
     * It is similar to the referer header, but, unlike this header, it doesn't disclose the whole path.
1359
     * Please refer to <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin> for more information.
1360
     *
1361
     * @return string|null URL origin of a CORS request, `null` if not available.
1362
     * @see getHeaders()
1363
     * @since 2.0.13
1364
     */
1365 1
    public function getOrigin()
1366
    {
1367 1
        return $this->getHeaderLine('origin');
1368
    }
1369
1370
    /**
1371
     * Returns the user agent.
1372
     * @return string|null user agent, null if not available
1373
     */
1374
    public function getUserAgent()
1375
    {
1376
        if (!$this->hasHeader('User-Agent')) {
1377
            return null;
1378
        }
1379
        return $this->getHeaderLine('User-Agent');
1380
    }
1381
1382
    /**
1383
     * Returns the user IP address.
1384
     * The IP is determined using headers and / or `$_SERVER` variables.
1385
     * @return string|null user IP address, null if not available
1386
     */
1387 37
    public function getUserIP()
1388
    {
1389 37
        foreach ($this->ipHeaders as $ipHeader) {
1390 37
            if ($this->hasHeader($ipHeader)) {
1391 37
                return trim(explode(',', $this->getHeaderLine($ipHeader))[0]);
1392
            }
1393
        }
1394
1395 36
        return $this->getRemoteIP();
1396
    }
1397
1398
    /**
1399
     * Returns the user host name.
1400
     * The HOST is determined using headers and / or `$_SERVER` variables.
1401
     * @return string|null user host name, null if not available
1402
     */
1403
    public function getUserHost()
1404
    {
1405
        foreach ($this->ipHeaders as $ipHeader) {
1406
            if ($this->hasHeader($ipHeader)) {
1407
                return gethostbyaddr(trim(explode(',', $this->getHeaderLine($ipHeader))[0]));
1408
            }
1409
        }
1410
1411
        return $this->getRemoteHost();
1412
    }
1413
1414
    /**
1415
     * Returns the IP on the other end of this connection.
1416
     * This is always the next hop, any headers are ignored.
1417
     * @return string|null remote IP address, `null` if not available.
1418
     * @since 2.0.13
1419
     */
1420 57
    public function getRemoteIP()
1421
    {
1422 57
        return $this->getServerParam('REMOTE_ADDR');
1423
    }
1424
1425
    /**
1426
     * Returns the host name of the other end of this connection.
1427
     * This is always the next hop, any headers are ignored.
1428
     * @return string|null remote host name, `null` if not available
1429
     * @see getUserHost()
1430
     * @see getRemoteIP()
1431
     * @since 2.0.13
1432
     */
1433
    public function getRemoteHost()
1434
    {
1435
        return $this->getServerParam('REMOTE_HOST');
1436
    }
1437
1438
    /**
1439
     * @return string|null the username sent via HTTP authentication, `null` if the username is not given
1440
     * @see getAuthCredentials() to get both username and password in one call
1441
     */
1442 9
    public function getAuthUser()
1443
    {
1444 9
        return $this->getAuthCredentials()[0];
1445
    }
1446
1447
    /**
1448
     * @return string|null the password sent via HTTP authentication, `null` if the password is not given
1449
     * @see getAuthCredentials() to get both username and password in one call
1450
     */
1451 9
    public function getAuthPassword()
1452
    {
1453 9
        return $this->getAuthCredentials()[1];
1454
    }
1455
1456
    /**
1457
     * @return array that contains exactly two elements:
1458
     * - 0: the username sent via HTTP authentication, `null` if the username is not given
1459
     * - 1: the password sent via HTTP authentication, `null` if the password is not given
1460
     * @see getAuthUser() to get only username
1461
     * @see getAuthPassword() to get only password
1462
     * @since 2.0.13
1463
     */
1464 19
    public function getAuthCredentials()
1465
    {
1466 19
        $username = $this->getServerParam('PHP_AUTH_USER');
1467 19
        $password = $this->getServerParam('PHP_AUTH_PW');
1468 19
        if ($username !== null || $password !== null) {
1469 11
            return [$username, $password];
1470
        }
1471
1472
        /*
1473
         * Apache with php-cgi does not pass HTTP Basic authentication to PHP by default.
1474
         * To make it work, add the following line to to your .htaccess file:
1475
         *
1476
         * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
1477
         */
1478 8
        $auth_token = $this->getHeader('HTTP_AUTHORIZATION') ?: $this->getHeader('REDIRECT_HTTP_AUTHORIZATION');
1479 8
        if ($auth_token !== [] && strpos(strtolower($auth_token[0]), 'basic') === 0) {
1480 8
            $parts = array_map(function ($value) {
1481 8
                return strlen($value) === 0 ? null : $value;
1482 8
            }, explode(':', base64_decode(mb_substr($auth_token[0], 6)), 2));
1483
1484 8
            if (count($parts) < 2) {
1485 2
                return [$parts[0], null];
1486
            }
1487
1488 6
            return $parts;
1489
        }
1490
1491
        return [null, null];
1492
    }
1493
1494
    private $_port;
1495
1496
    /**
1497
     * Returns the port to use for insecure requests.
1498
     * Defaults to 80, or the port specified by the server if the current
1499
     * request is insecure.
1500
     * @return int port number for insecure requests.
1501
     * @see setPort()
1502
     */
1503 1
    public function getPort()
1504
    {
1505 1
        if ($this->_port === null) {
1506 1
            $serverPort = $this->getServerPort();
1507 1
            $this->_port = !$this->getIsSecureConnection() && $serverPort !== null ? $serverPort : 80;
1508
        }
1509
1510 1
        return $this->_port;
1511
    }
1512
1513
    /**
1514
     * Sets the port to use for insecure requests.
1515
     * This setter is provided in case a custom port is necessary for certain
1516
     * server configurations.
1517
     * @param int $value port number.
1518
     */
1519
    public function setPort($value)
1520
    {
1521
        if ($value != $this->_port) {
1522
            $this->_port = (int) $value;
1523
            $this->_hostInfo = null;
1524
        }
1525
    }
1526
1527
    private $_securePort;
1528
1529
    /**
1530
     * Returns the port to use for secure requests.
1531
     * Defaults to 443, or the port specified by the server if the current
1532
     * request is secure.
1533
     * @return int port number for secure requests.
1534
     * @see setSecurePort()
1535
     */
1536
    public function getSecurePort()
1537
    {
1538
        if ($this->_securePort === null) {
1539
            $serverPort = $this->getServerPort();
1540
            $this->_securePort = $this->getIsSecureConnection() && $serverPort !== null ? $serverPort : 443;
1541
        }
1542
1543
        return $this->_securePort;
1544
    }
1545
1546
    /**
1547
     * Sets the port to use for secure requests.
1548
     * This setter is provided in case a custom port is necessary for certain
1549
     * server configurations.
1550
     * @param int $value port number.
1551
     */
1552
    public function setSecurePort($value)
1553
    {
1554
        if ($value != $this->_securePort) {
1555
            $this->_securePort = (int) $value;
1556
            $this->_hostInfo = null;
1557
        }
1558
    }
1559
1560
    private $_contentTypes;
1561
1562
    /**
1563
     * Returns the content types acceptable by the end user.
1564
     *
1565
     * This is determined by the `Accept` HTTP header. For example,
1566
     *
1567
     * ```php
1568
     * $_SERVER['HTTP_ACCEPT'] = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1569
     * $types = $request->getAcceptableContentTypes();
1570
     * print_r($types);
1571
     * // displays:
1572
     * // [
1573
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1574
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1575
     * //           'text/plain' => ['q' => 0.5],
1576
     * // ]
1577
     * ```
1578
     *
1579
     * @return array the content types ordered by the quality score. Types with the highest scores
1580
     * will be returned first. The array keys are the content types, while the array values
1581
     * are the corresponding quality score and other parameters as given in the header.
1582
     */
1583 3
    public function getAcceptableContentTypes()
1584
    {
1585 3
        if ($this->_contentTypes === null) {
1586 2
            if ($this->hasHeader('Accept')) {
1587 2
                $this->_contentTypes = $this->parseAcceptHeader($this->getHeaderLine('Accept'));
1588
            } else {
1589 1
                $this->_contentTypes = [];
1590
            }
1591
        }
1592
1593 3
        return $this->_contentTypes;
1594
    }
1595
1596
    /**
1597
     * Sets the acceptable content types.
1598
     * Please refer to [[getAcceptableContentTypes()]] on the format of the parameter.
1599
     * @param array $value the content types that are acceptable by the end user. They should
1600
     * be ordered by the preference level.
1601
     * @see getAcceptableContentTypes()
1602
     * @see parseAcceptHeader()
1603
     */
1604 1
    public function setAcceptableContentTypes($value)
1605
    {
1606 1
        $this->_contentTypes = $value;
1607 1
    }
1608
1609
    /**
1610
     * Returns request content-type
1611
     * The Content-Type header field indicates the MIME type of the data
1612
     * contained in [[getBody()]] or, in the case of the HEAD method, the
1613
     * media type that would have been sent had the request been a GET.
1614
     * For the MIME-types the user expects in response, see [[acceptableContentTypes]].
1615
     * @return string request content-type. Empty string is returned if this information is not available.
1616
     * @link https://tools.ietf.org/html/rfc2616#section-14.17
1617
     * HTTP 1.1 header field definitions
1618
     */
1619 12
    public function getContentType()
1620
    {
1621 12
        return $this->getHeaderLine('Content-Type');
1622
    }
1623
1624
    private $_languages;
1625
1626
    /**
1627
     * Returns the languages acceptable by the end user.
1628
     * This is determined by the `Accept-Language` HTTP header.
1629
     * @return array the languages ordered by the preference level. The first element
1630
     * represents the most preferred language.
1631
     */
1632 1
    public function getAcceptableLanguages()
1633
    {
1634 1
        if ($this->_languages === null) {
1635
            if ($this->hasHeader('Accept-Language')) {
1636
                $this->_languages = array_keys($this->parseAcceptHeader($this->getHeaderLine('Accept-Language')));
1637
            } else {
1638
                $this->_languages = [];
1639
            }
1640
        }
1641
1642 1
        return $this->_languages;
1643
    }
1644
1645
    /**
1646
     * @param array $value the languages that are acceptable by the end user. They should
1647
     * be ordered by the preference level.
1648
     */
1649 1
    public function setAcceptableLanguages($value)
1650
    {
1651 1
        $this->_languages = $value;
1652 1
    }
1653
1654
    /**
1655
     * Parses the given `Accept` (or `Accept-Language`) header.
1656
     *
1657
     * This method will return the acceptable values with their quality scores and the corresponding parameters
1658
     * as specified in the given `Accept` header. The array keys of the return value are the acceptable values,
1659
     * while the array values consisting of the corresponding quality scores and parameters. The acceptable
1660
     * values with the highest quality scores will be returned first. For example,
1661
     *
1662
     * ```php
1663
     * $header = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1664
     * $accepts = $request->parseAcceptHeader($header);
1665
     * print_r($accepts);
1666
     * // displays:
1667
     * // [
1668
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1669
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1670
     * //           'text/plain' => ['q' => 0.5],
1671
     * // ]
1672
     * ```
1673
     *
1674
     * @param string $header the header to be parsed
1675
     * @return array the acceptable values ordered by their quality score. The values with the highest scores
1676
     * will be returned first.
1677
     */
1678 3
    public function parseAcceptHeader($header)
1679
    {
1680 3
        $accepts = [];
1681 3
        foreach (explode(',', $header) as $i => $part) {
1682 3
            $params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY);
1683 3
            if (empty($params)) {
1684 1
                continue;
1685
            }
1686
            $values = [
1687 3
                'q' => [$i, array_shift($params), 1],
1688
            ];
1689 3
            foreach ($params as $param) {
1690 2
                if (strpos($param, '=') !== false) {
1691 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...
1692 2
                    if ($key === 'q') {
1693 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...
1694
                    } else {
1695 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...
1696
                    }
1697
                } else {
1698 2
                    $values[] = $param;
1699
                }
1700
            }
1701 3
            $accepts[] = $values;
1702
        }
1703
1704 3
        usort($accepts, function ($a, $b) {
1705 3
            $a = $a['q']; // index, name, q
1706 3
            $b = $b['q'];
1707 3
            if ($a[2] > $b[2]) {
1708 2
                return -1;
1709
            }
1710
1711 2
            if ($a[2] < $b[2]) {
1712 1
                return 1;
1713
            }
1714
1715 2
            if ($a[1] === $b[1]) {
1716
                return $a[0] > $b[0] ? 1 : -1;
1717
            }
1718
1719 2
            if ($a[1] === '*/*') {
1720
                return 1;
1721
            }
1722
1723 2
            if ($b[1] === '*/*') {
1724
                return -1;
1725
            }
1726
1727 2
            $wa = $a[1][strlen($a[1]) - 1] === '*';
1728 2
            $wb = $b[1][strlen($b[1]) - 1] === '*';
1729 2
            if ($wa xor $wb) {
1730
                return $wa ? 1 : -1;
1731
            }
1732
1733 2
            return $a[0] > $b[0] ? 1 : -1;
1734 3
        });
1735
1736 3
        $result = [];
1737 3
        foreach ($accepts as $accept) {
1738 3
            $name = $accept['q'][1];
1739 3
            $accept['q'] = $accept['q'][2];
1740 3
            $result[$name] = $accept;
1741
        }
1742
1743 3
        return $result;
1744
    }
1745
1746
    /**
1747
     * Returns the user-preferred language that should be used by this application.
1748
     * The language resolution is based on the user preferred languages and the languages
1749
     * supported by the application. The method will try to find the best match.
1750
     * @param array $languages a list of the languages supported by the application. If this is empty, the current
1751
     * application language will be returned without further processing.
1752
     * @return string the language that the application should use.
1753
     */
1754 1
    public function getPreferredLanguage(array $languages = [])
1755
    {
1756 1
        if (empty($languages)) {
1757 1
            return Yii::$app->language;
1758
        }
1759 1
        foreach ($this->getAcceptableLanguages() as $acceptableLanguage) {
1760 1
            $acceptableLanguage = str_replace('_', '-', strtolower($acceptableLanguage));
1761 1
            foreach ($languages as $language) {
1762 1
                $normalizedLanguage = str_replace('_', '-', strtolower($language));
1763
1764
                if (
1765 1
                    $normalizedLanguage === $acceptableLanguage // en-us==en-us
1766 1
                    || strpos($acceptableLanguage, $normalizedLanguage . '-') === 0 // en==en-us
1767 1
                    || strpos($normalizedLanguage, $acceptableLanguage . '-') === 0 // en-us==en
1768
                ) {
1769 1
                    return $language;
1770
                }
1771
            }
1772
        }
1773
1774 1
        return reset($languages);
1775
    }
1776
1777
    /**
1778
     * Gets the Etags.
1779
     *
1780
     * @return array The entity tags
1781
     */
1782
    public function getETags()
1783
    {
1784
        if ($this->hasHeader('if-none-match')) {
1785
            return preg_split('/[\s,]+/', str_replace('-gzip', '', $this->getHeaderLine('if-none-match')), -1, PREG_SPLIT_NO_EMPTY);
1786
        }
1787
1788
        return [];
1789
    }
1790
1791
    /**
1792
     * Returns the cookie collection.
1793
     *
1794
     * Through the returned cookie collection, you may access a cookie using the following syntax:
1795
     *
1796
     * ```php
1797
     * $cookie = $request->cookies['name']
1798
     * if ($cookie !== null) {
1799
     *     $value = $cookie->value;
1800
     * }
1801
     *
1802
     * // alternatively
1803
     * $value = $request->cookies->getValue('name');
1804
     * ```
1805
     *
1806
     * @return CookieCollection the cookie collection.
1807
     */
1808 45
    public function getCookies()
1809
    {
1810 45
        if ($this->_cookies === null) {
1811 45
            $this->_cookies = new CookieCollection($this->loadCookies(), [
1812 44
                'readOnly' => true,
1813
            ]);
1814
        }
1815
1816 44
        return $this->_cookies;
1817
    }
1818
1819
    /**
1820
     * Converts [[cookieParams]] into an array of [[Cookie]].
1821
     * @return array the cookies obtained from request
1822
     * @throws InvalidConfigException if [[cookieValidationKey]] is not set when [[enableCookieValidation]] is true
1823
     */
1824 45
    protected function loadCookies()
1825
    {
1826 45
        $cookies = [];
1827 45
        if ($this->enableCookieValidation) {
1828 44
            if ($this->cookieValidationKey == '') {
1829 1
                throw new InvalidConfigException(get_class($this) . '::$cookieValidationKey must be configured with a secret key.');
1830
            }
1831 43
            foreach ($this->getCookieParams() as $name => $value) {
1832
                if (!is_string($value)) {
1833
                    continue;
1834
                }
1835
                $data = Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey);
1836
                if ($data === false) {
1837
                    continue;
1838
                }
1839
                $data = @unserialize($data);
1840
                if (is_array($data) && isset($data[0], $data[1]) && $data[0] === $name) {
1841
                    $cookies[$name] = Yii::createObject([
1842 43
                        'class' => \yii\http\Cookie::class,
1843
                        'name' => $name,
1844
                        'value' => $data[1],
1845
                        'expire' => null,
1846
                    ]);
1847
                }
1848
            }
1849
        } else {
1850 1
            foreach ($this->getCookieParams() as $name => $value) {
1851 1
                $cookies[$name] = Yii::createObject([
1852 1
                    'class' => \yii\http\Cookie::class,
1853 1
                    'name' => $name,
1854 1
                    'value' => $value,
1855
                    'expire' => null,
1856
                ]);
1857
            }
1858
        }
1859
1860 44
        return $cookies;
1861
    }
1862
1863
    /**
1864
     * {@inheritdoc}
1865
     * @since 2.1.0
1866
     */
1867 12
    public function getUploadedFiles()
1868
    {
1869 12
        if ($this->_uploadedFiles === null) {
1870 6
            $this->getParsedBody(); // uploaded files are the part of the body and may be set while its parsing
1871 6
            if ($this->_uploadedFiles === null) {
1872 6
                $this->_uploadedFiles = $this->defaultUploadedFiles();
1873
            }
1874
        }
1875 12
        return $this->_uploadedFiles;
1876
    }
1877
1878
    /**
1879
     * Sets uploaded files for this request.
1880
     * Data structure for the uploaded files should follow [PSR-7 Uploaded Files specs](http://www.php-fig.org/psr/psr-7/#16-uploaded-files).
1881
     * @param array|null $uploadedFiles uploaded files.
1882
     * @since 2.1.0
1883
     */
1884 6
    public function setUploadedFiles($uploadedFiles)
1885
    {
1886 6
        $this->_uploadedFiles = $uploadedFiles;
0 ignored issues
show
Documentation Bug introduced by
It seems like $uploadedFiles can be null. However, the property $_uploadedFiles is declared as array. Maybe change the type of the property to array|null or add a type check?

Our type inference engine has found an assignment of a scalar value (like a string, an integer or null) to a property which is an array.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.

To type hint that a parameter can be either an array or null, you can set a type hint of array and a default value of null. The PHP interpreter will then accept both an array or null for that parameter.

function aContainsB(array $needle = null, array  $haystack) {
    if (!$needle) {
        return false;
    }

    return array_intersect($haystack, $needle) == $haystack;
}

The function can be called with either null or an array for the parameter $needle but will only accept an array as $haystack.

Loading history...
1887 6
    }
1888
1889
    /**
1890
     * {@inheritdoc}
1891
     * @since 2.1.0
1892
     */
1893
    public function withUploadedFiles(array $uploadedFiles)
1894
    {
1895
        $newInstance = clone $this;
1896
        $newInstance->setUploadedFiles($uploadedFiles);
1897
        return $newInstance;
1898
    }
1899
1900
    /**
1901
     * Initializes default uploaded files data structure parsing super-global $_FILES.
1902
     * @see http://www.php-fig.org/psr/psr-7/#16-uploaded-files
1903
     * @return array uploaded files.
1904
     * @since 2.1.0
1905
     */
1906 6
    protected function defaultUploadedFiles()
1907
    {
1908 6
        $files = [];
1909 6
        foreach ($_FILES as $class => $info) {
1910 3
            $files[$class] = [];
1911 3
            $this->populateUploadedFileRecursive($files[$class], $info['name'], $info['tmp_name'], $info['type'], $info['size'], $info['error']);
1912
        }
1913
1914 6
        return $files;
1915
    }
1916
1917
    /**
1918
     * Populates uploaded files array from $_FILE data structure recursively.
1919
     * @param array $files uploaded files array to be populated.
1920
     * @param mixed $names file names provided by PHP
1921
     * @param mixed $tempNames temporary file names provided by PHP
1922
     * @param mixed $types file types provided by PHP
1923
     * @param mixed $sizes file sizes provided by PHP
1924
     * @param mixed $errors uploading issues provided by PHP
1925
     * @since 2.1.0
1926
     */
1927 3
    private function populateUploadedFileRecursive(&$files, $names, $tempNames, $types, $sizes, $errors)
1928
    {
1929 3
        if (is_array($names)) {
1930 2
            foreach ($names as $i => $name) {
1931 2
                $files[$i] = [];
1932 2
                $this->populateUploadedFileRecursive($files[$i], $name, $tempNames[$i], $types[$i], $sizes[$i], $errors[$i]);
1933
            }
1934
        } else {
1935 3
            $files = Yii::createObject([
1936 3
                'class' => $this->uploadedFileClass,
1937 3
                'clientFilename' => $names,
1938 3
                'tempFilename' => $tempNames,
1939 3
                'clientMediaType' => $types,
1940 3
                'size' => $sizes,
1941 3
                'error' => $errors,
1942
            ]);
1943
        }
1944 3
    }
1945
1946
    /**
1947
     * Returns an uploaded file according to the given name.
1948
     * Name can be either a string HTML form input name, e.g. 'Item[file]' or array path, e.g. `['Item', 'file']`.
1949
     * Note: this method returns `null` in case given name matches multiple files.
1950
     * @param string|array $name HTML form input name or array path.
1951
     * @return UploadedFileInterface|null uploaded file instance, `null` - if not found.
1952
     * @since 2.1.0
1953
     */
1954 1
    public function getUploadedFileByName($name)
1955
    {
1956 1
        $uploadedFile = $this->findUploadedFiles($name);
1957 1
        if ($uploadedFile instanceof UploadedFileInterface) {
1958 1
            return $uploadedFile;
1959
        }
1960 1
        return null;
1961
    }
1962
1963
    /**
1964
     * Returns the list of uploaded file instances according to the given name.
1965
     * Name can be either a string HTML form input name, e.g. 'Item[file]' or array path, e.g. `['Item', 'file']`.
1966
     * Note: this method does NOT preserve uploaded files structure - it returns instances in single-level array (list),
1967
     * even if they are set by nested keys.
1968
     * @param string|array $name HTML form input name or array path.
1969
     * @return UploadedFileInterface[] list of uploaded file instances.
1970
     * @since 2.1.0
1971
     */
1972 1
    public function getUploadedFilesByName($name)
1973
    {
1974 1
        $uploadedFiles = $this->findUploadedFiles($name);
1975 1
        if ($uploadedFiles === null) {
1976
            return [];
1977
        }
1978 1
        if ($uploadedFiles instanceof UploadedFileInterface) {
1979 1
            return [$uploadedFiles];
1980
        }
1981 1
        return $this->reduceUploadedFiles($uploadedFiles);
1982
    }
1983
1984
    /**
1985
     * Finds the uploaded file or set of uploaded files inside [[$uploadedFiles]] according to given name.
1986
     * Name can be either a string HTML form input name, e.g. 'Item[file]' or array path, e.g. `['Item', 'file']`.
1987
     * @param string|array $name HTML form input name or array path.
1988
     * @return UploadedFileInterface|array|null
1989
     * @since 2.1.0
1990
     */
1991 2
    private function findUploadedFiles($name)
1992
    {
1993 2
        if (!is_array($name)) {
1994 2
            $name = preg_split('/\\]\\[|\\[|\\]/s', $name, -1, PREG_SPLIT_NO_EMPTY);
1995
        }
1996 2
        return ArrayHelper::getValue($this->getUploadedFiles(), $name);
1997
    }
1998
1999
    /**
2000
     * Reduces complex uploaded files structure to the single-level array (list).
2001
     * @param array $uploadedFiles raw set of the uploaded files.
2002
     * @return UploadedFileInterface[] list of uploaded files.
2003
     * @since 2.1.0
2004
     */
2005
    private function reduceUploadedFiles($uploadedFiles)
2006
    {
2007 1
        return array_reduce($uploadedFiles, function ($carry, $item) {
2008 1
            if ($item instanceof UploadedFileInterface) {
2009 1
                $carry[] = $item;
2010
            } else {
2011 1
                $carry = array_merge($carry, $this->reduceUploadedFiles($item));
2012
            }
2013 1
            return $carry;
2014 1
        }, []);
2015
    }
2016
2017
    private $_csrfToken;
2018
2019
    /**
2020
     * Returns the token used to perform CSRF validation.
2021
     *
2022
     * This token is generated in a way to prevent [BREACH attacks](http://breachattack.com/). It may be passed
2023
     * along via a hidden field of an HTML form or an HTTP header value to support CSRF validation.
2024
     * @param bool $regenerate whether to regenerate CSRF token. When this parameter is true, each time
2025
     * this method is called, a new CSRF token will be generated and persisted (in session or cookie).
2026
     * @return string the token used to perform CSRF validation.
2027
     */
2028 51
    public function getCsrfToken($regenerate = false)
2029
    {
2030 51
        if ($this->_csrfToken === null || $regenerate) {
2031 51
            $token = $this->loadCsrfToken();
2032 50
            if ($regenerate || empty($token)) {
2033 47
                $token = $this->generateCsrfToken();
2034
            }
2035 50
            $this->_csrfToken = Yii::$app->security->maskToken($token);
2036
        }
2037
2038 50
        return $this->_csrfToken;
2039
    }
2040
2041
    /**
2042
     * Loads the CSRF token from cookie or session.
2043
     * @return string the CSRF token loaded from cookie or session. Null is returned if the cookie or session
2044
     * does not have CSRF token.
2045
     */
2046 51
    protected function loadCsrfToken()
2047
    {
2048 51
        if ($this->enableCsrfCookie) {
2049 47
            return $this->getCookies()->getValue($this->csrfParam);
2050
        }
2051
2052 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...
2053
    }
2054
2055
    /**
2056
     * Generates an unmasked random token used to perform CSRF validation.
2057
     * @return string the random token for CSRF validation.
2058
     */
2059 47
    protected function generateCsrfToken()
2060
    {
2061 47
        $token = Yii::$app->getSecurity()->generateRandomString();
2062 47
        if ($this->enableCsrfCookie) {
2063 46
            $cookie = $this->createCsrfCookie($token);
2064 46
            Yii::$app->getResponse()->getCookies()->add($cookie);
2065
        } else {
2066 1
            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...
2067
        }
2068
2069 47
        return $token;
2070
    }
2071
2072
    /**
2073
     * @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent.
2074
     */
2075 3
    public function getCsrfTokenFromHeader()
2076
    {
2077 3
        return $this->getHeaderLine(static::CSRF_HEADER);
2078
    }
2079
2080
    /**
2081
     * Creates a cookie with a randomly generated CSRF token.
2082
     * Initial values specified in [[csrfCookie]] will be applied to the generated cookie.
2083
     * @param string $token the CSRF token
2084
     * @return Cookie the generated cookie
2085
     * @see enableCsrfValidation
2086
     */
2087 46
    protected function createCsrfCookie($token)
2088
    {
2089 46
        $options = $this->csrfCookie;
2090 46
        return Yii::createObject(array_merge($options, [
2091 46
            'class' => \yii\http\Cookie::class,
2092 46
            'name' => $this->csrfParam,
2093 46
            'value' => $token,
2094
        ]));
2095
    }
2096
2097
    /**
2098
     * Performs the CSRF validation.
2099
     *
2100
     * This method will validate the user-provided CSRF token by comparing it with the one stored in cookie or session.
2101
     * This method is mainly called in [[Controller::beforeAction()]].
2102
     *
2103
     * Note that the method will NOT perform CSRF validation if [[enableCsrfValidation]] is false or the HTTP method
2104
     * is among GET, HEAD or OPTIONS.
2105
     *
2106
     * @param string $clientSuppliedToken the user-provided CSRF token to be validated. If null, the token will be retrieved from
2107
     * the [[csrfParam]] POST field or HTTP header.
2108
     * This parameter is available since version 2.0.4.
2109
     * @return bool whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true.
2110
     */
2111 36
    public function validateCsrfToken($clientSuppliedToken = null)
2112
    {
2113 36
        $method = $this->getMethod();
2114
        // only validate CSRF token on non-"safe" methods https://tools.ietf.org/html/rfc2616#section-9.1.1
2115 36
        if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
2116 35
            return true;
2117
        }
2118
2119 4
        $trueToken = $this->getCsrfToken();
2120
2121 4
        if ($clientSuppliedToken !== null) {
2122 2
            return $this->validateCsrfTokenInternal($clientSuppliedToken, $trueToken);
2123
        }
2124
2125 3
        return $this->validateCsrfTokenInternal($this->getParsedBodyParam($this->csrfParam), $trueToken)
2126 3
            || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
2127
    }
2128
2129
    /**
2130
     * Validates CSRF token.
2131
     *
2132
     * @param string $clientSuppliedToken The masked client-supplied token.
2133
     * @param string $trueToken The masked true token.
2134
     * @return bool
2135
     */
2136 4
    private function validateCsrfTokenInternal($clientSuppliedToken, $trueToken)
2137
    {
2138 4
        if (!is_string($clientSuppliedToken)) {
2139 3
            return false;
2140
        }
2141
2142 4
        $security = Yii::$app->security;
2143
2144 4
        return $security->unmaskToken($clientSuppliedToken) === $security->unmaskToken($trueToken);
2145
    }
2146
2147
    /**
2148
     * {@inheritdoc}
2149
     * @since 2.1.0
2150
     */
2151 3
    public function getAttributes()
2152
    {
2153 3
        if ($this->_attributes === null) {
2154
            $this->_attributes = $this->defaultAttributes();
2155
        }
2156 3
        return $this->_attributes;
2157
    }
2158
2159
    /**
2160
     * @param array $attributes attributes derived from the request.
2161
     */
2162 3
    public function setAttributes(array $attributes)
2163
    {
2164 3
        $this->_attributes = $attributes;
2165 3
    }
2166
2167
    /**
2168
     * {@inheritdoc}
2169
     * @since 2.1.0
2170
     */
2171 1
    public function getAttribute($name, $default = null)
2172
    {
2173 1
        $attributes = $this->getAttributes();
2174 1
        if (!array_key_exists($name, $attributes)) {
2175 1
            return $default;
2176
        }
2177
2178 1
        return $attributes[$name];
2179
    }
2180
2181
    /**
2182
     * {@inheritdoc}
2183
     * @since 2.1.0
2184
     */
2185 1
    public function withAttribute($name, $value)
2186
    {
2187 1
        $attributes = $this->getAttributes();
2188 1
        if (array_key_exists($name, $attributes) && $attributes[$name] === $value) {
2189
            return $this;
2190
        }
2191
2192 1
        $attributes[$name] = $value;
2193
2194 1
        $newInstance = clone $this;
2195 1
        $newInstance->setAttributes($attributes);
2196 1
        return $newInstance;
2197
    }
2198
2199
    /**
2200
     * {@inheritdoc}
2201
     * @since 2.1.0
2202
     */
2203 1
    public function withoutAttribute($name)
2204
    {
2205 1
        $attributes = $this->getAttributes();
2206 1
        if (!array_key_exists($name, $attributes)) {
2207
            return $this;
2208
        }
2209
2210 1
        unset($attributes[$name]);
2211
2212 1
        $newInstance = clone $this;
2213 1
        $newInstance->setAttributes($attributes);
2214 1
        return $newInstance;
2215
    }
2216
2217
    /**
2218
     * Returns default server request attributes to be used in case they are not explicitly set.
2219
     * @return array attributes derived from the request.
2220
     * @since 2.1.0
2221
     */
2222
    protected function defaultAttributes()
2223
    {
2224
        return [];
2225
    }
2226
2227
    /**
2228
     * {@inheritdoc}
2229
     */
2230 3
    public function __clone()
2231
    {
2232 3
        parent::__clone();
2233
2234 3
        $this->cloneHttpMessageInternals();
2235
2236 3
        if (is_object($this->_cookies)) {
2237
            $this->_cookies = clone $this->_cookies;
2238
        }
2239 3
    }
2240
}
2241