This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
symfony/src/Symfony/Component/HttpFoundation/ResponseHeaderBag.php

362 lines
10 KiB
PHP
Raw Normal View History

<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\HttpFoundation;
/**
* ResponseHeaderBag is a container for Response HTTP headers.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ResponseHeaderBag extends HeaderBag
{
2014-10-22 19:27:13 +01:00
const COOKIES_FLAT = 'flat';
const COOKIES_ARRAY = 'array';
const DISPOSITION_ATTACHMENT = 'attachment';
2014-10-22 19:27:13 +01:00
const DISPOSITION_INLINE = 'inline';
2011-11-02 15:42:51 +00:00
/**
* @var array
*/
protected $computedCacheControl = array();
2011-12-18 13:33:54 +00:00
2011-11-02 15:42:51 +00:00
/**
* @var array
*/
2014-10-22 19:27:13 +01:00
protected $cookies = array();
/**
* @var array
*/
2014-10-22 19:27:13 +01:00
protected $headerNames = array();
/**
* Constructor.
*
* @param array $headers An array of HTTP headers
*/
2011-02-11 16:57:07 +00:00
public function __construct(array $headers = array())
{
parent::__construct($headers);
if (!isset($this->headers['cache-control'])) {
$this->set('Cache-Control', '');
}
/* RFC2616 - 14.18 says all Responses need to have a Date */
if (!isset($this->headers['date'])) {
$this->initDate();
}
}
2011-06-08 11:12:55 +01:00
/**
* Returns the headers, with original capitalizations.
*
* @return array An array of headers
*/
public function allPreserveCase()
{
$headers = array();
foreach ($this->all() as $name => $value) {
$headers[isset($this->headerNames[$name]) ? $this->headerNames[$name] : $name] = $value;
}
return $headers;
}
public function allPreserveCaseWithoutCookies()
{
$headers = $this->allPreserveCase();
if (isset($this->headerNames['set-cookie'])) {
unset($headers[$this->headerNames['set-cookie']]);
}
return $headers;
}
/**
* {@inheritdoc}
*/
public function replace(array $headers = array())
{
$this->headerNames = array();
parent::replace($headers);
if (!isset($this->headers['cache-control'])) {
$this->set('Cache-Control', '');
}
if (!isset($this->headers['date'])) {
$this->initDate();
}
}
/**
* {@inheritdoc}
*/
public function all()
{
$headers = parent::all();
foreach ($this->getCookies() as $cookie) {
$headers['set-cookie'][] = (string) $cookie;
}
return $headers;
}
/**
* {@inheritdoc}
*/
public function set($key, $values, $replace = true)
{
$uniqueKey = str_replace('_', '-', strtolower($key));
if ('set-cookie' === $uniqueKey) {
if ($replace) {
$this->cookies = array();
}
foreach ((array) $values as $cookie) {
$this->setCookie(Cookie::fromString($cookie));
}
$this->headerNames[$uniqueKey] = $key;
return;
}
$this->headerNames[$uniqueKey] = $key;
parent::set($key, $values, $replace);
// ensure the cache-control header has sensible defaults
if (in_array($uniqueKey, array('cache-control', 'etag', 'last-modified', 'expires'))) {
$computed = $this->computeCacheControlValue();
$this->headers['cache-control'] = array($computed);
$this->headerNames['cache-control'] = 'Cache-Control';
$this->computedCacheControl = $this->parseCacheControl($computed);
}
}
/**
* {@inheritdoc}
*/
made some method name changes to have a better coherence throughout the framework When an object has a "main" many relation with related "things" (objects, parameters, ...), the method names are normalized: * get() * set() * all() * replace() * remove() * clear() * isEmpty() * add() * register() * count() * keys() The classes below follow this method naming convention: * BrowserKit\CookieJar -> Cookie * BrowserKit\History -> Request * Console\Application -> Command * Console\Application\Helper\HelperSet -> HelperInterface * DependencyInjection\Container -> services * DependencyInjection\ContainerBuilder -> services * DependencyInjection\ParameterBag\ParameterBag -> parameters * DependencyInjection\ParameterBag\FrozenParameterBag -> parameters * DomCrawler\Form -> FormField * EventDispatcher\Event -> parameters * Form\FieldGroup -> Field * HttpFoundation\HeaderBag -> headers * HttpFoundation\ParameterBag -> parameters * HttpFoundation\Session -> attributes * HttpKernel\Profiler\Profiler -> DataCollectorInterface * Routing\RouteCollection -> Route * Security\Authentication\AuthenticationProviderManager -> AuthenticationProviderInterface * Templating\Engine -> HelperInterface * Translation\MessageCatalogue -> messages The usage of these methods are only allowed when it is clear that there is a main relation: * a CookieJar has many Cookies; * a Container has many services and many parameters (as services is the main relation, we use the naming convention for this relation); * a Console Input has many arguments and many options. There is no "main" relation, and so the naming convention does not apply. For many relations where the convention does not apply, the following methods must be used instead (where XXX is the name of the related thing): * get() -> getXXX() * set() -> setXXX() * all() -> getXXXs() * replace() -> setXXXs() * remove() -> removeXXX() * clear() -> clearXXX() * isEmpty() -> isEmptyXXX() * add() -> addXXX() * register() -> registerXXX() * count() -> countXXX() * keys()
2010-11-23 08:42:19 +00:00
public function remove($key)
{
$uniqueKey = str_replace('_', '-', strtolower($key));
unset($this->headerNames[$uniqueKey]);
if ('set-cookie' === $uniqueKey) {
$this->cookies = array();
return;
}
parent::remove($key);
if ('cache-control' === $uniqueKey) {
$this->computedCacheControl = array();
}
if ('date' === $uniqueKey) {
$this->initDate();
}
}
/**
* {@inheritdoc}
*/
public function hasCacheControlDirective($key)
{
return array_key_exists($key, $this->computedCacheControl);
}
/**
* {@inheritdoc}
*/
public function getCacheControlDirective($key)
{
return array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null;
}
/**
* Sets a cookie.
*
* @param Cookie $cookie
*/
public function setCookie(Cookie $cookie)
{
$this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie;
$this->headerNames['set-cookie'] = 'Set-Cookie';
}
/**
2014-12-21 17:00:50 +00:00
* Removes a cookie from the array, but does not unset it in the browser.
*
* @param string $name
* @param string $path
* @param string $domain
*/
public function removeCookie($name, $path = '/', $domain = null)
{
if (null === $path) {
$path = '/';
}
unset($this->cookies[$domain][$path][$name]);
if (empty($this->cookies[$domain][$path])) {
unset($this->cookies[$domain][$path]);
if (empty($this->cookies[$domain])) {
unset($this->cookies[$domain]);
}
}
if (empty($this->cookies)) {
unset($this->headerNames['set-cookie']);
}
}
/**
2014-12-21 17:00:50 +00:00
* Returns an array with all cookies.
*
* @param string $format
*
* @return array
*
* @throws \InvalidArgumentException When the $format is invalid
*/
public function getCookies($format = self::COOKIES_FLAT)
{
if (!in_array($format, array(self::COOKIES_FLAT, self::COOKIES_ARRAY))) {
throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', array(self::COOKIES_FLAT, self::COOKIES_ARRAY))));
}
if (self::COOKIES_ARRAY === $format) {
return $this->cookies;
}
$flattenedCookies = array();
foreach ($this->cookies as $path) {
foreach ($path as $cookies) {
foreach ($cookies as $cookie) {
$flattenedCookies[] = $cookie;
}
}
}
return $flattenedCookies;
}
/**
2014-12-21 17:00:50 +00:00
* Clears a cookie in the browser.
*
* @param string $name
* @param string $path
* @param string $domain
* @param bool $secure
* @param bool $httpOnly
*/
public function clearCookie($name, $path = '/', $domain = null, $secure = false, $httpOnly = true)
{
$this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly));
}
/**
* Generates a HTTP Content-Disposition field-value.
*
* @param string $disposition One of "inline" or "attachment"
* @param string $filename A unicode string
* @param string $filenameFallback A string containing only ASCII characters that
* is semantically equivalent to $filename. If the filename is already ASCII,
* it can be omitted, or just copied from $filename
*
* @return string A string suitable for use as a Content-Disposition field-value
*
* @throws \InvalidArgumentException
2014-12-21 17:00:50 +00:00
*
* @see RFC 6266
*/
public function makeDisposition($disposition, $filename, $filenameFallback = '')
{
if (!in_array($disposition, array(self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE))) {
throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
}
if ('' == $filenameFallback) {
$filenameFallback = $filename;
}
// filenameFallback is not ASCII.
if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) {
throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.');
}
// percent characters aren't safe in fallback.
if (false !== strpos($filenameFallback, '%')) {
throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.');
}
// path separators aren't allowed in either.
if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) {
throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.');
}
$output = sprintf('%s; filename="%s"', $disposition, str_replace('"', '\\"', $filenameFallback));
if ($filename !== $filenameFallback) {
$output .= sprintf("; filename*=utf-8''%s", rawurlencode($filename));
}
return $output;
}
/**
* Returns the calculated value of the cache-control header.
*
* This considers several other headers and calculates or modifies the
* cache-control header to a sensible, conservative value.
*
* @return string
*/
protected function computeCacheControlValue()
{
if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) {
return 'no-cache, private';
}
if (!$this->cacheControl) {
// conservative by default
return 'private, must-revalidate';
}
$header = $this->getCacheControlHeader();
if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) {
return $header;
}
// public if s-maxage is defined, private otherwise
if (!isset($this->cacheControl['s-maxage'])) {
return $header.', private';
}
return $header;
}
private function initDate()
{
$now = \DateTime::createFromFormat('U', time());
$now->setTimezone(new \DateTimeZone('UTC'));
$this->set('Date', $now->format('D, d M Y H:i:s').' GMT');
}
2011-06-08 11:16:48 +01:00
}