<?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 ;
/**
* HeaderBag is a container for HTTP headers.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HeaderBag implements \ IteratorAggregate , \ Countable
{
protected const UPPER = '_ABCDEFGHIJKLMNOPQRSTUVWXYZ' ;
protected const LOWER = '-abcdefghijklmnopqrstuvwxyz' ;
protected $headers = [];
protected $cacheControl = [];
public function __construct (array $headers = [])
{
foreach ( $headers as $key => $values ) {
$this -> set ( $key , $values );
}
}
/**
* Returns the headers as a string.
*
* @return string The headers
*/
public function __toString ()
{
if (! $headers = $this -> all ()) {
return '' ;
}
ksort ( $headers );
$max = max ( array_map ( 'strlen' , array_keys ( $headers ))) + 1 ;
$content = '' ;
foreach ( $headers as $name => $values ) {
$name = ucwords ( $name , '-' );
foreach ( $values as $value ) {
$content .= sprintf ( "%- { $max } s %s\r\n" , $name . ':' , $value );
}
}
return $content ;
}
/**
* Returns the headers.
*
* @param string|null $key The name of the headers to return or null to get them all
*
* @return array An array of headers
*/
public function all ( /*string $key = null*/ )
{
if ( 1 <= \ func_num_args () && null !== $key = func_get_arg ( 0 )) {
return $this -> headers [ strtr ( $key , self :: UPPER , self :: LOWER )] ?? [];
}
return $this -> headers ;
}
/**
* Returns the parameter keys.
*
* @return array An array of parameter keys
*/
public function keys ()
{
return array_keys ( $this -> all ());
}
/**
* Replaces the current HTTP headers by a new set.
*/
public function replace (array $headers = [])
{
$this -> headers = [];
$this -> add ( $headers );
}
/**
* Adds new headers the current HTTP headers set.
*/
public function add (array $headers )
{
foreach ( $headers as $key => $values ) {
$this -> set ( $key , $values );
}
}
/**
* Returns a header value by name.
*
* @param string $key The header name
* @param string|null $default The default value
*
* @return string|null The first header value or default value
*/
public function get ( $key , $default = null )
{
$headers = $this -> all ((string) $key );
if ( 2 < \ func_num_args ()) {
@ trigger_error ( sprintf ( 'Passing a third argument to "%s()" is deprecated since Symfony 4.4, use method "all()" instead' , __METHOD__ ), \ E_USER_DEPRECATED );
if (! func_get_arg ( 2 )) {
return $headers ;
}
}
if (! $headers ) {
return $default ;
}
if ( null === $headers [ 0 ]) {
return null ;
}
return (string) $headers [ 0 ];
}
/**
* Sets a header by name.
*
* @param string $key The key
* @param string|string[]|null $values The value or an array of values
* @param bool $replace Whether to replace the actual value or not (true by default)
*/
public function set ( $key , $values , $replace = true )
{
$key = strtr ( $key , self :: UPPER , self :: LOWER );
if (\ is_array ( $values )) {
$values = array_values ( $values );
if ( true === $replace || !isset( $this -> headers [ $key ])) {
$this -> headers [ $key ] = $values ;
} else {
$this -> headers [ $key ] = array_merge ( $this -> headers [ $key ], $values );
}
} else {
if ( true === $replace || !isset( $this -> headers [ $key ])) {
$this -> headers [ $key ] = [ $values ];
} else {
$this -> headers [ $key ][] = $values ;
}
}
if ( 'cache-control' === $key ) {
$this -> cacheControl = $this -> parseCacheControl ( implode ( ', ' , $this -> headers [ $key ]));
}
}
/**
* Returns true if the HTTP header is defined.
*
* @param string $key The HTTP header
*
* @return bool true if the parameter exists, false otherwise
*/
public function has ( $key )
{
return \ array_key_exists ( strtr ( $key , self :: UPPER , self :: LOWER ), $this -> all ());
}
/**
* Returns true if the given HTTP header contains the given value.
*
* @param string $key The HTTP header name
* @param string $value The HTTP value
*
* @return bool true if the value is contained in the header, false otherwise
*/
public function contains ( $key , $value )
{
return \ in_array ( $value , $this -> all ((string) $key ));
}
/**
* Removes a header.
*
* @param string $key The HTTP header name
*/
public function remove ( $key )
{
$key = strtr ( $key , self :: UPPER , self :: LOWER );
unset( $this -> headers [ $key ]);
if ( 'cache-control' === $key ) {
$this -> cacheControl = [];
}
}
/**
* Returns the HTTP header value converted to a date.
*
* @param string $key The parameter key
*
* @return \DateTimeInterface|null The parsed DateTime or the default value if the header does not exist
*
* @throws \RuntimeException When the HTTP header is not parseable
*/
public function getDate ( $key , \ DateTime $default = null )
{
if ( null === $value = $this -> get ( $key )) {
return $default ;
}
if ( false === $date = \ DateTime :: createFromFormat (\ DATE_RFC2822 , $value )) {
throw new \ RuntimeException ( sprintf ( 'The "%s" HTTP header is not parseable (%s).' , $key , $value ));
}
return $date ;
}
/**
* Adds a custom Cache-Control directive.
*
* @param string $key The Cache-Control directive name
* @param bool|string $value The Cache-Control directive value
*/
public function addCacheControlDirective ( $key , $value = true )
{
$this -> cacheControl [ $key ] = $value ;
$this -> set ( 'Cache-Control' , $this -> getCacheControlHeader ());
}
/**
* Returns true if the Cache-Control directive is defined.
*
* @param string $key The Cache-Control directive
*
* @return bool true if the directive exists, false otherwise
*/
public function hasCacheControlDirective ( $key )
{
return \ array_key_exists ( $key , $this -> cacheControl );
}
/**
* Returns a Cache-Control directive value by name.
*
* @param string $key The directive name
*
* @return bool|string|null The directive value if defined, null otherwise
*/
public function getCacheControlDirective ( $key )
{
return $this -> cacheControl [ $key ] ?? null ;
}
/**
* Removes a Cache-Control directive.
*
* @param string $key The Cache-Control directive
*/
public function removeCacheControlDirective ( $key )
{
unset( $this -> cacheControl [ $key ]);
$this -> set ( 'Cache-Control' , $this -> getCacheControlHeader ());
}
/**
* Returns an iterator for headers.
*
* @return \ArrayIterator An \ArrayIterator instance
*/
#[\ReturnTypeWillChange]
public function getIterator ()
{
return new \ ArrayIterator ( $this -> headers );
}
/**
* Returns the number of headers.
*
* @return int The number of headers
*/
#[\ReturnTypeWillChange]
public function count ()
{
return \ count ( $this -> headers );
}
protected function getCacheControlHeader ()
{
ksort ( $this -> cacheControl );
return HeaderUtils :: toString ( $this -> cacheControl , ',' );
}
/**
* Parses a Cache-Control HTTP header.
*
* @param string $header The value of the Cache-Control HTTP header
*
* @return array An array representing the attribute values
*/
protected function parseCacheControl ( $header )
{
$parts = HeaderUtils :: split ( $header , ',=' );
return HeaderUtils :: combine ( $parts );
}
}