Request::getFormat()   B
last analyzed

Complexity

Conditions 7
Paths 16

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 10
c 0
b 0
f 0
nc 16
nop 1
dl 0
loc 17
rs 8.8333
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
    /**
210
     * Constructor.
211
     *
212
     * @param array           $query      The GET parameters
213
     * @param array           $request    The POST parameters
214
     * @param array           $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
215
     * @param array           $cookies    The COOKIE parameters
216
     * @param array           $files      The FILES parameters
217
     * @param array           $server     The SERVER parameters
218
     * @param string|resource $content    The raw body data
219
     */
220
    public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
221
    {
222
        $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content);
223
    }
224
225
    /**
226
     * Sets the parameters for this request.
227
     *
228
     * This method also re-initializes all properties.
229
     *
230
     * @param array           $query      The GET parameters
231
     * @param array           $request    The POST parameters
232
     * @param array           $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
233
     * @param array           $cookies    The COOKIE parameters
234
     * @param array           $files      The FILES parameters
235
     * @param array           $server     The SERVER parameters
236
     * @param string|resource $content    The raw body data
237
     */
238
    public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
239
    {
240
        $this->request = new ParameterBag($request);
241
        $this->query = new ParameterBag($query);
242
        $this->attributes = new ParameterBag($attributes);
243
        $this->cookies = new ParameterBag($cookies);
244
        $this->files = new FileBag($files);
245
        $this->server = new ServerBag($server);
246
        $this->headers = new HeaderBag($this->server->getHeaders());
247
248
        $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...
249
        $this->languages = null;
250
        $this->charsets = null;
251
        $this->encodings = null;
252
        $this->acceptableContentTypes = null;
253
        $this->pathInfo = null;
254
        $this->requestUri = null;
255
        $this->baseUrl = null;
256
        $this->basePath = null;
257
        $this->method = null;
258
        $this->format = null;
259
    }
260
261
    /**
262
     * Creates a new request with values from PHP's super globals.
263
     *
264
     * @return static
265
     */
266
    public static function createFromGlobals()
267
    {
268
        // With the php's bug #66606, the php's built-in web server
269
        // stores the Content-Type and Content-Length header values in
270
        // HTTP_CONTENT_TYPE and HTTP_CONTENT_LENGTH fields.
271
        $server = $_SERVER;
272
        if ('cli-server' === PHP_SAPI) {
273
            if (array_key_exists('HTTP_CONTENT_LENGTH', $_SERVER)) {
274
                $server['CONTENT_LENGTH'] = $_SERVER['HTTP_CONTENT_LENGTH'];
275
            }
276
            if (array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) {
277
                $server['CONTENT_TYPE'] = $_SERVER['HTTP_CONTENT_TYPE'];
278
            }
279
        }
280
281
        $request = self::createRequestFromFactory($_GET, $_POST, array(), $_COOKIE, $_FILES, $server);
282
283
        if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
284
            && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE', 'PATCH'))
285
        ) {
286
            parse_str($request->getContent(), $data);
287
            $request->request = new ParameterBag($data);
288
        }
289
290
        return $request;
291
    }
292
293
    /**
294
     * Creates a Request based on a given URI and configuration.
295
     *
296
     * The information contained in the URI always take precedence
297
     * over the other information (server and parameters).
298
     *
299
     * @param string $uri        The URI
300
     * @param string $method     The HTTP method
301
     * @param array  $parameters The query (GET) or request (POST) parameters
302
     * @param array  $cookies    The request cookies ($_COOKIE)
303
     * @param array  $files      The request files ($_FILES)
304
     * @param array  $server     The server parameters ($_SERVER)
305
     * @param string $content    The raw body data
306
     *
307
     * @return static
308
     */
309
    public static function create($uri, $method = 'GET', $parameters = array(), $cookies = array(), $files = array(), $server = array(), $content = null)
310
    {
311
        $server = array_replace(array(
312
            'SERVER_NAME' => 'localhost',
313
            'SERVER_PORT' => 80,
314
            'HTTP_HOST' => 'localhost',
315
            'HTTP_USER_AGENT' => 'Symfony/2.X',
316
            'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
317
            'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5',
318
            'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
319
            'REMOTE_ADDR' => '127.0.0.1',
320
            'SCRIPT_NAME' => '',
321
            'SCRIPT_FILENAME' => '',
322
            'SERVER_PROTOCOL' => 'HTTP/1.1',
323
            'REQUEST_TIME' => time(),
324
        ), $server);
325
326
        $server['PATH_INFO'] = '';
327
        $server['REQUEST_METHOD'] = strtoupper($method);
328
329
        $components = parse_url($uri);
330
        if (isset($components['host'])) {
331
            $server['SERVER_NAME'] = $components['host'];
332
            $server['HTTP_HOST'] = $components['host'];
333
        }
334
335
        if (isset($components['scheme'])) {
336
            if ('https' === $components['scheme']) {
337
                $server['HTTPS'] = 'on';
338
                $server['SERVER_PORT'] = 443;
339
            } else {
340
                unset($server['HTTPS']);
341
                $server['SERVER_PORT'] = 80;
342
            }
343
        }
344
345
        if (isset($components['port'])) {
346
            $server['SERVER_PORT'] = $components['port'];
347
            $server['HTTP_HOST'] = $server['HTTP_HOST'].':'.$components['port'];
348
        }
349
350
        if (isset($components['user'])) {
351
            $server['PHP_AUTH_USER'] = $components['user'];
352
        }
353
354
        if (isset($components['pass'])) {
355
            $server['PHP_AUTH_PW'] = $components['pass'];
356
        }
357
358
        if (!isset($components['path'])) {
359
            $components['path'] = '/';
360
        }
361
362
        switch (strtoupper($method)) {
363
            case 'POST':
364
            case 'PUT':
365
            case 'DELETE':
366
                if (!isset($server['CONTENT_TYPE'])) {
367
                    $server['CONTENT_TYPE'] = 'application/x-www-form-urlencoded';
368
                }
369
                // no break
370
            case 'PATCH':
371
                $request = $parameters;
372
                $query = array();
373
                break;
374
            default:
375
                $request = array();
376
                $query = $parameters;
377
                break;
378
        }
379
380
        $queryString = '';
381
        if (isset($components['query'])) {
382
            parse_str(html_entity_decode($components['query']), $qs);
383
384
            if ($query) {
385
                $query = array_replace($qs, $query);
386
                $queryString = http_build_query($query, '', '&');
387
            } else {
388
                $query = $qs;
389
                $queryString = $components['query'];
390
            }
391
        } elseif ($query) {
392
            $queryString = http_build_query($query, '', '&');
393
        }
394
395
        $server['REQUEST_URI'] = $components['path'].('' !== $queryString ? '?'.$queryString : '');
396
        $server['QUERY_STRING'] = $queryString;
397
398
        return self::createRequestFromFactory($query, $request, array(), $cookies, $files, $server, $content);
399
    }
400
401
    /**
402
     * Sets a callable able to create a Request instance.
403
     *
404
     * This is mainly useful when you need to override the Request class
405
     * to keep BC with an existing system. It should not be used for any
406
     * other purpose.
407
     *
408
     * @param callable|null $callable A PHP callable
409
     */
410
    public static function setFactory($callable)
411
    {
412
        self::$requestFactory = $callable;
413
    }
414
415
    /**
416
     * Clones a request and overrides some of its parameters.
417
     *
418
     * @param array $query      The GET parameters
419
     * @param array $request    The POST parameters
420
     * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
421
     * @param array $cookies    The COOKIE parameters
422
     * @param array $files      The FILES parameters
423
     * @param array $server     The SERVER parameters
424
     *
425
     * @return static
426
     */
427
    public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
