Completed
Push — composer-installed ( 5832b4 )
by Ilia
08:49
created

Request::create()   F

Complexity

Conditions 17
Paths 6144

Size

Total Lines 91

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
nc 6144
nop 7
dl 0
loc 91
rs 1.0363
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Symfony\Component\HttpFoundation;
13
14
use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException;
15
use Symfony\Component\HttpFoundation\Session\SessionInterface;
16
17
/**
18
 * Request represents an HTTP request.
19
 *
20
 * The methods dealing with URL accept / return a raw path (% encoded):
21
 *   * getBasePath
22
 *   * getBaseUrl
23
 *   * getPathInfo
24
 *   * getRequestUri
25
 *   * getUri
26
 *   * getUriForPath
27
 *
28
 * @author Fabien Potencier <[email protected]>
29
 */
30
class Request
31
{
32
    const HEADER_FORWARDED = 'forwarded';
33
    const HEADER_CLIENT_IP = 'client_ip';
34
    const HEADER_CLIENT_HOST = 'client_host';
35
    const HEADER_CLIENT_PROTO = 'client_proto';
36
    const HEADER_CLIENT_PORT = 'client_port';
37
38
    const METHOD_HEAD = 'HEAD';
39
    const METHOD_GET = 'GET';
40
    const METHOD_POST = 'POST';
41
    const METHOD_PUT = 'PUT';
42
    const METHOD_PATCH = 'PATCH';
43
    const METHOD_DELETE = 'DELETE';
44
    const METHOD_PURGE = 'PURGE';
45
    const METHOD_OPTIONS = 'OPTIONS';
46
    const METHOD_TRACE = 'TRACE';
47
    const METHOD_CONNECT = 'CONNECT';
48
49
    /**
50
     * @var string[]
51
     */
52
    protected static $trustedProxies = array();
53
54
    /**
55
     * @var string[]
56
     */
57
    protected static $trustedHostPatterns = array();
58
59
    /**
60
     * @var string[]
61
     */
62
    protected static $trustedHosts = array();
63
64
    /**
65
     * Names for headers that can be trusted when
66
     * using trusted proxies.
67
     *
68
     * The FORWARDED header is the standard as of rfc7239.
69
     *
70
     * The other headers are non-standard, but widely used
71
     * by popular reverse proxies (like Apache mod_proxy or Amazon EC2).
72
     */
73
    protected static $trustedHeaders = array(
74
        self::HEADER_FORWARDED => 'FORWARDED',
75
        self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
76
        self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
77
        self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
78
        self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
79
    );
80
81
    protected static $httpMethodParameterOverride = false;
82
83
    /**
84
     * Custom parameters.
85
     *
86
     * @var \Symfony\Component\HttpFoundation\ParameterBag
87
     */
88
    public $attributes;
89
90
    /**
91
     * Request body parameters ($_POST).
92
     *
93
     * @var \Symfony\Component\HttpFoundation\ParameterBag
94
     */
95
    public $request;
96
97
    /**
98
     * Query string parameters ($_GET).
99
     *
100
     * @var \Symfony\Component\HttpFoundation\ParameterBag
101
     */
102
    public $query;
103
104
    /**
105
     * Server and execution environment parameters ($_SERVER).
106
     *
107
     * @var \Symfony\Component\HttpFoundation\ServerBag
108
     */
109
    public $server;
110
111
    /**
112
     * Uploaded files ($_FILES).
113
     *
114
     * @var \Symfony\Component\HttpFoundation\FileBag
115
     */
116
    public $files;
117
118
    /**
119
     * Cookies ($_COOKIE).
120
     *
121
     * @var \Symfony\Component\HttpFoundation\ParameterBag
122
     */
123
    public $cookies;
124
125
    /**
126
     * Headers (taken from the $_SERVER).
127
     *
128
     * @var \Symfony\Component\HttpFoundation\HeaderBag
129
     */
130
    public $headers;
131
132
    /**
133
     * @var string
134
     */
135
    protected $content;
136
137
    /**
138
     * @var array
139
     */
140
    protected $languages;
141
142
    /**
143
     * @var array
144
     */
145
    protected $charsets;
146
147
    /**
148
     * @var array
149
     */
150
    protected $encodings;
151
152
    /**
153
     * @var array
154
     */
155
    protected $acceptableContentTypes;
156
157
    /**
158
     * @var string
159
     */
160
    protected $pathInfo;
161
162
    /**
163
     * @var string
164
     */
165
    protected $requestUri;
166
167
    /**
168
     * @var string
169
     */
170
    protected $baseUrl;
171
172
    /**
173
     * @var string
174
     */
175
    protected $basePath;
176
177
    /**
178
     * @var string
179
     */
180
    protected $method;
181
182
    /**
183
     * @var string
184
     */
185
    protected $format;
186
187
    /**
188
     * @var \Symfony\Component\HttpFoundation\Session\SessionInterface
189
     */
190
    protected $session;
191
192
    /**
193
     * @var string
194
     */
195
    protected $locale;
196
197
    /**
198
     * @var string
199
     */
200
    protected $defaultLocale = 'en';
201
202
    /**
203
     * @var array
204
     */
205
    protected static $formats;
206
207
    protected static $requestFactory;
208
209
    private $isForwardedValid = true;
210
211
    private static $forwardedParams = array(
212
        self::HEADER_CLIENT_IP => 'for',
213
        self::HEADER_CLIENT_HOST => 'host',
214
        self::HEADER_CLIENT_PROTO => 'proto',
215
        self::HEADER_CLIENT_PORT => 'host',
216
    );
217
218
    /**
219
     * Constructor.
220
     *
221
     * @param array           $query      The GET parameters
222
     * @param array           $request    The POST parameters
223
     * @param array           $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
224
     * @param array           $cookies    The COOKIE parameters
225
     * @param array           $files      The FILES parameters
226
     * @param array           $server     The SERVER parameters
227
     * @param string|resource $content    The raw body data
228
     */
229
    public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
230
    {
231
        $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content);
232
    }
233
234
    /**
235
     * Sets the parameters for this request.
236
     *
237
     * This method also re-initializes all properties.
238
     *
239
     * @param array           $query      The GET parameters
240
     * @param array           $request    The POST parameters
241
     * @param array           $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
242
     * @param array           $cookies    The COOKIE parameters
243
     * @param array           $files      The FILES parameters
244
     * @param array           $server     The SERVER parameters
245
     * @param string|resource $content    The raw body data
246
     */
247
    public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
248
    {
249
        $this->request = new ParameterBag($request);
250
        $this->query = new ParameterBag($query);
251
        $this->attributes = new ParameterBag($attributes);
252
        $this->cookies = new ParameterBag($cookies);
253
        $this->files = new FileBag($files);
254
        $this->server = new ServerBag($server);
255
        $this->headers = new HeaderBag($this->server->getHeaders());
256
257
        $this->content = $content;
0 ignored issues
show
Documentation Bug introduced by
It seems like $content can also be of type resource. However, the property $content is declared as type string. 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...
258
        $this->languages = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $languages.

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...
259
        $this->charsets = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $charsets.

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...
260
        $this->encodings = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $encodings.

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...
261
        $this->acceptableContentTypes = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $acceptableContentTypes.

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...
262
        $this->pathInfo = null;
263
        $this->requestUri = null;
264
        $this->baseUrl = null;
265
        $this->basePath = null;
266
        $this->method = null;
267
        $this->format = null;
268
    }
269
270
    /**
271
     * Creates a new request with values from PHP's super globals.
272
     *
273
     * @return static
274
     */
275
    public static function createFromGlobals()
276
    {
277
        // With the php's bug #66606, the php's built-in web server
278
        // stores the Content-Type and Content-Length header values in
279
        // HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields.
280
        $server = $_SERVER;
281
        if ('cli-server' === PHP_SAPI) {
282
            if (array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) {
283
                $server['CONTENT_LENGTH'] = $_SERVER['HTTP_CONTENT_LENGTH'];
284
            }
285
            if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) {
286
                $server['CONTENT_TYPE'] = $_SERVER['HTTP_CONTENT_TYPE'];
287
            }
288
        }
289
290
        $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $server);
291
292
        if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
293
            && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))
