PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/system/classes/Kohana/HTTP/Header.php

https://bitbucket.org/sklyarov_ivan/trap
PHP | 949 lines | 463 code | 114 blank | 372 comment | 47 complexity | aa6d018a5b13102d3d80b8dc125b5902 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php defined('SYSPATH') OR die('No direct script access.');
  2. /**
  3. * The Kohana_HTTP_Header class provides an Object-Orientated interface
  4. * to HTTP headers. This can parse header arrays returned from the
  5. * PHP functions `apache_request_headers()` or the `http_parse_headers()`
  6. * function available within the PECL HTTP library.
  7. *
  8. * @package Kohana
  9. * @category HTTP
  10. * @author Kohana Team
  11. * @since 3.1.0
  12. * @copyright (c) 2008-2012 Kohana Team
  13. * @license http://kohanaphp.com/license
  14. */
  15. class Kohana_HTTP_Header extends ArrayObject {
  16. // Default Accept-* quality value if none supplied
  17. const DEFAULT_QUALITY = 1;
  18. /**
  19. * Parses an Accept(-*) header and detects the quality
  20. *
  21. * @param array $parts accept header parts
  22. * @return array
  23. * @since 3.2.0
  24. */
  25. public static function accept_quality(array $parts)
  26. {
  27. $parsed = array();
  28. // Resource light iteration
  29. $parts_keys = array_keys($parts);
  30. foreach ($parts_keys as $key)
  31. {
  32. $value = trim(str_replace(array("\r", "\n"), '', $parts[$key]));
  33. $pattern = '~\b(\;\s*+)?q\s*+=\s*+([.0-9]+)~';
  34. // If there is no quality directive, return default
  35. if ( ! preg_match($pattern, $value, $quality))
  36. {
  37. $parsed[$value] = (float) HTTP_Header::DEFAULT_QUALITY;
  38. }
  39. else
  40. {
  41. $quality = $quality[2];
  42. if ($quality[0] === '.')
  43. {
  44. $quality = '0'.$quality;
  45. }
  46. // Remove the quality value from the string and apply quality
  47. $parsed[trim(preg_replace($pattern, '', $value, 1), '; ')] = (float) $quality;
  48. }
  49. }
  50. return $parsed;
  51. }
  52. /**
  53. * Parses the accept header to provide the correct quality values
  54. * for each supplied accept type.
  55. *
  56. * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
  57. * @param string $accepts accept content header string to parse
  58. * @return array
  59. * @since 3.2.0
  60. */
  61. public static function parse_accept_header($accepts = NULL)
  62. {
  63. $accepts = explode(',', (string) $accepts);
  64. // If there is no accept, lets accept everything
  65. if ($accepts === NULL)
  66. return array('*' => array('*' => (float) HTTP_Header::DEFAULT_QUALITY));
  67. // Parse the accept header qualities
  68. $accepts = HTTP_Header::accept_quality($accepts);
  69. $parsed_accept = array();
  70. // This method of iteration uses less resource
  71. $keys = array_keys($accepts);
  72. foreach ($keys as $key)
  73. {
  74. // Extract the parts
  75. $parts = explode('/', $key, 2);
  76. // Invalid content type- bail
  77. if ( ! isset($parts[1]))
  78. continue;
  79. // Set the parsed output
  80. $parsed_accept[$parts[0]][$parts[1]] = $accepts[$key];
  81. }
  82. return $parsed_accept;
  83. }
  84. /**
  85. * Parses the `Accept-Charset:` HTTP header and returns an array containing
  86. * the charset and associated quality.
  87. *
  88. * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2
  89. * @param string $charset charset string to parse
  90. * @return array
  91. * @since 3.2.0
  92. */
  93. public static function parse_charset_header($charset = NULL)
  94. {
  95. if ($charset === NULL)
  96. {
  97. return array('*' => (float) HTTP_Header::DEFAULT_QUALITY);
  98. }
  99. return HTTP_Header::accept_quality(explode(',', (string) $charset));
  100. }
  101. /**
  102. * Parses the `Accept-Encoding:` HTTP header and returns an array containing
  103. * the charsets and associated quality.
  104. *
  105. * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
  106. * @param string $encoding charset string to parse
  107. * @return array
  108. * @since 3.2.0
  109. */
  110. public static function parse_encoding_header($encoding = NULL)
  111. {
  112. // Accept everything
  113. if ($encoding === NULL)
  114. {
  115. return array('*' => (float) HTTP_Header::DEFAULT_QUALITY);
  116. }
  117. elseif ($encoding === '')
  118. {
  119. return array('identity' => (float) HTTP_Header::DEFAULT_QUALITY);
  120. }
  121. else
  122. {
  123. return HTTP_Header::accept_quality(explode(',', (string) $encoding));
  124. }
  125. }
  126. /**
  127. * Parses the `Accept-Language:` HTTP header and returns an array containing
  128. * the languages and associated quality.
  129. *
  130. * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
  131. * @param string $language charset string to parse
  132. * @return array
  133. * @since 3.2.0
  134. */
  135. public static function parse_language_header($language = NULL)
  136. {
  137. if ($language === NULL)
  138. {
  139. return array('*' => array('*' => (float) HTTP_Header::DEFAULT_QUALITY));
  140. }
  141. $language = HTTP_Header::accept_quality(explode(',', (string) $language));
  142. $parsed_language = array();
  143. $keys = array_keys($language);
  144. foreach ($keys as $key)
  145. {
  146. // Extract the parts
  147. $parts = explode('-', $key, 2);
  148. // Invalid content type- bail
  149. if ( ! isset($parts[1]))
  150. {
  151. $parsed_language[$parts[0]]['*'] = $language[$key];
  152. }
  153. else
  154. {
  155. // Set the parsed output
  156. $parsed_language[$parts[0]][$parts[1]] = $language[$key];
  157. }
  158. }
  159. return $parsed_language;
  160. }
  161. /**
  162. * Generates a Cache-Control HTTP header based on the supplied array.
  163. *
  164. * // Set the cache control headers you want to use
  165. * $cache_control = array(
  166. * 'max-age' => 3600,
  167. * 'must-revalidate',
  168. * 'public'
  169. * );
  170. *
  171. * // Create the cache control header, creates :
  172. * // cache-control: max-age=3600, must-revalidate, public
  173. * $response->headers('Cache-Control', HTTP_Header::create_cache_control($cache_control);
  174. *
  175. * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13
  176. * @param array $cache_control Cache-Control to render to string
  177. * @return string
  178. */
  179. public static function create_cache_control(array $cache_control)
  180. {
  181. $parts = array();
  182. foreach ($cache_control as $key => $value)
  183. {
  184. $parts[] = (is_int($key)) ? $value : ($key.'='.$value);
  185. }
  186. return implode(', ', $parts);
  187. }
  188. /**
  189. * Parses the Cache-Control header and returning an array representation of the Cache-Control
  190. * header.
  191. *
  192. * // Create the cache control header
  193. * $response->headers('cache-control', 'max-age=3600, must-revalidate, public');
  194. *
  195. * // Parse the cache control header
  196. * if ($cache_control = HTTP_Header::parse_cache_control($response->headers('cache-control')))
  197. * {
  198. * // Cache-Control header was found
  199. * $maxage = $cache_control['max-age'];
  200. * }
  201. *
  202. * @param array $cache_control Array of headers
  203. * @return mixed
  204. */
  205. public static function parse_cache_control($cache_control)
  206. {
  207. $directives = explode(',', strtolower($cache_control));
  208. if ($directives === FALSE)
  209. return FALSE;
  210. $output = array();
  211. foreach ($directives as $directive)
  212. {
  213. if (strpos($directive, '=') !== FALSE)
  214. {
  215. list($key, $value) = explode('=', trim($directive), 2);
  216. $output[$key] = ctype_digit($value) ? (int) $value : $value;
  217. }
  218. else
  219. {
  220. $output[] = trim($directive);
  221. }
  222. }
  223. return $output;
  224. }
  225. /**
  226. * @var array Accept: (content) types
  227. */
  228. protected $_accept_content;
  229. /**
  230. * @var array Accept-Charset: parsed header
  231. */
  232. protected $_accept_charset;
  233. /**
  234. * @var array Accept-Encoding: parsed header
  235. */
  236. protected $_accept_encoding;
  237. /**
  238. * @var array Accept-Language: parsed header
  239. */
  240. protected $_accept_language;
  241. /**
  242. * Constructor method for [Kohana_HTTP_Header]. Uses the standard constructor
  243. * of the parent `ArrayObject` class.
  244. *
  245. * $header_object = new HTTP_Header(array('x-powered-by' => 'Kohana 3.1.x', 'expires' => '...'));
  246. *
  247. * @param mixed $input Input array
  248. * @param int $flags Flags
  249. * @param string $iterator_class The iterator class to use
  250. */
  251. public function __construct(array $input = array(), $flags = NULL, $iterator_class = 'ArrayIterator')
  252. {
  253. /**
  254. * @link http://www.w3.org/Protocols/rfc2616/rfc2616.html
  255. *
  256. * HTTP header declarations should be treated as case-insensitive
  257. */
  258. $input = array_change_key_case( (array) $input, CASE_LOWER);
  259. parent::__construct($input, $flags, $iterator_class);
  260. }
  261. /**
  262. * Returns the header object as a string, including
  263. * the terminating new line
  264. *
  265. * // Return the header as a string
  266. * echo (string) $request->headers();
  267. *
  268. * @return string
  269. */
  270. public function __toString()
  271. {
  272. $header = '';
  273. foreach ($this as $key => $value)
  274. {
  275. // Put the keys back the Case-Convention expected
  276. $key = Text::ucfirst($key);
  277. if (is_array($value))
  278. {
  279. $header .= $key.': '.(implode(', ', $value))."\r\n";
  280. }
  281. else
  282. {
  283. $header .= $key.': '.$value."\r\n";
  284. }
  285. }
  286. return $header."\r\n";
  287. }
  288. /**
  289. * Overloads `ArrayObject::offsetSet()` to enable handling of header
  290. * with multiple instances of the same directive. If the `$replace` flag
  291. * is `FALSE`, the header will be appended rather than replacing the
  292. * original setting.
  293. *
  294. * @param mixed $index index to set `$newval` to
  295. * @param mixed $newval new value to set
  296. * @param boolean $replace replace existing value
  297. * @return void
  298. * @since 3.2.0
  299. */
  300. public function offsetSet($index, $newval, $replace = TRUE)
  301. {
  302. // Ensure the index is lowercase
  303. $index = strtolower($index);
  304. if ($replace OR ! $this->offsetExists($index))
  305. {
  306. return parent::offsetSet($index, $newval);
  307. }
  308. $current_value = $this->offsetGet($index);
  309. if (is_array($current_value))
  310. {
  311. $current_value[] = $newval;
  312. }
  313. else
  314. {
  315. $current_value = array($current_value, $newval);
  316. }
  317. return parent::offsetSet($index, $current_value);
  318. }
  319. /**
  320. * Overloads the `ArrayObject::offsetExists()` method to ensure keys
  321. * are lowercase.
  322. *
  323. * @param string $index
  324. * @return boolean
  325. * @since 3.2.0
  326. */
  327. public function offsetExists($index)
  328. {
  329. return parent::offsetExists(strtolower($index));
  330. }
  331. /**
  332. * Overloads the `ArrayObject::offsetUnset()` method to ensure keys
  333. * are lowercase.
  334. *
  335. * @param string $index
  336. * @return void
  337. * @since 3.2.0
  338. */
  339. public function offsetUnset($index)
  340. {
  341. return parent::offsetUnset(strtolower($index));
  342. }
  343. /**
  344. * Overload the `ArrayObject::offsetGet()` method to ensure that all
  345. * keys passed to it are formatted correctly for this object.
  346. *
  347. * @param string $index index to retrieve
  348. * @return mixed
  349. * @since 3.2.0
  350. */
  351. public function offsetGet($index)
  352. {
  353. return parent::offsetGet(strtolower($index));
  354. }
  355. /**
  356. * Overloads the `ArrayObject::exchangeArray()` method to ensure that
  357. * all keys are changed to lowercase.
  358. *
  359. * @param mixed $input
  360. * @return array
  361. * @since 3.2.0
  362. */
  363. public function exchangeArray($input)
  364. {
  365. /**
  366. * @link http://www.w3.org/Protocols/rfc2616/rfc2616.html
  367. *
  368. * HTTP header declarations should be treated as case-insensitive
  369. */
  370. $input = array_change_key_case( (array) $input, CASE_LOWER);
  371. return parent::exchangeArray($input);
  372. }
  373. /**
  374. * Parses a HTTP Message header line and applies it to this HTTP_Header
  375. *
  376. * $header = $response->headers();
  377. * $header->parse_header_string(NULL, 'content-type: application/json');
  378. *
  379. * @param resource $resource the resource (required by Curl API)
  380. * @param string $header_line the line from the header to parse
  381. * @return int
  382. * @since 3.2.0
  383. */
  384. public function parse_header_string($resource, $header_line)
  385. {
  386. $headers = array();
  387. if (preg_match_all('/(\w[^\s:]*):[ ]*([^\r\n]*(?:\r\n[ \t][^\r\n]*)*)/', $header_line, $matches))
  388. {
  389. foreach ($matches[0] as $key => $value)
  390. {
  391. $this->offsetSet($matches[1][$key], $matches[2][$key], FALSE);
  392. }
  393. }
  394. return strlen($header_line);
  395. }
  396. /**
  397. * Returns the accept quality of a submitted mime type based on the
  398. * request `Accept:` header. If the `$explicit` argument is `TRUE`,
  399. * only precise matches will be returned, excluding all wildcard (`*`)
  400. * directives.
  401. *
  402. * // Accept: application/xml; application/json; q=.5; text/html; q=.2, text/*
  403. * // Accept quality for application/json
  404. *
  405. * // $quality = 0.5
  406. * $quality = $request->headers()->accepts_at_quality('application/json');
  407. *
  408. * // $quality_explicit = FALSE
  409. * $quality_explicit = $request->headers()->accepts_at_quality('text/plain', TRUE);
  410. *
  411. * @param string $type
  412. * @param boolean $explicit explicit check, excludes `*`
  413. * @return mixed
  414. * @since 3.2.0
  415. */
  416. public function accepts_at_quality($type, $explicit = FALSE)
  417. {
  418. // Parse Accept header if required
  419. if ($this->_accept_content === NULL)
  420. {
  421. if ($this->offsetExists('Accept'))
  422. {
  423. $accept = $this->offsetGet('Accept');
  424. }
  425. else
  426. {
  427. $accept = '*/*';
  428. }
  429. $this->_accept_content = HTTP_Header::parse_accept_header($accept);
  430. }
  431. // If not a real mime, try and find it in config
  432. if (strpos($type, '/') === FALSE)
  433. {
  434. $mime = Kohana::$config->load('mimes.'.$type);
  435. if ($mime === NULL)
  436. return FALSE;
  437. $quality = FALSE;
  438. foreach ($mime as $_type)
  439. {
  440. $quality_check = $this->accepts_at_quality($_type, $explicit);
  441. $quality = ($quality_check > $quality) ? $quality_check : $quality;
  442. }
  443. return $quality;
  444. }
  445. $parts = explode('/', $type, 2);
  446. if (isset($this->_accept_content[$parts[0]][$parts[1]]))
  447. {
  448. return $this->_accept_content[$parts[0]][$parts[1]];
  449. }
  450. elseif ($explicit === TRUE)
  451. {
  452. return FALSE;
  453. }
  454. else
  455. {
  456. if (isset($this->_accept_content[$parts[0]]['*']))
  457. {
  458. return $this->_accept_content[$parts[0]]['*'];
  459. }
  460. elseif (isset($this->_accept_content['*']['*']))
  461. {
  462. return $this->_accept_content['*']['*'];
  463. }
  464. else
  465. {
  466. return FALSE;
  467. }
  468. }
  469. }
  470. /**
  471. * Returns the preferred response content type based on the accept header
  472. * quality settings. If items have the same quality value, the first item
  473. * found in the array supplied as `$types` will be returned.
  474. *
  475. * // Get the preferred acceptable content type
  476. * // Accept: text/html, application/json; q=.8, text/*
  477. * $result = $header->preferred_accept(array(
  478. * 'text/html'
  479. * 'text/rtf',
  480. * 'application/json'
  481. * )); // $result = 'application/json'
  482. *
  483. * $result = $header->preferred_accept(array(
  484. * 'text/rtf',
  485. * 'application/xml'
  486. * ), TRUE); // $result = FALSE (none matched explicitly)
  487. *
  488. *
  489. * @param array $types the content types to examine
  490. * @param boolean $explicit only allow explicit references, no wildcards
  491. * @return string name of the preferred content type
  492. * @since 3.2.0
  493. */
  494. public function preferred_accept(array $types, $explicit = FALSE)
  495. {
  496. $preferred = FALSE;
  497. $ceiling = 0;
  498. foreach ($types as $type)
  499. {
  500. $quality = $this->accepts_at_quality($type, $explicit);
  501. if ($quality > $ceiling)
  502. {
  503. $preferred = $type;
  504. $ceiling = $quality;
  505. }
  506. }
  507. return $preferred;
  508. }
  509. /**
  510. * Returns the quality of the supplied `$charset` argument. This method
  511. * will automatically parse the `Accept-Charset` header if present and
  512. * return the associated resolved quality value.
  513. *
  514. * // Accept-Charset: utf-8, utf-16; q=.8, iso-8859-1; q=.5
  515. * $quality = $header->accepts_charset_at_quality('utf-8');
  516. * // $quality = (float) 1
  517. *
  518. * @param string $charset charset to examine
  519. * @return float the quality of the charset
  520. * @since 3.2.0
  521. */
  522. public function accepts_charset_at_quality($charset)
  523. {
  524. if ($this->_accept_charset === NULL)
  525. {
  526. if ($this->offsetExists('Accept-Charset'))
  527. {
  528. $charset_header = strtolower($this->offsetGet('Accept-Charset'));
  529. $this->_accept_charset = HTTP_Header::parse_charset_header($charset_header);
  530. }
  531. else
  532. {
  533. $this->_accept_charset = HTTP_Header::parse_charset_header(NULL);
  534. }
  535. }
  536. $charset = strtolower($charset);
  537. if (isset($this->_accept_charset[$charset]))
  538. {
  539. return $this->_accept_charset[$charset];
  540. }
  541. elseif (isset($this->_accept_charset['*']))
  542. {
  543. return $this->_accept_charset['*'];
  544. }
  545. elseif ($charset === 'iso-8859-1')
  546. {
  547. return (float) 1;
  548. }
  549. return (float) 0;
  550. }
  551. /**
  552. * Returns the preferred charset from the supplied array `$charsets` based
  553. * on the `Accept-Charset` header directive.
  554. *
  555. * // Accept-Charset: utf-8, utf-16; q=.8, iso-8859-1; q=.5
  556. * $charset = $header->preferred_charset(array(
  557. * 'utf-10', 'ascii', 'utf-16', 'utf-8'
  558. * )); // $charset = 'utf-8'
  559. *
  560. * @param array $charsets charsets to test
  561. * @return mixed preferred charset or `FALSE`
  562. * @since 3.2.0
  563. */
  564. public function preferred_charset(array $charsets)
  565. {
  566. $preferred = FALSE;
  567. $ceiling = 0;
  568. foreach ($charsets as $charset)
  569. {
  570. $quality = $this->accepts_charset_at_quality($charset);
  571. if ($quality > $ceiling)
  572. {
  573. $preferred = $charset;
  574. $ceiling = $quality;
  575. }
  576. }
  577. return $preferred;
  578. }
  579. /**
  580. * Returns the quality of the `$encoding` type passed to it. Encoding
  581. * is usually compression such as `gzip`, but could be some other
  582. * message encoding algorithm. This method allows explicit checks to be
  583. * done ignoring wildcards.
  584. *
  585. * // Accept-Encoding: compress, gzip, *; q=.5
  586. * $encoding = $header->accepts_encoding_at_quality('gzip');
  587. * // $encoding = (float) 1.0s
  588. *
  589. * @param string $encoding encoding type to interrogate
  590. * @param boolean $explicit explicit check, ignoring wildcards and `identity`
  591. * @return float
  592. * @since 3.2.0
  593. */
  594. public function accepts_encoding_at_quality($encoding, $explicit = FALSE)
  595. {
  596. if ($this->_accept_encoding === NULL)
  597. {
  598. if ($this->offsetExists('Accept-Encoding'))
  599. {
  600. $encoding_header = $this->offsetGet('Accept-Encoding');
  601. }
  602. else
  603. {
  604. $encoding_header = NULL;
  605. }
  606. $this->_accept_encoding = HTTP_Header::parse_encoding_header($encoding_header);
  607. }
  608. // Normalize the encoding
  609. $encoding = strtolower($encoding);
  610. if (isset($this->_accept_encoding[$encoding]))
  611. {
  612. return $this->_accept_encoding[$encoding];
  613. }
  614. if ($explicit === FALSE)
  615. {
  616. if (isset($this->_accept_encoding['*']))
  617. {
  618. return $this->_accept_encoding['*'];
  619. }
  620. elseif ($encoding === 'identity')
  621. {
  622. return (float) HTTP_Header::DEFAULT_QUALITY;
  623. }
  624. }
  625. return (float) 0;
  626. }
  627. /**
  628. * Returns the preferred message encoding type based on quality, and can
  629. * optionally ignore wildcard references. If two or more encodings have the
  630. * same quality, the first listed in `$encodings` will be returned.
  631. *
  632. * // Accept-Encoding: compress, gzip, *; q.5
  633. * $encoding = $header->preferred_encoding(array(
  634. * 'gzip', 'bzip', 'blowfish'
  635. * ));
  636. * // $encoding = 'gzip';
  637. *
  638. * @param array $encodings encodings to test against
  639. * @param boolean $explicit explicit check, if `TRUE` wildcards are excluded
  640. * @return mixed
  641. * @since 3.2.0
  642. */
  643. public function preferred_encoding(array $encodings, $explicit = FALSE)
  644. {
  645. $ceiling = 0;
  646. $preferred = FALSE;
  647. foreach ($encodings as $encoding)
  648. {
  649. $quality = $this->accepts_encoding_at_quality($encoding, $explicit);
  650. if ($quality > $ceiling)
  651. {
  652. $ceiling = $quality;
  653. $preferred = $encoding;
  654. }
  655. }
  656. return $preferred;
  657. }
  658. /**
  659. * Returns the quality of `$language` supplied, optionally ignoring
  660. * wildcards if `$explicit` is set to a non-`FALSE` value. If the quality
  661. * is not found, `0.0` is returned.
  662. *
  663. * // Accept-Language: en-us, en-gb; q=.7, en; q=.5
  664. * $lang = $header->accepts_language_at_quality('en-gb');
  665. * // $lang = (float) 0.7
  666. *
  667. * $lang2 = $header->accepts_language_at_quality('en-au');
  668. * // $lang2 = (float) 0.5
  669. *
  670. * $lang3 = $header->accepts_language_at_quality('en-au', TRUE);
  671. * // $lang3 = (float) 0.0
  672. *
  673. * @param string $language language to interrogate
  674. * @param boolean $explicit explicit interrogation, `TRUE` ignores wildcards
  675. * @return float
  676. * @since 3.2.0
  677. */
  678. public function accepts_language_at_quality($language, $explicit = FALSE)
  679. {
  680. if ($this->_accept_language === NULL)
  681. {
  682. if ($this->offsetExists('Accept-Language'))
  683. {
  684. $language_header = strtolower($this->offsetGet('Accept-Language'));
  685. }
  686. else
  687. {
  688. $language_header = NULL;
  689. }
  690. $this->_accept_language = HTTP_Header::parse_language_header($language_header);
  691. }
  692. // Normalize the language
  693. $language_parts = explode('-', strtolower($language), 2);
  694. if (isset($this->_accept_language[$language_parts[0]]))
  695. {
  696. if (isset($language_parts[1]))
  697. {
  698. if (isset($this->_accept_language[$language_parts[0]][$language_parts[1]]))
  699. {
  700. return $this->_accept_language[$language_parts[0]][$language_parts[1]];
  701. }
  702. elseif ($explicit === FALSE AND isset($this->_accept_language[$language_parts[0]]['*']))
  703. {
  704. return $this->_accept_language[$language_parts[0]]['*'];
  705. }
  706. }
  707. elseif (isset($this->_accept_language[$language_parts[0]]['*']))
  708. {
  709. return $this->_accept_language[$language_parts[0]]['*'];
  710. }
  711. }
  712. if ($explicit === FALSE AND isset($this->_accept_language['*']))
  713. {
  714. return $this->_accept_language['*'];
  715. }
  716. return (float) 0;
  717. }
  718. /**
  719. * Returns the preferred language from the supplied array `$languages` based
  720. * on the `Accept-Language` header directive.
  721. *
  722. * // Accept-Language: en-us, en-gb; q=.7, en; q=.5
  723. * $lang = $header->preferred_language(array(
  724. * 'en-gb', 'en-au', 'fr', 'es'
  725. * )); // $lang = 'en-gb'
  726. *
  727. * @param array $languages
  728. * @param boolean $explicit
  729. * @return mixed
  730. * @since 3.2.0
  731. */
  732. public function preferred_language(array $languages, $explicit = FALSE)
  733. {
  734. $ceiling = 0;
  735. $preferred = FALSE;
  736. foreach ($languages as $language)
  737. {
  738. $quality = $this->accepts_language_at_quality($language, $explicit);
  739. if ($quality > $ceiling)
  740. {
  741. $ceiling = $quality;
  742. $preferred = $language;
  743. }
  744. }
  745. return $preferred;
  746. }
  747. /**
  748. * Sends headers to the php processor, or supplied `$callback` argument.
  749. * This method formats the headers correctly for output, re-instating their
  750. * capitalization for transmission.
  751. *
  752. * [!!] if you supply a custom header handler via `$callback`, it is
  753. * recommended that `$response` is returned
  754. *
  755. * @param HTTP_Response $response header to send
  756. * @param boolean $replace replace existing value
  757. * @param callback $callback optional callback to replace PHP header function
  758. * @return mixed
  759. * @since 3.2.0
  760. */
  761. public function send_headers(HTTP_Response $response = NULL, $replace = FALSE, $callback = NULL)
  762. {
  763. if ($response === NULL)
  764. {
  765. // Default to the initial request message
  766. $response = Request::initial()->response();
  767. }
  768. $protocol = $response->protocol();
  769. $status = $response->status();
  770. // Create the response header
  771. $processed_headers = array($protocol.' '.$status.' '.Response::$messages[$status]);
  772. // Get the headers array
  773. $headers = $response->headers()->getArrayCopy();
  774. foreach ($headers as $header => $value)
  775. {
  776. if (is_array($value))
  777. {
  778. $value = implode(', ', $value);
  779. }
  780. $processed_headers[] = Text::ucfirst($header).': '.$value;
  781. }
  782. if ( ! isset($headers['content-type']))
  783. {
  784. $processed_headers[] = 'Content-Type: '.Kohana::$content_type.'; charset='.Kohana::$charset;
  785. }
  786. if (Kohana::$expose AND ! isset($headers['x-powered-by']))
  787. {
  788. $processed_headers[] = 'X-Powered-By: '.Kohana::version();
  789. }
  790. // Get the cookies and apply
  791. if ($cookies = $response->cookie())
  792. {
  793. $processed_headers['Set-Cookie'] = $cookies;
  794. }
  795. if (is_callable($callback))
  796. {
  797. // Use the callback method to set header
  798. return call_user_func($callback, $response, $processed_headers, $replace);
  799. }
  800. else
  801. {
  802. $this->_send_headers_to_php($processed_headers, $replace);
  803. return $response;
  804. }
  805. }
  806. /**
  807. * Sends the supplied headers to the PHP output buffer. If cookies
  808. * are included in the message they will be handled appropriately.
  809. *
  810. * @param array $headers headers to send to php
  811. * @param boolean $replace replace existing headers
  812. * @return self
  813. * @since 3.2.0
  814. */
  815. protected function _send_headers_to_php(array $headers, $replace)
  816. {
  817. // If the headers have been sent, get out
  818. if (headers_sent())
  819. return $this;
  820. foreach ($headers as $key => $line)
  821. {
  822. if ($key == 'Set-Cookie' AND is_array($line))
  823. {
  824. // Send cookies
  825. foreach ($line as $name => $value)
  826. {
  827. Cookie::set($name, $value['value'], $value['expiration']);
  828. }
  829. continue;
  830. }
  831. header($line, $replace);
  832. }
  833. return $this;
  834. }
  835. } // End Kohana_HTTP_Header