428
    {
429
        $dup = clone $this;
430
        if ($query !== null) {
431
            $dup->query = new ParameterBag($query);
432
        }
433
        if ($request !== null) {
434
            $dup->request = new ParameterBag($request);
435
        }
436
        if ($attributes !== null) {
437
            $dup->attributes = new ParameterBag($attributes);
438
        }
439
        if ($cookies !== null) {
440
            $dup->cookies = new ParameterBag($cookies);
441
        }
442
        if ($files !== null) {
443
            $dup->files = new FileBag($files);
444
        }
445
        if ($server !== null) {
446
            $dup->server = new ServerBag($server);
447
            $dup->headers = new HeaderBag($dup->server->getHeaders());
448
        }
449
        $dup->languages = null;
450
        $dup->charsets = null;
451
        $dup->encodings = null;
452
        $dup->acceptableContentTypes = null;
453
        $dup->pathInfo = null;
454
        $dup->requestUri = null;
455
        $dup->baseUrl = null;
456
        $dup->basePath = null;
457
        $dup->method = null;
458
        $dup->format = null;
459
460
        if (!$dup->get('_format') && $this->get('_format')) {
461
            $dup->attributes->set('_format', $this->get('_format'));
462
        }
463
464
        if (!$dup->getRequestFormat(null)) {
465
            $dup->setRequestFormat($this->getRequestFormat(null));
466
        }
467
468
        return $dup;
469
    }
470
471
    /**
472
     * Clones the current request.
473
     *
474
     * Note that the session is not cloned as duplicated requests
475
     * are most of the time sub-requests of the main one.
476
     */
477
    public function __clone()
478
    {
479
        $this->query = clone $this->query;
480
        $this->request = clone $this->request;
481
        $this->attributes = clone $this->attributes;
482
        $this->cookies = clone $this->cookies;
483
        $this->files = clone $this->files;
484
        $this->server = clone $this->server;
485
        $this->headers = clone $this->headers;
486
    }
487
488
    /**
489
     * Returns the request as a string.
490
     *
491
     * @return string The request
492
     */
493
    public function __toString()
494
    {
495
        try {
496
            $content = $this->getContent();
497
        } catch (\LogicException $e) {
498
            return trigger_error($e, E_USER_ERROR);
0 ignored issues
show
Bug Best Practice introduced by
The expression return trigger_error($e,...oundation\E_USER_ERROR) returns the type boolean which is incompatible with the documented return type string.
Loading history...
499
        }
500
501
        return
502
            sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n".
503
            $this->headers."\r\n".
504
            $content;
505
    }
506
507
    /**
508
     * Overrides the PHP global variables according to this request instance.
509
     *
510
     * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE.
511
     * $_FILES is never overridden, see rfc1867
512
     */
513
    public function overrideGlobals()
514
    {
515
        $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), null, '&')));
0 ignored issues
show
Bug introduced by
null of type null is incompatible with the type string expected by parameter $numeric_prefix of http_build_query(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

515
        $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), /** @scrutinizer ignore-type */ null, '&')));
Loading history...
516
517
        $_GET = $this->query->all();
518
        $_POST = $this->request->all();
519
        $_SERVER = $this->server->all();
520
        $_COOKIE = $this->cookies->all();
521
522
        foreach ($this->headers->all() as $key => $value) {
523
            $key = strtoupper(str_replace('-', '_', $key));
524
            if (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) {
525
                $_SERVER[$key] = implode(', ', $value);
526
            } else {
527
                $_SERVER['HTTP_'.$key] = implode(', ', $value);
528
            }
529
        }
530
531
        $request = array('g' => $_GET, 'p' => $_POST, 'c' => $_COOKIE);
532
533
        $requestOrder = ini_get('request_order') ?: ini_get('variables_order');
534
        $requestOrder = preg_replace('#[^cgp]#', '', strtolower($requestOrder)) ?: 'gp';
535
536
        $_REQUEST = array();
537
        foreach (str_split($requestOrder) as $order) {
538
            $_REQUEST = array_merge($_REQUEST, $request[$order]);
539
        }
540
    }
541
542
    /**
543
     * Sets a list of trusted proxies.
544
     *
545
     * You should only list the reverse proxies that you manage directly.
546
     *
547
     * @param array $proxies A list of trusted proxies
548
     */
549
    public static function setTrustedProxies(array $proxies)
550
    {
551
        self::$trustedProxies = $proxies;
552
    }
553
554
    /**
555
     * Gets the list of trusted proxies.
556
     *
557
     * @return array An array of trusted proxies
558
     */
559
    public static function getTrustedProxies()
560
    {
561
        return self::$trustedProxies;
562
    }
563
564
    /**
565
     * Sets a list of trusted host patterns.
566
     *
567
     * You should only list the hosts you manage using regexs.
568
     *
569
     * @param array $hostPatterns A list of trusted host patterns
570
     */
571
    public static function setTrustedHosts(array $hostPatterns)
572
    {
573
        self::$trustedHostPatterns = array_map(function ($hostPattern) {
574
            return sprintf('#%s#i', $hostPattern);
575
        }, $hostPatterns);
576
        // we need to reset trusted hosts on trusted host patterns change
577
        self::$trustedHosts = array();
578
    }
579
580
    /**
581
     * Gets the list of trusted host patterns.
582
     *
583
     * @return array An array of trusted host patterns
584
     */
585
    public static function getTrustedHosts()
586
    {
587
        return self::$trustedHostPatterns;
588
    }
589
590
    /**
591
     * Sets the name for trusted headers.
592
     *
593
     * The following header keys are supported:
594
     *
595
     *  * Request::HEADER_CLIENT_IP:    defaults to X-Forwarded-For   (see getClientIp())
596
     *  * Request::HEADER_CLIENT_HOST:  defaults to X-Forwarded-Host  (see getHost())
597
     *  * Request::HEADER_CLIENT_PORT:  defaults to X-Forwarded-Port  (see getPort())
598
     *  * Request::HEADER_CLIENT_PROTO: defaults to X-Forwarded-Proto (see getScheme() and isSecure())
599
     *  * Request::HEADER_FORWARDED:    defaults to Forwarded         (see RFC 7239)
600
     *
601
     * Setting an empty value allows to disable the trusted header for the given key.
602
     *
603
     * @param string $key   The header key
604
     * @param string $value The header name
605
     *
606
     * @throws \InvalidArgumentException
607
     */
608
    public static function setTrustedHeaderName($key, $value)
609
    {
610
        if (!array_key_exists($key, self::$trustedHeaders)) {
611
            throw new \InvalidArgumentException(sprintf('Unable to set the trusted header name for key "%s".', $key));
612
        }
613
614
        self::$trustedHeaders[$key] = $value;
615
    }
616
617
    /**
618
     * Gets the trusted proxy header name.
619
     *
620
     * @param string $key The header key
621
     *
622
     * @return string The header name
623
     *
624
     * @throws \InvalidArgumentException
625
     */
626
    public static function getTrustedHeaderName($key)
627
    {
628
        if (!array_key_exists($key, self::$trustedHeaders)) {
629
            throw new \InvalidArgumentException(sprintf('Unable to get the trusted header name for key "%s".', $key));
630
        }
631
632
        return self::$trustedHeaders[$key];
633
    }
634
635
    /**
636
     * Normalizes a query string.
637
     *
638
     * It builds a normalized query string, where keys/value pairs are alphabetized,
639
     * have consistent escaping and unneeded delimiters are removed.
640
     *
641
     * @param string $qs Query string
642
     *
643
     * @return string A normalized query string for the Request
644
     */
645
    public static function normalizeQueryString($qs)