294
        ) {
295
            parse_str($request->getContent(), $data);
296
            $request->request = new ParameterBag($data);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type null; however, Symfony\Component\HttpFo...meterBag::__construct() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
297
        }
298
299
        return $request;
300
    }
301
302
    /**
303
     * Creates a Request based on a given URI and configuration.
304
     *
305
     * The information contained in the URI always take precedence
306
     * over the other information (server and parameters).
307
     *
308
     * @param string $uri        The URI
309
     * @param string $method     The HTTP method
310
     * @param array  $parameters The query (GET) or request (POST) parameters
311
     * @param array  $cookies    The request cookies ($_COOKIE)
312
     * @param array  $files      The request files ($_FILES)
313
     * @param array  $server     The server parameters ($_SERVER)
314
     * @param string $content    The raw body data
315
     *
316
     * @return static
317
     */
318
    public static function create($uri, $method = 'GET', $parameters = array(), $cookies = array(), $files = array(), $server = array(), $content = null)
319
    {
320
        $server = array_replace(array(
321
            'SERVER_NAME' => 'localhost',
322
            'SERVER_PORT' => 80,
323
            'HTTP_HOST' => 'localhost',
324
            'HTTP_USER_AGENT' => 'Symfony/2.X',
325
            'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
326
            'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5',
327
            'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
328
            'REMOTE_ADDR' => '127.0.0.1',
329
            'SCRIPT_NAME' => '',
330
            'SCRIPT_FILENAME' => '',
331
            'SERVER_PROTOCOL' => 'HTTP/1.1',
332
            'REQUEST_TIME' => time(),
333
        ), $server);
334
335
        $server['PATH_INFO'] = '';
336
        $server['REQUEST_METHOD'] = strtoupper($method);
337
338
        $components = parse_url($uri);
339
        if (isset($components['host'])) {
340
            $server['SERVER_NAME'] = $components['host'];
341
            $server['HTTP_HOST'] = $components['host'];
342
        }
343
344
        if (isset($components['scheme'])) {
345
            if ('https' === $components['scheme']) {
346
                $server['HTTPS'] = 'on';
347
                $server['SERVER_PORT'] = 443;
348
            } else {
349
                unset($server['HTTPS']);
350
                $server['SERVER_PORT'] = 80;
351
            }
352
        }
353
354
        if (isset($components['port'])) {
355
            $server['SERVER_PORT'] = $components['port'];
356
            $server['HTTP_HOST'] = $server['HTTP_HOST'].':'.$components['port'];
357
        }
358
359
        if (isset($components['user'])) {
360
            $server['PHP_AUTH_USER'] = $components['user'];
361
        }
362
363
        if (isset($components['pass'])) {
364
            $server['PHP_AUTH_PW'] = $components['pass'];
365
        }
366
367
        if (!isset($components['path'])) {
368
            $components['path'] = '/';
369
        }
370
371
        switch (strtoupper($method)) {
372
            case 'POST':
373
            case 'PUT':
374
            case 'DELETE':
375
                if (!isset($server['CONTENT_TYPE'])) {
376
                    $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
377
                }
378
                // no break
379
            case 'PATCH':
380
                $request = $parameters;
381
                $query = array();
382
                break;
383
            default:
384
                $request = array();
385
                $query = $parameters;
386
                break;
387
        }
388
389
        $queryString = '';
390
        if (isset($components['query'])) {
391
            parse_str(html_entity_decode($components['query']), $qs);
392
393
            if ($query) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $query of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
394
                $query = array_replace($qs, $query);
395
                $queryString = http_build_query($query, '', '&');
396
            } else {
397
                $query = $qs;
398
                $queryString = $components['query'];
399
            }
400
        } elseif ($query) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $query of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
401
            $queryString = http_build_query($query, '', '&');
402
        }
403
404
        $server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : '');
405
        $server['QUERY_STRING'] = $queryString;
406
407
        return self::createRequestFromFactory($query, $request, array(), $cookies, $files, $server, $content);
0 ignored issues
show
Bug introduced by
It seems like $query defined by $qs on line 397 can also be of type null; however, Symfony\Component\HttpFo...ateRequestFromFactory() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
408
    }
409
410
    /**
411
     * Sets a callable able to create a Request instance.
412
     *
413
     * This is mainly useful when you need to override the Request class
414
     * to keep BC with an existing system. It should not be used for any
415
     * other purpose.
416
     *
417
     * @param callable|null $callable A PHP callable
418
     */
419
    public static function setFactory($callable)
420
    {
421
        self::$requestFactory = $callable;
422
    }
423
424
    /**
425
     * Clones a request and overrides some of its parameters.
426
     *
427
     * @param array $query      The GET parameters
428
     * @param array $request    The POST parameters
429
     * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
430
     * @param array $cookies    The COOKIE parameters
431
     * @param array $files      The FILES parameters
432
     * @param array $server     The SERVER parameters
433
     *
434
     * @return static
435
     */
436
    public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
437
    {
438
        $dup = clone $this;
439
        if ($query !== null) {
440
            $dup->query = new ParameterBag($query);
441
        }
442
        if ($request !== null) {
443
            $dup->request = new ParameterBag($request);
444
        }
445
        if ($attributes !== null) {
446
            $dup->attributes = new ParameterBag($attributes);
447
        }
448
        if ($cookies !== null) {
449
            $dup->cookies = new ParameterBag($cookies);
450
        }
451
        if ($files !== null) {
452
            $dup->files = new FileBag($files);
453
        }
454
        if ($server !== null) {
455
            $dup->server = new ServerBag($server);
456
            $dup->headers = new HeaderBag($dup->server->getHeaders());
457
        }
458
        $dup->languages = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $languages.

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...
459
        $dup->charsets = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $charsets.

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...
460
        $dup->encodings = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $encodings.

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...
461
        $dup->acceptableContentTypes = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $acceptableContentTypes.

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...
462
        $dup->pathInfo = null;
463
        $dup->requestUri = null;
464
        $dup->baseUrl = null;
465
        $dup->basePath = null;
466
        $dup->method = null;
467
        $dup->format = null;
468
469
        if (!$dup->get('_format') && $this->get('_format')) {
470
            $dup->attributes->set('_format', $this->get('_format'));
471
        }
472
473
        if (!$dup->getRequestFormat(null)) {
474
            $dup->setRequestFormat($this->getRequestFormat(null));
475
        }
476
477
        return $dup;
478
    }
479
480
    /**
481
     * Clones the current request.
482
     *
483
     * Note that the session is not cloned as duplicated requests
484
     * are most of the time sub-requests of the main one.
485
     */
486
    public function __clone()
487
    {
488
        $this->query = clone $this->query;
489
        $this->request = clone $this->request;
490
        $this->attributes = clone $this->attributes;
491
        $this->cookies = clone $this->cookies;
492
        $this->files = clone $this->files;
493
        $this->server = clone $this->server;
494
        $this->headers = clone $this->headers;
495
    }
496
497
    /**
498
     * Returns the request as a string.
499
     *
500
     * @return string The request
501
     */
502
    public function __toString()
503
    {
504
        try {
505
            $content = $this->getContent();
506
        } catch (\LogicException $e) {
507
            return trigger_error($e, E_USER_ERROR);
508
        }
509
510
        return
511
            sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n".
512
            $this->headers."\r\n".
513
            $content;
514
    }
515
516
    /**
517
     * Overrides the PHP global variables according to this request instance.
518
     *
519
     * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE.
520
     * $_FILES is never overridden, see rfc1867
521
     */
522
    public function overrideGlobals()
523
    {
524
        $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), null, '&')));
525
526
        $_GET = $this->query->all();
527
        $_POST = $this->request->all();
528
        $_SERVER = $this->server->all();
529
        $_COOKIE = $this->cookies->all();
530
531
        foreach ($this->headers->all() as $key => $value) {
532
            $key = strtoupper(str_replace('-', '_', $key));
533
            if (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) {
534
                $_SERVER[$key] = implode(', ', $value);
535
            } else {
536
                $_SERVER['HTTP_'.$key] = implode(', ', $value);
537
            }
538
        }
