Completed
Push — 2.1 ( 8ce498...854ae7 )
by
unknown
12:21
created

Request::setScriptUrl()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

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

Loading history...
695 7
            } elseif (isset($this->parsers['*'])) {
696
                $parser = Yii::createObject($this->parsers['*']);
697
                if (!($parser instanceof RequestParserInterface)) {
698
                    throw new InvalidConfigException('The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface.');
699
                }
700
                $this->_parsedBody = $parser->parse($this);
701 7
            } elseif ($this->getMethod() === 'POST') {
702 6
                if ($contentType !== 'application/x-www-form-urlencoded' && $contentType !== 'multipart/form-data') {
703 1
                    throw new UnsupportedMediaTypeHttpException();
704
                }
705
                // PHP has already parsed the body so we have all params in $_POST
706 6
                $this->_parsedBody = $_POST;
707
708 6
                if ($contentType === 'multipart/form-data') {
709 6
                    $this->_parsedBody = ArrayHelper::merge($this->_parsedBody, $this->getUploadedFiles());
0 ignored issues
show
Documentation Bug introduced by
It seems like \yii\helpers\ArrayHelper...is->getUploadedFiles()) of type array is incompatible with the declared type boolean of property $_parsedBody.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

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

Loading history...
710
                }
711 2
            } elseif (empty($contentType) && $this->getBody()->getSize() === 0) {
712 1
                $this->_parsedBody = null;
713
            } else {
714 2
                if ($contentType !== 'application/x-www-form-urlencoded') {
715 2
                    throw new UnsupportedMediaTypeHttpException();
716
                }
717 1
                $this->_parsedBody = [];
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type boolean of property $_parsedBody.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

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

Loading history...
718 1
                mb_parse_str($this->getBody()->__toString(), $this->_parsedBody);
719
            }
720
        }
721
722 12
        return $this->_parsedBody;
723
    }
724
725
    /**
726
     * Sets the request body parameters.
727
     * @param array $values the request body parameters (name-value pairs)
728
     * @see getParsedBodyParam()
729
     * @see getParsedBody()
730
     */
731 4
    public function setParsedBody($values)
732
    {
733 4
        $this->_parsedBody = $values;
0 ignored issues
show
Documentation Bug introduced by
It seems like $values of type array is incompatible with the declared type boolean of property $_parsedBody.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

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

Loading history...
734 4
    }
735
736
    /**
737
     * {@inheritdoc}
738
     * @since 2.1.0
739
     */
740
    public function withParsedBody($data)
741
    {
742
        $newInstance = clone $this;
743
        $newInstance->setParsedBody($data);
0 ignored issues
show
Bug introduced by
It seems like $data defined by parameter $data on line 740 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...
744
        return $newInstance;
745
    }
746
747
    /**
748
     * Returns the named request body parameter value.
749
     * If the parameter does not exist, the second parameter passed to this method will be returned.
750
     * @param string $name the parameter name
751
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
752
     * @return mixed the parameter value
753
     * @see getParsedBody()
754
     * @see setParsedBody()
755
     */
756 5
    public function getParsedBodyParam($name, $defaultValue = null)
757
    {
758 5
        $params = $this->getParsedBody();
759
760 5
        if (is_object($params)) {
761
            // unable to use `ArrayHelper::getValue()` due to different dots in key logic and lack of exception handling
762
            try {
763 1
                return $params->{$name};
764 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...
765 1
                return $defaultValue;
766
            }
767
        }
768
769 5
        return isset($params[$name]) ? $params[$name] : $defaultValue;
770
    }
771
772
    /**
773
     * Returns POST parameter with a given name. If name isn't specified, returns an array of all POST parameters.
774
     *
775
     * @param string $name the parameter name
776
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
777
     * @return array|mixed
778
     */
779
    public function post($name = null, $defaultValue = null)
780
    {
781
        if ($name === null) {
782
            return $this->getParsedBody();
783
        }
784
785
        return $this->getParsedBodyParam($name, $defaultValue);
786
    }
787
788
    private $_queryParams;
789
790
    /**
791
     * Returns the request parameters given in the [[queryString]].
792
     *
793
     * This method will return the contents of `$_GET` if params where not explicitly set.
794
     * @return array the request GET parameter values.
795
     * @see setQueryParams()
796
     */
797 18
    public function getQueryParams()
798
    {
799 18
        if ($this->_queryParams === null) {
800 14
            return $_GET;
801
        }
802
803 5
        return $this->_queryParams;
804
    }
805
806
    /**
807
     * Sets the request [[queryString]] parameters.
808
     * @param array $values the request query parameters (name-value pairs)
809
     * @see getQueryParam()
810
     * @see getQueryParams()
811
     */
812 5
    public function setQueryParams($values)
813
    {
814 5
        $this->_queryParams = $values;
815 5
    }
816
817
    /**
818
     * {@inheritdoc}
819
     */
820
    public function withQueryParams(array $query)
821
    {
822
        if ($this->getQueryParams() === $query) {
823
            return $this;
824
        }
825
826
        $newInstance = clone $this;
827
        $newInstance->setQueryParams($query);
828
        return $newInstance;
829
    }
830
831
    /**
832
     * Returns GET parameter with a given name. If name isn't specified, returns an array of all GET parameters.
833
     *
834
     * @param string $name the parameter name
835
     * @param mixed $defaultValue the default parameter value if the parameter does not exist.
836
     * @return array|mixed
837
     */
838 6
    public function get($name = null, $defaultValue = null)
839
    {
840 6
        if ($name === null) {
841
            return $this->getQueryParams();
842
        }
843
844 6
        return $this->getQueryParam($name, $defaultValue);
845
    }
846
847
    /**
848
     * Returns the named GET parameter value.
849
     * If the GET parameter does not exist, the second parameter passed to this method will be returned.
850
     * @param string $name the GET parameter name.
851
     * @param mixed $defaultValue the default parameter value if the GET parameter does not exist.
852
     * @return mixed the GET parameter value
853
     * @see getParsedBodyParam()
854
     */
855 9
    public function getQueryParam($name, $defaultValue = null)
856
    {
857 9
        $params = $this->getQueryParams();
858
859 9
        return isset($params[$name]) ? $params[$name] : $defaultValue;
860
    }
861
862
    /**
863
     * Sets the data related to the incoming request environment.
864
     * @param array $serverParams server parameters.
865
     * @since 2.1.0
866
     */
867 4
    public function setServerParams(array $serverParams)
868
    {
869 4
        $this->_serverParams = $serverParams;
870 4
    }
871
872
    /**
873
     * {@inheritdoc}
874
     * @since 2.1.0
875
     */
876 122
    public function getServerParams()
877
    {
878 122
        if ($this->_serverParams === null) {
879 119
            $this->_serverParams = $_SERVER;
880
        }
881 122
        return $this->_serverParams;
882
    }
883
884
    /**
885
     * Return the server environment parameter by name.
886
     * @param string $name parameter name.
887
     * @param mixed $default default value to return if the parameter does not exist.
888
     * @return mixed parameter value.
889
     * @since 2.1.0
890
     */
891 122
    public function getServerParam($name, $default = null)
892
    {
893 122
        $params = $this->getServerParams();
894 122
        if (!isset($params[$name])) {
895 102
            return $default;
896
        }
897 43
        return $params[$name];
898
    }