646
    {
647
        if ('' == $qs) {
648
            return '';
649
        }
650
651
        $parts = array();
652
        $order = array();
653
654
        foreach (explode('&', $qs) as $param) {
655
            if ('' === $param || '=' === $param[0]) {
656
                // Ignore useless delimiters, e.g. "x=y&".
657
                // Also ignore pairs with empty key, even if there was a value, e.g. "=value", as such nameless values cannot be retrieved anyway.
658
                // PHP also does not include them when building _GET.
659
                continue;
660
            }
661
662
            $keyValuePair = explode('=', $param, 2);
663
664
            // GET parameters, that are submitted from a HTML form, encode spaces as "+" by default (as defined in enctype application/x-www-form-urlencoded).
665
            // 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
666
            // RFC 3986 with rawurlencode.
667
            $parts[] = isset($keyValuePair[1]) ?
668
                rawurlencode(urldecode($keyValuePair[0])).'='.rawurlencode(urldecode($keyValuePair[1])) :
669
                rawurlencode(urldecode($keyValuePair[0]));
670
            $order[] = urldecode($keyValuePair[0]);
671
        }
672
673
        array_multisort($order, SORT_ASC, $parts);
0 ignored issues
show
Bug introduced by
Symfony\Component\HttpFoundation\SORT_ASC cannot be passed to array_multisort() as the parameter $rest expects a reference. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

673
        array_multisort($order, /** @scrutinizer ignore-type */ SORT_ASC, $parts);
Loading history...
674
675
        return implode('&', $parts);
676
    }
677
678
    /**
679
     * Enables support for the _method request parameter to determine the intended HTTP method.
680
     *
681
     * Be warned that enabling this feature might lead to CSRF issues in your code.
682
     * Check that you are using CSRF tokens when required.
683
     * If the HTTP method parameter override is enabled, an html-form with method "POST" can be altered
684
     * and used to send a "PUT" or "DELETE" request via the _method request parameter.
685
     * If these methods are not protected against CSRF, this presents a possible vulnerability.
686
     *
687
     * The HTTP method can only be overridden when the real HTTP method is POST.
688
     */
689
    public static function enableHttpMethodParameterOverride()
690
    {
691
        self::$httpMethodParameterOverride = true;
692
    }
693
694
    /**
695
     * Checks whether support for the _method request parameter is enabled.
696
     *
697
     * @return bool True when the _method request parameter is enabled, false otherwise
698
     */
699
    public static function getHttpMethodParameterOverride()
700
    {
701
        return self::$httpMethodParameterOverride;
702
    }
703
704
    /**
705
     * Gets a "parameter" value.
706
     *
707
     * This method is mainly useful for libraries that want to provide some flexibility.
708
     *
709
     * Order of precedence: GET, PATH, POST
710
     *
711
     * Avoid using this method in controllers:
712
     *
713
     *  * slow
714
     *  * prefer to get from a "named" source
715
     *
716
     * It is better to explicitly get request parameters from the appropriate
717
     * public property instead (query, attributes, request).
718
     *
719
     * @param string $key     the key
720
     * @param mixed  $default the default value if the parameter key does not exist
721
     * @param bool   $deep    is parameter deep in multidimensional array
722
     *
723
     * @return mixed
724
     */
725
    public function get($key, $default = null, $deep = false)
726
    {
727
        if ($this !== $result = $this->query->get($key, $this, $deep)) {
728
            return $result;
729
        }
730
731
        if ($this !== $result = $this->attributes->get($key, $this, $deep)) {
732
            return $result;
733
        }
734
735
        if ($this !== $result = $this->request->get($key, $this, $deep)) {
736
            return $result;
737
        }
738
739
        return $default;
740
    }
741
742
    /**
743
     * Gets the Session.
744
     *
745
     * @return SessionInterface|null The session
746
     */
747
    public function getSession()
748
    {
749
        return $this->session;
750
    }
751
752
    /**
753
     * Whether the request contains a Session which was started in one of the
754
     * previous requests.
755
     *
756
     * @return bool
757
     */
758
    public function hasPreviousSession()
759
    {
760
        // the check for $this->session avoids malicious users trying to fake a session cookie with proper name
761
        return $this->hasSession() && $this->cookies->has($this->session->getName());
762
    }
763
764
    /**
765
     * Whether the request contains a Session object.
766
     *
767
     * This method does not give any information about the state of the session object,
768
     * like whether the session is started or not. It is just a way to check if this Request
769
     * is associated with a Session instance.
770
     *
771
     * @return bool true when the Request contains a Session object, false otherwise
772
     */
773
    public function hasSession()
774
    {
775
        return null !== $this->session;
776
    }
777
778
    /**
779
     * Sets the Session.
780
     *
781
     * @param SessionInterface $session The Session
782
     */
783
    public function setSession(SessionInterface $session)
784
    {
785
        $this->session = $session;
786
    }
787
788
    /**
789
     * Returns the client IP addresses.
790
     *
791
     * In the returned array the most trusted IP address is first, and the
792
     * least trusted one last. The "real" client IP address is the last one,
793
     * but this is also the least trusted one. Trusted proxies are stripped.
794
     *
795
     * Use this method carefully; you should use getClientIp() instead.
796
     *
797
     * @return array The client IP addresses
798
     *
799
     * @see getClientIp()
800
     */
801
    public function getClientIps()
802
    {
803
        $clientIps = array();
804
        $ip = $this->server->get('REMOTE_ADDR');
805
806
        if (!$this->isFromTrustedProxy()) {
807
            return array($ip);
808
        }
809
810
        $hasTrustedForwardedHeader = self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED]);
811
        $hasTrustedClientIpHeader = self::$trustedHeaders[self::HEADER_CLIENT_IP] && $this->headers->has(self::$trustedHeaders[self::HEADER_CLIENT_IP]);
812
813
        if ($hasTrustedForwardedHeader) {
814
            $forwardedHeader = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
815
            preg_match_all('{(for)=("?\[?)([a-z0-9\.:_\-/]*)}', $forwardedHeader, $matches);
816
            $forwardedClientIps = $matches[3];
817
818
            $forwardedClientIps = $this->normalizeAndFilterClientIps($forwardedClientIps, $ip);
819
            $clientIps = $forwardedClientIps;
820
        }
821
822
        if ($hasTrustedClientIpHeader) {
823
            $xForwardedForClientIps = array_map('trim', explode(',', $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_IP])));
824
825
            $xForwardedForClientIps = $this->normalizeAndFilterClientIps($xForwardedForClientIps, $ip);
826
            $clientIps = $xForwardedForClientIps;
827
        }
828
829
        if ($hasTrustedForwardedHeader && $hasTrustedClientIpHeader && $forwardedClientIps !== $xForwardedForClientIps) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $forwardedClientIps does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $xForwardedForClientIps does not seem to be defined for all execution paths leading up to this point.
Loading history...
830
            throw new ConflictingHeadersException('The request has both a trusted Forwarded header and a trusted Client IP header, conflicting with each other with regards to the originating IP addresses of the request. This is the result of a misconfiguration. You should either configure your proxy only to send one of these headers, or configure Symfony to distrust one of them.');
831
        }
832
833
        if (!$hasTrustedForwardedHeader && !$hasTrustedClientIpHeader) {
834
            return $this->normalizeAndFilterClientIps(array(), $ip);
835
        }
836
837
        return $clientIps;
838
    }
839
840
    /**
841
     * Returns the client IP address.
842
     *
843
     * This method can read the client IP address from the "X-Forwarded-For" header
844
     * when trusted proxies were set via "setTrustedProxies()". The "X-Forwarded-For"
845
     * header value is a comma+space separated list of IP addresses, the left-most
846
     * being the original client, and each successive proxy that passed the request
847
     * adding the IP address where it received the request from.
848
     *
849
     * If your reverse proxy uses a different header name than "X-Forwarded-For",
850
     * ("Client-Ip" for instance), configure it via "setTrustedHeaderName()" with
851
     * the "client-ip" key.
852
     *
853
     * @return string|null The client IP address
854
     *
855
     * @see getClientIps()
856
     * @see http://en.wikipedia.org/wiki/X-Forwarded-For
857
     */
858
    public function getClientIp()