539
540
        $request = array('g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE);
541
542
        $requestOrder = ini_get('request_order') ?: ini_get('variables_order');
543
        $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp';
544
545
        $_REQUEST = array();
546
        foreach (str_split($requestOrder) as $order) {
547
            $_REQUEST = array_merge($_REQUEST, $request[$order]);
548
        }
549
    }
550
551
    /**
552
     * Sets a list of trusted proxies.
553
     *
554
     * You should only list the reverse proxies that you manage directly.
555
     *
556
     * @param array $proxies A list of trusted proxies
557
     */
558
    public static function setTrustedProxies(array $proxies)
559
    {
560
        self::$trustedProxies = $proxies;
561
    }
562
563
    /**
564
     * Gets the list of trusted proxies.
565
     *
566
     * @return array An array of trusted proxies
567
     */
568
    public static function getTrustedProxies()
569
    {
570
        return self::$trustedProxies;
571
    }
572
573
    /**
574
     * Sets a list of trusted host patterns.
575
     *
576
     * You should only list the hosts you manage using regexs.
577
     *
578
     * @param array $hostPatterns A list of trusted host patterns
579
     */
580
    public static function setTrustedHosts(array $hostPatterns)
581
    {
582
        self::$trustedHostPatterns = array_map(function ($hostPattern) {
583
            return sprintf('#%s#i', $hostPattern);
584
        }, $hostPatterns);
585
        // we need to reset trusted hosts on trusted host patterns change
586
        self::$trustedHosts = array();
587
    }
588
589
    /**
590
     * Gets the list of trusted host patterns.
591
     *
592
     * @return array An array of trusted host patterns
593
     */
594
    public static function getTrustedHosts()
595
    {
596
        return self::$trustedHostPatterns;
597
    }
598
599
    /**
600
     * Sets the name for trusted headers.
601
     *
602
     * The following header keys are supported:
603
     *
604
     *  * Request::HEADER_CLIENT_IP:    defaults to X-Forwarded-For   (see getClientIp())
605
     *  * Request::HEADER_CLIENT_HOST:  defaults to X-Forwarded-Host  (see getHost())
606
     *  * Request::HEADER_CLIENT_PORT:  defaults to X-Forwarded-Port  (see getPort())
607
     *  * Request::HEADER_CLIENT_PROTO: defaults to X-Forwarded-Proto (see getScheme() and isSecure())
608
     *  * Request::HEADER_FORWARDED:    defaults to Forwarded         (see RFC 7239)
609
     *
610
     * Setting an empty value allows to disable the trusted header for the given key.
611
     *
612
     * @param string $key   The header key
613
     * @param string $value The header name
614
     *
615
     * @throws \InvalidArgumentException
616
     */
617 View Code Duplication
    public static function setTrustedHeaderName($key, $value)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
618
    {
619
        if (!array_key_exists($key, self::$trustedHeaders)) {
620
            throw new \InvalidArgumentException(sprintf('Unable to set the trusted header name for key "%s".', $key));
621
        }
622
623
        self::$trustedHeaders[$key] = $value;
624
    }
625
626
    /**
627
     * Gets the trusted proxy header name.
628
     *
629
     * @param string $key The header key
630
     *
631
     * @return string The header name
632
     *
633
     * @throws \InvalidArgumentException
634
     */
635 View Code Duplication
    public static function getTrustedHeaderName($key)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
636
    {
637
        if (!array_key_exists($key, self::$trustedHeaders)) {
638
            throw new \InvalidArgumentException(sprintf('Unable to get the trusted header name for key "%s".', $key));
639
        }
640
641
        return self::$trustedHeaders[$key];
642
    }
643
644
    /**
645
     * Normalizes a query string.
646
     *
647
     * It builds a normalized query string, where keys/value pairs are alphabetized,
648
     * have consistent escaping and unneeded delimiters are removed.
649
     *
650
     * @param string $qs Query string
651
     *
652
     * @return string A normalized query string for the Request
653
     */
654
    public static function normalizeQueryString($qs)
655
    {
656
        if ('' == $qs) {
657
            return '';
658
        }
659
660
        $parts = array();
661
        $order = array();
662
663
        foreach (explode('&', $qs) as $param) {
664
            if ('' === $param || '=' === $param[0]) {
665
                // Ignore useless delimiters, e.g. "x=y&".
666
                // Also ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway.
667
                // PHP also does not include them when building _GET.
668
                continue;
669
            }
670
671
            $keyValuePair = explode('=', $param, 2);
672
673
            // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded).
674
            // PHP also converts "+" to spaces when filling the global _GET or when using the function parse_str. This is why we use urldecode and then normalize to
675
            // RFC 3986 with rawurlencode.
676
            $parts[] = isset($keyValuePair[1]) ?
677
                rawurlencode(urldecode($keyValuePair[0])).'='.rawurlencode(urldecode($keyValuePair[1])) :
678
                rawurlencode(urldecode($keyValuePair[0]));
679
            $order[] = urldecode($keyValuePair[0]);
680
        }
681
682
        array_multisort($order, SORT_ASC, $parts);
683
684
        return implode('&', $parts);
685
    }
686
687
    /**
688
     * Enables support for the _method request parameter to determine the intended HTTP method.
689
     *
690
     * Be warned that enabling this feature might lead to CSRF issues in your code.
691
     * Check that you are using CSRF tokens when required.
692
     * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered
693
     * and used to send a "PUT" or "DELETE" request via the _method request parameter.
694
     * If these methods are not protected against CSRF, this presents a possible vulnerability.
695
     *
696
     * The HTTP method can only be overridden when the real HTTP method is POST.
697
     */
698
    public static function enableHttpMethodParameterOverride()
699
    {
700
        self::$httpMethodParameterOverride = true;
701
    }
702
703
    /**
704
     * Checks whether support for the _method request parameter is enabled.
705
     *
706
     * @return bool True when the _method request parameter is enabled, false otherwise
707
     */
708
    public static function getHttpMethodParameterOverride()
709
    {
710
        return self::$httpMethodParameterOverride;
711
    }
712
713
    /**
714
     * Gets a "parameter" value.
715
     *
716
     * This method is mainly useful for libraries that want to provide some flexibility.
717
     *
718
     * Order of precedence: GET, PATH, POST
719
     *
720
     * Avoid using this method in controllers:
721
     *
722
     *  * slow
723
     *  * prefer to get from a "named" source
724
     *
725
     * It is better to explicitly get request parameters from the appropriate
726
     * public property instead (query, attributes, request).
727
     *
728
     * Note: Finding deep items is deprecated since version 2.8, to be removed in 3.0.
729
     *
730
     * @param string $key     the key
731
     * @param mixed  $default the default value if the parameter key does not exist
732
     * @param bool   $deep    is parameter deep in multidimensional array
733
     *
734
     * @return mixed
735
     */
736
    public function get($key, $default = null, $deep = false)
