namespace Symfony\Component\HttpFoundation;
use Symfony\Component\HttpFoundation\Exception\ConflictingHeadersException;
+use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
/**
*/
class Request
{
- const HEADER_FORWARDED = 'forwarded';
- const HEADER_CLIENT_IP = 'client_ip';
- const HEADER_CLIENT_HOST = 'client_host';
- const HEADER_CLIENT_PROTO = 'client_proto';
- const HEADER_CLIENT_PORT = 'client_port';
+ const HEADER_FORWARDED = 0b00001; // When using RFC 7239
+ const HEADER_X_FORWARDED_FOR = 0b00010;
+ const HEADER_X_FORWARDED_HOST = 0b00100;
+ const HEADER_X_FORWARDED_PROTO = 0b01000;
+ const HEADER_X_FORWARDED_PORT = 0b10000;
+ const HEADER_X_FORWARDED_ALL = 0b11110; // All "X-Forwarded-*" headers
+ const HEADER_X_FORWARDED_AWS_ELB = 0b11010; // AWS ELB doesn't send X-Forwarded-Host
+
+ /** @deprecated since version 3.3, to be removed in 4.0 */
+ const HEADER_CLIENT_IP = self::HEADER_X_FORWARDED_FOR;
+ /** @deprecated since version 3.3, to be removed in 4.0 */
+ const HEADER_CLIENT_HOST = self::HEADER_X_FORWARDED_HOST;
+ /** @deprecated since version 3.3, to be removed in 4.0 */
+ const HEADER_CLIENT_PROTO = self::HEADER_X_FORWARDED_PROTO;
+ /** @deprecated since version 3.3, to be removed in 4.0 */
+ const HEADER_CLIENT_PORT = self::HEADER_X_FORWARDED_PORT;
const METHOD_HEAD = 'HEAD';
const METHOD_GET = 'GET';
*
* The other headers are non-standard, but widely used
* by popular reverse proxies (like Apache mod_proxy or Amazon EC2).
+ *
+ * @deprecated since version 3.3, to be removed in 4.0
*/
protected static $trustedHeaders = array(
self::HEADER_FORWARDED => 'FORWARDED',
public $headers;
/**
- * @var string
+ * @var string|resource|false|null
*/
protected $content;
protected static $requestFactory;
+ private $isHostValid = true;
private $isForwardedValid = true;
+ private static $trustedHeaderSet = -1;
+
+ /** @deprecated since version 3.3, to be removed in 4.0 */
+ private static $trustedHeaderNames = array(
+ self::HEADER_FORWARDED => 'FORWARDED',
+ self::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
+ self::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
+ self::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
+ self::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
+ );
+
private static $forwardedParams = array(
- self::HEADER_CLIENT_IP => 'for',
- self::HEADER_CLIENT_HOST => 'host',
- self::HEADER_CLIENT_PROTO => 'proto',
- self::HEADER_CLIENT_PORT => 'host',
+ self::HEADER_X_FORWARDED_FOR => 'for',
+ self::HEADER_X_FORWARDED_HOST => 'host',
+ self::HEADER_X_FORWARDED_PROTO => 'proto',
+ self::HEADER_X_FORWARDED_PORT => 'host',
);
/**
- * Constructor.
- *
- * @param array $query The GET parameters
- * @param array $request The POST parameters
- * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
- * @param array $cookies The COOKIE parameters
- * @param array $files The FILES parameters
- * @param array $server The SERVER parameters
- * @param string|resource $content The raw body data
+ * @param array $query The GET parameters
+ * @param array $request The POST parameters
+ * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
+ * @param array $cookies The COOKIE parameters
+ * @param array $files The FILES parameters
+ * @param array $server The SERVER parameters
+ * @param string|resource|null $content The raw body data
*/
public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
{
*
* This method also re-initializes all properties.
*
- * @param array $query The GET parameters
- * @param array $request The POST parameters
- * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
- * @param array $cookies The COOKIE parameters
- * @param array $files The FILES parameters
- * @param array $server The SERVER parameters
- * @param string|resource $content The raw body data
+ * @param array $query The GET parameters
+ * @param array $request The POST parameters
+ * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
+ * @param array $cookies The COOKIE parameters
+ * @param array $files The FILES parameters
+ * @param array $server The SERVER parameters
+ * @param string|resource|null $content The raw body data
*/
public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null)
{
* The information contained in the URI always take precedence
* over the other information (server and parameters).
*
- * @param string $uri The URI
- * @param string $method The HTTP method
- * @param array $parameters The query (GET) or request (POST) parameters
- * @param array $cookies The request cookies ($_COOKIE)
- * @param array $files The request files ($_FILES)
- * @param array $server The server parameters ($_SERVER)
- * @param string $content The raw body data
+ * @param string $uri The URI
+ * @param string $method The HTTP method
+ * @param array $parameters The query (GET) or request (POST) parameters
+ * @param array $cookies The request cookies ($_COOKIE)
+ * @param array $files The request files ($_FILES)
+ * @param array $server The server parameters ($_SERVER)
+ * @param string|resource|null $content The raw body data
*
* @return static
*/
public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null)
{
$dup = clone $this;
- if ($query !== null) {
+ if (null !== $query) {
$dup->query = new ParameterBag($query);
}
- if ($request !== null) {
+ if (null !== $request) {
$dup->request = new ParameterBag($request);
}
- if ($attributes !== null) {
+ if (null !== $attributes) {
$dup->attributes = new ParameterBag($attributes);
}
- if ($cookies !== null) {
+ if (null !== $cookies) {
$dup->cookies = new ParameterBag($cookies);
}
- if ($files !== null) {
+ if (null !== $files) {
$dup->files = new FileBag($files);
}
- if ($server !== null) {
+ if (null !== $server) {
$dup->server = new ServerBag($server);
$dup->headers = new HeaderBag($dup->server->getHeaders());
}
return trigger_error($e, E_USER_ERROR);
}
+ $cookieHeader = '';
+ $cookies = array();
+
+ foreach ($this->cookies as $k => $v) {
+ $cookies[] = $k.'='.$v;
+ }
+
+ if (!empty($cookies)) {
+ $cookieHeader = 'Cookie: '.implode('; ', $cookies)."\r\n";
+ }
+
return
sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n".
- $this->headers."\r\n".
+ $this->headers.
+ $cookieHeader."\r\n".
$content;
}
*/
public function overrideGlobals()
{
- $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), null, '&')));
+ $this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), '', '&')));
$_GET = $this->query->all();
$_POST = $this->request->all();
*
* You should only list the reverse proxies that you manage directly.
*
- * @param array $proxies A list of trusted proxies
+ * @param array $proxies A list of trusted proxies
+ * @param int $trustedHeaderSet A bit field of Request::HEADER_*, to set which headers to trust from your proxies
+ *
+ * @throws \InvalidArgumentException When $trustedHeaderSet is invalid
*/
- public static function setTrustedProxies(array $proxies)
+ public static function setTrustedProxies(array $proxies/*, int $trustedHeaderSet*/)
{
self::$trustedProxies = $proxies;
+
+ if (2 > func_num_args()) {
+ @trigger_error(sprintf('The %s() method expects a bit field of Request::HEADER_* as second argument since Symfony 3.3. Defining it will be required in 4.0. ', __METHOD__), E_USER_DEPRECATED);
+
+ return;
+ }
+ $trustedHeaderSet = (int) func_get_arg(1);
+
+ foreach (self::$trustedHeaderNames as $header => $name) {
+ self::$trustedHeaders[$header] = $header & $trustedHeaderSet ? $name : null;
+ }
+ self::$trustedHeaderSet = $trustedHeaderSet;
}
/**
return self::$trustedProxies;
}
+ /**
+ * Gets the set of trusted headers from trusted proxies.
+ *
+ * @return int A bit field of Request::HEADER_* that defines which headers are trusted from your proxies
+ */
+ public static function getTrustedHeaderSet()
+ {
+ return self::$trustedHeaderSet;
+ }
+
/**
* Sets a list of trusted host patterns.
*
* @param string $value The header name
*
* @throws \InvalidArgumentException
+ *
+ * @deprecated since version 3.3, to be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.
*/
public static function setTrustedHeaderName($key, $value)
{
- if (!array_key_exists($key, self::$trustedHeaders)) {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the $trustedHeaderSet argument of the Request::setTrustedProxies() method instead.', __METHOD__), E_USER_DEPRECATED);
+
+ if ('forwarded' === $key) {
+ $key = self::HEADER_FORWARDED;
+ } elseif ('client_ip' === $key) {
+ $key = self::HEADER_CLIENT_IP;
+ } elseif ('client_host' === $key) {
+ $key = self::HEADER_CLIENT_HOST;
+ } elseif ('client_proto' === $key) {
+ $key = self::HEADER_CLIENT_PROTO;
+ } elseif ('client_port' === $key) {
+ $key = self::HEADER_CLIENT_PORT;
+ } elseif (!array_key_exists($key, self::$trustedHeaders)) {
throw new \InvalidArgumentException(sprintf('Unable to set the trusted header name for key "%s".', $key));
}
self::$trustedHeaders[$key] = $value;
+
+ if (null !== $value) {
+ self::$trustedHeaderNames[$key] = $value;
+ self::$trustedHeaderSet |= $key;
+ } else {
+ self::$trustedHeaderSet &= ~$key;
+ }
}
/**
* @return string The header name
*
* @throws \InvalidArgumentException
+ *
+ * @deprecated since version 3.3, to be removed in 4.0. Use the Request::getTrustedHeaderSet() method instead.
*/
public static function getTrustedHeaderName($key)
{
+ if (2 > func_num_args() || func_get_arg(1)) {
+ @trigger_error(sprintf('The "%s()" method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the Request::getTrustedHeaderSet() method instead.', __METHOD__), E_USER_DEPRECATED);
+ }
+
if (!array_key_exists($key, self::$trustedHeaders)) {
throw new \InvalidArgumentException(sprintf('Unable to get the trusted header name for key "%s".', $key));
}
*
* Order of precedence: PATH (routing placeholders or custom attributes), GET, BODY
*
- * @param string $key the key
- * @param mixed $default the default value if the parameter key does not exist
+ * @param string $key The key
+ * @param mixed $default The default value if the parameter key does not exist
*
* @return mixed
*/
* adding the IP address where it received the request from.
*
* If your reverse proxy uses a different header name than "X-Forwarded-For",
- * ("Client-Ip" for instance), configure it via "setTrustedHeaderName()" with
- * the "client-ip" key.
+ * ("Client-Ip" for instance), configure it via the $trustedHeaderSet
+ * argument of the Request::setTrustedProxies() method instead.
*
* @return string|null The client IP address
*
* The "X-Forwarded-Port" header must contain the client port.
*
* If your reverse proxy uses a different header name than "X-Forwarded-Port",
- * configure it via "setTrustedHeaderName()" with the "client-port" key.
+ * configure it via via the $trustedHeaderSet argument of the
+ * Request::setTrustedProxies() method instead.
*
* @return int|string can be a string if fetched from the server bag
*/
return $this->server->get('SERVER_PORT');
}
- if ($host[0] === '[') {
+ if ('[' === $host[0]) {
$pos = strpos($host, ':', strrpos($host, ']'));
} else {
$pos = strrpos($host, ':');
$scheme = $this->getScheme();
$port = $this->getPort();
- if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) {
+ if (('http' == $scheme && 80 == $port) || ('https' == $scheme && 443 == $port)) {
return $this->getHost();
}
* The "X-Forwarded-Proto" header must contain the protocol: "https" or "http".
*
* If your reverse proxy uses a different header name than "X-Forwarded-Proto"
- * ("SSL_HTTPS" for instance), configure it via "setTrustedHeaderName()" with
- * the "client-proto" key.
+ * ("SSL_HTTPS" for instance), configure it via the $trustedHeaderSet
+ * argument of the Request::setTrustedProxies() method instead.
*
* @return bool
*/
* The "X-Forwarded-Host" header must contain the client host name.
*
* If your reverse proxy uses a different header name than "X-Forwarded-Host",
- * configure it via "setTrustedHeaderName()" with the "client-host" key.
+ * configure it via the $trustedHeaderSet argument of the
+ * Request::setTrustedProxies() method instead.
*
* @return string
*
- * @throws \UnexpectedValueException when the host name is invalid
+ * @throws SuspiciousOperationException when the host name is invalid or not trusted
*/
public function getHost()
{
// check that it does not contain forbidden characters (see RFC 952 and RFC 2181)
// use preg_replace() instead of preg_match() to prevent DoS attacks with long host names
if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) {
- throw new \UnexpectedValueException(sprintf('Invalid Host "%s"', $host));
+ if (!$this->isHostValid) {
+ return '';
+ }
+ $this->isHostValid = false;
+
+ throw new SuspiciousOperationException(sprintf('Invalid Host "%s".', $host));
}
if (count(self::$trustedHostPatterns) > 0) {
}
}
- throw new \UnexpectedValueException(sprintf('Untrusted Host "%s"', $host));
+ if (!$this->isHostValid) {
+ return '';
+ }
+ $this->isHostValid = false;
+
+ throw new SuspiciousOperationException(sprintf('Untrusted Host "%s".', $host));
}
return $host;
if (!func_num_args() || func_get_arg(0)) {
// This deprecation should be turned into a BadMethodCallException in 4.0 (without adding the argument in the signature)
// then setting $andCacheable to false should be deprecated in 4.1
- @trigger_error('Checking only for cacheable HTTP methods with Symfony\Component\HttpFoundation\Request::isMethodSafe() is deprecated since version 3.2 and will throw an exception in 4.0. Disable checking only for cacheable methods by calling the method with `false` as first argument or use the Request::isMethodCacheable() instead.', E_USER_DEPRECATED);
+ @trigger_error('Checking only for cacheable HTTP methods with Symfony\Component\HttpFoundation\Request::isMethodSafe() is deprecated since Symfony 3.2 and will throw an exception in 4.0. Disable checking only for cacheable methods by calling the method with `false` as first argument or use the Request::isMethodCacheable() instead.', E_USER_DEPRECATED);
return in_array($this->getMethod(), array('GET', 'HEAD'));
}
return in_array($this->getMethod(), array('GET', 'HEAD'));
}
+ /**
+ * Returns the protocol version.
+ *
+ * If the application is behind a proxy, the protocol version used in the
+ * requests between the client and the proxy and between the proxy and the
+ * server might be different. This returns the former (from the "Via" header)
+ * if the proxy is trusted (see "setTrustedProxies()"), otherwise it returns
+ * the latter (from the "SERVER_PROTOCOL" server parameter).
+ *
+ * @return string
+ */
+ public function getProtocolVersion()
+ {
+ if ($this->isFromTrustedProxy()) {
+ preg_match('~^(HTTP/)?([1-9]\.[0-9]) ~', $this->headers->get('Via'), $matches);
+
+ if ($matches) {
+ return 'HTTP/'.$matches[2];
+ }
+ }
+
+ return $this->server->get('SERVER_PROTOCOL');
+ }
+
/**
* Returns the request body content.
*
}
} else {
for ($i = 0, $max = count($codes); $i < $max; ++$i) {
- if ($i === 0) {
+ if (0 === $i) {
$lang = strtolower($codes[0]);
} else {
$lang .= '_'.strtoupper($codes[$i]);
// IIS with ISAPI_Rewrite
$requestUri = $this->headers->get('X_REWRITE_URL');
$this->headers->remove('X_REWRITE_URL');
- } elseif ($this->server->get('IIS_WasUrlRewritten') == '1' && $this->server->get('UNENCODED_URL') != '') {
+ } elseif ('1' == $this->server->get('IIS_WasUrlRewritten') && '' != $this->server->get('UNENCODED_URL')) {
// IIS7 with URL Rewrite: make sure we get the unencoded URL (double slash problem)
$requestUri = $this->server->get('UNENCODED_URL');
$this->server->remove('UNENCODED_URL');
$requestUri = $this->server->get('REQUEST_URI');
// HTTP proxy reqs setup request URI with scheme and host [and port] + the URL path, only use URL path
$schemeAndHttpHost = $this->getSchemeAndHttpHost();
- if (strpos($requestUri, $schemeAndHttpHost) === 0) {
+ if (0 === strpos($requestUri, $schemeAndHttpHost)) {
$requestUri = substr($requestUri, strlen($schemeAndHttpHost));
}
} elseif ($this->server->has('ORIG_PATH_INFO')) {
// Does the baseUrl have anything in common with the request_uri?
$requestUri = $this->getRequestUri();
- if ($requestUri !== '' && $requestUri[0] !== '/') {
+ if ('' !== $requestUri && '/' !== $requestUri[0]) {
$requestUri = '/'.$requestUri;
}
// If using mod_rewrite or ISAPI_Rewrite strip the script filename
// out of baseUrl. $pos !== 0 makes sure it is not matching a value
// from PATH_INFO or QUERY_STRING
- if (strlen($requestUri) >= strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && $pos !== 0) {
+ if (strlen($requestUri) >= strlen($baseUrl) && (false !== $pos = strpos($requestUri, $baseUrl)) && 0 !== $pos) {
$baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl));
}
*/
protected function prepareBasePath()
{
- $filename = basename($this->server->get('SCRIPT_FILENAME'));
$baseUrl = $this->getBaseUrl();
if (empty($baseUrl)) {
return '';
}
+ $filename = basename($this->server->get('SCRIPT_FILENAME'));
if (basename($baseUrl) === $filename) {
$basePath = dirname($baseUrl);
} else {
*/
protected function preparePathInfo()
{
- $baseUrl = $this->getBaseUrl();
-
if (null === ($requestUri = $this->getRequestUri())) {
return '/';
}
if (false !== $pos = strpos($requestUri, '?')) {
$requestUri = substr($requestUri, 0, $pos);
}
- if ($requestUri !== '' && $requestUri[0] !== '/') {
+ if ('' !== $requestUri && '/' !== $requestUri[0]) {
$requestUri = '/'.$requestUri;
}
+ if (null === ($baseUrl = $this->getBaseUrl())) {
+ return $requestUri;
+ }
+
$pathInfo = substr($requestUri, strlen($baseUrl));
- if (null !== $baseUrl && (false === $pathInfo || '' === $pathInfo)) {
+ if (false === $pathInfo || '' === $pathInfo) {
// If substr() returns false then PATH_INFO is set to an empty string
return '/';
- } elseif (null === $baseUrl) {
- return $requestUri;
}
return (string) $pathInfo;
'js' => array('application/javascript', 'application/x-javascript', 'text/javascript'),
'css' => array('text/css'),
'json' => array('application/json', 'application/x-json'),
+ 'jsonld' => array('application/ld+json'),
'xml' => array('text/xml', 'application/xml', 'application/x-xml'),
'rdf' => array('application/rdf+xml'),
'atom' => array('application/atom+xml'),