859
    {
860
        $ipAddresses = $this->getClientIps();
861
862
        return $ipAddresses[0];
863
    }
864
865
    /**
866
     * Returns current script name.
867
     *
868
     * @return string
869
     */
870
    public function getScriptName()
871
    {
872
        return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', ''));
873
    }
874
875
    /**
876
     * Returns the path being requested relative to the executed script.
877
     *
878
     * The path info always starts with a /.
879
     *
880
     * Suppose this request is instantiated from /mysite on localhost:
881
     *
882
     *  * http://localhost/mysite              returns an empty string
883
     *  * http://localhost/mysite/about        returns '/about'
884
     *  * http://localhost/mysite/enco%20ded   returns '/enco%20ded'
885
     *  * http://localhost/mysite/about?var=1  returns '/about'
886
     *
887
     * @return string The raw path (i.e. not urldecoded)
888
     */
889
    public function getPathInfo()
890
    {
891
        if (null === $this->pathInfo) {
892
            $this->pathInfo = $this->preparePathInfo();
893
        }
894
895
        return $this->pathInfo;
896
    }
897
898
    /**
899
     * Returns the root path from which this request is executed.
900
     *
901
     * Suppose that an index.php file instantiates this request object:
902
     *
903
     *  * http://localhost/index.php         returns an empty string
904
     *  * http://localhost/index.php/page    returns an empty string
905
     *  * http://localhost/web/index.php     returns '/web'
906
     *  * http://localhost/we%20b/index.php  returns '/we%20b'
907
     *
908
     * @return string The raw path (i.e. not urldecoded)
909
     */
910
    public function getBasePath()
911
    {
912
        if (null === $this->basePath) {
913
            $this->basePath = $this->prepareBasePath();
914
        }
915
916
        return $this->basePath;
917
    }
918
919
    /**
920
     * Returns the root URL from which this request is executed.
921
     *
922
     * The base URL never ends with a /.
923
     *
924
     * This is similar to getBasePath(), except that it also includes the
925
     * script filename (e.g. index.php) if one exists.
926
     *
927
     * @return string The raw URL (i.e. not urldecoded)
928
     */
929
    public function getBaseUrl()
930
    {
931
        if (null === $this->baseUrl) {
932
            $this->baseUrl = $this->prepareBaseUrl();
933
        }
934
935
        return $this->baseUrl;
936
    }
937
938
    /**
939
     * Gets the request's scheme.
940
     *
941
     * @return string
942
     */
943
    public function getScheme()
944
    {
945
        return $this->isSecure() ? 'https' : 'http';
946
    }
947
948
    /**
949
     * Returns the port on which the request is made.
950
     *
951
     * This method can read the client port from the "X-Forwarded-Port" header
952
     * when trusted proxies were set via "setTrustedProxies()".
953
     *
954
     * The "X-Forwarded-Port" header must contain the client port.
955
     *
956
     * If your reverse proxy uses a different header name than "X-Forwarded-Port",
957
     * configure it via "setTrustedHeaderName()" with the "client-port" key.
958
     *
959
     * @return int|string can be a string if fetched from the server bag
960
     */
961
    public function getPort()
962
    {
963
        if ($this->isFromTrustedProxy()) {
964
            if (self::$trustedHeaders[self::HEADER_CLIENT_PORT] && $port = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PORT])) {
965
                return (int) $port;
966
            }
967
968
            if (self::$trustedHeaders[self::HEADER_CLIENT_PROTO] && 'https' === $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PROTO], 'http')) {
969
                return 443;
970
            }
971
        }
972
973
        if ($host = $this->headers->get('HOST')) {
974
            if ($host[0] === '[') {
975
                $pos = strpos($host, ':', strrpos($host, ']'));
976
            } else {
977
                $pos = strrpos($host, ':');
978
            }
979
980
            if (false !== $pos) {
981
                return (int) substr($host, $pos + 1);
982
            }
983
984
            return 'https' === $this->getScheme() ? 443 : 80;
985
        }
986
987
        return $this->server->get('SERVER_PORT');
988
    }
989
990
    /**
991
     * Returns the user.
992
     *
993
     * @return string|null
994
     */
995
    public function getUser()
996
    {
997
        return $this->headers->get('PHP_AUTH_USER');
998
    }
999
1000
    /**
1001
     * Returns the password.
1002
     *
1003
     * @return string|null
1004
     */
1005
    public function getPassword()
1006
    {
1007
        return $this->headers->get('PHP_AUTH_PW');
1008
    }
1009
1010
    /**
1011
     * Gets the user info.
1012
     *
1013
     * @return string A user name and, optionally, scheme-specific information about how to gain authorization to access the server
1014
     */
1015
    public function getUserInfo()
1016
    {
1017
        $userinfo = $this->getUser();
1018
1019
        $pass = $this->getPassword();
1020
        if ('' != $pass) {
1021
            $userinfo .= ":$pass";
1022
        }
1023
1024
        return $userinfo;
1025
    }
1026
1027
    /**
1028
     * Returns the HTTP host being requested.
1029
     *
1030
     * The port name will be appended to the host if it's non-standard.
1031
     *
1032
     * @return string
1033
     */
1034
    public function getHttpHost()
1035
    {
1036
        $scheme = $this->getScheme();
1037
        $port = $this->getPort();
1038
1039
        if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) {
1040
            return $this->getHost();
1041
        }
1042
1043
        return $this->getHost().':'.$port;
1044
    }
1045
1046
    /**
1047
     * Returns the requested URI (path and query string).
1048
     *
1049
     * @return string The raw URI (i.e. not URI decoded)
1050
     */
1051
    public function getRequestUri()
1052
    {
1053
        if (null === $this->requestUri) {
1054
            $this->requestUri = $this->prepareRequestUri();
1055
        }
1056
1057
        return $this->requestUri;
1058
    }
1059
1060
    /**
1061
     * Gets the scheme and HTTP host.
1062
     *
1063
     * If the URL was called with basic authentication, the user
1064
     * and the password are not added to the generated string.
1065
     *
1066
     * @return string The scheme and HTTP host
1067
     */
1068
    public function getSchemeAndHttpHost()
1069
    {
1070
        return $this->getScheme().'://'.$this->getHttpHost();
1071
    }
1072
1073
    /**
1074
     * Generates a normalized URI (URL) for the Request.
1075
     *
1076
     * @return string A normalized URI (URL) for the Request
1077
     *
1078
     * @see getQueryString()
1079
     */
1080
    public function getUri()
1081
    {
1082
        if (null !== $qs = $this->getQueryString()) {
1083
            $qs = '?'.$qs;
1084
        }
1085
1086
        return $this->getSchemeAndHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs;
1087
    }
1088
1089
    /**
1090
     * Generates a normalized URI for the given path.
1091
     *
1092
     * @param string $path A path to use instead of the current one
1093
     *
1094
     * @return string The normalized URI for the path
1095
     */
1096
    public function getUriForPath($path)
1097
    {
1098
        return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path;
1099
    }
1100
1101
    /**
1102
     * Returns the path as relative reference from the current Request path.
1103
     *
1104
     * Only the URIs path component (no schema, host etc.) is relevant and must be given.
1105
     * Both paths must be absolute and not contain relative parts.
1106
     * Relative URLs from one resource to another are useful when generating self-contained downloadable document archives.
1107
     * Furthermore, they can be used to reduce the link size in documents.
1108
     *
1109
     * Example target paths, given a base path of "/a/b/c/d":
1110
     * - "/a/b/c/d"     -> ""
1111
     * - "/a/b/c/"      -> "./"
1112
     * - "/a/b/"        -> "../"
1113
     * - "/a/b/c/other" -> "other"
1114
     * - "/a/x/y"       -> "../../x/y"
1115
     *
1116
     * @param string $path The target path
1117
     *
1118
     * @return string The relative target path
1119
     */