737
    {
738
        if ($deep) {
739
            @trigger_error('Using paths to find deeper items in '.__METHOD__.' is deprecated since version 2.8 and will be removed in 3.0. Filter the returned value in your own code instead.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
740
        }
741
742
        if ($this !== $result = $this->query->get($key, $this, $deep)) {
743
            return $result;
744
        }
745
746
        if ($this !== $result = $this->attributes->get($key, $this, $deep)) {
747
            return $result;
748
        }
749
750
        if ($this !== $result = $this->request->get($key, $this, $deep)) {
751
            return $result;
752
        }
753
754
        return $default;
755
    }
756
757
    /**
758
     * Gets the Session.
759
     *
760
     * @return SessionInterface|null The session
761
     */
762
    public function getSession()
763
    {
764
        return $this->session;
765
    }
766
767
    /**
768
     * Whether the request contains a Session which was started in one of the
769
     * previous requests.
770
     *
771
     * @return bool
772
     */
773
    public function hasPreviousSession()
774
    {
775
        // the check for $this->session avoids malicious users trying to fake a session cookie with proper name
776
        return $this->hasSession() && $this->cookies->has($this->session->getName());
777
    }
778
779
    /**
780
     * Whether the request contains a Session object.
781
     *
782
     * This method does not give any information about the state of the session object,
783
     * like whether the session is started or not. It is just a way to check if this Request
784
     * is associated with a Session instance.
785
     *
786
     * @return bool true when the Request contains a Session object, false otherwise
787
     */
788
    public function hasSession()
789
    {
790
        return null !== $this->session;
791
    }
792
793
    /**
794
     * Sets the Session.
795
     *
796
     * @param SessionInterface $session The Session
797
     */
798
    public function setSession(SessionInterface $session)
799
    {
800
        $this->session = $session;
801
    }
802
803
    /**
804
     * Returns the client IP addresses.
805
     *
806
     * In the returned array the most trusted IP address is first, and the
807
     * least trusted one last. The "real" client IP address is the last one,
808
     * but this is also the least trusted one. Trusted proxies are stripped.
809
     *
810
     * Use this method carefully; you should use getClientIp() instead.
811
     *
812
     * @return array The client IP addresses
813
     *
814
     * @see getClientIp()
815
     */
816
    public function getClientIps()
817
    {
818
        $ip = $this->server->get('REMOTE_ADDR');
819
820
        if (!$this->isFromTrustedProxy()) {
821
            return array($ip);
822
        }
823
824
        return $this->getTrustedValues(self::HEADER_CLIENT_IP, $ip) ?: array($ip);
825
    }
826
827
    /**
828
     * Returns the client IP address.
829
     *
830
     * This method can read the client IP address from the "X-Forwarded-For" header
831
     * when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For"
832
     * header value is a comma+space separated list of IP addresses, the left-most
833
     * being the original client, and each successive proxy that passed the request
834
     * adding the IP address where it received the request from.
835
     *
836
     * If your reverse proxy uses a different header name than "X-Forwarded-For",
837
     * ("Client-Ip" for instance), configure it via "setTrustedHeaderName()" with
838
     * the "client-ip" key.
839
     *
840
     * @return string|null The client IP address
841
     *
842
     * @see getClientIps()
843
     * @see http://en.wikipedia.org/wiki/X-Forwarded-For
844
     */
845
    public function getClientIp()
846
    {
847
        $ipAddresses = $this->getClientIps();
848
849
        return $ipAddresses[0];
850
    }
851
852
    /**
853
     * Returns current script name.
854
     *
855
     * @return string
856
     */
857
    public function getScriptName()
858
    {
859
        return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', ''));
860
    }
861
862
    /**
863
     * Returns the path being requested relative to the executed script.
864
     *
865
     * The path info always starts with a /.
866
     *
867
     * Suppose this request is instantiated from /mysite on localhost:
868
     *
869
     *  * http://localhost/mysite              returns an empty string
870
     *  * http://localhost/mysite/about        returns '/about'
871
     *  * http://localhost/mysite/enco%20ded   returns '/enco%20ded'
872
     *  * http://localhost/mysite/about?var=1  returns '/about'
873
     *
874
     * @return string The raw path (i.e. not urldecoded)
875
     */
876
    public function getPathInfo()
877
    {
878
        if (null === $this->pathInfo) {
879
            $this->pathInfo = $this->preparePathInfo();
880
        }
881
882
        return $this->pathInfo;
883
    }
884
885
    /**
886
     * Returns the root path from which this request is executed.
887
     *
888
     * Suppose that an index.php file instantiates this request object:
889
     *
890
     *  * http://localhost/index.php         returns an empty string
891
     *  * http://localhost/index.php/page    returns an empty string
892
     *  * http://localhost/web/index.php     returns '/web'
893
     *  * http://localhost/we%20b/index.php  returns '/we%20b'
894
     *
895
     * @return string The raw path (i.e. not urldecoded)
896
     */
897
    public function getBasePath()
898
    {
899
        if (null === $this->basePath) {
900
            $this->basePath = $this->prepareBasePath();
901
        }
902
903
        return $this->basePath;
904
    }
905
906
    /**
907
     * Returns the root URL from which this request is executed.
908
     *
909
     * The base URL never ends with a /.
910
     *
911
     * This is similar to getBasePath(), except that it also includes the
912
     * script filename (e.g. index.php) if one exists.
913
     *
914
     * @return string The raw URL (i.e. not urldecoded)
915
     */
916
    public function getBaseUrl()
917
    {
918
        if (null === $this->baseUrl) {
919
            $this->baseUrl = $this->prepareBaseUrl();
920
        }
921
922
        return $this->baseUrl;
923
    }
924
925
    /**
926
     * Gets the request's scheme.
927
     *
928
     * @return string
929
     */
930
    public function getScheme()
931
    {
932
        return $this->isSecure() ? 'https' : 'http';
933
    }
934
935
    /**
936
     * Returns the port on which the request is made.
937
     *
938
     * This method can read the client port from the "X-Forwarded-Port" header
939
     * when trusted proxies were set via "setTrustedProxies()".
940
     *
941
     * The "X-Forwarded-Port" header must contain the client port.
942
     *
943
     * If your reverse proxy uses a different header name than "X-Forwarded-Port",
944
     * configure it via "setTrustedHeaderName()" with the "client-port" key.
945
     *
946
     * @return int|string can be a string if fetched from the server bag
947
     */
948
    public function getPort()
949
    {
950
        if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_PORT)) {
951
            $host = $host[0];
952
        } elseif ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) {
953
            $host = $host[0];
954
        } elseif (!$host = $this->headers->get('HOST')) {
955
            return $this->server->get('SERVER_PORT');
956
        }
957
958
        if ($host[0] === '[') {
959
            $pos = strpos($host, ':', strrpos($host, ']'));
960
        } else {
961
            $pos = strrpos($host, ':');
962
        }
963
964
        if (false !== $pos) {
965
            return (int) substr($host, $pos + 1);
966
        }
967
968
        return 'https' === $this->getScheme() ? 443 : 80;
969
    }
970
971
    /**
972
     * Returns the user.
973
     *
974
     * @return string|null
975
     */
976
    public function getUser()
977
    {
978
        return $this->headers->get('PHP_AUTH_USER');
979
    }
980
981
    /**
982
     * Returns the password.
983
     *
984
     * @return string|null
985
     */
986
    public function getPassword()
987
    {
988
        return $this->headers->get('PHP_AUTH_PW');
989
    }
990
991
    /**
992
     * Gets the user info.
993
     *
994
     * @return string A user name and, optionally, scheme-specific information about how to gain authorization to access the server
995
     */
996
    public function getUserInfo()
997
    {
998
        $userinfo = $this->getUser();
999
1000
        $pass = $this->getPassword();
1001
        if ('' != $pass) {
1002
            $userinfo .= ":$pass";
1003
        }
1004
1005
        return $userinfo;
1006
    }
1007
1008
    /**
1009
     * Returns the HTTP host being requested.
1010
     *
1011
     * The port name will be appended to the host if it's non-standard.
1012
     *
1013
     * @return string
1014
     */
1015
    public function getHttpHost()
1016
    {
1017
        $scheme = $this->getScheme();
1018
        $port = $this->getPort();
1019
1020
        if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) {
1021
            return $this->getHost();
1022
        }
1023
1024
        return $this->getHost().':'.$port;
1025
    }
1026
1027
    /**
1028
     * Returns the requested URI (path and query string).
1029
     *
1030
     * @return string The raw URI (i.e. not URI decoded)
1031
     */
1032
    public function getRequestUri()
1033
    {
1034
        if (null === $this->requestUri) {
1035
            $this->requestUri = $this->prepareRequestUri();
1036
        }
1037
1038
        return $this->requestUri;
1039
    }
1040
1041
    /**
1042
     * Gets the scheme and HTTP host.
1043
     *
1044
     * If the URL was called with basic authentication, the user
1045
     * and the password are not added to the generated string.
1046
     *
1047
     * @return string The scheme and HTTP host
1048
     */
1049
    public function getSchemeAndHttpHost()
1050
    {
1051
        return $this->getScheme().'://'.$this->getHttpHost();
1052
    }
1053
1054
    /**
1055
     * Generates a normalized URI (URL) for the Request.
1056
     *
1057
     * @return string A normalized URI (URL) for the Request
1058
     *
1059
     * @see getQueryString()
1060
     */
1061
    public function getUri()
1062
    {
1063
        if (null !== $qs = $this->getQueryString()) {
1064
            $qs = '?'.$qs;
1065
        }
1066
1067
        return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs;
1068
    }