899
900
    /**
901
     * Specifies cookies.
902
     * @param array $cookies array of key/value pairs representing cookies.
903
     * @since 2.1.0
904
     */
905
    public function setCookieParams(array $cookies)
906
    {
907
        $this->_cookieParams = $cookies;
908
        $this->_cookies = null;
909
    }
910
911
    /**
912
     * {@inheritdoc}
913
     * @since 2.1.0
914
     */
915 45
    public function getCookieParams()
916
    {
917 45
        if ($this->_cookieParams === null) {
918 45
            $this->_cookieParams = $_COOKIE;
919
        }
920 45
        return $this->_cookieParams;
921
    }
922
923
    /**
924
     * {@inheritdoc}
925
     * @since 2.1.0
926
     */
927
    public function withCookieParams(array $cookies)
928
    {
929
        if ($this->getCookieParams() === $cookies) {
930
            return $this;
931
        }
932
933
        $newInstance = clone $this;
934
        $newInstance->setCookieParams($cookies);
935
        return $newInstance;
936
    }
937
938
    private $_hostInfo;
939
    private $_hostName;
940
941
    /**
942
     * Returns the schema and host part of the current request URL.
943
     *
944
     * The returned URL does not have an ending slash.
945
     *
946
     * By default this value is based on the user request information. This method will
947
     * return the value of `$_SERVER['HTTP_HOST']` if it is available or `$_SERVER['SERVER_NAME']` if not.
948
     * You may want to check out the [PHP documentation](http://php.net/manual/en/reserved.variables.server.php)
949
     * for more information on these variables.
950
     *
951
     * You may explicitly specify it by setting the [[setHostInfo()|hostInfo]] property.
952
     *
953
     * > Warning: Dependent on the server configuration this information may not be
954
     * > reliable and [may be faked by the user sending the HTTP request](https://www.acunetix.com/vulnerabilities/web/host-header-attack).
955
     * > If the webserver is configured to serve the same site independent of the value of
956
     * > the `Host` header, this value is not reliable. In such situations you should either
957
     * > fix your webserver configuration or explicitly set the value by setting the [[setHostInfo()|hostInfo]] property.
958
     * > If you don't have access to the server configuration, you can setup [[\yii\filters\HostControl]] filter at
959
     * > application level in order to protect against such kind of attack.
960
     *
961
     * @property string|null schema and hostname part (with port number if needed) of the request URL
962
     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
963
     * See [[getHostInfo()]] for security related notes on this property.
964
     * @return string|null schema and hostname part (with port number if needed) of the request URL
965
     * (e.g. `http://www.yiiframework.com`), null if can't be obtained from `$_SERVER` and wasn't set.
966
     * @see setHostInfo()
967
     */
968 23
    public function getHostInfo()
969
    {
970 23
        if ($this->_hostInfo === null) {
971 19
            $secure = $this->getIsSecureConnection();
972 19
            $http = $secure ? 'https' : 'http';
973
974 19
            if ($this->hasHeader('X-Forwarded-Host')) {
975 1
                $this->_hostInfo = $http . '://' . $this->getHeaderLine('X-Forwarded-Host');
976 18
            } elseif ($this->hasHeader('Host')) {
977 9
                $this->_hostInfo = $http . '://' . $this->getHeaderLine('Host');
978 9
            } elseif (($serverName = $this->getServerParam('SERVER_NAME')) !== null) {
979 1
                $this->_hostInfo = $http . '://' . $serverName;
980 1
                $port = $secure ? $this->getSecurePort() : $this->getPort();
981 1
                if (($port !== 80 && !$secure) || ($port !== 443 && $secure)) {
982
                    $this->_hostInfo .= ':' . $port;
983
                }
984
            }
985
        }
986
987 23
        return $this->_hostInfo;
988
    }
989
990
    /**
991
     * Sets the schema and host part of the application URL.
992
     * This setter is provided in case the schema and hostname cannot be determined
993
     * on certain Web servers.
994
     * @param string|null $value the schema and host part of the application URL. The trailing slashes will be removed.
995
     * @see getHostInfo() for security related notes on this property.
996
     */
997 47
    public function setHostInfo($value)
998
    {
999 47
        $this->_hostName = null;
1000 47
        $this->_hostInfo = $value === null ? null : rtrim($value, '/');
1001 47
    }
1002
1003
    /**
1004
     * Returns the host part of the current request URL.
1005
     * Value is calculated from current [[getHostInfo()|hostInfo]] property.
1006
     *
1007
     * > Warning: The content of this value may not be reliable, dependent on the server
1008
     * > configuration. Please refer to [[getHostInfo()]] for more information.
1009
     *
1010
     * @return string|null hostname part of the request URL (e.g. `www.yiiframework.com`)
1011
     * @see getHostInfo()
1012
     * @since 2.0.10
1013
     */
1014 16
    public function getHostName()
1015
    {
1016 16
        if ($this->_hostName === null) {
1017 16
            $this->_hostName = parse_url($this->getHostInfo(), PHP_URL_HOST);
1018
        }
1019
1020 16
        return $this->_hostName;
1021
    }
1022
1023
    private $_baseUrl;
1024
1025
    /**
1026
     * Returns the relative URL for the application.
1027
     * This is similar to [[scriptUrl]] except that it does not include the script file name,
1028
     * and the ending slashes are removed.
1029
     * @return string the relative URL for the application
1030
     * @see setScriptUrl()
1031
     */
1032 253
    public function getBaseUrl()
1033
    {
1034 253
        if ($this->_baseUrl === null) {
1035 252
            $this->_baseUrl = rtrim(dirname($this->getScriptUrl()), '\\/');
1036
        }
1037
1038 253
        return $this->_baseUrl;
1039
    }
1040
1041
    /**
1042
     * Sets the relative URL for the application.
1043
     * By default the URL is determined based on the entry script URL.
1044
     * This setter is provided in case you want to change this behavior.
1045
     * @param string $value the relative URL for the application
1046
     */
1047 1
    public function setBaseUrl($value)
1048
    {
1049 1
        $this->_baseUrl = $value;
1050 1
    }
1051
1052
    private $_scriptUrl;
1053
1054
    /**
1055
     * Returns the relative URL of the entry script.
1056
     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
1057
     * @return string the relative URL of the entry script.
1058
     * @throws InvalidConfigException if unable to determine the entry script URL
1059
     */
1060 254
    public function getScriptUrl()
