Implements: IteratorAggregate | Traversable | ArrayAccess | Serializable | Countable
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/HTTP/Header.php on line 3.
integer 1
integer 1
integer 2
array
$_accept_charsetAccept-Charset: parsed header
NULL
array
$_accept_contentAccept: (content) types
NULL
array
$_accept_encodingAccept-Encoding: parsed header
NULL
array
$_accept_languageAccept-Language: parsed header
NULL
Constructor method for Kohana_HTTP_Header. Uses the standard constructor
of the parent ArrayObject
class.
$header_object = new HTTP_Header(['x-powered-by' => 'Kohana 3.1.x', 'expires' => '...']);
mixed
$input
= array(0) - Input array int
$flags
= integer 0 - Flags string
$iterator_class
= string(13) "ArrayIterator" - The iterator class to use public function __construct(array $input = [], $flags = 0, $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 parts array
public static function accept_quality(array $parts)
{
$parsed = [];
// Resource light iteration
$parts_keys = array_keys($parts);
foreach ($parts_keys as $key) {
$value = trim(str_replace(["\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 - $type boolean
$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 examine float
- 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 interrogate boolean
$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 interrogate boolean
$explicit
= bool FALSE - Explicit interrogation, `true` ignores wildcards float
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 = [
'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 string string
public static function create_cache_control(array $cache_control)
{
$parts = [];
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 - $input array
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 - $index boolean
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 retrieve mixed
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` to mixed
$newval
required - New value to set boolean
$replace
= bool TRUE - Replace existing value void
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 = [$current_value, $newval];
}
return parent::offsetSet($index, $current_value);
}
Overloads the ArrayObject::offsetUnset()
method to ensure keys
are lowercase.
string
$index
required - $index void
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 parse array
public static function parse_accept_header($accepts = null)
{
$accepts = explode(',', (string) $accepts);
// If there is no accept, lets accept everything
if ($accepts === null)
return ['*' => ['*' => (float) HTTP_Header::DEFAULT_QUALITY]];
// Parse the accept header qualities
$accepts = HTTP_Header::accept_quality($accepts);
$parsed_accept = [];
// 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 headers mixed
public static function parse_cache_control($cache_control)
{
$directives = explode(',', strtolower($cache_control));
if ($directives === false)
return false;
$output = [];
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 parse array
public static function parse_charset_header($charset = null)
{
if ($charset === null) {
return ['*' => (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 parse array
public static function parse_encoding_header($encoding = null)
{
// Accept everything
if ($encoding === null) {
return ['*' => (float) HTTP_Header::DEFAULT_QUALITY];
} elseif ($encoding === '') {
return ['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 parse int
public function parse_header_string($resource, $header_line)
{
$headers = [];
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 parse array
public static function parse_language_header($language = null)
{
if ($language === null) {
return ['*' => ['*' => (float) HTTP_Header::DEFAULT_QUALITY]];
}
$language = HTTP_Header::accept_quality(explode(',', (string) $language));
$parsed_language = [];
$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([
'text/html'
'text/rtf',
'application/json'
]); // $result = 'application/json'
$result = $header->preferred_accept([
'text/rtf',
'application/xml'
], true); // $result = false (none matched explicitly)
array
$types
required - The content types to examine boolean
$explicit
= bool FALSE - Only allow explicit references, no wildcards string
- 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([
'utf-10', 'ascii', 'utf-16', 'utf-8'
]); // $charset = 'utf-8'
array
$charsets
required - Charsets to test mixed
- 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([
'gzip', 'bzip', 'blowfish'
]);
// $encoding = 'gzip';
array
$encodings
required - Encodings to test against boolean
$explicit
= bool FALSE - Explicit check, if `true` wildcards are excluded mixed
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([
'en-gb', 'en-au', 'fr', 'es'
]); // $lang = 'en-gb'
array
$languages
required - $languages boolean
$explicit
= bool FALSE - $explicit mixed
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 send boolean
$replace
= bool FALSE - Replace existing value callback
$callback
= NULL - Optional callback to replace PHP header function mixed
public function send_headers(HTTP_Response $response = null, $replace = false, $callback = null)
{
$protocol = $response->protocol();
$status = $response->status();
// Create the response header
$processed_headers = [$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::version();
}
// 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 php boolean
$replace
required - Replace existing headers self
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;
}