1120
    public function getRelativeUriForPath($path)
1121
    {
1122
        // be sure that we are dealing with an absolute path
1123
        if (!isset($path[0]) || '/' !== $path[0]) {
1124
            return $path;
1125
        }
1126
1127
        if ($path === $basePath = $this->getPathInfo()) {
1128
            return '';
1129
        }
1130
1131
        $sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath);
1132
        $targetDirs = explode('/', isset($path[0]) && '/' === $path[0] ? substr($path, 1) : $path);
1133
        array_pop($sourceDirs);
1134
        $targetFile = array_pop($targetDirs);
1135
1136
        foreach ($sourceDirs as $i => $dir) {
1137
            if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) {
1138
                unset($sourceDirs[$i], $targetDirs[$i]);
1139
            } else {
1140
                break;
1141
            }
1142
        }
1143
1144
        $targetDirs[] = $targetFile;
1145
        $path = str_repeat('../', count($sourceDirs)).implode('/', $targetDirs);
1146
1147
        // A reference to the same base directory or an empty subdirectory must be prefixed with "./".
1148
        // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
1149
        // as the first segment of a relative-path reference, as it would be mistaken for a scheme name
1150
        // (see http://tools.ietf.org/html/rfc3986#section-4.2).
1151
        return !isset($path[0]) || '/' === $path[0]
1152
            || false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos)
1153
            ? "./$path" : $path;
1154
    }
1155
1156
    /**
1157
     * Generates the normalized query string for the Request.
1158
     *
1159
     * It builds a normalized query string, where keys/value pairs are alphabetized
1160
     * and have consistent escaping.
1161
     *
1162
     * @return string|null A normalized query string for the Request
1163
     */
1164
    public function getQueryString()
1165
    {
1166
        $qs = static::normalizeQueryString($this->server->get('QUERY_STRING'));
1167
1168
        return '' === $qs ? null : $qs;
1169
    }
1170
1171
    /**
1172
     * Checks whether the request is secure or not.
1173
     *
1174
     * This method can read the client protocol from the "X-Forwarded-Proto" header
1175
     * when trusted proxies were set via "setTrustedProxies()".
1176
     *
1177
     * The "X-Forwarded-Proto" header must contain the protocol: "https" or "http".
1178
     *
1179
     * If your reverse proxy uses a different header name than "X-Forwarded-Proto"
1180
     * ("SSL_HTTPS" for instance), configure it via "setTrustedHeaderName()" with
1181
     * the "client-proto" key.
1182
     *
1183
     * @return bool
1184
     */
1185
    public function isSecure()
1186
    {
1187
        if ($this->isFromTrustedProxy() && self::$trustedHeaders[self::HEADER_CLIENT_PROTO] && $proto = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_PROTO])) {
1188
            return in_array(strtolower(current(explode(',', $proto))), array('https', 'on', 'ssl', '1'));
1189
        }
1190
1191
        $https = $this->server->get('HTTPS');
1192
1193
        return !empty($https) && 'off' !== strtolower($https);
1194
    }
1195
1196
    /**
1197
     * Returns the host name.
1198
     *
1199
     * This method can read the client host name from the "X-Forwarded-Host" header
1200
     * when trusted proxies were set via "setTrustedProxies()".
1201
     *
1202
     * The "X-Forwarded-Host" header must contain the client host name.
1203
     *
1204
     * If your reverse proxy uses a different header name than "X-Forwarded-Host",
1205
     * configure it via "setTrustedHeaderName()" with the "client-host" key.
1206
     *
1207
     * @return string
1208
     *
1209
     * @throws \UnexpectedValueException when the host name is invalid
1210
     */
1211
    public function getHost()
1212
    {
1213
        if ($this->isFromTrustedProxy() && self::$trustedHeaders[self::HEADER_CLIENT_HOST] && $host = $this->headers->get(self::$trustedHeaders[self::HEADER_CLIENT_HOST])) {
1214
            $elements = explode(',', $host, 2);
1215
1216
            $host = $elements[0];
1217
        } elseif (!$host = $this->headers->get('HOST')) {
1218
            if (!$host = $this->server->get('SERVER_NAME')) {
1219
                $host = $this->server->get('SERVER_ADDR', '');
1220
            }
1221
        }
1222
1223
        // trim and remove port number from host
1224
        // host is lowercase as per RFC 952/2181
1225
        $host = strtolower(preg_replace('/:\d+$/', '', trim($host)));
1226
1227
        // as the host can come from the user (HTTP_HOST and depending on the configuration, SERVER_NAME too can come from the user)
1228
        // check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
1229
        // use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
1230
        if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
1231
            throw new \UnexpectedValueException(sprintf('Invalid Host "%s"', $host));
1232
        }
1233
1234
        if (count(self::$trustedHostPatterns) > 0) {
1235
            // to avoid host header injection attacks, you should provide a list of trusted host patterns
1236
1237
            if (in_array($host, self::$trustedHosts)) {
1238
                return $host;
1239
            }
1240
1241
            foreach (self::$trustedHostPatterns as $pattern) {
1242
                if (preg_match($pattern, $host)) {
1243
                    self::$trustedHosts[] = $host;
1244
1245
                    return $host;
1246
                }
1247
            }
1248
1249
            throw new \UnexpectedValueException(sprintf('Untrusted Host "%s"', $host));
1250
        }
1251
1252
        return $host;
1253
    }
1254
1255
    /**
1256
     * Sets the request method.
1257
     *
1258
     * @param string $method
1259
     */
1260
    public function setMethod($method)
1261
    {
1262
        $this->method = null;
1263
        $this->server->set('REQUEST_METHOD', $method);
1264
    }
1265
1266
    /**
1267
     * Gets the request "intended" method.
1268
     *
1269
     * If the X-HTTP-Method-Override header is set, and if the method is a POST,
1270
     * then it is used to determine the "real" intended HTTP method.
1271
     *
1272
     * The _method request parameter can also be used to determine the HTTP method,
1273
     * but only if enableHttpMethodParameterOverride() has been called.
1274
     *
1275
     * The method is always an uppercased string.
1276
     *
1277
     * @return string The request method
1278
     *
1279
     * @see getRealMethod()
1280
     */
1281
    public function getMethod()
1282
    {
1283
        if (null === $this->method) {
1284
            $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
1285
1286
            if ('POST' === $this->method) {
1287
                if ($method = $this->headers->get('X-HTTP-METHOD-OVERRIDE')) {
1288
                    $this->method = strtoupper($method);
1289
                } elseif (self::$httpMethodParameterOverride) {
1290
                    $this->method = strtoupper($this->request->get('_method', $this->query->get('_method', 'POST')));
1291
                }
1292
            }
1293
        }
1294
1295
        return $this->method;
1296
    }
1297
1298
    /**
1299
     * Gets the "real" request method.
1300
     *
1301
     * @return string The request method
1302
     *
1303
     * @see getMethod()
1304
     */
1305
    public function getRealMethod()
1306
    {
1307
        return strtoupper($this->server->get('REQUEST_METHOD', 'GET'));
1308
    }
1309
1310
    /**
1311
     * Gets the mime type associated with the format.
1312
     *
1313
     * @param string $format The format
1314
     *
1315
     * @return string The associated mime type (null if not found)
1316
     */
1317
    public function getMimeType($format)
1318
    {
1319
        if (null === static::$formats) {
0 ignored issues
show
introduced by
The condition null === static::formats is always false.
Loading history...
1320
            static::initializeFormats();
1321
        }
1322
1323
        return isset(static::$formats[$format]) ? static::$formats[$format][0] : null;
1324
    }
1325
1326
    /**
1327
     * Gets the format associated with the mime type.
1328
     *
1329
     * @param string $mimeType The associated mime type
1330
     *
1331
     * @return string|null The format (null if not found)
1332
     */
1333
    public function getFormat($mimeType)