1061
    {
1062 254
        if ($this->_scriptUrl === null) {
1063 2
            $scriptFile = $this->getScriptFile();
1064 1
            $scriptName = basename($scriptFile);
1065 1
            $serverParams = $this->getServerParams();
1066 1
            if (isset($serverParams['SCRIPT_NAME']) && basename($serverParams['SCRIPT_NAME']) === $scriptName) {
1067 1
                $this->_scriptUrl = $serverParams['SCRIPT_NAME'];
1068
            } elseif (isset($serverParams['PHP_SELF']) && basename($serverParams['PHP_SELF']) === $scriptName) {
1069
                $this->_scriptUrl = $serverParams['PHP_SELF'];
1070
            } elseif (isset($serverParams['ORIG_SCRIPT_NAME']) && basename($serverParams['ORIG_SCRIPT_NAME']) === $scriptName) {
1071
                $this->_scriptUrl = $serverParams['ORIG_SCRIPT_NAME'];
1072
            } elseif (isset($serverParams['PHP_SELF']) && ($pos = strpos($serverParams['PHP_SELF'], '/' . $scriptName)) !== false) {
1073
                $this->_scriptUrl = substr($serverParams['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName;
1074
            } elseif (!empty($serverParams['DOCUMENT_ROOT']) && strpos($scriptFile, $serverParams['DOCUMENT_ROOT']) === 0) {
1075
                $this->_scriptUrl = str_replace('\\', '/', str_replace($serverParams['DOCUMENT_ROOT'], '', $scriptFile));
1076
            } else {
1077
                throw new InvalidConfigException('Unable to determine the entry script URL.');
1078
            }
1079
        }
1080
1081 253
        return $this->_scriptUrl;
1082
    }
1083
1084
    /**
1085
     * Sets the relative URL for the application entry script.
1086
     * This setter is provided in case the entry script URL cannot be determined
1087
     * on certain Web servers.
1088
     * @param string $value the relative URL for the application entry script.
1089
     */
1090 264
    public function setScriptUrl($value)
1091
    {
1092 264
        $this->_scriptUrl = $value === null ? null : '/' . trim($value, '/');
1093 264
    }
1094
1095
    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...
1096
1097
    /**
1098
     * Returns the entry script file path.
1099
     * The default implementation will simply return `$_SERVER['SCRIPT_FILENAME']`.
1100
     * @return string the entry script file path
1101
     * @throws InvalidConfigException
1102
     */
1103 255
    public function getScriptFile()
1104
    {
1105 255
        if (isset($this->_scriptFile)) {
1106 243
            return $this->_scriptFile;
1107
        }
1108
1109 13
        if (($scriptFilename = $this->getServerParam('SCRIPT_FILENAME')) !== null) {
1110 11
            return $scriptFilename;
1111
        }
1112
1113 2
        throw new InvalidConfigException('Unable to determine the entry script file path.');
1114
    }
1115
1116
    /**
1117
     * Sets the entry script file path.
1118
     * The entry script file path normally can be obtained from `$_SERVER['SCRIPT_FILENAME']`.
1119
     * If your server configuration does not return the correct value, you may configure
1120
     * this property to make it right.
1121
     * @param string $value the entry script file path.
1122
     */
1123 243
    public function setScriptFile($value)
1124
    {
1125 243
        $this->_scriptFile = $value;
1126 243
    }
1127
1128
    private $_pathInfo;
1129
1130
    /**
1131
     * Returns the path info of the currently requested URL.
1132
     * A path info refers to the part that is after the entry script and before the question mark (query string).
1133
     * The starting and ending slashes are both removed.
1134
     * @return string part of the request URL that is after the entry script and before the question mark.
1135
     * Note, the returned path info is already URL-decoded.
1136
     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
1137
     */
1138 17
    public function getPathInfo()
1139
    {
1140 17
        if ($this->_pathInfo === null) {
1141
            $this->_pathInfo = $this->resolvePathInfo();
1142
        }
1143
1144 17
        return $this->_pathInfo;
1145
    }
1146
1147
    /**
1148
     * Sets the path info of the current request.
1149
     * This method is mainly provided for testing purpose.
1150
     * @param string $value the path info of the current request
1151
     */
1152 18
    public function setPathInfo($value)
1153
    {
1154 18
        $this->_pathInfo = $value === null ? null : ltrim($value, '/');
1155 18
    }
1156
1157
    /**
1158
     * Resolves the path info part of the currently requested URL.
1159
     * A path info refers to the part that is after the entry script and before the question mark (query string).
1160
     * The starting slashes are both removed (ending slashes will be kept).
1161
     * @return string part of the request URL that is after the entry script and before the question mark.
1162
     * Note, the returned path info is decoded.
1163
     * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration
1164
     */
1165
    protected function resolvePathInfo()
1166
    {
1167
        $pathInfo = $this->getUrl();
1168
1169
        if (($pos = strpos($pathInfo, '?')) !== false) {
1170
            $pathInfo = substr($pathInfo, 0, $pos);
1171
        }
1172
1173
        $pathInfo = urldecode($pathInfo);
1174
1175
        // try to encode in UTF8 if not so
1176
        // http://w3.org/International/questions/qa-forms-utf-8.html
1177
        if (!preg_match('%^(?:
1178
            [\x09\x0A\x0D\x20-\x7E]              # ASCII
1179
            | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
1180
            | \xE0[\xA0-\xBF][\x80-\xBF]         # excluding overlongs
1181
            | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
1182
            | \xED[\x80-\x9F][\x80-\xBF]         # excluding surrogates
1183
            | \xF0[\x90-\xBF][\x80-\xBF]{2}      # planes 1-3
1184
            | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
1185
            | \xF4[\x80-\x8F][\x80-\xBF]{2}      # plane 16
1186
            )*$%xs', $pathInfo)
1187
        ) {
1188
            $pathInfo = utf8_encode($pathInfo);
1189
        }
1190
1191
        $scriptUrl = $this->getScriptUrl();
1192
        $baseUrl = $this->getBaseUrl();
1193
        if (strpos($pathInfo, $scriptUrl) === 0) {
1194
            $pathInfo = substr($pathInfo, strlen($scriptUrl));
1195
        } elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) {
1196
            $pathInfo = substr($pathInfo, strlen($baseUrl));
1197
        } elseif (($phpSelf = $this->getServerParam('PHP_SELF')) !== null && strpos($phpSelf, $scriptUrl) === 0) {
1198
            $pathInfo = substr($phpSelf, strlen($scriptUrl));
1199
        } else {
1200
            throw new InvalidConfigException('Unable to determine the path info of the current request.');
1201
        }
1202
1203
        if (substr($pathInfo, 0, 1) === '/') {
1204
            $pathInfo = substr($pathInfo, 1);
1205
        }
1206
1207
        return (string) $pathInfo;
1208
    }
1209
1210
    /**
1211
     * Returns the currently requested absolute URL.
1212
     * This is a shortcut to the concatenation of [[hostInfo]] and [[url]].
1213
     * @return string the currently requested absolute URL.
1214
     */
1215
    public function getAbsoluteUrl()
1216
    {
1217
        return $this->getHostInfo() . $this->getUrl();
1218
    }
1219
1220
    private $_url;
1221
1222
    /**
1223
     * Returns the currently requested relative URL.
1224
     * This refers to the portion of the URL that is after the [[hostInfo]] part.
1225
     * It includes the [[queryString]] part if any.
1226
     * @return string the currently requested relative URL. Note that the URI returned may be URL-encoded depending on the client.
1227
     * @throws InvalidConfigException if the URL cannot be determined due to unusual server configuration
1228
     */
1229 9
    public function getUrl()
1230
    {
1231 9
        if ($this->_url === null) {
1232 2
            $this->_url = $this->resolveRequestUri();
1233
        }
1234
1235 9
        return $this->_url;
1236
    }
1237
1238
    /**
1239
     * Sets the currently requested relative URL.
1240
     * The URI must refer to the portion that is after [[hostInfo]].
1241
     * Note that the URI should be URL-encoded.
1242
     * @param string $value the request URI to be set
1243
     */
1244 24
    public function setUrl($value)
1245
    {
1246 24
        $this->_url = $value;
1247 24
    }
1248
1249
    /**
1250
     * Resolves the request URI portion for the currently requested URL.
1251
     * This refers to the portion that is after the [[hostInfo]] part. It includes the [[queryString]] part if any.
1252
     * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework.
1253
     * @return string|bool the request URI portion for the currently requested URL.
1254
     * Note that the URI returned may be URL-encoded depending on the client.
1255
     * @throws InvalidConfigException if the request URI cannot be determined due to unusual server configuration
1256
     */
1257 2
    protected function resolveRequestUri()
1258
    {
1259 2
        $serverParams = $this->getServerParams();
1260
1261 2
        if ($this->hasHeader('x-rewrite-url')) { // IIS
1262
            $requestUri = $this->getHeaderLine('x-rewrite-url');
1263 2
        } elseif (isset($serverParams['REQUEST_URI'])) {
1264 2
            $requestUri = $serverParams['REQUEST_URI'];
1265 2
            if ($requestUri !== '' && $requestUri[0] !== '/') {
1266 2
                $requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri);
1267
            }
1268
        } elseif (isset($serverParams['ORIG_PATH_INFO'])) { // IIS 5.0 CGI
1269
            $requestUri = $serverParams['ORIG_PATH_INFO'];
1270
            if (!empty($serverParams['QUERY_STRING'])) {
1271
                $requestUri .= '?' . $serverParams['QUERY_STRING'];
1272
            }
1273
        } else {
1274
            throw new InvalidConfigException('Unable to determine the request URI.');
1275
        }
1276
1277 2
        return $requestUri;
1278
    }