1069
1070
    /**
1071
     * Generates a normalized URI for the given path.
1072
     *
1073
     * @param string $path A path to use instead of the current one
1074
     *
1075
     * @return string The normalized URI for the path
1076
     */
1077
    public function getUriForPath($path)
1078
    {
1079
        return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path;
1080
    }
1081
1082
    /**
1083
     * Returns the path as relative reference from the current Request path.
1084
     *
1085
     * Only the URIs path component (no schema, host etc.) is relevant and must be given.
1086
     * Both paths must be absolute and not contain relative parts.
1087
     * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives.
1088
     * Furthermore, they can be used to reduce the link size in documents.
1089
     *
1090
     * Example target paths, given a base path of "/a/b/c/d":
1091
     * - "/a/b/c/d"     -> ""
1092
     * - "/a/b/c/"      -> "./"
1093
     * - "/a/b/"        -> "../"
1094
     * - "/a/b/c/other" -> "other"
1095
     * - "/a/x/y"       -> "../../x/y"
1096
     *
1097
     * @param string $path The target path
1098
     *
1099
     * @return string The relative target path
1100
     */
1101
    public function getRelativeUriForPath($path)
1102
    {
1103
        // be sure that we are dealing with an absolute path
1104
        if (!isset($path[0]) || '/' !== $path[0]) {
1105
            return $path;
1106
        }
1107
1108
        if ($path === $basePath = $this->getPathInfo()) {
1109
            return '';
1110
        }
1111
1112
        $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath);
1113
        $targetDirs = explode('/', isset($path[0]) && '/' === $path[0] ? substr($path, 1) : $path);
1114
        array_pop($sourceDirs);
1115
        $targetFile = array_pop($targetDirs);
1116
1117
        foreach ($sourceDirs as $i => $dir) {
1118
            if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) {
1119
                unset($sourceDirs[$i], $targetDirs[$i]);
1120
            } else {
1121
                break;
1122
            }
1123
        }
1124
1125
        $targetDirs[] = $targetFile;
1126
        $path = str_repeat('../', count($sourceDirs)).implode('/', $targetDirs);
1127
1128
        // A reference to the same base directory or an empty subdirectory must be prefixed with "./".
1129
        // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
1130
        // as the first segment of a relative-path reference, as it would be mistaken for a scheme name
1131
        // (see http://tools.ietf.org/html/rfc3986#section-4.2).
1132
        return !isset($path[0]) || '/' === $path[0]
1133
            || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos)
1134
            ? "./$path" : $path;
1135
    }
1136
1137
    /**
1138
     * Generates the normalized query string for the Request.
1139
     *
1140
     * It builds a normalized query string, where keys/value pairs are alphabetized
1141
     * and have consistent escaping.
1142
     *
1143
     * @return string|null A normalized query string for the Request
1144
     */
1145
    public function getQueryString()
1146
    {
1147
        $qs = static::normalizeQueryString($this->server->get('QUERY_STRING'));
1148
1149
        return '' === $qs ? null : $qs;
1150
    }
1151
1152
    /**
1153
     * Checks whether the request is secure or not.
1154
     *
1155
     * This method can read the client protocol from the "X-Forwarded-Proto" header
1156
     * when trusted proxies were set via "setTrustedProxies()".
1157
     *
1158
     * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http".
1159
     *
1160
     * If your reverse proxy uses a different header name than "X-Forwarded-Proto"
1161
     * ("SSL_HTTPS" for instance), configure it via "setTrustedHeaderName()" with
1162
     * the "client-proto" key.
1163
     *
1164
     * @return bool
1165
     */
1166
    public function isSecure()
1167
    {
1168
        if ($this->isFromTrustedProxy() && $proto = $this->getTrustedValues(self::HEADER_CLIENT_PROTO)) {
1169
            return in_array(strtolower($proto[0]), array('https', 'on', 'ssl', '1'), true);
1170
        }
1171
1172
        $https = $this->server->get('HTTPS');
1173
1174
        return !empty($https) && 'off' !== strtolower($https);
1175
    }
1176
1177
    /**
1178
     * Returns the host name.
1179
     *
1180
     * This method can read the client host name from the "X-Forwarded-Host" header
1181
     * when trusted proxies were set via "setTrustedProxies()".
1182
     *
1183
     * The "X-Forwarded-Host" header must contain the client host name.
1184
     *
1185
     * If your reverse proxy uses a different header name than "X-Forwarded-Host",
1186
     * configure it via "setTrustedHeaderName()" with the "client-host" key.
1187
     *
1188
     * @return string
1189
     *
1190
     * @throws \UnexpectedValueException when the host name is invalid
1191
     */
1192
    public function getHost()
1193
    {
1194
        if ($this->isFromTrustedProxy() && $host = $this->getTrustedValues(self::HEADER_CLIENT_HOST)) {
1195
            $host = $host[0];
1196
        } elseif (!$host = $this->headers->get('HOST')) {
1197
            if (!$host = $this->server->get('SERVER_NAME')) {
1198
                $host = $this->server->get('SERVER_ADDR', '');
1199
            }
1200
        }
1201
1202
        // trim and remove port number from host
1203
        // host is lowercase as per RFC 952/2181
1204
        $host = strtolower(preg_replace('/:\d+$/', '', trim($host)));
1205
1206
        // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
1207
        // check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
1208
        // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
1209
        if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
1210
            throw new \UnexpectedValueException(sprintf('Invalid Host "%s"', $host));
1211
        }
1212
1213
        if (count(self::$trustedHostPatterns) > 0) {
1214
            // to avoid host header injection attacks, you should provide a list of trusted host patterns
1215
1216
            if (in_array($host, self::$trustedHosts)) {
1217
                return $host;
1218
            }
1219
1220
            foreach (self::$trustedHostPatterns as $pattern) {
1221
                if (preg_match($pattern, $host)) {
1222
                    self::$trustedHosts[] = $host;
1223
1224
                    return $host;
1225
                }
1226
            }
1227
1228
            throw new \UnexpectedValueException(sprintf('Untrusted Host "%s"', $host));
1229
        }
1230
1231
        return $host;
1232
    }
1233
1234
    /**
1235
     * Sets the request method.
1236
     *
1237
     * @param string $method
1238
     */
1239
    public function setMethod($method)
1240
    {
1241
        $this->method = null;
1242
        $this->server->set('REQUEST_METHOD', $method);
1243
    }
1244
1245
    /**
1246
     * Gets the request "intended" method.
1247
     *
1248
     * If the X-HTTP-Method-Override header is set, and if the method is a POST,
1249
     * then it is used to determine the "real" intended HTTP method.
1250
     *
1251
     * The _method request parameter can also be used to determine the HTTP method,
1252
     * but only if enableHttpMethodParameterOverride() has been called.
1253
     *
1254
     * The method is always an uppercased string.
1255
     *
1256
     * @return string The request method
1257
     *
1258
     * @see getRealMethod()
1259
     */
1260
    public function getMethod()
1261
    {
1262
        if (null === $this->method) {
1263
            $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
1264
1265
            if ('POST' === $this->method) {
1266
                if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) {
1267
                    $this->method = strtoupper($method);
1268
                } elseif (self::$httpMethodParameterOverride) {
1269
                    $this->method = strtoupper($this->request->get('_method', $this->query->get('_method', 'POST')));
1270
                }
1271
            }
1272
        }
1273
1274
        return $this->method;
1275
    }
1276
1277
    /**
1278
     * Gets the "real" request method.
1279
     *
1280
     * @return string The request method
1281
     *
1282
     * @see getMethod()
1283
     */
1284
    public function getRealMethod()
1285
    {
1286
        return strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
1287
    }
1288
1289
    /**
1290
     * Gets the mime type associated with the format.
1291
     *
1292
     * @param string $format The format
1293
     *
1294
     * @return string The associated mime type (null if not found)
1295
     */
1296
    public function getMimeType($format)
1297
    {
1298
        if (null === static::$formats) {
1299
            static::initializeFormats();
1300
        }
1301
1302
        return isset(static::$formats[$format]) ? static::$formats[$format][0] : null;
1303
    }
