Projekt

Obecné

Profil

Stáhnout (61.4 KB) Statistiky
| Větev: | Revize:
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

    
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 <fabien@symfony.com>
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;
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
}
(17-17/26)