1279
1280
    /**
1281
     * Returns part of the request URL that is after the question mark.
1282
     * @return string part of the request URL that is after the question mark
1283
     */
1284
    public function getQueryString()
1285
    {
1286
        return $this->getServerParam('QUERY_STRING', '');
1287
    }
1288
1289
    /**
1290
     * Return if the request is sent via secure channel (https).
1291
     * @return bool if the request is sent via secure channel (https)
1292
     */
1293 36
    public function getIsSecureConnection()
1294
    {
1295 36
        $https = $this->getServerParam('HTTPS');
1296 36
        if ($https !== null && (strcasecmp($https, 'on') === 0 || $https == 1)) {
1297 2
            return true;
1298
        }
1299 34
        foreach ($this->secureProtocolHeaders as $header => $values) {
1300 34
            if ($this->hasHeader($header)) {
1301 2
                foreach ($values as $value) {
1302 2
                    if (strcasecmp($this->getHeaderLine($header), $value) === 0) {
1303 34
                        return true;
1304
                    }
1305
                }
1306
            }
1307
        }
1308
1309 32
        return false;
1310
    }
1311
1312
    /**
1313
     * Returns the server name.
1314
     * @return string server name, null if not available
1315
     */
1316 1
    public function getServerName()
1317
    {
1318 1
        return $this->getServerParam('SERVER_NAME');
1319
    }
1320
1321
    /**
1322
     * Returns the server port number.
1323
     * @return int|null server port number, null if not available
1324
     */
1325 2
    public function getServerPort()
1326
    {
1327 2
        $port = $this->getServerParam('SERVER_PORT');
1328 2
        return $port === null ? null : (int) $port;
1329
    }
1330
1331
    /**
1332
     * Returns the URL referrer.
1333
     * @return string|null URL referrer, null if not available
1334
     */
1335
    public function getReferrer()
1336
    {
1337
        if (!$this->hasHeader('Referer')) {
1338
            return null;
1339
        }
1340
        return $this->getHeaderLine('Referer');
1341
    }
1342
1343
    /**
1344
     * Returns the URL origin of a CORS request.
1345
     *
1346
     * The return value is taken from the `Origin` [[getHeaders()|header]] sent by the browser.
1347
     *
1348
     * Note that the origin request header indicates where a fetch originates from.
1349
     * It doesn't include any path information, but only the server name.
1350
     * It is sent with a CORS requests, as well as with POST requests.
1351
     * It is similar to the referer header, but, unlike this header, it doesn't disclose the whole path.
1352
     * Please refer to <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin> for more information.
1353
     *
1354
     * @return string|null URL origin of a CORS request, `null` if not available.
1355
     * @see getHeaders()
1356
     * @since 2.0.13
1357
     */
1358 1
    public function getOrigin()
1359
    {
1360 1
        return $this->getHeaderLine('origin');
1361
    }
1362
1363
    /**
1364
     * Returns the user agent.
1365
     * @return string|null user agent, null if not available
1366
     */
1367
    public function getUserAgent()
1368
    {
1369
        if (!$this->hasHeader('User-Agent')) {
1370
            return null;
1371
        }
1372
        return $this->getHeaderLine('User-Agent');
1373
    }
1374
1375
    /**
1376
     * Returns the user IP address.
1377
     * The IP is determined using headers and / or `$_SERVER` variables.
1378
     * @return string|null user IP address, null if not available
1379
     */
1380 37
    public function getUserIP()
1381
    {
1382 37
        foreach ($this->ipHeaders as $ipHeader) {
1383 37
            if ($this->hasHeader($ipHeader)) {
1384 37
                return trim(explode(',', $this->getHeaderLine($ipHeader))[0]);
1385
            }
1386
        }
1387
1388 36
        return $this->getRemoteIP();
1389
    }
1390
1391
    /**
1392
     * Returns the user host name.
1393
     * The HOST is determined using headers and / or `$_SERVER` variables.
1394
     * @return string|null user host name, null if not available
1395
     */
1396
    public function getUserHost()
1397
    {
1398
        foreach ($this->ipHeaders as $ipHeader) {
1399
            if ($this->hasHeader($ipHeader)) {
1400
                return gethostbyaddr(trim(explode(',', $this->getHeaderLine($ipHeader))[0]));
1401
            }
1402
        }
1403
1404
        return $this->getRemoteHost();
1405
    }
1406
1407
    /**
1408
     * Returns the IP on the other end of this connection.
1409
     * This is always the next hop, any headers are ignored.
1410
     * @return string|null remote IP address, `null` if not available.
1411
     * @since 2.0.13
1412
     */
1413 57
    public function getRemoteIP()
1414
    {
1415 57
        return $this->getServerParam('REMOTE_ADDR');
1416
    }
1417
1418
    /**
1419
     * Returns the host name of the other end of this connection.
1420
     * This is always the next hop, any headers are ignored.
1421
     * @return string|null remote host name, `null` if not available
1422
     * @see getUserHost()
1423
     * @see getRemoteIP()
1424
     * @since 2.0.13
1425
     */
1426
    public function getRemoteHost()
1427
    {
1428
        return $this->getServerParam('REMOTE_HOST');
1429
    }
1430
1431
    /**
1432
     * @return string|null the username sent via HTTP authentication, `null` if the username is not given
1433
     * @see getAuthCredentials() to get both username and password in one call
1434
     */
1435 9
    public function getAuthUser()
1436
    {
1437 9
        return $this->getAuthCredentials()[0];
1438
    }
1439
1440
    /**
1441
     * @return string|null the password sent via HTTP authentication, `null` if the password is not given
1442
     * @see getAuthCredentials() to get both username and password in one call
1443
     */
1444 9
    public function getAuthPassword()
1445
    {
1446 9
        return $this->getAuthCredentials()[1];
1447
    }