1304
1305
    /**
1306
     * Gets the format associated with the mime type.
1307
     *
1308
     * @param string $mimeType The associated mime type
1309
     *
1310
     * @return string|null The format (null if not found)
1311
     */
1312
    public function getFormat($mimeType)
1313
    {
1314
        $canonicalMimeType = null;
1315
        if (false !== $pos = strpos($mimeType, ';')) {
1316
            $canonicalMimeType = substr($mimeType, 0, $pos);
1317
        }
1318
1319
        if (null === static::$formats) {
1320
            static::initializeFormats();
1321
        }
1322
1323
        foreach (static::$formats as $format => $mimeTypes) {
1324
            if (in_array($mimeType, (array) $mimeTypes)) {
1325
                return $format;
1326
            }
1327
            if (null !== $canonicalMimeType && in_array($canonicalMimeType, (array) $mimeTypes)) {
1328
                return $format;
1329
            }
1330
        }
1331
    }
1332
1333
    /**
1334
     * Associates a format with mime types.
1335
     *
1336
     * @param string       $format    The format
1337
     * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type)
1338
     */
1339
    public function setFormat($format, $mimeTypes)
1340
    {
1341
        if (null === static::$formats) {
1342
            static::initializeFormats();
1343
        }
1344
1345
        static::$formats[$format] = is_array($mimeTypes) ? $mimeTypes : array($mimeTypes);
1346
    }
1347
1348
    /**
1349
     * Gets the request format.
1350
     *
1351
     * Here is the process to determine the format:
1352
     *
1353
     *  * format defined by the user (with setRequestFormat())
1354
     *  * _format request parameter
1355
     *  * $default
1356
     *
1357
     * @param string $default The default format
1358
     *
1359
     * @return string The request format
1360
     */
1361
    public function getRequestFormat($default = 'html')
1362
    {
1363
        if (null === $this->format) {
1364
            $this->format = $this->get('_format');
1365
        }
1366
1367
        return null === $this->format ? $default : $this->format;
1368
    }
1369
1370
    /**
1371
     * Sets the request format.
1372
     *
1373
     * @param string $format The request format
1374
     */
1375
    public function setRequestFormat($format)
1376
    {
1377
        $this->format = $format;
1378
    }
1379
1380
    /**
1381
     * Gets the format associated with the request.
1382
     *
1383
     * @return string|null The format (null if no content type is present)
1384
     */
1385
    public function getContentType()
1386
    {
1387
        return $this->getFormat($this->headers->get('CONTENT_TYPE'));
1388
    }
1389
1390
    /**
1391
     * Sets the default locale.
1392
     *
1393
     * @param string $locale
1394
     */
1395
    public function setDefaultLocale($locale)
1396
    {
1397
        $this->defaultLocale = $locale;
1398
1399
        if (null === $this->locale) {
1400
            $this->setPhpDefaultLocale($locale);
1401
        }
1402
    }
1403
1404
    /**
1405
     * Get the default locale.
1406
     *
1407
     * @return string
1408
     */
1409
    public function getDefaultLocale()
1410
    {
1411
        return $this->defaultLocale;
1412
    }
1413
1414
    /**
1415
     * Sets the locale.
1416
     *
1417
     * @param string $locale
1418
     */
1419
    public function setLocale($locale)
1420
    {
1421
        $this->setPhpDefaultLocale($this->locale = $locale);
1422
    }
1423
1424
    /**
1425
     * Get the locale.
1426
     *
1427
     * @return string
1428
     */
1429
    public function getLocale()
1430
    {
1431
        return null === $this->locale ? $this->defaultLocale : $this->locale;
1432
    }
1433
1434
    /**
1435
     * Checks if the request method is of specified type.
1436
     *
1437
     * @param string $method Uppercase request method (GET, POST etc)
1438
     *
1439
     * @return bool
1440
     */
1441
    public function isMethod($method)
1442
    {
1443
        return $this->getMethod() === strtoupper($method);
1444
    }
1445
1446
    /**
1447
     * Checks whether the method is safe or not.
1448
     *
1449
     * @see https://tools.ietf.org/html/rfc7231#section-4.2.1
1450
     *
1451
     * @param bool $andCacheable Adds the additional condition that the method should be cacheable. True by default.
0 ignored issues
show
Bug introduced by
There is no parameter named $andCacheable. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1452
     *
1453
     * @return bool
1454
     */
1455
    public function isMethodSafe(/* $andCacheable = true */)
1456
    {
1457
        return in_array($this->getMethod(), 0 < func_num_args() && !func_get_arg(0) ? array('GET', 'HEAD', 'OPTIONS', 'TRACE') : array('GET', 'HEAD'));
1458
    }
1459
1460
    /**
1461
     * Checks whether the method is cacheable or not.
1462
     *
1463
     * @see https://tools.ietf.org/html/rfc7231#section-4.2.3
1464
     *
1465
     * @return bool
1466
     */
1467
    public function isMethodCacheable()
1468
    {
1469
        return in_array($this->getMethod(), array('GET', 'HEAD'));
1470
    }
1471
1472
    /**
1473
     * Returns the request body content.
1474
     *
1475
     * @param bool $asResource If true, a resource will be returned
1476
     *
1477
     * @return string|resource The request body content or a resource to read the body stream
1478
     *
1479
     * @throws \LogicException
1480
     */
1481
    public function getContent($asResource = false)
1482
    {
1483
        $currentContentIsResource = is_resource($this->content);
1484
        if (\PHP_VERSION_ID < 50600 && false === $this->content) {
1485
            throw new \LogicException('getContent() can only be called once when using the resource return type and PHP below 5.6.');
1486
        }
1487
1488
        if (true === $asResource) {
1489
            if ($currentContentIsResource) {
1490
                rewind($this->content);
1491
1492
                return $this->content;
1493
            }
1494
1495
            // Content passed in parameter (test)
1496
            if (is_string($this->content)) {
1497
                $resource = fopen('php://temp', 'r+');
1498
                fwrite($resource, $this->content);
1499
                rewind($resource);
1500
1501
                return $resource;
1502
            }
1503
1504
            $this->content = false;
0 ignored issues
show
Documentation Bug introduced by
The property $content was declared of type string, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
1505
1506
            return fopen('php://input', 'rb');
1507
        }
1508
1509
        if ($currentContentIsResource) {
1510
            rewind($this->content);
1511
1512
            return stream_get_contents($this->content);
1513
        }
1514
1515
        if (null === $this->content || false === $this->content) {
1516
            $this->content = file_get_contents('php://input');
1517
        }
1518
1519
        return $this->content;
1520
    }
1521
1522
    /**
1523
     * Gets the Etags.
1524
     *
1525
     * @return array The entity tags
1526
     */
1527
    public function getETags()
1528
    {
1529
        return preg_split('/\s*,\s*/', $this->headers->get('if_none_match'), null, PREG_SPLIT_NO_EMPTY);
1530
    }
1531
1532
    /**
1533
     * @return bool
1534
     */
1535
    public function isNoCache()
1536
    {
1537
        return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma');
1538
    }
1539
1540
    /**
1541
     * Returns the preferred language.
1542
     *
1543
     * @param array $locales An array of ordered available locales
1544
     *
1545
     * @return string|null The preferred locale
1546
     */
1547
    public function getPreferredLanguage(array $locales = null)
1548
    {
1549
        $preferredLanguages = $this->getLanguages();
1550
1551
        if (empty($locales)) {
1552
            return isset($preferredLanguages[0]) ? $preferredLanguages[0] : null;
1553
        }
1554
1555
        if (!$preferredLanguages) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $preferredLanguages of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1556
            return $locales[0];
1557
        }
1558
1559
        $extendedPreferredLanguages = array();
1560
        foreach ($preferredLanguages as $language) {
1561
            $extendedPreferredLanguages[] = $language;
1562
            if (false !== $position = strpos($language, '_')) {
1563
                $superLanguage = substr($language, 0, $position);
1564
                if (!in_array($superLanguage, $preferredLanguages)) {
1565
                    $extendedPreferredLanguages[] = $superLanguage;
1566
                }
1567
            }
1568
        }
1569
1570
        $preferredLanguages = array_values(array_intersect($extendedPreferredLanguages, $locales));