1334
    {
1335
        $canonicalMimeType = null;
1336
        if (false !== $pos = strpos($mimeType, ';')) {
1337
            $canonicalMimeType = substr($mimeType, 0, $pos);
1338
        }
1339
1340
        if (null === static::$formats) {
0 ignored issues
show
introduced by
The condition null === static::formats is always false.
Loading history...
1341
            static::initializeFormats();
1342
        }
1343
1344
        foreach (static::$formats as $format => $mimeTypes) {
1345
            if (in_array($mimeType, (array) $mimeTypes)) {
1346
                return $format;
1347
            }
1348
            if (null !== $canonicalMimeType && in_array($canonicalMimeType, (array) $mimeTypes)) {
1349
                return $format;
1350
            }
1351
        }
1352
    }
1353
1354
    /**
1355
     * Associates a format with mime types.
1356
     *
1357
     * @param string       $format    The format
1358
     * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type)
1359
     */
1360
    public function setFormat($format, $mimeTypes)
1361
    {
1362
        if (null === static::$formats) {
0 ignored issues
show
introduced by
The condition null === static::formats is always false.
Loading history...
1363
            static::initializeFormats();
1364
        }
1365
1366
        static::$formats[$format] = is_array($mimeTypes) ? $mimeTypes : array($mimeTypes);
1367
    }
1368
1369
    /**
1370
     * Gets the request format.
1371
     *
1372
     * Here is the process to determine the format:
1373
     *
1374
     *  * format defined by the user (with setRequestFormat())
1375
     *  * _format request parameter
1376
     *  * $default
1377
     *
1378
     * @param string $default The default format
1379
     *
1380
     * @return string The request format
1381
     */
1382
    public function getRequestFormat($default = 'html')
1383
    {
1384
        if (null === $this->format) {
1385
            $this->format = $this->get('_format');
1386
        }
1387
1388
        return null === $this->format ? $default : $this->format;
1389
    }
1390
1391
    /**
1392
     * Sets the request format.
1393
     *
1394
     * @param string $format The request format
1395
     */
1396
    public function setRequestFormat($format)
1397
    {
1398
        $this->format = $format;
1399
    }
1400
1401
    /**
1402
     * Gets the format associated with the request.
1403
     *
1404
     * @return string|null The format (null if no content type is present)
1405
     */
1406
    public function getContentType()
1407
    {
1408
        return $this->getFormat($this->headers->get('CONTENT_TYPE'));
1409
    }
1410
1411
    /**
1412
     * Sets the default locale.
1413
     *
1414
     * @param string $locale
1415
     */
1416
    public function setDefaultLocale($locale)
1417
    {
1418
        $this->defaultLocale = $locale;
1419
1420
        if (null === $this->locale) {
1421
            $this->setPhpDefaultLocale($locale);
1422
        }
1423
    }
1424
1425
    /**
1426
     * Get the default locale.
1427
     *
1428
     * @return string
1429
     */
1430
    public function getDefaultLocale()
1431
    {
1432
        return $this->defaultLocale;
1433
    }
1434
1435
    /**
1436
     * Sets the locale.
1437
     *
1438
     * @param string $locale
1439
     */
1440
    public function setLocale($locale)
1441
    {
1442
        $this->setPhpDefaultLocale($this->locale = $locale);
1443
    }
1444
1445
    /**
1446
     * Get the locale.
1447
     *
1448
     * @return string
1449
     */
1450
    public function getLocale()
1451
    {
1452
        return null === $this->locale ? $this->defaultLocale : $this->locale;
1453
    }
1454
1455
    /**
1456
     * Checks if the request method is of specified type.
1457
     *
1458
     * @param string $method Uppercase request method (GET, POST etc)
1459
     *
1460
     * @return bool
1461
     */
1462
    public function isMethod($method)
1463
    {
1464
        return $this->getMethod() === strtoupper($method);
1465
    }
1466
1467
    /**
1468
     * Checks whether the method is safe or not.
1469
     *
1470
     * @see https://tools.ietf.org/html/rfc7231#section-4.2.1
1471
     *
1472
     * @param bool $andCacheable Adds the additional condition that the method should be cacheable. True by default.
1473
     *
1474
     * @return bool
1475
     */
1476
    public function isMethodSafe(/* $andCacheable = true */)
1477
    {
1478
        return in_array($this->getMethod(), 0 < func_num_args() && !func_get_arg(0) ? array('GET', 'HEAD', 'OPTIONS', 'TRACE') : array('GET', 'HEAD'));
1479
    }
1480
1481
    /**
1482
     * Checks whether the method is cacheable or not.
1483
     *
1484
     * @see https://tools.ietf.org/html/rfc7231#section-4.2.3
1485
     *
1486
     * @return bool
1487
     */
1488
    public function isMethodCacheable()
1489
    {
1490
        return in_array($this->getMethod(), array('GET', 'HEAD'));
1491
    }
1492
1493
    /**
1494
     * Returns the request body content.
1495
     *
1496
     * @param bool $asResource If true, a resource will be returned
1497
     *
1498
     * @return string|resource The request body content or a resource to read the body stream
1499
     *
1500
     * @throws \LogicException
1501
     */
1502
    public function getContent($asResource = false)
1503
    {
1504
        $currentContentIsResource = is_resource($this->content);
1505
        if (\PHP_VERSION_ID < 50600 && false === $this->content) {
0 ignored issues
show
introduced by
The condition false === $this->content is always false.
Loading history...
1506
            throw new \LogicException('getContent() can only be called once when using the resource return type and PHP below 5.6.');
1507
        }
1508
1509
        if (true === $asResource) {
1510
            if ($currentContentIsResource) {
0 ignored issues
show
introduced by
The condition $currentContentIsResource is always false.
Loading history...
1511
                rewind($this->content);
1512
1513
                return $this->content;
1514
            }
1515
1516
            // Content passed in parameter (test)
1517
            if (is_string($this->content)) {
0 ignored issues
show
introduced by
The condition is_string($this->content) is always true.
Loading history...
1518
                $resource = fopen('php://temp', 'r+');
1519
                fwrite($resource, $this->content);
1520
                rewind($resource);
1521
1522
                return $resource;
1523
            }
1524
1525
            $this->content = false;
1526
1527
            return fopen('php://input', 'rb');
1528
        }
1529
1530
        if ($currentContentIsResource) {
0 ignored issues
show
introduced by
The condition $currentContentIsResource is always false.
Loading history...
1531
            rewind($this->content);
1532
1533
            return stream_get_contents($this->content);
1534
        }
1535
1536
        if (null === $this->content || false === $this->content) {
1537
            $this->content = file_get_contents('php://input');
1538
        }
1539
1540
        return $this->content;
1541
    }
1542
1543
    /**
1544
     * Gets the Etags.
1545
     *
1546
     * @return array The entity tags
1547
     */
1548
    public function getETags()
1549
    {
1550
        return preg_split('/\s*,\s*/', $this->headers->get('if_none_match'), null, PREG_SPLIT_NO_EMPTY);
1551
    }
1552
1553
    /**
1554
     * @return bool
1555
     */
1556
    public function isNoCache()
1557
    {
1558
        return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma');
1559
    }
1560
1561
    /**
1562
     * Returns the preferred language.
1563
     *
1564
     * @param array $locales An array of ordered available locales
1565
     *
1566
     * @return string|null The preferred locale
1567
     */
1568
    public function getPreferredLanguage(array $locales = null)
