Implements: Countable | Serializable | ArrayAccess | Traversable | IteratorAggregate
The Kohana_HTTP_Header class provides an Object-Orientated interface
to HTTP headers. This can parse header arrays returned from the
PHP functions apache_request_headers()
or the http_parse_headers()
function available within the PECL HTTP library.
Class declared in SYSPATH/classes/kohana/http/header.php on line 15.
integer 1
integer 1
integer 2
array
$_accept_charsetAccept-Charset: parsed header
array
$_accept_contentAccept: (content) types
array
$_accept_encodingAccept-Encoding: parsed header
array
$_accept_languageAccept-Language: parsed header
Constructor method for Kohana_HTTP_Header. Uses the standard constructor
of the parent ArrayObject
class.
$header_object = new HTTP_Header(array('x-powered-by' => 'Kohana 3.1.x', 'expires' => '...'));
mixed
$input
= array(0) - Input arrayint
$flags
= NULL - Flagsstring
$iterator_class
= string(13) "ArrayIterator" - The iterator class to usepublic function __construct(array $input = array(), $flags = NULL, $iterator_class = 'ArrayIterator')
{
/**
* @link http://www.w3.org/Protocols/rfc2616/rfc2616.html
*
* HTTP header declarations should be treated as case-insensitive
*/
$input = array_change_key_case((array) $input, CASE_LOWER);
parent::__construct($input, $flags, $iterator_class);
}
Returns the header object as a string, including the terminating new line
// Return the header as a string
echo (string) $request->headers();
string
public function __toString()
{
$header = '';
foreach ($this as $key => $value)
{
// Put the keys back the Case-Convention expected
$key = Text::ucfirst($key);
if (is_array($value))
{
$header .= $key.': '.(implode(', ', $value))."\r\n";
}
else
{
$header .= $key.': '.$value."\r\n";
}
}
return $header."\r\n";
}
Parses an Accept(-*) header and detects the quality
array
$parts
required - Accept header partsarray
public static function accept_quality(array $parts)
{
$parsed = array();
// Resource light iteration
$parts_keys = array_keys($parts);
foreach ($parts_keys as $key)
{
$value = trim(str_replace(array("\r", "\n"), '', $parts[$key]));
$pattern = '~\b(\;\s*+)?q\s*+=\s*+([.0-9]+)~';
// If there is no quality directive, return default
if ( ! preg_match($pattern, $value, $quality))
{
$parsed[$value] = (float) HTTP_Header::DEFAULT_QUALITY;
}
else
{
$quality = $quality[2];
if ($quality[0] === '.')
{
$quality = '0'.$quality;
}
// Remove the quality value from the string and apply quality
$parsed[trim(preg_replace($pattern, '', $value, 1), '; ')] = (float) $quality;
}
}
return $parsed;
}
Returns the accept quality of a submitted mime type based on the
request Accept:
header. If the $explicit
argument is TRUE
,
only precise matches will be returned, excluding all wildcard (*
)
directives.
// Accept: application/xml; application/json; q=.5; text/html; q=.2, text/*
// Accept quality for application/json
// $quality = 0.5
$quality = $request->headers()->accepts_at_quality('application/json');
// $quality_explicit = FALSE
$quality_explicit = $request->headers()->accepts_at_quality('text/plain', TRUE);
string
$type
required - $typeboolean
$explicit
= bool FALSE - Explicit check, excludes `*`mixed
public function accepts_at_quality($type, $explicit = FALSE)
{
// Parse Accept header if required
if ($this->_accept_content === NULL)
{
if ($this->offsetExists('Accept'))
{
$accept = $this->offsetGet('Accept');
}
else
{
$accept = '*/*';
}
$this->_accept_content = HTTP_Header::parse_accept_header($accept);
}
// If not a real mime, try and find it in config
if (strpos($type, '/') === FALSE)
{
$mime = Kohana::$config->load('mimes.'.$type);
if ($mime === NULL)
return FALSE;
$quality = FALSE;
foreach ($mime as $_type)
{
$quality_check = $this->accepts_at_quality($_type, $explicit);
$quality = ($quality_check > $quality) ? $quality_check : $quality;
}
return $quality;
}
$parts = explode('/', $type, 2);
if (isset($this->_accept_content[$parts[0]][$parts[1]]))
{
return $this->_accept_content[$parts[0]][$parts[1]];
}
elseif ($explicit === TRUE)
{
return FALSE;
}
else
{
if (isset($this->_accept_content[$parts[0]]['*']))
{
return $this->_accept_content[$parts[0]]['*'];
}
elseif (isset($this->_accept_content['*']['*']))
{
return $this->_accept_content['*']['*'];
}
else
{
return FALSE;
}
}
}
Returns the quality of the supplied $charset
argument. This method
will automatically parse the Accept-Charset
header if present and
return the associated resolved quality value.
// Accept-Charset: utf-8, utf-16; q=.8, iso-8859-1; q=.5
$quality = $header->accepts_charset_at_quality('utf-8');
// $quality = (float) 1
string
$charset
required - Charset to examinefloat
- The quality of the charsetpublic function accepts_charset_at_quality($charset)
{
if ($this->_accept_charset === NULL)
{
if ($this->offsetExists('Accept-Charset'))
{
$charset_header = strtolower($this->offsetGet('Accept-Charset'));
$this->_accept_charset = HTTP_Header::parse_charset_header($charset_header);
}
else
{
$this->_accept_charset = HTTP_Header::parse_charset_header(NULL);
}
}
$charset = strtolower($charset);
if (isset($this->_accept_charset[$charset]))
{
return $this->_accept_charset[$charset];
}
elseif (isset($this->_accept_charset['*']))
{
return $this->_accept_charset['*'];
}
elseif ($charset === 'iso-8859-1')
{
return (float) 1;
}
return (float) 0;
}
Returns the quality of the $encoding
type passed to it. Encoding
is usually compression such as gzip
, but could be some other
message encoding algorithm. This method allows explicit checks to be
done ignoring wildcards.
// Accept-Encoding: compress, gzip, *; q=.5
$encoding = $header->accepts_encoding_at_quality('gzip');
// $encoding = (float) 1.0s
string
$encoding
required - Encoding type to interrogateboolean
$explicit
= bool FALSE - Explicit check, ignoring wildcards and `identity`float
public function accepts_encoding_at_quality($encoding, $explicit = FALSE)
{
if ($this->_accept_encoding === NULL)
{
if ($this->offsetExists('Accept-Encoding'))
{
$encoding_header = $this->offsetGet('Accept-Encoding');
}
else
{
$encoding_header = NULL;
}
$this->_accept_encoding = HTTP_Header::parse_encoding_header($encoding_header);
}
// Normalize the encoding
$encoding = strtolower($encoding);
if (isset($this->_accept_encoding[$encoding]))
{
return $this->_accept_encoding[$encoding];
}
if ($explicit === FALSE)
{
if (isset($this->_accept_encoding['*']))
{
return $this->_accept_encoding['*'];
}
elseif ($encoding === 'identity')
{
return (float) HTTP_Header::DEFAULT_QUALITY;
}
}
return (float) 0;
}
Returns the quality of $language
supplied, optionally ignoring
wildcards if $explicit
is set to a non-FALSE
value. If the quality
is not found, 0.0
is returned.
// Accept-Language: en-us, en-gb; q=.7, en; q=.5
$lang = $header->accepts_language_at_quality('en-gb');
// $lang = (float) 0.7
$lang2 = $header->accepts_language_at_quality('en-au');
// $lang2 = (float) 0.5
$lang3 = $header->accepts_language_at_quality('en-au', TRUE);
// $lang3 = (float) 0.0
string
$language
required - Language to interrogateboolean
$explicit
= bool FALSE - Explicit interrogation, `TRUE` ignores wildcardsfloat
public function accepts_language_at_quality($language, $explicit = FALSE)
{
if ($this->_accept_language === NULL)
{
if ($this->offsetExists('Accept-Language'))
{
$language_header = strtolower($this->offsetGet('Accept-Language'));
}
else
{
$language_header = NULL;
}
$this->_accept_language = HTTP_Header::parse_language_header($language_header);
}
// Normalize the language
$language_parts = explode('-', strtolower($language), 2);
if (isset($this->_accept_language[$language_parts[0]]))
{
if (isset($language_parts[1]))
{
if (isset($this->_accept_language[$language_parts[0]][$language_parts[1]]))
{
return $this->_accept_language[$language_parts[0]][$language_parts[1]];
}
elseif ($explicit === FALSE AND isset($this->_accept_language[$language_parts[0]]['*']))
{
return $this->_accept_language[$language_parts[0]]['*'];
}
}
elseif (isset($this->_accept_language[$language_parts[0]]['*']))
{
return $this->_accept_language[$language_parts[0]]['*'];
}
}
if ($explicit === FALSE AND isset($this->_accept_language['*']))
{
return $this->_accept_language['*'];
}
return (float) 0;
}
Generates a Cache-Control HTTP header based on the supplied array.
// Set the cache control headers you want to use
$cache_control = array(
'max-age' => 3600,
'must-revalidate',
'public'
);
// Create the cache control header, creates :
// cache-control: max-age=3600, must-revalidate, public
$response->headers('Cache-Control', HTTP_Header::create_cache_control($cache_control);
array
$cache_control
required - Cache-Control to render to stringstring
public static function create_cache_control(array $cache_control)
{
$parts = array();
foreach ($cache_control as $key => $value)
{
$parts[] = (is_int($key)) ? $value : ($key.'='.$value);
}
return implode(', ', $parts);
}
Overloads the ArrayObject::exchangeArray()
method to ensure that
all keys are changed to lowercase.
mixed
$input
required - $inputarray
public function exchangeArray($input)
{
/**
* @link http://www.w3.org/Protocols/rfc2616/rfc2616.html
*
* HTTP header declarations should be treated as case-insensitive
*/
$input = array_change_key_case((array) $input, CASE_LOWER);
return parent::exchangeArray($input);
}
Overloads the ArrayObject::offsetExists()
method to ensure keys
are lowercase.
string
$index
required - $indexboolean
public function offsetExists($index)
{
return parent::offsetExists(strtolower($index));
}
Overload the ArrayObject::offsetGet()
method to ensure that all
keys passed to it are formatted correctly for this object.
string
$index
required - Index to retrievemixed
public function offsetGet($index)
{
return parent::offsetGet(strtolower($index));
}
Overloads ArrayObject::offsetSet()
to enable handling of header
with multiple instances of the same directive. If the $replace
flag
is FALSE
, the header will be appended rather than replacing the
original setting.
mixed
$index
required - Index to set `$newval` tomixed
$newval
required - New value to setboolean
$replace
= bool TRUE - Replace existing valuevoid
public function offsetSet($index, $newval, $replace = TRUE)
{
// Ensure the index is lowercase
$index = strtolower($index);
if ($replace OR ! $this->offsetExists($index))
{
return parent::offsetSet($index, $newval);
}
$current_value = $this->offsetGet($index);
if (is_array($current_value))
{
$current_value[] = $newval;
}
else
{
$current_value = array($current_value, $newval);
}
return parent::offsetSet($index, $current_value);
}
Overloads the ArrayObject::offsetUnset()
method to ensure keys
are lowercase.
string
$index
required - $indexvoid
public function offsetUnset($index)
{
return parent::offsetUnset(strtolower($index));
}
Parses the accept header to provide the correct quality values for each supplied accept type.
string
$accepts
= NULL - Accept content header string to parsearray
public static function parse_accept_header($accepts = NULL)
{
$accepts = explode(',', (string) $accepts);
// If there is no accept, lets accept everything
if ($accepts === NULL)
return array('*' => array('*' => (float) HTTP_Header::DEFAULT_QUALITY));
// Parse the accept header qualities
$accepts = HTTP_Header::accept_quality($accepts);
$parsed_accept = array();
// This method of iteration uses less resource
$keys = array_keys($accepts);
foreach ($keys as $key)
{
// Extract the parts
$parts = explode('/', $key, 2);
// Invalid content type- bail
if ( ! isset($parts[1]))
continue;
// Set the parsed output
$parsed_accept[$parts[0]][$parts[1]] = $accepts[$key];
}
return $parsed_accept;
}
Parses the Cache-Control header and returning an array representation of the Cache-Control header.
// Create the cache control header
$response->headers('cache-control', 'max-age=3600, must-revalidate, public');
// Parse the cache control header
if ($cache_control = HTTP_Header::parse_cache_control($response->headers('cache-control')))
{
// Cache-Control header was found
$maxage = $cache_control['max-age'];
}
array
$cache_control
required - Array of headersmixed
public static function parse_cache_control($cache_control)
{
$directives = explode(',', strtolower($cache_control));
if ($directives === FALSE)
return FALSE;
$output = array();
foreach ($directives as $directive)
{
if (strpos($directive, '=') !== FALSE)
{
list($key, $value) = explode('=', trim($directive), 2);
$output[$key] = ctype_digit($value) ? (int) $value : $value;
}
else
{
$output[] = trim($directive);
}
}
return $output;
}
Parses the Accept-Charset:
HTTP header and returns an array containing
the charset and associated quality.
string
$charset
= NULL - Charset string to parsearray
public static function parse_charset_header($charset = NULL)
{
if ($charset === NULL)
{
return array('*' => (float) HTTP_Header::DEFAULT_QUALITY);
}
return HTTP_Header::accept_quality(explode(',', (string) $charset));
}
Parses the Accept-Encoding:
HTTP header and returns an array containing
the charsets and associated quality.
string
$encoding
= NULL - Charset string to parsearray
public static function parse_encoding_header($encoding = NULL)
{
// Accept everything
if ($encoding === NULL)
{
return array('*' => (float) HTTP_Header::DEFAULT_QUALITY);
}
elseif ($encoding === '')
{
return array('identity' => (float) HTTP_Header::DEFAULT_QUALITY);
}
else
{
return HTTP_Header::accept_quality(explode(',', (string) $encoding));
}
}
Parses a HTTP Message header line and applies it to this HTTP_Header
$header = $response->headers();
$header->parse_header_string(NULL, 'content-type: application/json');
resource
$resource
required - The resource (required by Curl API)string
$header_line
required - The line from the header to parseint
public function parse_header_string($resource, $header_line)
{
$headers = array();
if (preg_match_all('/(\w[^\s:]*):[ ]*([^\r\n]*(?:\r\n[ \t][^\r\n]*)*)/', $header_line, $matches))
{
foreach ($matches[0] as $key => $value)
{
$this->offsetSet($matches[1][$key], $matches[2][$key], FALSE);
}
}
return strlen($header_line);
}
Parses the Accept-Language:
HTTP header and returns an array containing
the languages and associated quality.
string
$language
= NULL - Charset string to parsearray
public static function parse_language_header($language = NULL)
{
if ($language === NULL)
{
return array('*' => array('*' => (float) HTTP_Header::DEFAULT_QUALITY));
}
$language = HTTP_Header::accept_quality(explode(',', (string) $language));
$parsed_language = array();
$keys = array_keys($language);
foreach ($keys as $key)
{
// Extract the parts
$parts = explode('-', $key, 2);
// Invalid content type- bail
if ( ! isset($parts[1]))
{
$parsed_language[$parts[0]]['*'] = $language[$key];
}
else
{
// Set the parsed output
$parsed_language[$parts[0]][$parts[1]] = $language[$key];
}
}
return $parsed_language;
}
Returns the preferred response content type based on the accept header
quality settings. If items have the same quality value, the first item
found in the array supplied as $types
will be returned.
// Get the preferred acceptable content type
// Accept: text/html, application/json; q=.8, text/*
$result = $header->preferred_accept(array(
'text/html'
'text/rtf',
'application/json'
)); // $result = 'application/json'
$result = $header->preferred_accept(array(
'text/rtf',
'application/xml'
), TRUE); // $result = FALSE (none matched explicitly)
array
$types
required - The content types to examineboolean
$explicit
= bool FALSE - Only allow explicit references, no wildcardsstring
- Name of the preferred content typepublic function preferred_accept(array $types, $explicit = FALSE)
{
$preferred = FALSE;
$ceiling = 0;
foreach ($types as $type)
{
$quality = $this->accepts_at_quality($type, $explicit);
if ($quality > $ceiling)
{
$preferred = $type;
$ceiling = $quality;
}
}
return $preferred;
}
Returns the preferred charset from the supplied array $charsets
based
on the Accept-Charset
header directive.
// Accept-Charset: utf-8, utf-16; q=.8, iso-8859-1; q=.5
$charset = $header->preferred_charset(array(
'utf-10', 'ascii', 'utf-16', 'utf-8'
)); // $charset = 'utf-8'
array
$charsets
required - Charsets to testmixed
- Preferred charset or `FALSE`public function preferred_charset(array $charsets)
{
$preferred = FALSE;
$ceiling = 0;
foreach ($charsets as $charset)
{
$quality = $this->accepts_charset_at_quality($charset);
if ($quality > $ceiling)
{
$preferred = $charset;
$ceiling = $quality;
}
}
return $preferred;
}
Returns the preferred message encoding type based on quality, and can
optionally ignore wildcard references. If two or more encodings have the
same quality, the first listed in $encodings
will be returned.
// Accept-Encoding: compress, gzip, *; q.5
$encoding = $header->preferred_encoding(array(
'gzip', 'bzip', 'blowfish'
));
// $encoding = 'gzip';
array
$encodings
required - Encodings to test againstboolean
$explicit
= bool FALSE - Explicit check, if `TRUE` wildcards are excludedmixed
public function preferred_encoding(array $encodings, $explicit = FALSE)
{
$ceiling = 0;
$preferred = FALSE;
foreach ($encodings as $encoding)
{
$quality = $this->accepts_encoding_at_quality($encoding, $explicit);
if ($quality > $ceiling)
{
$ceiling = $quality;
$preferred = $encoding;
}
}
return $preferred;
}
Returns the preferred language from the supplied array $languages
based
on the Accept-Language
header directive.
// Accept-Language: en-us, en-gb; q=.7, en; q=.5
$lang = $header->preferred_language(array(
'en-gb', 'en-au', 'fr', 'es'
)); // $lang = 'en-gb'
array
$languages
required - $languagesboolean
$explicit
= bool FALSE - $explicitmixed
public function preferred_language(array $languages, $explicit = FALSE)
{
$ceiling = 0;
$preferred = FALSE;
foreach ($languages as $language)
{
$quality = $this->accepts_language_at_quality($language, $explicit);
if ($quality > $ceiling)
{
$ceiling = $quality;
$preferred = $language;
}
}
return $preferred;
}
Sends headers to the php processor, or supplied $callback
argument.
This method formats the headers correctly for output, re-instating their
capitalization for transmission.
if you supply a custom header handler via $callback
, it is
recommended that $response
is returned
HTTP_Response
$response
= NULL - Header to sendboolean
$replace
= bool FALSE - Replace existing valuecallback
$callback
= NULL - Optional callback to replace PHP header functionmixed
public function send_headers(HTTP_Response $response = NULL, $replace = FALSE, $callback = NULL)
{
if ($response === NULL)
{
// Default to the initial request message
$response = Request::initial()->response();
}
$protocol = $response->protocol();
$status = $response->status();
// Create the response header
$processed_headers = array($protocol.' '.$status.' '.Response::$messages[$status]);
// Get the headers array
$headers = $response->headers()->getArrayCopy();
foreach ($headers as $header => $value)
{
if (is_array($value))
{
$value = implode(', ', $value);
}
$processed_headers[] = Text::ucfirst($header).': '.$value;
}
if ( ! isset($headers['content-type']))
{
$processed_headers[] = 'Content-Type: '.Kohana::$content_type.
'; charset='.Kohana::$charset;
}
if (Kohana::$expose AND ! isset($headers['x-powered-by']))
{
$processed_headers[] = 'X-Powered-By: Kohana Framework '.
Kohana::VERSION.' ('.Kohana::CODENAME.')';
}
// Get the cookies and apply
if ($cookies = $response->cookie())
{
$processed_headers['Set-Cookie'] = $cookies;
}
if (is_callable($callback))
{
// Use the callback method to set header
return call_user_func($callback, $response, $processed_headers, $replace);
}
else
{
$this->_send_headers_to_php($processed_headers, $replace);
return $response;
}
}
Sends the supplied headers to the PHP output buffer. If cookies are included in the message they will be handled appropriately.
array
$headers
required - Headers to send to phpboolean
$replace
required - Replace existing headersself
protected function _send_headers_to_php(array $headers, $replace)
{
// If the headers have been sent, get out
if (headers_sent())
return $this;
foreach ($headers as $key => $line)
{
if ($key == 'Set-Cookie' AND is_array($line))
{
// Send cookies
foreach ($line as $name => $value)
{
Cookie::set($name, $value['value'], $value['expiration']);
}
continue;
}
header($line, $replace);
}
return $this;
}