Source of file AbstractAccept.php
Size: 14,354 Bytes - Last Modified: 2014-03-12T23:21:18+01:00
/home/theseer/Downloads/ZendFramework-2.3.0/library/Zend/Http/Header/AbstractAccept.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464 | <?php /** * Zend Framework (http://framework.zend.com/) * * @link http://github.com/zendframework/zf2 for the canonical source repository * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com) * @license http://framework.zend.com/license/new-bsd New BSD License */ namespace Zend\Http\Header; use stdClass; /** * Abstract Accept Header * * Naming conventions: * * Accept: audio/mp3; q=0.2; version=0.5, audio/basic+mp3 * |------------------------------------------------------| header line * |------| field name * |-----------------------------------------------| field value * |-------------------------------| field value part * |------| type * |--| subtype * |--| format * |----| subtype * |---| format * |-------------------| parameter set * |-----------| parameter * |-----| parameter key * |--| parameter value * |---| priority * * * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 * @author Dolf Schimmel - Freeaqingme */ abstract class AbstractAccept implements HeaderInterface { /** * * @var stdClass[] */ protected $fieldValueParts = array(); protected $regexAddType; /** * Determines if since last mutation the stack was sorted * * @var bool */ protected $sorted = false; /** * Parse a full header line or just the field value part. * * @param string $headerLine */ public function parseHeaderLine($headerLine) { if (strpos($headerLine, ':') !== false) { list($name, $value) = GenericHeader::splitHeaderLine($headerLine); if (strtolower($name) !== strtolower($this->getFieldName())) { $value = $headerLine; // This is just for preserve the BC. } } else { $value = $headerLine; } foreach ($this->getFieldValuePartsFromHeaderLine($value) as $value) { $this->addFieldValuePartToQueue($value); } } /** * Factory method: parse Accept header string * * @param string $headerLine * @return Accept */ public static function fromString($headerLine) { $obj = new static(); $obj->parseHeaderLine($headerLine); return $obj; } /** * Parse the Field Value Parts represented by a header line * * @param string $headerLine * @throws Exception\InvalidArgumentException If header is invalid * @return array */ public function getFieldValuePartsFromHeaderLine($headerLine) { // process multiple accept values, they may be between quotes if (!preg_match_all('/(?:[^,"]|"(?:[^\\\"]|\\\.)*")+/', $headerLine, $values) || !isset($values[0]) ) { throw new Exception\InvalidArgumentException( 'Invalid header line for ' . $this->getFieldName() . ' header string' ); } $out = array(); foreach ($values[0] as $value) { $value = trim($value); $out[] = $this->parseFieldValuePart($value); } return $out; } /** * Parse the accept params belonging to a media range * * @param string $fieldValuePart * @return stdClass */ protected function parseFieldValuePart($fieldValuePart) { $raw = $subtypeWhole = $type = $fieldValuePart; if ($pos = strpos($fieldValuePart, ';')) { $type = substr($fieldValuePart, 0, $pos); } $params = $this->getParametersFromFieldValuePart($fieldValuePart); if ($pos = strpos($fieldValuePart, ';')) { $fieldValuePart = trim(substr($fieldValuePart, 0, $pos)); } $format = '*'; $subtype = '*'; return (object) array( 'typeString' => trim($fieldValuePart), 'type' => $type, 'subtype' => $subtype, 'subtypeRaw' => $subtypeWhole, 'format' => $format, 'priority' => isset($params['q']) ? $params['q'] : 1, 'params' => $params, 'raw' => trim($raw) ); } /** * Parse the keys contained in the header line * * @param string $fieldValuePart * @return array */ protected function getParametersFromFieldValuePart($fieldValuePart) { $params = array(); if ((($pos = strpos($fieldValuePart, ';')) !== false)) { preg_match_all('/(?:[^;"]|"(?:[^\\\"]|\\\.)*")+/', $fieldValuePart, $paramsStrings); if (isset($paramsStrings[0])) { array_shift($paramsStrings[0]); $paramsStrings = $paramsStrings[0]; } foreach ($paramsStrings as $param) { $explode = explode('=', $param, 2); $value = trim($explode[1]); if (isset($value[0]) && $value[0] == '"' && substr($value, -1) == '"') { $value = substr(substr($value, 1), 0, -1); } $params[trim($explode[0])] = stripslashes($value); } } return $params; } /** * Get field value * * @param array|null $values * @return string */ public function getFieldValue($values = null) { if (!$values) { return $this->getFieldValue($this->fieldValueParts); } $strings = array(); foreach ($values as $value) { $params = $value->params; array_walk($params, array($this, 'assembleAcceptParam')); $strings[] = implode(';', array($value->typeString) + $params); } return implode(', ', $strings); } /** * Assemble and escape the field value parameters based on RFC 2616 section 2.1 * * @todo someone should review this thoroughly * @param string $value * @param string $key * @return string */ protected function assembleAcceptParam(&$value, $key) { $separators = array('(', ')', '<', '>', '@', ',', ';', ':', '/', '[', ']', '?', '=', '{', '}', ' ', "\t"); $escaped = preg_replace_callback('/[[:cntrl:]"\\\\]/', // escape cntrl, ", \ function ($v) { return '\\' . $v[0]; }, $value ); if ($escaped == $value && !array_intersect(str_split($value), $separators)) { $value = $key . '=' . $value; } else { $value = $key . '="' . $escaped . '"'; } return $value; } /** * Add a type, with the given priority * * @param string $type * @param int|float $priority * @param array (optional) $params * @throws Exception\InvalidArgumentException * @return Accept */ protected function addType($type, $priority = 1, array $params = array()) { if (!preg_match($this->regexAddType, $type)) { throw new Exception\InvalidArgumentException(sprintf( '%s expects a valid type; received "%s"', __METHOD__, (string) $type )); } if (!is_int($priority) && !is_float($priority) && !is_numeric($priority) || $priority > 1 || $priority < 0 ) { throw new Exception\InvalidArgumentException(sprintf( '%s expects a numeric priority; received %s', __METHOD__, (string) $priority )); } if ($priority != 1) { $params = array('q' => sprintf('%01.1f', $priority)) + $params; } $assembledString = $this->getFieldValue( array((object) array('typeString' => $type, 'params' => $params)) ); $value = $this->parseFieldValuePart($assembledString); $this->addFieldValuePartToQueue($value); return $this; } /** * Does the header have the requested type? * * @param array|string $matchAgainst * @return bool */ protected function hasType($matchAgainst) { return (bool) $this->match($matchAgainst); } /** * Match a media string against this header * * @param array|string $matchAgainst * @return Accept\FieldValuePArt\AcceptFieldValuePart|bool The matched value or false */ public function match($matchAgainst) { if (is_string($matchAgainst)) { $matchAgainst = $this->getFieldValuePartsFromHeaderLine($matchAgainst); } foreach ($this->getPrioritized() as $left) { foreach ($matchAgainst as $right) { if ($right->type == '*' || $left->type == '*') { if ($this->matchAcceptParams($left, $right)) { $left->setMatchedAgainst($right); return $left; } } if ($left->type == $right->type) { if ((($left->subtype == $right->subtype || ($right->subtype == '*' || $left->subtype == '*')) && ($left->format == $right->format || $right->format == '*' || $left->format == '*'))) { if ($this->matchAcceptParams($left, $right)) { $left->setMatchedAgainst($right); return $left; } } } } } return false; } /** * Return a match where all parameters in argument #1 match those in argument #2 * * @param array $match1 * @param array $match2 * @return bool|array */ protected function matchAcceptParams($match1, $match2) { foreach ($match2->params as $key => $value) { if (isset($match1->params[$key])) { if (strpos($value, '-')) { preg_match( '/^(?|([^"-]*)|"([^"]*)")-(?|([^"-]*)|"([^"]*)")\z/', $value, $pieces ); if (count($pieces) == 3 && (version_compare($pieces[1], $match1->params[$key], '<=') xor version_compare($pieces[2], $match1->params[$key], '>=') ) ) { return false; } } elseif (strpos($value, '|')) { $options = explode('|', $value); $good = false; foreach ($options as $option) { if ($option == $match1->params[$key]) { $good = true; break; } } if (!$good) { return false; } } elseif ($match1->params[$key] != $value) { return false; } } } return $match1; } /** * Add a key/value combination to the internal queue * * @param stdClass $value * @return number */ protected function addFieldValuePartToQueue($value) { $this->fieldValueParts[] = $value; $this->sorted = false; } /** * Sort the internal Field Value Parts * * @See rfc2616 sect 14.1 * Media ranges can be overridden by more specific media ranges or * specific media types. If more than one media range applies to a given * type, the most specific reference has precedence. For example, * * Accept: text/*, text/html, text/html;level=1, * /* * * have the following precedence: * * 1) text/html;level=1 * 2) text/html * 3) text/* * 4) * /* * * @return number */ protected function sortFieldValueParts() { $sort = function ($a, $b) { // If A has higher precedence than B, return -1. if ($a->priority > $b->priority) { return -1; } elseif ($a->priority < $b->priority) { return 1; } // Asterisks $values = array('type', 'subtype', 'format'); foreach ($values as $value) { if ($a->$value == '*' && $b->$value != '*') { return 1; } elseif ($b->$value == '*' && $a->$value != '*') { return -1; } } if ($a->type == 'application' && $b->type != 'application') { return -1; } elseif ($b->type == 'application' && $a->type != 'application') { return 1; } //@todo count number of dots in case of type==application in subtype // So far they're still the same. Longest string length may be more specific if (strlen($a->raw) == strlen($b->raw)) return 0; return (strlen($a->raw) > strlen($b->raw)) ? -1 : 1; }; usort($this->fieldValueParts, $sort); $this->sorted = true; } /** * @return array with all the keys, values and parameters this header represents: */ public function getPrioritized() { if (!$this->sorted) { $this->sortFieldValueParts(); } return $this->fieldValueParts; } } |