1448
1449
    /**
1450
     * @return array that contains exactly two elements:
1451
     * - 0: the username sent via HTTP authentication, `null` if the username is not given
1452
     * - 1: the password sent via HTTP authentication, `null` if the password is not given
1453
     * @see getAuthUser() to get only username
1454
     * @see getAuthPassword() to get only password
1455
     * @since 2.0.13
1456
     */
1457 19
    public function getAuthCredentials()
1458
    {
1459 19
        $username = $this->getServerParam('PHP_AUTH_USER');
1460 19
        $password = $this->getServerParam('PHP_AUTH_PW');
1461 19
        if ($username !== null || $password !== null) {
1462 11
            return [$username, $password];
1463
        }
1464
1465
        /*
1466
         * Apache with php-cgi does not pass HTTP Basic authentication to PHP by default.
1467
         * To make it work, add the following line to to your .htaccess file:
1468
         *
1469
         * RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
1470
         */
1471 8
        $auth_token = $this->getHeader('HTTP_AUTHORIZATION') ?: $this->getHeader('REDIRECT_HTTP_AUTHORIZATION');
1472 8
        if ($auth_token !== [] && strpos(strtolower($auth_token[0]), 'basic') === 0) {
1473 8
            $parts = array_map(function ($value) {
1474 8
                return strlen($value) === 0 ? null : $value;
1475 8
            }, explode(':', base64_decode(mb_substr($auth_token[0], 6)), 2));
1476
1477 8
            if (count($parts) < 2) {
1478 2
                return [$parts[0], null];
1479
            }
1480
1481 6
            return $parts;
1482
        }
1483
1484
        return [null, null];
1485
    }
1486
1487
    private $_port;
1488
1489
    /**
1490
     * Returns the port to use for insecure requests.
1491
     * Defaults to 80, or the port specified by the server if the current
1492
     * request is insecure.
1493
     * @return int port number for insecure requests.
1494
     * @see setPort()
1495
     */
1496 1
    public function getPort()
1497
    {
1498 1
        if ($this->_port === null) {
1499 1
            $serverPort = $this->getServerPort();
1500 1
            $this->_port = !$this->getIsSecureConnection() && $serverPort !== null ? $serverPort : 80;
1501
        }
1502
1503 1
        return $this->_port;
1504
    }
1505
1506
    /**
1507
     * Sets the port to use for insecure requests.
1508
     * This setter is provided in case a custom port is necessary for certain
1509
     * server configurations.
1510
     * @param int $value port number.
1511
     */
1512
    public function setPort($value)
1513
    {
1514
        if ($value != $this->_port) {
1515
            $this->_port = (int) $value;
1516
            $this->_hostInfo = null;
1517
        }
1518
    }
1519
1520
    private $_securePort;
1521
1522
    /**
1523
     * Returns the port to use for secure requests.
1524
     * Defaults to 443, or the port specified by the server if the current
1525
     * request is secure.
1526
     * @return int port number for secure requests.
1527
     * @see setSecurePort()
1528
     */
1529
    public function getSecurePort()
1530
    {
1531
        if ($this->_securePort === null) {
1532
            $serverPort = $this->getServerPort();
1533
            $this->_securePort = $this->getIsSecureConnection() && $serverPort !== null ? $serverPort : 443;
1534
        }
1535
1536
        return $this->_securePort;
1537
    }
1538
1539
    /**
1540
     * Sets the port to use for secure requests.
1541
     * This setter is provided in case a custom port is necessary for certain
1542
     * server configurations.
1543
     * @param int $value port number.
1544
     */
1545
    public function setSecurePort($value)
1546
    {
1547
        if ($value != $this->_securePort) {
1548
            $this->_securePort = (int) $value;
1549
            $this->_hostInfo = null;
1550
        }
1551
    }
1552
1553
    private $_contentTypes;
1554
1555
    /**
1556
     * Returns the content types acceptable by the end user.
1557
     *
1558
     * This is determined by the `Accept` HTTP header. For example,
1559
     *
1560
     * ```php
1561
     * $_SERVER['HTTP_ACCEPT'] = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1562
     * $types = $request->getAcceptableContentTypes();
1563
     * print_r($types);
1564
     * // displays:
1565
     * // [
1566
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1567
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1568
     * //           'text/plain' => ['q' => 0.5],
1569
     * // ]
1570
     * ```
1571
     *
1572
     * @return array the content types ordered by the quality score. Types with the highest scores
1573
     * will be returned first. The array keys are the content types, while the array values
1574
     * are the corresponding quality score and other parameters as given in the header.
1575
     */
1576 3
    public function getAcceptableContentTypes()
1577
    {
1578 3
        if ($this->_contentTypes === null) {
1579 2
            if ($this->hasHeader('Accept')) {
1580 2
                $this->_contentTypes = $this->parseAcceptHeader($this->getHeaderLine('Accept'));
1581
            } else {
1582 1
                $this->_contentTypes = [];
1583
            }
1584
        }
1585
1586 3
        return $this->_contentTypes;
1587
    }
1588
1589
    /**
1590
     * Sets the acceptable content types.
1591
     * Please refer to [[getAcceptableContentTypes()]] on the format of the parameter.
1592
     * @param array $value the content types that are acceptable by the end user. They should
1593
     * be ordered by the preference level.
1594
     * @see getAcceptableContentTypes()
1595
     * @see parseAcceptHeader()
1596
     */
1597 1
    public function setAcceptableContentTypes($value)
1598
    {
1599 1
        $this->_contentTypes = $value;
1600 1
    }
1601
1602
    /**
1603
     * Returns request content-type
1604
     * The Content-Type header field indicates the MIME type of the data
1605
     * contained in [[getBody()]] or, in the case of the HEAD method, the
1606
     * media type that would have been sent had the request been a GET.
1607
     * For the MIME-types the user expects in response, see [[acceptableContentTypes]].
1608
     * @return string request content-type. Empty string is returned if this information is not available.
1609
     * @link https://tools.ietf.org/html/rfc2616#section-14.17
1610
     * HTTP 1.1 header field definitions
1611
     */
1612 13
    public function getContentType()
1613
    {
1614 13
        return $this->getHeaderLine('Content-Type');
1615
    }
1616
1617
    private $_languages;
1618
1619
    /**
1620
     * Returns the languages acceptable by the end user.
1621
     * This is determined by the `Accept-Language` HTTP header.
1622
     * @return array the languages ordered by the preference level. The first element
1623
     * represents the most preferred language.
1624
     */
1625 1
    public function getAcceptableLanguages()
1626
    {
1627 1
        if ($this->_languages === null) {
1628
            if ($this->hasHeader('Accept-Language')) {
1629
                $this->_languages = array_keys($this->parseAcceptHeader($this->getHeaderLine('Accept-Language')));
1630
            } else {
1631
                $this->_languages = [];
1632
            }
1633
        }
1634
1635 1
        return $this->_languages;
1636
    }
1637
1638
    /**
1639
     * @param array $value the languages that are acceptable by the end user. They should
1640
     * be ordered by the preference level.
1641
     */
1642 1
    public function setAcceptableLanguages($value)
1643
    {
1644 1
        $this->_languages = $value;
1645 1
    }