1571
1572
        return isset($preferredLanguages[0]) ? $preferredLanguages[0] : $locales[0];
1573
    }
1574
1575
    /**
1576
     * Gets a list of languages acceptable by the client browser.
1577
     *
1578
     * @return array Languages ordered in the user browser preferences
1579
     */
1580
    public function getLanguages()
1581
    {
1582
        if (null !== $this->languages) {
1583
            return $this->languages;
1584
        }
1585
1586
        $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all();
1587
        $this->languages = array();
1588
        foreach ($languages as $lang => $acceptHeaderItem) {
1589
            if (false !== strpos($lang, '-')) {
1590
                $codes = explode('-', $lang);
1591
                if ('i' === $codes[0]) {
1592
                    // Language not listed in ISO 639 that are not variants
1593
                    // of any listed language, which can be registered with the
1594
                    // i-prefix, such as i-cherokee
1595
                    if (count($codes) > 1) {
1596
                        $lang = $codes[1];
1597
                    }
1598
                } else {
1599
                    for ($i = 0, $max = count($codes); $i < $max; ++$i) {
1600
                        if ($i === 0) {
1601
                            $lang = strtolower($codes[0]);
1602
                        } else {
1603
                            $lang .= '_'.strtoupper($codes[$i]);
1604
                        }
1605
                    }
1606
                }
1607
            }
1608
1609
            $this->languages[] = $lang;
1610
        }
1611
1612
        return $this->languages;
1613
    }
1614
1615
    /**
1616
     * Gets a list of charsets acceptable by the client browser.
1617
     *
1618
     * @return array List of charsets in preferable order
1619
     */
1620 View Code Duplication
    public function getCharsets()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1621
    {
1622
        if (null !== $this->charsets) {
1623
            return $this->charsets;
1624
        }
1625
1626
        return $this->charsets = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all());
1627
    }
1628
1629
    /**
1630
     * Gets a list of encodings acceptable by the client browser.
1631
     *
1632
     * @return array List of encodings in preferable order
1633
     */
1634 View Code Duplication
    public function getEncodings()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1635
    {
1636
        if (null !== $this->encodings) {
1637
            return $this->encodings;
1638
        }
1639
1640
        return $this->encodings = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all());
1641
    }
1642
1643
    /**
1644
     * Gets a list of content types acceptable by the client browser.
1645
     *
1646
     * @return array List of content types in preferable order
1647
     */
1648 View Code Duplication
    public function getAcceptableContentTypes()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1649
    {
1650
        if (null !== $this->acceptableContentTypes) {
1651
            return $this->acceptableContentTypes;
1652
        }
1653
1654
        return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all());
1655
    }
1656
1657
    /**
1658
     * Returns true if the request is a XMLHttpRequest.
1659
     *
1660
     * It works if your JavaScript library sets an X-Requested-With HTTP header.
1661
     * It is known to work with common JavaScript frameworks:
1662
     *
1663
     * @see http://en.wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript
1664
     *
1665
     * @return bool true if the request is an XMLHttpRequest, false otherwise
1666
     */
1667
    public function isXmlHttpRequest()
1668
    {
1669
        return 'XMLHttpRequest' == $this->headers->get('X-Requested-With');
1670
    }
1671
1672
    /*
1673
     * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24)
1674
     *
1675
     * Code subject to the new BSD license (http://framework.zend.com/license/new-bsd).
1676
     *
1677
     * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
1678
     */
1679
1680
    protected function prepareRequestUri()
1681
    {
1682
        $requestUri = '';
1683
1684
        if ($this->headers->has('X_ORIGINAL_URL')) {
1685
            // IIS with Microsoft Rewrite Module
1686
            $requestUri = $this->headers->get('X_ORIGINAL_URL');
1687
            $this->headers->remove('X_ORIGINAL_URL');
1688
            $this->server->remove('HTTP_X_ORIGINAL_URL');
1689
            $this->server->remove('UNENCODED_URL');
1690
            $this->server->remove('IIS_WasUrlRewritten');
1691
        } elseif ($this->headers->has('X_REWRITE_URL')) {
1692
            // IIS with ISAPI_Rewrite
1693
            $requestUri = $this->headers->get('X_REWRITE_URL');
1694
            $this->headers->remove('X_REWRITE_URL');
1695
        } elseif ($this->server->get('IIS_WasUrlRewritten') == '1' && $this->server->get('UNENCODED_URL') != '') {
1696
            // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem)
1697
            $requestUri = $this->server->get('UNENCODED_URL');
1698
            $this->server->remove('UNENCODED_URL');
1699
            $this->server->remove('IIS_WasUrlRewritten');
1700
        } elseif ($this->server->has('REQUEST_URI')) {
1701
            $requestUri = $this->server->get('REQUEST_URI');
1702
            // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, only use URL path
1703
            $schemeAndHttpHost = $this->getSchemeAndHttpHost();
1704
            if (strpos($requestUri, $schemeAndHttpHost) === 0) {
1705
                $requestUri = substr($requestUri, strlen($schemeAndHttpHost));
1706
            }
1707
        } elseif ($this->server->has('ORIG_PATH_INFO')) {
1708
            // IIS 5.0, PHP as CGI
1709
            $requestUri = $this->server->get('ORIG_PATH_INFO');
1710
            if ('' != $this->server->get('QUERY_STRING')) {
1711
                $requestUri .= '?'.$this->server->get('QUERY_STRING');
1712
            }
1713
            $this->server->remove('ORIG_PATH_INFO');
1714
        }
1715
1716
        // normalize the request URI to ease creating sub-requests from this request
1717
        $this->server->set('REQUEST_URI', $requestUri);
1718
1719
        return $requestUri;
1720
    }
1721
1722
    /**
1723
     * Prepares the base URL.
1724
     *
1725
     * @return string
1726
     */
1727
    protected function prepareBaseUrl()
1728
    {
1729
        $filename = basename($this->server->get('SCRIPT_FILENAME'));
1730
1731
        if (basename($this->server->get('SCRIPT_NAME')) === $filename) {
1732
            $baseUrl = $this->server->get('SCRIPT_NAME');
1733
        } elseif (basename($this->server->get('PHP_SELF')) === $filename) {
1734
            $baseUrl = $this->server->get('PHP_SELF');
1735
        } elseif (basename($this->server->get('ORIG_SCRIPT_NAME')) === $filename) {
1736
            $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility
1737
        } else {
1738
            // Backtrack up the script_filename to find the portion matching
1739
            // php_self
1740
            $path = $this->server->get('PHP_SELF', '');
1741
            $file = $this->server->get('SCRIPT_FILENAME', '');
1742
            $segs = explode('/', trim($file, '/'));
1743
            $segs = array_reverse($segs);
1744
            $index = 0;
1745
            $last = count($segs);
1746
            $baseUrl = '';
1747
            do {
1748
                $seg = $segs[$index];
1749
                $baseUrl = '/'.$seg.$baseUrl;
1750
                ++$index;
1751
            } while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos);
1752
        }
1753
1754
        // Does the baseUrl have anything in common with the request_uri?
1755
        $requestUri = $this->getRequestUri();
1756
        if ($requestUri !== '' && $requestUri[0] !== '/') {
1757
            $requestUri = '/'.$requestUri;
1758
        }
1759
1760
        if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) {
1761
            // full $baseUrl matches
1762
            return $prefix;
1763
        }
1764
1765
        if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(dirname($baseUrl), '/'.DIRECTORY_SEPARATOR).'/')) {
1766
            // directory portion of $baseUrl matches
1767
            return rtrim($prefix, '/'.DIRECTORY_SEPARATOR);
1768
        }
1769
1770
        $truncatedRequestUri = $requestUri;
1771 View Code Duplication
        if (false !== $pos = strpos($requestUri, '?')) {
1772
            $truncatedRequestUri = substr($requestUri, 0, $pos);
1773
        }
1774
1775
        $basename = basename($baseUrl);
1776
        if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) {
1777
            // no match whatsoever; set it blank
1778
            return '';
1779
        }
1780
1781
        // If using mod_rewrite or ISAPI_Rewrite strip the script filename
1782
        // out of baseUrl. $pos !== 0 makes sure it is not matching a value