1569
    {
1570
        $preferredLanguages = $this->getLanguages();
1571
1572
        if (empty($locales)) {
1573
            return isset($preferredLanguages[0]) ? $preferredLanguages[0] : null;
1574
        }
1575
1576
        if (!$preferredLanguages) {
1577
            return $locales[0];
1578
        }
1579
1580
        $extendedPreferredLanguages = array();
1581
        foreach ($preferredLanguages as $language) {
1582
            $extendedPreferredLanguages[] = $language;
1583
            if (false !== $position = strpos($language, '_')) {
1584
                $superLanguage = substr($language, 0, $position);
1585
                if (!in_array($superLanguage, $preferredLanguages)) {
1586
                    $extendedPreferredLanguages[] = $superLanguage;
1587
                }
1588
            }
1589
        }
1590
1591
        $preferredLanguages = array_values(array_intersect($extendedPreferredLanguages, $locales));
1592
1593
        return isset($preferredLanguages[0]) ? $preferredLanguages[0] : $locales[0];
1594
    }
1595
1596
    /**
1597
     * Gets a list of languages acceptable by the client browser.
1598
     *
1599
     * @return array Languages ordered in the user browser preferences
1600
     */
1601
    public function getLanguages()
1602
    {
1603
        if (null !== $this->languages) {
1604
            return $this->languages;
1605
        }
1606
1607
        $languages = AcceptHeader::fromString($this->headers->get('Accept-Language'))->all();
1608
        $this->languages = array();
1609
        foreach ($languages as $lang => $acceptHeaderItem) {
1610
            if (false !== strpos($lang, '-')) {
1611
                $codes = explode('-', $lang);
1612
                if ('i' === $codes[0]) {
1613
                    // Language not listed in ISO 639 that are not variants
1614
                    // of any listed language, which can be registered with the
1615
                    // i-prefix, such as i-cherokee
1616
                    if (count($codes) > 1) {
1617
                        $lang = $codes[1];
1618
                    }
1619
                } else {
1620
                    for ($i = 0, $max = count($codes); $i < $max; ++$i) {
1621
                        if ($i === 0) {
1622
                            $lang = strtolower($codes[0]);
1623
                        } else {
1624
                            $lang .= '_'.strtoupper($codes[$i]);
1625
                        }
1626
                    }
1627
                }
1628
            }
1629
1630
            $this->languages[] = $lang;
1631
        }
1632
1633
        return $this->languages;
1634
    }
1635
1636
    /**
1637
     * Gets a list of charsets acceptable by the client browser.
1638
     *
1639
     * @return array List of charsets in preferable order
1640
     */
1641
    public function getCharsets()
1642
    {
1643
        if (null !== $this->charsets) {
1644
            return $this->charsets;
1645
        }
1646
1647
        return $this->charsets = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Charset'))->all());
1648
    }
1649
1650
    /**
1651
     * Gets a list of encodings acceptable by the client browser.
1652
     *
1653
     * @return array List of encodings in preferable order
1654
     */
1655
    public function getEncodings()
1656
    {
1657
        if (null !== $this->encodings) {
1658
            return $this->encodings;
1659
        }
1660
1661
        return $this->encodings = array_keys(AcceptHeader::fromString($this->headers->get('Accept-Encoding'))->all());
1662
    }
1663
1664
    /**
1665
     * Gets a list of content types acceptable by the client browser.
1666
     *
1667
     * @return array List of content types in preferable order
1668
     */
1669
    public function getAcceptableContentTypes()
1670
    {
1671
        if (null !== $this->acceptableContentTypes) {
1672
            return $this->acceptableContentTypes;
1673
        }
1674
1675
        return $this->acceptableContentTypes = array_keys(AcceptHeader::fromString($this->headers->get('Accept'))->all());
1676
    }
1677
1678
    /**
1679
     * Returns true if the request is a XMLHttpRequest.
1680
     *
1681
     * It works if your JavaScript library sets an X-Requested-With HTTP header.
1682
     * It is known to work with common JavaScript frameworks:
1683
     *
1684
     * @see http://en.wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript
1685
     *
1686
     * @return bool true if the request is an XMLHttpRequest, false otherwise
1687
     */
1688
    public function isXmlHttpRequest()
1689
    {
1690
        return 'XMLHttpRequest' == $this->headers->get('X-Requested-With');
1691
    }
1692
1693
    /*
1694
     * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24)
1695
     *
1696
     * Code subject to the new BSD license (http://framework.zend.com/license/new-bsd).
1697
     *
1698
     * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
1699
     */
1700
1701
    protected function prepareRequestUri()
1702
    {
1703
        $requestUri = '';
1704
1705
        if ($this->headers->has('X_ORIGINAL_URL')) {
1706
            // IIS with Microsoft Rewrite Module
1707
            $requestUri = $this->headers->get('X_ORIGINAL_URL');
1708
            $this->headers->remove('X_ORIGINAL_URL');
1709
            $this->server->remove('HTTP_X_ORIGINAL_URL');
1710
            $this->server->remove('UNENCODED_URL');
1711
            $this->server->remove('IIS_WasUrlRewritten');
1712
        } elseif ($this->headers->has('X_REWRITE_URL')) {
1713
            // IIS with ISAPI_Rewrite
1714
            $requestUri = $this->headers->get('X_REWRITE_URL');
1715
            $this->headers->remove('X_REWRITE_URL');
1716
        } elseif ($this->server->get('IIS_WasUrlRewritten') == '1' && $this->server->get('UNENCODED_URL') != '') {
1717
            // IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem)
1718
            $requestUri = $this->server->get('UNENCODED_URL');
1719
            $this->server->remove('UNENCODED_URL');
1720
            $this->server->remove('IIS_WasUrlRewritten');
1721
        } elseif ($this->server->has('REQUEST_URI')) {
1722
            $requestUri = $this->server->get('REQUEST_URI');
1723
            // HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, only use URL path
1724
            $schemeAndHttpHost = $this->getSchemeAndHttpHost();
1725
            if (strpos($requestUri, $schemeAndHttpHost) === 0) {
1726
                $requestUri = substr($requestUri, strlen($schemeAndHttpHost));
1727
            }
1728
        } elseif ($this->server->has('ORIG_PATH_INFO')) {
1729
            // IIS 5.0, PHP as CGI
1730
            $requestUri = $this->server->get('ORIG_PATH_INFO');
1731
            if ('' != $this->server->get('QUERY_STRING')) {
1732
                $requestUri .= '?'.$this->server->get('QUERY_STRING');
1733
            }
1734
            $this->server->remove('ORIG_PATH_INFO');
1735
        }
1736
1737
        // normalize the request URI to ease creating sub-requests from this request
1738
        $this->server->set('REQUEST_URI', $requestUri);
1739
1740
        return $requestUri;
1741
    }
1742
1743
    /**
1744
     * Prepares the base URL.
1745
     *
1746
     * @return string
1747
     */
1748
    protected function prepareBaseUrl()
1749
    {
1750
        $filename = basename($this->server->get('SCRIPT_FILENAME'));
1751
1752
        if (basename($this->server->get('SCRIPT_NAME')) === $filename) {
1753
            $baseUrl = $this->server->get('SCRIPT_NAME');
1754
        } elseif (basename($this->server->get('PHP_SELF')) === $filename) {
1755
            $baseUrl = $this->server->get('PHP_SELF');
1756
        } elseif (basename($this->server->get('ORIG_SCRIPT_NAME')) === $filename) {
1757
            $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility
1758
        } else {
1759
            // Backtrack up the script_filename to find the portion matching
1760
            // php_self
1761
            $path = $this->server->get('PHP_SELF', '');
1762
            $file = $this->server->get('SCRIPT_FILENAME', '');
1763
            $segs = explode('/', trim($file, '/'));
1764
            $segs = array_reverse($segs);
1765
            $index = 0;
1766
            $last = count($segs);
1767
            $baseUrl = '';
1768
            do {
1769
                $seg = $segs[$index];
1770
                $baseUrl = '/'.$seg.$baseUrl;
1771
                ++$index;
1772
            } while ($last > $index && (false !== $pos = strpos($path, $baseUrl)) && 0 != $pos);
1773
        }
1774
1775
        // Does the baseUrl have anything in common with the request_uri?
1776
        $requestUri = $this->getRequestUri();
1777
        if ($requestUri !== '' && $requestUri[0] !== '/') {
1778
            $requestUri = '/'.$requestUri;
1779
        }
1780
1781
        if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, $baseUrl)) {
1782
            // full $baseUrl matches
1783
            return $prefix;
1784
        }
