annotate vendor/symfony/http-foundation/ResponseHeaderBag.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 /*
Chris@0 4 * This file is part of the Symfony package.
Chris@0 5 *
Chris@0 6 * (c) Fabien Potencier <fabien@symfony.com>
Chris@0 7 *
Chris@0 8 * For the full copyright and license information, please view the LICENSE
Chris@0 9 * file that was distributed with this source code.
Chris@0 10 */
Chris@0 11
Chris@0 12 namespace Symfony\Component\HttpFoundation;
Chris@0 13
Chris@0 14 /**
Chris@0 15 * ResponseHeaderBag is a container for Response HTTP headers.
Chris@0 16 *
Chris@0 17 * @author Fabien Potencier <fabien@symfony.com>
Chris@0 18 */
Chris@0 19 class ResponseHeaderBag extends HeaderBag
Chris@0 20 {
Chris@0 21 const COOKIES_FLAT = 'flat';
Chris@0 22 const COOKIES_ARRAY = 'array';
Chris@0 23
Chris@0 24 const DISPOSITION_ATTACHMENT = 'attachment';
Chris@0 25 const DISPOSITION_INLINE = 'inline';
Chris@0 26
Chris@17 27 protected $computedCacheControl = [];
Chris@17 28 protected $cookies = [];
Chris@17 29 protected $headerNames = [];
Chris@0 30
Chris@17 31 public function __construct(array $headers = [])
Chris@0 32 {
Chris@0 33 parent::__construct($headers);
Chris@0 34
Chris@0 35 if (!isset($this->headers['cache-control'])) {
Chris@0 36 $this->set('Cache-Control', '');
Chris@0 37 }
Chris@0 38
Chris@14 39 /* RFC2616 - 14.18 says all Responses need to have a Date */
Chris@14 40 if (!isset($this->headers['date'])) {
Chris@14 41 $this->initDate();
Chris@0 42 }
Chris@0 43 }
Chris@0 44
Chris@0 45 /**
Chris@0 46 * Returns the headers, with original capitalizations.
Chris@0 47 *
Chris@0 48 * @return array An array of headers
Chris@0 49 */
Chris@0 50 public function allPreserveCase()
Chris@0 51 {
Chris@17 52 $headers = [];
Chris@14 53 foreach ($this->all() as $name => $value) {
Chris@14 54 $headers[isset($this->headerNames[$name]) ? $this->headerNames[$name] : $name] = $value;
Chris@14 55 }
Chris@14 56
Chris@14 57 return $headers;
Chris@14 58 }
Chris@14 59
Chris@14 60 public function allPreserveCaseWithoutCookies()
Chris@14 61 {
Chris@14 62 $headers = $this->allPreserveCase();
Chris@14 63 if (isset($this->headerNames['set-cookie'])) {
Chris@14 64 unset($headers[$this->headerNames['set-cookie']]);
Chris@14 65 }
Chris@14 66
Chris@14 67 return $headers;
Chris@0 68 }
Chris@0 69
Chris@0 70 /**
Chris@0 71 * {@inheritdoc}
Chris@0 72 */
Chris@17 73 public function replace(array $headers = [])
Chris@0 74 {
Chris@17 75 $this->headerNames = [];
Chris@0 76
Chris@0 77 parent::replace($headers);
Chris@0 78
Chris@0 79 if (!isset($this->headers['cache-control'])) {
Chris@0 80 $this->set('Cache-Control', '');
Chris@0 81 }
Chris@14 82
Chris@14 83 if (!isset($this->headers['date'])) {
Chris@14 84 $this->initDate();
Chris@14 85 }
Chris@14 86 }
Chris@14 87
Chris@14 88 /**
Chris@14 89 * {@inheritdoc}
Chris@14 90 */
Chris@14 91 public function all()
Chris@14 92 {
Chris@14 93 $headers = parent::all();
Chris@14 94 foreach ($this->getCookies() as $cookie) {
Chris@14 95 $headers['set-cookie'][] = (string) $cookie;
Chris@14 96 }
Chris@14 97
Chris@14 98 return $headers;
Chris@0 99 }
Chris@0 100
Chris@0 101 /**
Chris@0 102 * {@inheritdoc}
Chris@0 103 */
Chris@0 104 public function set($key, $values, $replace = true)
Chris@0 105 {
Chris@14 106 $uniqueKey = str_replace('_', '-', strtolower($key));
Chris@14 107
Chris@14 108 if ('set-cookie' === $uniqueKey) {
Chris@14 109 if ($replace) {
Chris@17 110 $this->cookies = [];
Chris@14 111 }
Chris@14 112 foreach ((array) $values as $cookie) {
Chris@14 113 $this->setCookie(Cookie::fromString($cookie));
Chris@14 114 }
Chris@14 115 $this->headerNames[$uniqueKey] = $key;
Chris@14 116
Chris@14 117 return;
Chris@14 118 }
Chris@14 119
Chris@14 120 $this->headerNames[$uniqueKey] = $key;
Chris@14 121
Chris@0 122 parent::set($key, $values, $replace);
Chris@0 123
Chris@0 124 // ensure the cache-control header has sensible defaults
Chris@17 125 if (\in_array($uniqueKey, ['cache-control', 'etag', 'last-modified', 'expires'], true)) {
Chris@0 126 $computed = $this->computeCacheControlValue();
Chris@17 127 $this->headers['cache-control'] = [$computed];
Chris@0 128 $this->headerNames['cache-control'] = 'Cache-Control';
Chris@0 129 $this->computedCacheControl = $this->parseCacheControl($computed);
Chris@0 130 }
Chris@0 131 }
Chris@0 132
Chris@0 133 /**
Chris@0 134 * {@inheritdoc}
Chris@0 135 */
Chris@0 136 public function remove($key)
Chris@0 137 {
Chris@0 138 $uniqueKey = str_replace('_', '-', strtolower($key));
Chris@0 139 unset($this->headerNames[$uniqueKey]);
Chris@0 140
Chris@14 141 if ('set-cookie' === $uniqueKey) {
Chris@17 142 $this->cookies = [];
Chris@14 143
Chris@14 144 return;
Chris@14 145 }
Chris@14 146
Chris@14 147 parent::remove($key);
Chris@14 148
Chris@0 149 if ('cache-control' === $uniqueKey) {
Chris@17 150 $this->computedCacheControl = [];
Chris@0 151 }
Chris@14 152
Chris@14 153 if ('date' === $uniqueKey) {
Chris@14 154 $this->initDate();
Chris@14 155 }
Chris@0 156 }
Chris@0 157
Chris@0 158 /**
Chris@0 159 * {@inheritdoc}
Chris@0 160 */
Chris@0 161 public function hasCacheControlDirective($key)
Chris@0 162 {
Chris@18 163 return \array_key_exists($key, $this->computedCacheControl);
Chris@0 164 }
Chris@0 165
Chris@0 166 /**
Chris@0 167 * {@inheritdoc}
Chris@0 168 */
Chris@0 169 public function getCacheControlDirective($key)
Chris@0 170 {
Chris@18 171 return \array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null;
Chris@0 172 }
Chris@0 173
Chris@0 174 public function setCookie(Cookie $cookie)
Chris@0 175 {
Chris@0 176 $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie;
Chris@14 177 $this->headerNames['set-cookie'] = 'Set-Cookie';
Chris@0 178 }
Chris@0 179
Chris@0 180 /**
Chris@0 181 * Removes a cookie from the array, but does not unset it in the browser.
Chris@0 182 *
Chris@0 183 * @param string $name
Chris@0 184 * @param string $path
Chris@0 185 * @param string $domain
Chris@0 186 */
Chris@0 187 public function removeCookie($name, $path = '/', $domain = null)
Chris@0 188 {
Chris@0 189 if (null === $path) {
Chris@0 190 $path = '/';
Chris@0 191 }
Chris@0 192
Chris@0 193 unset($this->cookies[$domain][$path][$name]);
Chris@0 194
Chris@0 195 if (empty($this->cookies[$domain][$path])) {
Chris@0 196 unset($this->cookies[$domain][$path]);
Chris@0 197
Chris@0 198 if (empty($this->cookies[$domain])) {
Chris@0 199 unset($this->cookies[$domain]);
Chris@0 200 }
Chris@0 201 }
Chris@14 202
Chris@14 203 if (empty($this->cookies)) {
Chris@14 204 unset($this->headerNames['set-cookie']);
Chris@14 205 }
Chris@0 206 }
Chris@0 207
Chris@0 208 /**
Chris@0 209 * Returns an array with all cookies.
Chris@0 210 *
Chris@0 211 * @param string $format
Chris@0 212 *
Chris@16 213 * @return Cookie[]
Chris@0 214 *
Chris@0 215 * @throws \InvalidArgumentException When the $format is invalid
Chris@0 216 */
Chris@0 217 public function getCookies($format = self::COOKIES_FLAT)
Chris@0 218 {
Chris@17 219 if (!\in_array($format, [self::COOKIES_FLAT, self::COOKIES_ARRAY])) {
Chris@17 220 throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', [self::COOKIES_FLAT, self::COOKIES_ARRAY])));
Chris@0 221 }
Chris@0 222
Chris@0 223 if (self::COOKIES_ARRAY === $format) {
Chris@0 224 return $this->cookies;
Chris@0 225 }
Chris@0 226
Chris@17 227 $flattenedCookies = [];
Chris@0 228 foreach ($this->cookies as $path) {
Chris@0 229 foreach ($path as $cookies) {
Chris@0 230 foreach ($cookies as $cookie) {
Chris@0 231 $flattenedCookies[] = $cookie;
Chris@0 232 }
Chris@0 233 }
Chris@0 234 }
Chris@0 235
Chris@0 236 return $flattenedCookies;
Chris@0 237 }
Chris@0 238
Chris@0 239 /**
Chris@0 240 * Clears a cookie in the browser.
Chris@0 241 *
Chris@0 242 * @param string $name
Chris@0 243 * @param string $path
Chris@0 244 * @param string $domain
Chris@0 245 * @param bool $secure
Chris@0 246 * @param bool $httpOnly
Chris@0 247 */
Chris@0 248 public function clearCookie($name, $path = '/', $domain = null, $secure = false, $httpOnly = true)
Chris@0 249 {
Chris@0 250 $this->setCookie(new Cookie($name, null, 1, $path, $domain, $secure, $httpOnly));
Chris@0 251 }
Chris@0 252
Chris@0 253 /**
Chris@0 254 * Generates a HTTP Content-Disposition field-value.
Chris@0 255 *
Chris@0 256 * @param string $disposition One of "inline" or "attachment"
Chris@0 257 * @param string $filename A unicode string
Chris@0 258 * @param string $filenameFallback A string containing only ASCII characters that
Chris@0 259 * is semantically equivalent to $filename. If the filename is already ASCII,
Chris@0 260 * it can be omitted, or just copied from $filename
Chris@0 261 *
Chris@0 262 * @return string A string suitable for use as a Content-Disposition field-value
Chris@0 263 *
Chris@0 264 * @throws \InvalidArgumentException
Chris@0 265 *
Chris@0 266 * @see RFC 6266
Chris@0 267 */
Chris@0 268 public function makeDisposition($disposition, $filename, $filenameFallback = '')
Chris@0 269 {
Chris@17 270 if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) {
Chris@0 271 throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));
Chris@0 272 }
Chris@0 273
Chris@0 274 if ('' == $filenameFallback) {
Chris@0 275 $filenameFallback = $filename;
Chris@0 276 }
Chris@0 277
Chris@0 278 // filenameFallback is not ASCII.
Chris@0 279 if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) {
Chris@0 280 throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.');
Chris@0 281 }
Chris@0 282
Chris@0 283 // percent characters aren't safe in fallback.
Chris@0 284 if (false !== strpos($filenameFallback, '%')) {
Chris@0 285 throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.');
Chris@0 286 }
Chris@0 287
Chris@0 288 // path separators aren't allowed in either.
Chris@0 289 if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($filenameFallback, '\\')) {
Chris@0 290 throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.');
Chris@0 291 }
Chris@0 292
Chris@0 293 $output = sprintf('%s; filename="%s"', $disposition, str_replace('"', '\\"', $filenameFallback));
Chris@0 294
Chris@0 295 if ($filename !== $filenameFallback) {
Chris@0 296 $output .= sprintf("; filename*=utf-8''%s", rawurlencode($filename));
Chris@0 297 }
Chris@0 298
Chris@0 299 return $output;
Chris@0 300 }
Chris@0 301
Chris@0 302 /**
Chris@0 303 * Returns the calculated value of the cache-control header.
Chris@0 304 *
Chris@0 305 * This considers several other headers and calculates or modifies the
Chris@0 306 * cache-control header to a sensible, conservative value.
Chris@0 307 *
Chris@0 308 * @return string
Chris@0 309 */
Chris@0 310 protected function computeCacheControlValue()
Chris@0 311 {
Chris@0 312 if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) {
Chris@0 313 return 'no-cache, private';
Chris@0 314 }
Chris@0 315
Chris@0 316 if (!$this->cacheControl) {
Chris@0 317 // conservative by default
Chris@0 318 return 'private, must-revalidate';
Chris@0 319 }
Chris@0 320
Chris@0 321 $header = $this->getCacheControlHeader();
Chris@0 322 if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) {
Chris@0 323 return $header;
Chris@0 324 }
Chris@0 325
Chris@0 326 // public if s-maxage is defined, private otherwise
Chris@0 327 if (!isset($this->cacheControl['s-maxage'])) {
Chris@0 328 return $header.', private';
Chris@0 329 }
Chris@0 330
Chris@0 331 return $header;
Chris@0 332 }
Chris@14 333
Chris@14 334 private function initDate()
Chris@14 335 {
Chris@14 336 $now = \DateTime::createFromFormat('U', time());
Chris@14 337 $now->setTimezone(new \DateTimeZone('UTC'));
Chris@14 338 $this->set('Date', $now->format('D, d M Y H:i:s').' GMT');
Chris@14 339 }
Chris@0 340 }