1646
1647
    /**
1648
     * Parses the given `Accept` (or `Accept-Language`) header.
1649
     *
1650
     * This method will return the acceptable values with their quality scores and the corresponding parameters
1651
     * as specified in the given `Accept` header. The array keys of the return value are the acceptable values,
1652
     * while the array values consisting of the corresponding quality scores and parameters. The acceptable
1653
     * values with the highest quality scores will be returned first. For example,
1654
     *
1655
     * ```php
1656
     * $header = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;';
1657
     * $accepts = $request->parseAcceptHeader($header);
1658
     * print_r($accepts);
1659
     * // displays:
1660
     * // [
1661
     * //     'application/json' => ['q' => 1, 'version' => '1.0'],
1662
     * //      'application/xml' => ['q' => 1, 'version' => '2.0'],
1663
     * //           'text/plain' => ['q' => 0.5],
1664
     * // ]
1665
     * ```
1666
     *
1667
     * @param string $header the header to be parsed
1668
     * @return array the acceptable values ordered by their quality score. The values with the highest scores
1669
     * will be returned first.
1670
     */
1671 3
    public function parseAcceptHeader($header)
1672
    {
1673 3
        $accepts = [];
1674 3
        foreach (explode(',', $header) as $i => $part) {
1675 3
            $params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY);
1676 3
            if (empty($params)) {
1677 1
                continue;
1678
            }
1679
            $values = [
1680 3
                'q' => [$i, array_shift($params), 1],
1681
            ];
1682 3
            foreach ($params as $param) {
1683 2
                if (strpos($param, '=') !== false) {
1684 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...
1685 2
                    if ($key === 'q') {
1686 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...
1687
                    } else {
1688 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...
1689
                    }
1690
                } else {
1691 2
                    $values[] = $param;
1692
                }
1693
            }
1694 3
            $accepts[] = $values;
1695
        }
1696
1697 3
        usort($accepts, function ($a, $b) {
1698 3
            $a = $a['q']; // index, name, q
1699 3
            $b = $b['q'];
1700 3
            if ($a[2] > $b[2]) {
1701 2
                return -1;
1702
            }
1703
1704 2
            if ($a[2] < $b[2]) {
1705 1
                return 1;
1706
            }
1707
1708 2
            if ($a[1] === $b[1]) {
1709
                return $a[0] > $b[0] ? 1 : -1;
1710
            }
1711
1712 2
            if ($a[1] === '*/*') {
1713
                return 1;
1714
            }
1715
1716 2
            if ($b[1] === '*/*') {
1717
                return -1;
1718
            }
1719
1720 2
            $wa = $a[1][strlen($a[1]) - 1] === '*';
1721 2
            $wb = $b[1][strlen($b[1]) - 1] === '*';
1722 2
            if ($wa xor $wb) {
1723
                return $wa ? 1 : -1;
1724
            }
1725
1726 2
            return $a[0] > $b[0] ? 1 : -1;
1727 3
        });
1728
1729 3
        $result = [];
1730 3
        foreach ($accepts as $accept) {
1731 3
            $name = $accept['q'][1];
1732 3
            $accept['q'] = $accept['q'][2];
1733 3
            $result[$name] = $accept;
1734
        }
1735
1736 3
        return $result;
1737
    }
1738
1739
    /**
1740
     * Returns the user-preferred language that should be used by this application.
1741
     * The language resolution is based on the user preferred languages and the languages
1742
     * supported by the application. The method will try to find the best match.
1743
     * @param array $languages a list of the languages supported by the application. If this is empty, the current
1744
     * application language will be returned without further processing.
1745
     * @return string the language that the application should use.
1746
     */
1747 1
    public function getPreferredLanguage(array $languages = [])
1748
    {
1749 1
        if (empty($languages)) {
1750 1
            return Yii::$app->language;
1751
        }
1752 1
        foreach ($this->getAcceptableLanguages() as $acceptableLanguage) {
1753 1
            $acceptableLanguage = str_replace('_', '-', strtolower($acceptableLanguage));
1754 1
            foreach ($languages as $language) {
1755 1
                $normalizedLanguage = str_replace('_', '-', strtolower($language));
1756
1757
                if (
1758 1
                    $normalizedLanguage === $acceptableLanguage // en-us==en-us
1759 1
                    || strpos($acceptableLanguage, $normalizedLanguage . '-') === 0 // en==en-us
1760 1
                    || strpos($normalizedLanguage, $acceptableLanguage . '-') === 0 // en-us==en
1761
                ) {
1762 1
                    return $language;
1763
                }
1764
            }
1765
        }
1766
1767 1
        return reset($languages);
1768
    }
1769
1770
    /**
1771
     * Gets the Etags.
1772
     *
1773
     * @return array The entity tags
1774
     */
1775
    public function getETags()
1776
    {
1777
        if ($this->hasHeader('if-none-match')) {
1778
            return preg_split('/[\s,]+/', str_replace('-gzip', '', $this->getHeaderLine('if-none-match')), -1, PREG_SPLIT_NO_EMPTY);
1779
        }
1780
1781
        return [];
1782
    }
1783
1784
    /**
1785
     * Returns the cookie collection.
1786
     *
1787
     * Through the returned cookie collection, you may access a cookie using the following syntax:
1788
     *
1789
     * ```php
1790
     * $cookie = $request->cookies['name']
1791
     * if ($cookie !== null) {
1792
     *     $value = $cookie->value;
1793
     * }
1794
     *
1795
     * // alternatively
1796
     * $value = $request->cookies->getValue('name');
1797
     * ```
1798
     *
1799
     * @return CookieCollection the cookie collection.
1800
     */
1801 46
    public function getCookies()
1802
    {
1803 46
        if ($this->_cookies === null) {
1804 46
            $this->_cookies = new CookieCollection($this->loadCookies(), [
1805 45
                'readOnly' => true,
1806
            ]);
1807
        }
1808
1809 45
        return $this->_cookies;
1810
    }
1811
1812
    /**
1813
     * Converts [[cookieParams]] into an array of [[Cookie]].
1814
     * @return array the cookies obtained from request
1815
     * @throws InvalidConfigException if [[cookieValidationKey]] is not set when [[enableCookieValidation]] is true
1816
     */
1817 46
    protected function loadCookies()
1818
    {
1819 46
        $cookies = [];
1820 46
        if ($this->enableCookieValidation) {
1821 45
            if ($this->cookieValidationKey == '') {
1822 1
                throw new InvalidConfigException(get_class($this) . '::$cookieValidationKey must be configured with a secret key.');
1823
            }
1824 44
            foreach ($this->getCookieParams() as $name => $value) {
1825
                if (!is_string($value)) {
1826
                    continue;
1827
                }
1828
                $data = Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey);
1829
                if ($data === false) {
1830
                    continue;
1831
                }
1832
                $data = @unserialize($data);
1833
                if (is_array($data) && isset($data[0], $data[1]) && $data[0] === $name) {
1834
                    $cookies[$name] = Yii::createObject([
1835 44
                        '__class' => \yii\http\Cookie::class,
1836
                        'name' => $name,
1837
                        'value' => $data[1],
1838
                        'expire' => null,
1839
                    ]);
1840
                }
1841
            }
1842
        } else {
1843 1
            foreach ($this->getCookieParams() as $name => $value) {
1844 1
                $cookies[$name] = Yii::createObject([
1845 1
                    '__class' => \yii\http\Cookie::class,
1846 1
                    'name' => $name,
1847 1
                    'value' => $value,
1848
                    'expire' => null,
1849
                ]);
1850
            }
1851
        }
1852
1853 45
        return $cookies;
