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
|
}
|