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