1854
    }
1855
1856
    /**
1857
     * {@inheritdoc}
1858
     * @since 2.1.0
1859
     */
1860 12
    public function getUploadedFiles()
1861
    {
1862 12
        if ($this->_uploadedFiles === null) {
1863 6
            $this->getParsedBody(); // uploaded files are the part of the body and may be set while its parsing
1864 6
            if ($this->_uploadedFiles === null) {
1865 6
                $this->_uploadedFiles = $this->defaultUploadedFiles();
1866
            }
1867
        }
1868 12
        return $this->_uploadedFiles;
1869
    }
1870
1871
    /**
1872
     * Sets uploaded files for this request.
1873
     * Data structure for the uploaded files should follow [PSR-7 Uploaded Files specs](http://www.php-fig.org/psr/psr-7/#16-uploaded-files).
1874
     * @param array|null $uploadedFiles uploaded files.
1875
     * @since 2.1.0
1876
     */
1877 6
    public function setUploadedFiles($uploadedFiles)
1878
    {
1879 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...
1880 6
    }
1881
1882
    /**
1883
     * {@inheritdoc}
1884
     * @since 2.1.0
1885
     */
1886
    public function withUploadedFiles(array $uploadedFiles)
1887
    {
1888
        $newInstance = clone $this;
1889
        $newInstance->setUploadedFiles($uploadedFiles);
1890
        return $newInstance;
1891
    }
1892
1893
    /**
1894
     * Initializes default uploaded files data structure parsing super-global $_FILES.
1895
     * @see http://www.php-fig.org/psr/psr-7/#16-uploaded-files
1896
     * @return array uploaded files.
1897
     * @since 2.1.0
1898
     */
1899 6
    protected function defaultUploadedFiles()
1900
    {
1901 6
        $files = [];
1902 6
        foreach ($_FILES as $class => $info) {
1903 3
            $files[$class] = [];
1904 3
            $this->populateUploadedFileRecursive($files[$class], $info['name'], $info['tmp_name'], $info['type'], $info['size'], $info['error']);
1905
        }
1906
1907 6
        return $files;
1908
    }
1909
1910
    /**
1911
     * Populates uploaded files array from $_FILE data structure recursively.
1912
     * @param array $files uploaded files array to be populated.
1913
     * @param mixed $names file names provided by PHP
1914
     * @param mixed $tempNames temporary file names provided by PHP
1915
     * @param mixed $types file types provided by PHP
1916
     * @param mixed $sizes file sizes provided by PHP
1917
     * @param mixed $errors uploading issues provided by PHP
1918
     * @since 2.1.0
1919
     */
1920 3
    private function populateUploadedFileRecursive(&$files, $names, $tempNames, $types, $sizes, $errors)
1921
    {
1922 3
        if (is_array($names)) {
1923 2
            foreach ($names as $i => $name) {
1924 2
                $files[$i] = [];
1925 2
                $this->populateUploadedFileRecursive($files[$i], $name, $tempNames[$i], $types[$i], $sizes[$i], $errors[$i]);
1926
            }
1927
        } else {
1928 3
            $files = Yii::createObject([
1929 3
                '__class' => $this->uploadedFileClass,
1930 3
                'clientFilename' => $names,
1931 3
                'tempFilename' => $tempNames,
1932 3
                'clientMediaType' => $types,
1933 3
                'size' => $sizes,
1934 3
                'error' => $errors,
1935
            ]);
1936
        }
1937 3
    }
1938
1939
    /**
1940
     * Returns an uploaded file according to the given name.
1941
     * Name can be either a string HTML form input name, e.g. 'Item[file]' or array path, e.g. `['Item', 'file']`.
1942
     * Note: this method returns `null` in case given name matches multiple files.
1943
     * @param string|array $name HTML form input name or array path.
1944
     * @return UploadedFileInterface|null uploaded file instance, `null` - if not found.
1945
     * @since 2.1.0
1946
     */
1947 1
    public function getUploadedFileByName($name)
1948
    {
1949 1
        $uploadedFile = $this->findUploadedFiles($name);
1950 1
        if ($uploadedFile instanceof UploadedFileInterface) {
1951 1
            return $uploadedFile;
1952
        }
1953 1
        return null;
1954
    }
1955
1956
    /**
1957
     * Returns the list of uploaded file instances according to the given name.
1958
     * Name can be either a string HTML form input name, e.g. 'Item[file]' or array path, e.g. `['Item', 'file']`.
1959
     * Note: this method does NOT preserve uploaded files structure - it returns instances in single-level array (list),
1960
     * even if they are set by nested keys.
1961
     * @param string|array $name HTML form input name or array path.
1962
     * @return UploadedFileInterface[] list of uploaded file instances.
1963
     * @since 2.1.0
1964
     */
1965 1
    public function getUploadedFilesByName($name)
1966
    {
1967 1
        $uploadedFiles = $this->findUploadedFiles($name);
1968 1
        if ($uploadedFiles === null) {
1969
            return [];
1970
        }
1971 1
        if ($uploadedFiles instanceof UploadedFileInterface) {
1972 1
            return [$uploadedFiles];
1973
        }
1974 1
        return $this->reduceUploadedFiles($uploadedFiles);
1975
    }
1976
1977
    /**
1978
     * Finds the uploaded file or set of uploaded files inside [[$uploadedFiles]] according to given name.
1979
     * Name can be either a string HTML form input name, e.g. 'Item[file]' or array path, e.g. `['Item', 'file']`.
1980
     * @param string|array $name HTML form input name or array path.
1981
     * @return UploadedFileInterface|array|null
1982
     * @since 2.1.0
1983
     */
1984 2
    private function findUploadedFiles($name)
1985
    {
1986 2
        if (!is_array($name)) {
1987 2
            $name = preg_split('/\\]\\[|\\[|\\]/s', $name, -1, PREG_SPLIT_NO_EMPTY);
1988
        }
1989 2
        return ArrayHelper::getValue($this->getUploadedFiles(), $name);
1990
    }
1991
1992
    /**
1993
     * Reduces complex uploaded files structure to the single-level array (list).
1994
     * @param array $uploadedFiles raw set of the uploaded files.
1995
     * @return UploadedFileInterface[] list of uploaded files.
1996
     * @since 2.1.0
1997
     */
1998
    private function reduceUploadedFiles($uploadedFiles)
1999
    {
2000 1
        return array_reduce($uploadedFiles, function ($carry, $item) {
2001 1
            if ($item instanceof UploadedFileInterface) {
2002 1
                $carry[] = $item;
2003
            } else {
2004 1
                $carry = array_merge($carry, $this->reduceUploadedFiles($item));
2005
            }
2006 1
            return $carry;
2007 1
        }, []);
2008
    }
2009
2010
    private $_csrfToken;
2011
2012
    /**
2013
     * Returns the token used to perform CSRF validation.
2014
     *
2015
     * This token is generated in a way to prevent [BREACH attacks](http://breachattack.com/). It may be passed
2016
     * along via a hidden field of an HTML form or an HTTP header value to support CSRF validation.
2017
     * @param bool $regenerate whether to regenerate CSRF token. When this parameter is true, each time
2018
     * this method is called, a new CSRF token will be generated and persisted (in session or cookie).
2019
     * @return string the token used to perform CSRF validation.
2020
     */
2021 52
    public function getCsrfToken($regenerate = false)