1785
1786
        if ($baseUrl && false !== $prefix = $this->getUrlencodedPrefix($requestUri, rtrim(dirname($baseUrl), '/'.DIRECTORY_SEPARATOR).'/')) {
1787
            // directory portion of $baseUrl matches
1788
            return rtrim($prefix, '/'.DIRECTORY_SEPARATOR);
1789
        }
1790
1791
        $truncatedRequestUri = $requestUri;
1792
        if (false !== $pos = strpos($requestUri, '?')) {
1793
            $truncatedRequestUri = substr($requestUri, 0, $pos);
1794
        }
1795
1796
        $basename = basename($baseUrl);
1797
        if (empty($basename) || !strpos(rawurldecode($truncatedRequestUri), $basename)) {
1798
            // no match whatsoever; set it blank
1799
            return '';
1800
        }
1801
1802
        // If using mod_rewrite or ISAPI_Rewrite strip the script filename
1803
        // out of baseUrl. $pos !== 0 makes sure it is not matching a value
1804
        // from PATH_INFO or QUERY_STRING
1805
        if (strlen($requestUri) >= strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && $pos !== 0) {
1806
            $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl));
1807
        }
1808
1809
        return rtrim($baseUrl, '/'.DIRECTORY_SEPARATOR);
1810
    }
1811
1812
    /**
1813
     * Prepares the base path.
1814
     *
1815
     * @return string base path
1816
     */
1817
    protected function prepareBasePath()
1818
    {
1819
        $filename = basename($this->server->get('SCRIPT_FILENAME'));
1820
        $baseUrl = $this->getBaseUrl();
1821
        if (empty($baseUrl)) {
1822
            return '';
1823
        }
1824
1825
        if (basename($baseUrl) === $filename) {
1826
            $basePath = dirname($baseUrl);
1827
        } else {
1828
            $basePath = $baseUrl;
1829
        }
1830
1831
        if ('\\' === DIRECTORY_SEPARATOR) {
1832
            $basePath = str_replace('\\', '/', $basePath);
1833
        }
1834
1835
        return rtrim($basePath, '/');
1836
    }
1837
1838
    /**
1839
     * Prepares the path info.
1840
     *
1841
     * @return string path info
1842
     */
1843
    protected function preparePathInfo()
1844
    {
1845
        $baseUrl = $this->getBaseUrl();
1846
1847
        if (null === ($requestUri = $this->getRequestUri())) {
0 ignored issues
show
introduced by
The condition null === $requestUri = $this->getRequestUri() is always false.
Loading history...
1848
            return '/';
1849
        }
1850
1851
        // Remove the query string from REQUEST_URI
1852
        if (false !== $pos = strpos($requestUri, '?')) {
1853
            $requestUri = substr($requestUri, 0, $pos);
1854
        }
1855
        if ($requestUri !== '' && $requestUri[0] !== '/') {
1856
            $requestUri = '/'.$requestUri;
1857
        }
1858
1859
        $pathInfo = substr($requestUri, strlen($baseUrl));
1860
        if (null !== $baseUrl && (false === $pathInfo || '' === $pathInfo)) {
1861
            // If substr() returns false then PATH_INFO is set to an empty string
1862
            return '/';
1863
        } elseif (null === $baseUrl) {
0 ignored issues
show
introduced by
The condition null === $baseUrl is always false.
Loading history...
1864
            return $requestUri;
1865
        }
1866
1867
        return (string) $pathInfo;
1868
    }
1869
1870
    /**
1871
     * Initializes HTTP request formats.
1872
     */
1873
    protected static function initializeFormats()
1874
    {
1875
        static::$formats = array(
1876
            'html' => array('text/html', 'application/xhtml+xml'),
1877
            'txt' => array('text/plain'),
1878
            'js' => array('application/javascript', 'application/x-javascript', 'text/javascript'),
1879
            'css' => array('text/css'),
1880
            'json' => array('application/json', 'application/x-json'),
1881
            'xml' => array('text/xml', 'application/xml', 'application/x-xml'),
1882
            'rdf' => array('application/rdf+xml'),
1883
            'atom' => array('application/atom+xml'),
1884
            'rss' => array('application/rss+xml'),
1885
            'form' => array('application/x-www-form-urlencoded'),
1886
        );
1887
    }
1888
1889
    /**
1890
     * Sets the default PHP locale.
1891
     *
1892
     * @param string $locale
1893
     */
1894
    private function setPhpDefaultLocale($locale)
1895
    {
1896
        // if either the class Locale doesn't exist, or an exception is thrown when
1897
        // setting the default locale, the intl module is not installed, and
1898
        // the call can be ignored:
1899
        try {
1900
            if (class_exists('Locale', false)) {
1901
                \Locale::setDefault($locale);
1902
            }
1903
        } catch (\Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1904
        }
1905
    }
1906
1907
    /*
1908
     * Returns the prefix as encoded in the string when the string starts with
1909
     * the given prefix, false otherwise.
1910
     *
1911
     * @param string $string The urlencoded string
1912
     * @param string $prefix The prefix not encoded
1913
     *
1914
     * @return string|false The prefix as it is encoded in $string, or false
1915
     */
1916
    private function getUrlencodedPrefix($string, $prefix)
1917
    {
1918
        if (0 !== strpos(rawurldecode($string), $prefix)) {
1919
            return false;
1920
        }
1921
1922
        $len = strlen($prefix);
1923
1924
        if (preg_match(sprintf('#^(%%[[:xdigit:]]{2}|.){%d}#', $len), $string, $match)) {
1925
            return $match[0];
1926
        }
1927
1928
        return false;
1929
    }
1930
1931
    private static function createRequestFromFactory(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
1932
    {
1933
        if (self::$requestFactory) {
1934
            $request = call_user_func(self::$requestFactory, $query, $request, $attributes, $cookies, $files, $server, $content);
1935
1936
            if (!$request instanceof self) {
1937
                throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.');
1938
            }
1939
1940
            return $request;
1941
        }
1942
1943
        return new static($query, $request, $attributes, $cookies, $files, $server, $content);
1944
    }
1945
1946
    private function isFromTrustedProxy()
1947
    {
1948
        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...
1949
    }
1950
1951
    private function normalizeAndFilterClientIps(array $clientIps, $ip)
1952
    {
1953
        $clientIps[] = $ip; // Complete the IP chain with the IP the request actually came from
1954
        $firstTrustedIp = null;
1955
1956
        foreach ($clientIps as $key => $clientIp) {
1957
            // Remove port (unfortunately, it does happen)
1958
            if (preg_match('{((?:\d+\.){3}\d+)\:\d+}', $clientIp, $match)) {
1959
                $clientIps[$key] = $clientIp = $match[1];
1960
            }
1961
1962
            if (!filter_var($clientIp, FILTER_VALIDATE_IP)) {
1963
                unset($clientIps[$key]);
1964
1965
                continue;
1966
            }
1967
1968
            if (IpUtils::checkIp($clientIp, self::$trustedProxies)) {
1969
                unset($clientIps[$key]);
1970
1971
                // Fallback to this when the client IP falls into the range of trusted proxies
1972
                if (null === $firstTrustedIp) {
1973
                    $firstTrustedIp = $clientIp;
1974
                }
1975
            }
1976
        }
1977
1978
        // Now the IP chain contains only untrusted proxies and the client IP
1979
        return $clientIps ? array_reverse($clientIps) : array($firstTrustedIp);
0 ignored issues
show
introduced by
$clientIps is a non-empty array, thus is always true.
Loading history...
1980
    }
1981
}
1982