|
| 1 | +<?php |
| 2 | + |
| 3 | +/* |
| 4 | + * This file is part of the FOSHttpCache package. |
| 5 | + * |
| 6 | + * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> |
| 7 | + * |
| 8 | + * For the full copyright and license information, please view the LICENSE |
| 9 | + * file that was distributed with this source code. |
| 10 | + */ |
| 11 | + |
| 12 | +namespace FOS\HttpCache\TagHeaderFormatter; |
| 13 | + |
| 14 | +use FOS\HttpCache\Exception\InvalidTagException; |
| 15 | + |
| 16 | +/** |
| 17 | + * A header formatter that splits the value(s) from a given |
| 18 | + * other header formatter (e.g. the CommaSeparatedTagHeaderFormatter) |
| 19 | + * into multiple headers making sure none of the header values |
| 20 | + * exceeds the configured limit. |
| 21 | + * |
| 22 | + * @author Yanick Witschi <[email protected]> |
| 23 | + */ |
| 24 | +class MaxHeaderValueLengthFormatter implements TagHeaderFormatter |
| 25 | +{ |
| 26 | + /** |
| 27 | + * @var TagHeaderFormatter |
| 28 | + */ |
| 29 | + private $inner; |
| 30 | + |
| 31 | + /** |
| 32 | + * @var int |
| 33 | + */ |
| 34 | + private $maxHeaderValueLength; |
| 35 | + |
| 36 | + /** |
| 37 | + * The default value of the maximum header length is 4096 because most |
| 38 | + * servers limit header values to 4kb. |
| 39 | + * HTTP messages cannot carry characters outside the ISO-8859-1 standard so they all |
| 40 | + * use up just one byte. |
| 41 | + * |
| 42 | + * @param TagHeaderFormatter $inner |
| 43 | + * @param int $maxHeaderValueLength |
| 44 | + */ |
| 45 | + public function __construct(TagHeaderFormatter $inner, $maxHeaderValueLength = 4096) |
| 46 | + { |
| 47 | + $this->inner = $inner; |
| 48 | + $this->maxHeaderValueLength = $maxHeaderValueLength; |
| 49 | + } |
| 50 | + |
| 51 | + /** |
| 52 | + * {@inheritdoc} |
| 53 | + */ |
| 54 | + public function getTagsHeaderName() |
| 55 | + { |
| 56 | + $this->inner->getTagsHeaderName(); |
| 57 | + } |
| 58 | + |
| 59 | + /** |
| 60 | + * {@inheritdoc} |
| 61 | + */ |
| 62 | + public function getTagsHeaderValue(array $tags) |
| 63 | + { |
| 64 | + $values = (array) $this->inner->getTagsHeaderValue($tags); |
| 65 | + $newValues = [[]]; |
| 66 | + |
| 67 | + foreach ($values as $value) { |
| 68 | + if ($this->isHeaderTooLong($value)) { |
| 69 | + list($firstTags, $secondTags) = $this->splitTagsInHalves($tags); |
| 70 | + |
| 71 | + $newValues[] = (array) $this->getTagsHeaderValue($firstTags); |
| 72 | + $newValues[] = (array) $this->getTagsHeaderValue($secondTags); |
| 73 | + } else { |
| 74 | + $newValues[] = [$value]; |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + $newValues = array_merge(...$newValues); |
| 79 | + |
| 80 | + if (1 === count($newValues)) { |
| 81 | + return $newValues[0]; |
| 82 | + } |
| 83 | + |
| 84 | + return $newValues; |
| 85 | + } |
| 86 | + |
| 87 | + /** |
| 88 | + * @param string $value |
| 89 | + * |
| 90 | + * @return bool |
| 91 | + */ |
| 92 | + private function isHeaderTooLong($value) |
| 93 | + { |
| 94 | + return mb_strlen($value) > $this->maxHeaderValueLength; |
| 95 | + } |
| 96 | + |
| 97 | + /** |
| 98 | + * Split an array of tags in two more or less equal sized arrays. |
| 99 | + * |
| 100 | + * @param array $tags |
| 101 | + * |
| 102 | + * @return array |
| 103 | + * |
| 104 | + * @throws InvalidTagException |
| 105 | + */ |
| 106 | + private function splitTagsInHalves(array $tags) |
| 107 | + { |
| 108 | + if (1 === count($tags)) { |
| 109 | + throw new InvalidTagException(sprintf( |
| 110 | + 'You configured a maximum header length of %d but the tag "%s" is too long.', |
| 111 | + $this->maxHeaderValueLength, |
| 112 | + $tags[0] |
| 113 | + )); |
| 114 | + } |
| 115 | + |
| 116 | + $size = ceil(count($tags) / 2); |
| 117 | + |
| 118 | + return array_chunk($tags, $size); |
| 119 | + } |
| 120 | +} |
0 commit comments