2022
    {
2023 52
        if ($this->_csrfToken === null || $regenerate) {
2024 52
            $token = $this->loadCsrfToken();
2025 51
            if ($regenerate || empty($token)) {
2026 48
                $token = $this->generateCsrfToken();
2027
            }
2028 51
            $this->_csrfToken = Yii::$app->security->maskToken($token);
2029
        }
2030
2031 51
        return $this->_csrfToken;
2032
    }
2033
2034
    /**
2035
     * Loads the CSRF token from cookie or session.
2036
     * @return string the CSRF token loaded from cookie or session. Null is returned if the cookie or session
2037
     * does not have CSRF token.
2038
     */
2039 52
    protected function loadCsrfToken()
2040
    {
2041 52
        if ($this->enableCsrfCookie) {
2042 48
            return $this->getCookies()->getValue($this->csrfParam);
2043
        }
2044
2045 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...
2046
    }
2047
2048
    /**
2049
     * Generates an unmasked random token used to perform CSRF validation.
2050
     * @return string the random token for CSRF validation.
2051
     */
2052 48
    protected function generateCsrfToken()
2053
    {
2054 48
        $token = Yii::$app->getSecurity()->generateRandomString();
2055 48
        if ($this->enableCsrfCookie) {
2056 47
            $cookie = $this->createCsrfCookie($token);
2057 47
            Yii::$app->getResponse()->getCookies()->add($cookie);
2058
        } else {
2059 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...
2060
        }
2061
2062 48
        return $token;
2063
    }
2064
2065
    /**
2066
     * @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent.
2067
     */
2068 3
    public function getCsrfTokenFromHeader()
2069
    {
2070 3
        return $this->getHeaderLine(static::CSRF_HEADER);
2071
    }
2072
2073
    /**
2074
     * Creates a cookie with a randomly generated CSRF token.
2075
     * Initial values specified in [[csrfCookie]] will be applied to the generated cookie.
2076
     * @param string $token the CSRF token
2077
     * @return Cookie the generated cookie
2078
     * @see enableCsrfValidation
2079
     */
2080 47
    protected function createCsrfCookie($token)
2081
    {
2082 47
        $options = $this->csrfCookie;
2083 47
        return Yii::createObject(array_merge($options, [
2084 47
            '__class' => \yii\http\Cookie::class,
2085 47
            'name' => $this->csrfParam,
2086 47
            'value' => $token,
2087
        ]));
2088
    }
2089
2090
    /**
2091
     * Performs the CSRF validation.
2092
     *
2093
     * This method will validate the user-provided CSRF token by comparing it with the one stored in cookie or session.
2094
     * This method is mainly called in [[Controller::beforeAction()]].
2095
     *
2096
     * Note that the method will NOT perform CSRF validation if [[enableCsrfValidation]] is false or the HTTP method
2097
     * is among GET, HEAD or OPTIONS.
2098
     *
2099
     * @param string $clientSuppliedToken the user-provided CSRF token to be validated. If null, the token will be retrieved from
2100
     * the [[csrfParam]] POST field or HTTP header.
2101
     * This parameter is available since version 2.0.4.
2102
     * @return bool whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true.
2103
     */
2104 36
    public function validateCsrfToken($clientSuppliedToken = null)
2105
    {
2106 36
        $method = $this->getMethod();
2107
        // only validate CSRF token on non-"safe" methods https://tools.ietf.org/html/rfc2616#section-9.1.1
2108 36
        if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
2109 35
            return true;
2110
        }
2111
2112 4
        $trueToken = $this->getCsrfToken();
2113
2114 4
        if ($clientSuppliedToken !== null) {
2115 2
            return $this->validateCsrfTokenInternal($clientSuppliedToken, $trueToken);
2116
        }
2117
2118 3
        return $this->validateCsrfTokenInternal($this->getParsedBodyParam($this->csrfParam), $trueToken)
2119 3
            || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken);
2120
    }
2121
2122
    /**
2123
     * Validates CSRF token.
2124
     *
2125
     * @param string $clientSuppliedToken The masked client-supplied token.
2126
     * @param string $trueToken The masked true token.
2127
     * @return bool
2128
     */
2129 4
    private function validateCsrfTokenInternal($clientSuppliedToken, $trueToken)
2130
    {
2131 4
        if (!is_string($clientSuppliedToken)) {
2132 3
            return false;
2133
        }
2134
2135 4
        $security = Yii::$app->security;
2136
2137 4
        return $security->unmaskToken($clientSuppliedToken) === $security->unmaskToken($trueToken);
2138
    }
2139
2140
    /**
2141
     * {@inheritdoc}
2142
     * @since 2.1.0
2143
     */
2144 3
    public function getAttributes()
2145
    {
2146 3
        if ($this->_attributes === null) {
2147
            $this->_attributes = $this->defaultAttributes();
2148
        }
2149 3
        return $this->_attributes;
2150
    }
2151
2152
    /**
2153
     * @param array $attributes attributes derived from the request.
2154
     */
2155 3
    public function setAttributes(array $attributes)
2156
    {
2157 3
        $this->_attributes = $attributes;
2158 3
    }
2159
2160
    /**
2161
     * {@inheritdoc}
2162
     * @since 2.1.0
2163
     */
2164 1
    public function getAttribute($name, $default = null)
2165
    {
2166 1
        $attributes = $this->getAttributes();
2167 1
        if (!array_key_exists($name, $attributes)) {
2168 1
            return $default;
2169
        }
2170
2171 1
        return $attributes[$name];
2172
    }
2173
2174
    /**
2175
     * {@inheritdoc}
2176
     * @since 2.1.0
2177
     */
2178 1
    public function withAttribute($name, $value)
2179
    {
2180 1
        $attributes = $this->getAttributes();
2181 1
        if (array_key_exists($name, $attributes) && $attributes[$name] === $value) {
2182
            return $this;
2183
        }
2184
2185 1
        $attributes[$name] = $value;
2186
2187 1
        $newInstance = clone $this;
2188 1
        $newInstance->setAttributes($attributes);
2189 1
        return $newInstance;
2190
    }
2191
2192
    /**
2193
     * {@inheritdoc}
2194
     * @since 2.1.0
2195
     */
2196 1
    public function withoutAttribute($name)
2197
    {
2198 1
        $attributes = $this->getAttributes();
2199 1
        if (!array_key_exists($name, $attributes)) {
2200
            return $this;
2201
        }
2202
2203 1
        unset($attributes[$name]);
2204
2205 1
        $newInstance = clone $this;
2206 1
        $newInstance->setAttributes($attributes);
2207 1
        return $newInstance;
2208
    }
2209
2210
    /**
2211
     * Returns default server request attributes to be used in case they are not explicitly set.
2212
     * @return array attributes derived from the request.
2213
     * @since 2.1.0
2214
     */
2215
    protected function defaultAttributes()
2216
    {
2217
        return [];
2218
    }
2219
2220
    /**
2221
     * {@inheritdoc}
2222
     */
2223 4
    public function __clone()
2224
    {
2225 4
        parent::__clone();
2226
2227 4
        $this->cloneHttpMessageInternals();
2228
2229 4
        if (is_object($this->_cookies)) {
2230
            $this->_cookies = clone $this->_cookies;
2231
        }
2232
2233 4
        $this->_parsedBody = false;
2234 4
    }
2235
}
2236