1783
        // from PATH_INFO or QUERY_STRING
1784
        if (strlen($requestUri) >= strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && $pos !== 0) {
1785
            $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl));
1786
        }
1787
1788
        return rtrim($baseUrl, '/'.DIRECTORY_SEPARATOR);
1789
    }
1790
1791
    /**
1792
     * Prepares the base path.
1793
     *
1794
     * @return string base path
1795
     */
1796
    protected function prepareBasePath()
1797
    {
1798
        $filename = basename($this->server->get('SCRIPT_FILENAME'));
1799
        $baseUrl = $this->getBaseUrl();
1800
        if (empty($baseUrl)) {
1801
            return '';
1802
        }
1803
1804
        if (basename($baseUrl) === $filename) {
1805
            $basePath = dirname($baseUrl);
1806
        } else {
1807
            $basePath = $baseUrl;
1808
        }
1809
1810
        if ('\\' === DIRECTORY_SEPARATOR) {
1811
            $basePath = str_replace('\\', '/', $basePath);
1812
        }
1813
1814
        return rtrim($basePath, '/');
1815
    }
1816
1817
    /**
1818
     * Prepares the path info.
1819
     *
1820
     * @return string path info
1821
     */
1822
    protected function preparePathInfo()
1823
    {
1824
        $baseUrl = $this->getBaseUrl();
1825
1826
        if (null === ($requestUri = $this->getRequestUri())) {
1827
            return '/';
1828
        }
1829
1830
        // Remove the query string from REQUEST_URI
1831 View Code Duplication
        if (false !== $pos = strpos($requestUri, '?')) {
1832
            $requestUri = substr($requestUri, 0, $pos);
1833
        }
1834
        if ($requestUri !== '' && $requestUri[0] !== '/') {
1835
            $requestUri = '/'.$requestUri;
1836
        }
1837
1838
        $pathInfo = substr($requestUri, strlen($baseUrl));
1839
        if (null !== $baseUrl && (false === $pathInfo || '' === $pathInfo)) {
1840
            // If substr() returns false then PATH_INFO is set to an empty string
1841
            return '/';
1842
        } elseif (null === $baseUrl) {
1843
            return $requestUri;
1844
        }
1845
1846
        return (string) $pathInfo;
1847
    }
1848
1849
    /**
1850
     * Initializes HTTP request formats.
1851
     */
1852
    protected static function initializeFormats()
1853
    {
1854
        static::$formats = array(
1855
            'html' => array('text/html', 'application/xhtml+xml'),
1856
            'txt' => array('text/plain'),
1857
            'js' => array('application/javascript', 'application/x-javascript', 'text/javascript'),
1858
            'css' => array('text/css'),
1859
            'json' => array('application/json', 'application/x-json'),
1860
            'xml' => array('text/xml', 'application/xml', 'application/x-xml'),
1861
            'rdf' => array('application/rdf+xml'),
1862
            'atom' => array('application/atom+xml'),
1863
            'rss' => array('application/rss+xml'),
1864
            'form' => array('application/x-www-form-urlencoded'),
1865
        );
1866
    }
1867
1868
    /**
1869
     * Sets the default PHP locale.
1870
     *
1871
     * @param string $locale
1872
     */
1873
    private function setPhpDefaultLocale($locale)
1874
    {
1875
        // if either the class Locale doesn't exist, or an exception is thrown when
1876
        // setting the default locale, the intl module is not installed, and
1877
        // the call can be ignored:
1878
        try {
1879
            if (class_exists('Locale', false)) {
1880
                \Locale::setDefault($locale);
1881
            }
1882
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1883
        }
1884
    }
1885
1886
    /*
1887
     * Returns the prefix as encoded in the string when the string starts with
1888
     * the given prefix, false otherwise.
1889
     *
1890
     * @param string $string The urlencoded string
1891
     * @param string $prefix The prefix not encoded
1892
     *
1893
     * @return string|false The prefix as it is encoded in $string, or false
1894
     */
1895
    private function getUrlencodedPrefix($string, $prefix)
1896
    {
1897
        if (0 !== strpos(rawurldecode($string), $prefix)) {
1898
            return false;
1899
        }
1900
1901
        $len = strlen($prefix);
1902
1903
        if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) {
1904
            return $match[0];
1905
        }
1906
1907
        return false;
1908
    }
1909
1910
    private static function createRequestFromFactory(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
1911
    {
1912
        if (self::$requestFactory) {
1913
            $request = call_user_func(self::$requestFactory, $query, $request, $attributes, $cookies, $files, $server, $content);
1914
1915
            if (!$request instanceof self) {
1916
                throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.');
1917
            }
1918
1919
            return $request;
1920
        }
1921
1922
        return new static($query, $request, $attributes, $cookies, $files, $server, $content);
1923
    }
1924
1925
    private function isFromTrustedProxy()
1926
    {
1927
        return self::$trustedProxies && IpUtils::checkIp($this->server->get('REMOTE_ADDR'), self::$trustedProxies);
0 ignored issues
show
Bug Best Practice introduced by
The expression self::$trustedProxies of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1928
    }
1929
1930
    private function getTrustedValues($type, $ip = null)
1931
    {
1932
        $clientValues = array();
1933
        $forwardedValues = array();
1934
1935
        if (self::$trustedHeaders[$type] && $this->headers->has(self::$trustedHeaders[$type])) {
1936
            foreach (explode(',', $this->headers->get(self::$trustedHeaders[$type])) as $v) {
1937
                $clientValues[] = (self::HEADER_CLIENT_PORT === $type ? '0.0.0.0:' : '').trim($v);
1938
            }
1939
        }
1940
1941
        if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
1942
            $forwardedValues = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
1943
            $forwardedValues = preg_match_all(sprintf('{(?:%s)=(?:"?\[?)([a-zA-Z0-9\.:_\-/]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : array();
1944
        }
1945
1946
        if (null !== $ip) {
1947
            $clientValues = $this->normalizeAndFilterClientIps($clientValues, $ip);
1948
            $forwardedValues = $this->normalizeAndFilterClientIps($forwardedValues, $ip);
1949
        }
1950
1951
        if ($forwardedValues === $clientValues || !$clientValues) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $clientValues of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1952
            return $forwardedValues;
1953
        }
1954
1955
        if (!$forwardedValues) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $forwardedValues of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1956
            return $clientValues;
1957
        }
1958
1959
        if (!$this->isForwardedValid) {
1960
            return null !== $ip ? array('0.0.0.0', $ip) : array();
1961
        }
1962
        $this->isForwardedValid = false;
1963
1964
        throw new ConflictingHeadersException(sprintf('The request has both a trusted "%s" header and a trusted "%s" header, conflicting with each other. You should either configure your proxy to remove one of them, or configure your project to distrust the offending one.', self::$trustedHeaders[self::HEADER_FORWARDED], self::$trustedHeaders[$type]));
1965
    }
1966
1967
    private function normalizeAndFilterClientIps(array $clientIps, $ip)
1968
    {
1969
        if (!$clientIps) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $clientIps of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1970
            return array();
1971
        }
1972
        $clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from
1973
        $firstTrustedIp = null;
1974
1975
        foreach ($clientIps as $key => $clientIp) {
1976
            // Remove port (unfortunately, it does happen)
1977
            if (preg_match('{((?:\d+\.){3}\d+)\:\d+}', $clientIp, $match)) {
1978
                $clientIps[$key] = $clientIp = $match[1];
1979
            }
1980
1981
            if (!filter_var($clientIp, FILTER_VALIDATE_IP)) {
1982
                unset($clientIps[$key]);
1983
1984
                continue;
1985
            }
1986
1987
            if (IpUtils::checkIp($clientIp, self::$trustedProxies)) {
1988
                unset($clientIps[$key]);
1989
1990
                // Fallback to this when the client IP falls into the range of trusted proxies
1991
                if (null === $firstTrustedIp) {
1992
                    $firstTrustedIp = $clientIp;
1993
                }
1994
            }
1995
        }
1996
1997
        // Now the IP chain contains only untrusted proxies and the client IP
1998
        return $clientIps ? array_reverse($clientIps) : array($firstTrustedIp);
1999
    }
2000
}
2001