Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
Response.php
0001 <?php
0002
0003 /*
0004 * This file is part of the Symfony package.
0005 *
0006 * (c) Fabien Potencier <fabien@symfony.com>
0007 *
0008 * For the full copyright and license information, please view the LICENSE
0009 * file that was distributed with this source code.
0010 */
0011
0012 namespace Symfony\Component\HttpFoundation;
0013
0014 /**
0015 * Response represents an HTTP response.
0016 *
0017 * @author Fabien Potencier <fabien@symfony.com>
0018 */
0019 class Response
0020 {
0021 const HTTP_CONTINUE = 100;
0022 const HTTP_SWITCHING_PROTOCOLS = 101;
0023 const HTTP_PROCESSING = 102; // RFC2518
0024 const HTTP_EARLY_HINTS = 103; // RFC8297
0025 const HTTP_OK = 200;
0026 const HTTP_CREATED = 201;
0027 const HTTP_ACCEPTED = 202;
0028 const HTTP_NON_AUTHORITATIVE_INFORMATION = 203;
0029 const HTTP_NO_CONTENT = 204;
0030 const HTTP_RESET_CONTENT = 205;
0031 const HTTP_PARTIAL_CONTENT = 206;
0032 const HTTP_MULTI_STATUS = 207; // RFC4918
0033 const HTTP_ALREADY_REPORTED = 208; // RFC5842
0034 const HTTP_IM_USED = 226; // RFC3229
0035 const HTTP_MULTIPLE_CHOICES = 300;
0036 const HTTP_MOVED_PERMANENTLY = 301;
0037 const HTTP_FOUND = 302;
0038 const HTTP_SEE_OTHER = 303;
0039 const HTTP_NOT_MODIFIED = 304;
0040 const HTTP_USE_PROXY = 305;
0041 const HTTP_RESERVED = 306;
0042 const HTTP_TEMPORARY_REDIRECT = 307;
0043 const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238
0044 const HTTP_BAD_REQUEST = 400;
0045 const HTTP_UNAUTHORIZED = 401;
0046 const HTTP_PAYMENT_REQUIRED = 402;
0047 const HTTP_FORBIDDEN = 403;
0048 const HTTP_NOT_FOUND = 404;
0049 const HTTP_METHOD_NOT_ALLOWED = 405;
0050 const HTTP_NOT_ACCEPTABLE = 406;
0051 const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407;
0052 const HTTP_REQUEST_TIMEOUT = 408;
0053 const HTTP_CONFLICT = 409;
0054 const HTTP_GONE = 410;
0055 const HTTP_LENGTH_REQUIRED = 411;
0056 const HTTP_PRECONDITION_FAILED = 412;
0057 const HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
0058 const HTTP_REQUEST_URI_TOO_LONG = 414;
0059 const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
0060 const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
0061 const HTTP_EXPECTATION_FAILED = 417;
0062 const HTTP_I_AM_A_TEAPOT = 418; // RFC2324
0063 const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540
0064 const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918
0065 const HTTP_LOCKED = 423; // RFC4918
0066 const HTTP_FAILED_DEPENDENCY = 424; // RFC4918
0067
0068 /**
0069 * @deprecated
0070 */
0071 const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425; // RFC2817
0072 const HTTP_TOO_EARLY = 425; // RFC-ietf-httpbis-replay-04
0073 const HTTP_UPGRADE_REQUIRED = 426; // RFC2817
0074 const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585
0075 const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585
0076 const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585
0077 const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451;
0078 const HTTP_INTERNAL_SERVER_ERROR = 500;
0079 const HTTP_NOT_IMPLEMENTED = 501;
0080 const HTTP_BAD_GATEWAY = 502;
0081 const HTTP_SERVICE_UNAVAILABLE = 503;
0082 const HTTP_GATEWAY_TIMEOUT = 504;
0083 const HTTP_VERSION_NOT_SUPPORTED = 505;
0084 const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295
0085 const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918
0086 const HTTP_LOOP_DETECTED = 508; // RFC5842
0087 const HTTP_NOT_EXTENDED = 510; // RFC2774
0088 const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585
0089
0090 /**
0091 * @var ResponseHeaderBag
0092 */
0093 public $headers;
0094
0095 /**
0096 * @var string
0097 */
0098 protected $content;
0099
0100 /**
0101 * @var string
0102 */
0103 protected $version;
0104
0105 /**
0106 * @var int
0107 */
0108 protected $statusCode;
0109
0110 /**
0111 * @var string
0112 */
0113 protected $statusText;
0114
0115 /**
0116 * @var string
0117 */
0118 protected $charset;
0119
0120 /**
0121 * Status codes translation table.
0122 *
0123 * The list of codes is complete according to the
0124 * {@link https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml Hypertext Transfer Protocol (HTTP) Status Code Registry}
0125 * (last updated 2016-03-01).
0126 *
0127 * Unless otherwise noted, the status code is defined in RFC2616.
0128 *
0129 * @var array
0130 */
0131 public static $statusTexts = [
0132 100 => 'Continue',
0133 101 => 'Switching Protocols',
0134 102 => 'Processing', // RFC2518
0135 103 => 'Early Hints',
0136 200 => 'OK',
0137 201 => 'Created',
0138 202 => 'Accepted',
0139 203 => 'Non-Authoritative Information',
0140 204 => 'No Content',
0141 205 => 'Reset Content',
0142 206 => 'Partial Content',
0143 207 => 'Multi-Status', // RFC4918
0144 208 => 'Already Reported', // RFC5842
0145 226 => 'IM Used', // RFC3229
0146 300 => 'Multiple Choices',
0147 301 => 'Moved Permanently',
0148 302 => 'Found',
0149 303 => 'See Other',
0150 304 => 'Not Modified',
0151 305 => 'Use Proxy',
0152 307 => 'Temporary Redirect',
0153 308 => 'Permanent Redirect', // RFC7238
0154 400 => 'Bad Request',
0155 401 => 'Unauthorized',
0156 402 => 'Payment Required',
0157 403 => 'Forbidden',
0158 404 => 'Not Found',
0159 405 => 'Method Not Allowed',
0160 406 => 'Not Acceptable',
0161 407 => 'Proxy Authentication Required',
0162 408 => 'Request Timeout',
0163 409 => 'Conflict',
0164 410 => 'Gone',
0165 411 => 'Length Required',
0166 412 => 'Precondition Failed',
0167 413 => 'Payload Too Large',
0168 414 => 'URI Too Long',
0169 415 => 'Unsupported Media Type',
0170 416 => 'Range Not Satisfiable',
0171 417 => 'Expectation Failed',
0172 418 => 'I\'m a teapot', // RFC2324
0173 421 => 'Misdirected Request', // RFC7540
0174 422 => 'Unprocessable Entity', // RFC4918
0175 423 => 'Locked', // RFC4918
0176 424 => 'Failed Dependency', // RFC4918
0177 425 => 'Too Early', // RFC-ietf-httpbis-replay-04
0178 426 => 'Upgrade Required', // RFC2817
0179 428 => 'Precondition Required', // RFC6585
0180 429 => 'Too Many Requests', // RFC6585
0181 431 => 'Request Header Fields Too Large', // RFC6585
0182 451 => 'Unavailable For Legal Reasons', // RFC7725
0183 500 => 'Internal Server Error',
0184 501 => 'Not Implemented',
0185 502 => 'Bad Gateway',
0186 503 => 'Service Unavailable',
0187 504 => 'Gateway Timeout',
0188 505 => 'HTTP Version Not Supported',
0189 506 => 'Variant Also Negotiates', // RFC2295
0190 507 => 'Insufficient Storage', // RFC4918
0191 508 => 'Loop Detected', // RFC5842
0192 510 => 'Not Extended', // RFC2774
0193 511 => 'Network Authentication Required', // RFC6585
0194 ];
0195
0196 /**
0197 * @param mixed $content The response content, see setContent()
0198 * @param int $status The response status code
0199 * @param array $headers An array of response headers
0200 *
0201 * @throws \InvalidArgumentException When the HTTP status code is not valid
0202 */
0203 public function __construct($content = '', $status = 200, $headers = [])
0204 {
0205 $this->headers = new ResponseHeaderBag($headers);
0206 $this->setContent($content);
0207 $this->setStatusCode($status);
0208 $this->setProtocolVersion('1.0');
0209 }
0210
0211 /**
0212 * Factory method for chainability.
0213 *
0214 * Example:
0215 *
0216 * return Response::create($body, 200)
0217 * ->setSharedMaxAge(300);
0218 *
0219 * @param mixed $content The response content, see setContent()
0220 * @param int $status The response status code
0221 * @param array $headers An array of response headers
0222 *
0223 * @return static
0224 */
0225 public static function create($content = '', $status = 200, $headers = [])
0226 {
0227 return new static($content, $status, $headers);
0228 }
0229
0230 /**
0231 * Returns the Response as an HTTP string.
0232 *
0233 * The string representation of the Response is the same as the
0234 * one that will be sent to the client only if the prepare() method
0235 * has been called before.
0236 *
0237 * @return string The Response as an HTTP string
0238 *
0239 * @see prepare()
0240 */
0241 public function __toString()
0242 {
0243 return
0244 sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n".
0245 $this->headers."\r\n".
0246 $this->getContent();
0247 }
0248
0249 /**
0250 * Clones the current Response instance.
0251 */
0252 public function __clone()
0253 {
0254 $this->headers = clone $this->headers;
0255 }
0256
0257 /**
0258 * Prepares the Response before it is sent to the client.
0259 *
0260 * This method tweaks the Response to ensure that it is
0261 * compliant with RFC 2616. Most of the changes are based on
0262 * the Request that is "associated" with this Response.
0263 *
0264 * @return $this
0265 */
0266 public function prepare(Request $request)
0267 {
0268 $headers = $this->headers;
0269
0270 if ($this->isInformational() || $this->isEmpty()) {
0271 $this->setContent(null);
0272 $headers->remove('Content-Type');
0273 $headers->remove('Content-Length');
0274 } else {
0275 // Content-type based on the Request
0276 if (!$headers->has('Content-Type')) {
0277 $format = $request->getRequestFormat();
0278 if (null !== $format && $mimeType = $request->getMimeType($format)) {
0279 $headers->set('Content-Type', $mimeType);
0280 }
0281 }
0282
0283 // Fix Content-Type
0284 $charset = $this->charset ?: 'UTF-8';
0285 if (!$headers->has('Content-Type')) {
0286 $headers->set('Content-Type', 'text/html; charset='.$charset);
0287 } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) {
0288 // add the charset
0289 $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset);
0290 }
0291
0292 // Fix Content-Length
0293 if ($headers->has('Transfer-Encoding')) {
0294 $headers->remove('Content-Length');
0295 }
0296
0297 if ($request->isMethod('HEAD')) {
0298 // cf. RFC2616 14.13
0299 $length = $headers->get('Content-Length');
0300 $this->setContent(null);
0301 if ($length) {
0302 $headers->set('Content-Length', $length);
0303 }
0304 }
0305 }
0306
0307 // Fix protocol
0308 if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) {
0309 $this->setProtocolVersion('1.1');
0310 }
0311
0312 // Check if we need to send extra expire info headers
0313 if ('1.0' == $this->getProtocolVersion() && false !== strpos($headers->get('Cache-Control'), 'no-cache')) {
0314 $headers->set('pragma', 'no-cache');
0315 $headers->set('expires', -1);
0316 }
0317
0318 $this->ensureIEOverSSLCompatibility($request);
0319
0320 return $this;
0321 }
0322
0323 /**
0324 * Sends HTTP headers.
0325 *
0326 * @return $this
0327 */
0328 public function sendHeaders()
0329 {
0330 // headers have already been sent by the developer
0331 if (headers_sent()) {
0332 return $this;
0333 }
0334
0335 // headers
0336 foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
0337 $replace = 0 === strcasecmp($name, 'Content-Type');
0338 foreach ($values as $value) {
0339 header($name.': '.$value, $replace, $this->statusCode);
0340 }
0341 }
0342
0343 // cookies
0344 foreach ($this->headers->getCookies() as $cookie) {
0345 header('Set-Cookie: '.$cookie, false, $this->statusCode);
0346 }
0347
0348 // status
0349 header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
0350
0351 return $this;
0352 }
0353
0354 /**
0355 * Sends content for the current web response.
0356 *
0357 * @return $this
0358 */
0359 public function sendContent()
0360 {
0361 echo $this->content;
0362
0363 return $this;
0364 }
0365
0366 /**
0367 * Sends HTTP headers and content.
0368 *
0369 * @return $this
0370 */
0371 public function send()
0372 {
0373 $this->sendHeaders();
0374 $this->sendContent();
0375
0376 if (\function_exists('fastcgi_finish_request')) {
0377 fastcgi_finish_request();
0378 } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
0379 static::closeOutputBuffers(0, true);
0380 }
0381
0382 return $this;
0383 }
0384
0385 /**
0386 * Sets the response content.
0387 *
0388 * Valid types are strings, numbers, null, and objects that implement a __toString() method.
0389 *
0390 * @param mixed $content Content that can be cast to string
0391 *
0392 * @return $this
0393 *
0394 * @throws \UnexpectedValueException
0395 */
0396 public function setContent($content)
0397 {
0398 if (null !== $content && !\is_string($content) && !is_numeric($content) && !\is_callable([$content, '__toString'])) {
0399 throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', \gettype($content)));
0400 }
0401
0402 $this->content = (string) $content;
0403
0404 return $this;
0405 }
0406
0407 /**
0408 * Gets the current response content.
0409 *
0410 * @return string|false
0411 */
0412 public function getContent()
0413 {
0414 return $this->content;
0415 }
0416
0417 /**
0418 * Sets the HTTP protocol version (1.0 or 1.1).
0419 *
0420 * @param string $version The HTTP protocol version
0421 *
0422 * @return $this
0423 *
0424 * @final since version 3.2
0425 */
0426 public function setProtocolVersion($version)
0427 {
0428 $this->version = $version;
0429
0430 return $this;
0431 }
0432
0433 /**
0434 * Gets the HTTP protocol version.
0435 *
0436 * @return string The HTTP protocol version
0437 *
0438 * @final since version 3.2
0439 */
0440 public function getProtocolVersion()
0441 {
0442 return $this->version;
0443 }
0444
0445 /**
0446 * Sets the response status code.
0447 *
0448 * If the status text is null it will be automatically populated for the known
0449 * status codes and left empty otherwise.
0450 *
0451 * @param int $code HTTP status code
0452 * @param mixed $text HTTP status text
0453 *
0454 * @return $this
0455 *
0456 * @throws \InvalidArgumentException When the HTTP status code is not valid
0457 *
0458 * @final since version 3.2
0459 */
0460 public function setStatusCode($code, $text = null)
0461 {
0462 $this->statusCode = $code = (int) $code;
0463 if ($this->isInvalid()) {
0464 throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code));
0465 }
0466
0467 if (null === $text) {
0468 $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : 'unknown status';
0469
0470 return $this;
0471 }
0472
0473 if (false === $text) {
0474 $this->statusText = '';
0475
0476 return $this;
0477 }
0478
0479 $this->statusText = $text;
0480
0481 return $this;
0482 }
0483
0484 /**
0485 * Retrieves the status code for the current web response.
0486 *
0487 * @return int Status code
0488 *
0489 * @final since version 3.2
0490 */
0491 public function getStatusCode()
0492 {
0493 return $this->statusCode;
0494 }
0495
0496 /**
0497 * Sets the response charset.
0498 *
0499 * @param string $charset Character set
0500 *
0501 * @return $this
0502 *
0503 * @final since version 3.2
0504 */
0505 public function setCharset($charset)
0506 {
0507 $this->charset = $charset;
0508
0509 return $this;
0510 }
0511
0512 /**
0513 * Retrieves the response charset.
0514 *
0515 * @return string Character set
0516 *
0517 * @final since version 3.2
0518 */
0519 public function getCharset()
0520 {
0521 return $this->charset;
0522 }
0523
0524 /**
0525 * Returns true if the response may safely be kept in a shared (surrogate) cache.
0526 *
0527 * Responses marked "private" with an explicit Cache-Control directive are
0528 * considered uncacheable.
0529 *
0530 * Responses with neither a freshness lifetime (Expires, max-age) nor cache
0531 * validator (Last-Modified, ETag) are considered uncacheable because there is
0532 * no way to tell when or how to remove them from the cache.
0533 *
0534 * Note that RFC 7231 and RFC 7234 possibly allow for a more permissive implementation,
0535 * for example "status codes that are defined as cacheable by default [...]
0536 * can be reused by a cache with heuristic expiration unless otherwise indicated"
0537 * (https://tools.ietf.org/html/rfc7231#section-6.1)
0538 *
0539 * @return bool true if the response is worth caching, false otherwise
0540 *
0541 * @final since version 3.3
0542 */
0543 public function isCacheable()
0544 {
0545 if (!\in_array($this->statusCode, [200, 203, 300, 301, 302, 404, 410])) {
0546 return false;
0547 }
0548
0549 if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) {
0550 return false;
0551 }
0552
0553 return $this->isValidateable() || $this->isFresh();
0554 }
0555
0556 /**
0557 * Returns true if the response is "fresh".
0558 *
0559 * Fresh responses may be served from cache without any interaction with the
0560 * origin. A response is considered fresh when it includes a Cache-Control/max-age
0561 * indicator or Expires header and the calculated age is less than the freshness lifetime.
0562 *
0563 * @return bool true if the response is fresh, false otherwise
0564 *
0565 * @final since version 3.3
0566 */
0567 public function isFresh()
0568 {
0569 return $this->getTtl() > 0;
0570 }
0571
0572 /**
0573 * Returns true if the response includes headers that can be used to validate
0574 * the response with the origin server using a conditional GET request.
0575 *
0576 * @return bool true if the response is validateable, false otherwise
0577 *
0578 * @final since version 3.3
0579 */
0580 public function isValidateable()
0581 {
0582 return $this->headers->has('Last-Modified') || $this->headers->has('ETag');
0583 }
0584
0585 /**
0586 * Marks the response as "private".
0587 *
0588 * It makes the response ineligible for serving other clients.
0589 *
0590 * @return $this
0591 *
0592 * @final since version 3.2
0593 */
0594 public function setPrivate()
0595 {
0596 $this->headers->removeCacheControlDirective('public');
0597 $this->headers->addCacheControlDirective('private');
0598
0599 return $this;
0600 }
0601
0602 /**
0603 * Marks the response as "public".
0604 *
0605 * It makes the response eligible for serving other clients.
0606 *
0607 * @return $this
0608 *
0609 * @final since version 3.2
0610 */
0611 public function setPublic()
0612 {
0613 $this->headers->addCacheControlDirective('public');
0614 $this->headers->removeCacheControlDirective('private');
0615
0616 return $this;
0617 }
0618
0619 /**
0620 * Marks the response as "immutable".
0621 *
0622 * @param bool $immutable enables or disables the immutable directive
0623 *
0624 * @return $this
0625 *
0626 * @final
0627 */
0628 public function setImmutable($immutable = true)
0629 {
0630 if ($immutable) {
0631 $this->headers->addCacheControlDirective('immutable');
0632 } else {
0633 $this->headers->removeCacheControlDirective('immutable');
0634 }
0635
0636 return $this;
0637 }
0638
0639 /**
0640 * Returns true if the response is marked as "immutable".
0641 *
0642 * @return bool returns true if the response is marked as "immutable"; otherwise false
0643 *
0644 * @final
0645 */
0646 public function isImmutable()
0647 {
0648 return $this->headers->hasCacheControlDirective('immutable');
0649 }
0650
0651 /**
0652 * Returns true if the response must be revalidated by shared caches once it has become stale.
0653 *
0654 * This method indicates that the response must not be served stale by a
0655 * cache in any circumstance without first revalidating with the origin.
0656 * When present, the TTL of the response should not be overridden to be
0657 * greater than the value provided by the origin.
0658 *
0659 * @return bool true if the response must be revalidated by a cache, false otherwise
0660 *
0661 * @final since version 3.3
0662 */
0663 public function mustRevalidate()
0664 {
0665 return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate');
0666 }
0667
0668 /**
0669 * Returns the Date header as a DateTime instance.
0670 *
0671 * @return \DateTime A \DateTime instance
0672 *
0673 * @throws \RuntimeException When the header is not parseable
0674 *
0675 * @final since version 3.2
0676 */
0677 public function getDate()
0678 {
0679 return $this->headers->getDate('Date');
0680 }
0681
0682 /**
0683 * Sets the Date header.
0684 *
0685 * @return $this
0686 *
0687 * @final since version 3.2
0688 */
0689 public function setDate(\DateTime $date)
0690 {
0691 $date->setTimezone(new \DateTimeZone('UTC'));
0692 $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT');
0693
0694 return $this;
0695 }
0696
0697 /**
0698 * Returns the age of the response.
0699 *
0700 * @return int The age of the response in seconds
0701 *
0702 * @final since version 3.2
0703 */
0704 public function getAge()
0705 {
0706 if (null !== $age = $this->headers->get('Age')) {
0707 return (int) $age;
0708 }
0709
0710 return max(time() - (int) $this->getDate()->format('U'), 0);
0711 }
0712
0713 /**
0714 * Marks the response stale by setting the Age header to be equal to the maximum age of the response.
0715 *
0716 * @return $this
0717 */
0718 public function expire()
0719 {
0720 if ($this->isFresh()) {
0721 $this->headers->set('Age', $this->getMaxAge());
0722 $this->headers->remove('Expires');
0723 }
0724
0725 return $this;
0726 }
0727
0728 /**
0729 * Returns the value of the Expires header as a DateTime instance.
0730 *
0731 * @return \DateTime|null A DateTime instance or null if the header does not exist
0732 *
0733 * @final since version 3.2
0734 */
0735 public function getExpires()
0736 {
0737 try {
0738 return $this->headers->getDate('Expires');
0739 } catch (\RuntimeException $e) {
0740 // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past
0741 return \DateTime::createFromFormat(\DATE_RFC2822, 'Sat, 01 Jan 00 00:00:00 +0000');
0742 }
0743 }
0744
0745 /**
0746 * Sets the Expires HTTP header with a DateTime instance.
0747 *
0748 * Passing null as value will remove the header.
0749 *
0750 * @param \DateTime|null $date A \DateTime instance or null to remove the header
0751 *
0752 * @return $this
0753 *
0754 * @final since version 3.2
0755 */
0756 public function setExpires(\DateTime $date = null)
0757 {
0758 if (null === $date) {
0759 $this->headers->remove('Expires');
0760 } else {
0761 $date = clone $date;
0762 $date->setTimezone(new \DateTimeZone('UTC'));
0763 $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT');
0764 }
0765
0766 return $this;
0767 }
0768
0769 /**
0770 * Returns the number of seconds after the time specified in the response's Date
0771 * header when the response should no longer be considered fresh.
0772 *
0773 * First, it checks for a s-maxage directive, then a max-age directive, and then it falls
0774 * back on an expires header. It returns null when no maximum age can be established.
0775 *
0776 * @return int|null Number of seconds
0777 *
0778 * @final since version 3.2
0779 */
0780 public function getMaxAge()
0781 {
0782 if ($this->headers->hasCacheControlDirective('s-maxage')) {
0783 return (int) $this->headers->getCacheControlDirective('s-maxage');
0784 }
0785
0786 if ($this->headers->hasCacheControlDirective('max-age')) {
0787 return (int) $this->headers->getCacheControlDirective('max-age');
0788 }
0789
0790 if (null !== $this->getExpires()) {
0791 return (int) $this->getExpires()->format('U') - (int) $this->getDate()->format('U');
0792 }
0793
0794 return null;
0795 }
0796
0797 /**
0798 * Sets the number of seconds after which the response should no longer be considered fresh.
0799 *
0800 * This methods sets the Cache-Control max-age directive.
0801 *
0802 * @param int $value Number of seconds
0803 *
0804 * @return $this
0805 *
0806 * @final since version 3.2
0807 */
0808 public function setMaxAge($value)
0809 {
0810 $this->headers->addCacheControlDirective('max-age', $value);
0811
0812 return $this;
0813 }
0814
0815 /**
0816 * Sets the number of seconds after which the response should no longer be considered fresh by shared caches.
0817 *
0818 * This methods sets the Cache-Control s-maxage directive.
0819 *
0820 * @param int $value Number of seconds
0821 *
0822 * @return $this
0823 *
0824 * @final since version 3.2
0825 */
0826 public function setSharedMaxAge($value)
0827 {
0828 $this->setPublic();
0829 $this->headers->addCacheControlDirective('s-maxage', $value);
0830
0831 return $this;
0832 }
0833
0834 /**
0835 * Returns the response's time-to-live in seconds.
0836 *
0837 * It returns null when no freshness information is present in the response.
0838 *
0839 * When the responses TTL is <= 0, the response may not be served from cache without first
0840 * revalidating with the origin.
0841 *
0842 * @return int|null The TTL in seconds
0843 *
0844 * @final since version 3.2
0845 */
0846 public function getTtl()
0847 {
0848 if (null !== $maxAge = $this->getMaxAge()) {
0849 return $maxAge - $this->getAge();
0850 }
0851
0852 return null;
0853 }
0854
0855 /**
0856 * Sets the response's time-to-live for shared caches.
0857 *
0858 * This method adjusts the Cache-Control/s-maxage directive.
0859 *
0860 * @param int $seconds Number of seconds
0861 *
0862 * @return $this
0863 *
0864 * @final since version 3.2
0865 */
0866 public function setTtl($seconds)
0867 {
0868 $this->setSharedMaxAge($this->getAge() + $seconds);
0869
0870 return $this;
0871 }
0872
0873 /**
0874 * Sets the response's time-to-live for private/client caches.
0875 *
0876 * This method adjusts the Cache-Control/max-age directive.
0877 *
0878 * @param int $seconds Number of seconds
0879 *
0880 * @return $this
0881 *
0882 * @final since version 3.2
0883 */
0884 public function setClientTtl($seconds)
0885 {
0886 $this->setMaxAge($this->getAge() + $seconds);
0887
0888 return $this;
0889 }
0890
0891 /**
0892 * Returns the Last-Modified HTTP header as a DateTime instance.
0893 *
0894 * @return \DateTime|null A DateTime instance or null if the header does not exist
0895 *
0896 * @throws \RuntimeException When the HTTP header is not parseable
0897 *
0898 * @final since version 3.2
0899 */
0900 public function getLastModified()
0901 {
0902 return $this->headers->getDate('Last-Modified');
0903 }
0904
0905 /**
0906 * Sets the Last-Modified HTTP header with a DateTime instance.
0907 *
0908 * Passing null as value will remove the header.
0909 *
0910 * @param \DateTime|null $date A \DateTime instance or null to remove the header
0911 *
0912 * @return $this
0913 *
0914 * @final since version 3.2
0915 */
0916 public function setLastModified(\DateTime $date = null)
0917 {
0918 if (null === $date) {
0919 $this->headers->remove('Last-Modified');
0920 } else {
0921 $date = clone $date;
0922 $date->setTimezone(new \DateTimeZone('UTC'));
0923 $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT');
0924 }
0925
0926 return $this;
0927 }
0928
0929 /**
0930 * Returns the literal value of the ETag HTTP header.
0931 *
0932 * @return string|null The ETag HTTP header or null if it does not exist
0933 *
0934 * @final since version 3.2
0935 */
0936 public function getEtag()
0937 {
0938 return $this->headers->get('ETag');
0939 }
0940
0941 /**
0942 * Sets the ETag value.
0943 *
0944 * @param string|null $etag The ETag unique identifier or null to remove the header
0945 * @param bool $weak Whether you want a weak ETag or not
0946 *
0947 * @return $this
0948 *
0949 * @final since version 3.2
0950 */
0951 public function setEtag($etag = null, $weak = false)
0952 {
0953 if (null === $etag) {
0954 $this->headers->remove('Etag');
0955 } else {
0956 if (0 !== strpos($etag, '"')) {
0957 $etag = '"'.$etag.'"';
0958 }
0959
0960 $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag);
0961 }
0962
0963 return $this;
0964 }
0965
0966 /**
0967 * Sets the response's cache headers (validation and/or expiration).
0968 *
0969 * Available options are: etag, last_modified, max_age, s_maxage, private, public and immutable.
0970 *
0971 * @param array $options An array of cache options
0972 *
0973 * @return $this
0974 *
0975 * @throws \InvalidArgumentException
0976 *
0977 * @final since version 3.3
0978 */
0979 public function setCache(array $options)
0980 {
0981 if ($diff = array_diff(array_keys($options), ['etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public', 'immutable'])) {
0982 throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', $diff)));
0983 }
0984
0985 if (isset($options['etag'])) {
0986 $this->setEtag($options['etag']);
0987 }
0988
0989 if (isset($options['last_modified'])) {
0990 $this->setLastModified($options['last_modified']);
0991 }
0992
0993 if (isset($options['max_age'])) {
0994 $this->setMaxAge($options['max_age']);
0995 }
0996
0997 if (isset($options['s_maxage'])) {
0998 $this->setSharedMaxAge($options['s_maxage']);
0999 }
1000
1001 if (isset($options['public'])) {
1002 if ($options['public']) {
1003 $this->setPublic();
1004 } else {
1005 $this->setPrivate();
1006 }
1007 }
1008
1009 if (isset($options['private'])) {
1010 if ($options['private']) {
1011 $this->setPrivate();
1012 } else {
1013 $this->setPublic();
1014 }
1015 }
1016
1017 if (isset($options['immutable'])) {
1018 $this->setImmutable((bool) $options['immutable']);
1019 }
1020
1021 return $this;
1022 }
1023
1024 /**
1025 * Modifies the response so that it conforms to the rules defined for a 304 status code.
1026 *
1027 * This sets the status, removes the body, and discards any headers
1028 * that MUST NOT be included in 304 responses.
1029 *
1030 * @return $this
1031 *
1032 * @see https://tools.ietf.org/html/rfc2616#section-10.3.5
1033 *
1034 * @final since version 3.3
1035 */
1036 public function setNotModified()
1037 {
1038 $this->setStatusCode(304);
1039 $this->setContent(null);
1040
1041 // remove headers that MUST NOT be included with 304 Not Modified responses
1042 foreach (['Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified'] as $header) {
1043 $this->headers->remove($header);
1044 }
1045
1046 return $this;
1047 }
1048
1049 /**
1050 * Returns true if the response includes a Vary header.
1051 *
1052 * @return bool true if the response includes a Vary header, false otherwise
1053 *
1054 * @final since version 3.2
1055 */
1056 public function hasVary()
1057 {
1058 return null !== $this->headers->get('Vary');
1059 }
1060
1061 /**
1062 * Returns an array of header names given in the Vary header.
1063 *
1064 * @return array An array of Vary names
1065 *
1066 * @final since version 3.2
1067 */
1068 public function getVary()
1069 {
1070 if (!$vary = $this->headers->get('Vary', null, false)) {
1071 return [];
1072 }
1073
1074 $ret = [];
1075 foreach ($vary as $item) {
1076 $ret = array_merge($ret, preg_split('/[\s,]+/', $item));
1077 }
1078
1079 return $ret;
1080 }
1081
1082 /**
1083 * Sets the Vary header.
1084 *
1085 * @param string|array $headers
1086 * @param bool $replace Whether to replace the actual value or not (true by default)
1087 *
1088 * @return $this
1089 *
1090 * @final since version 3.2
1091 */
1092 public function setVary($headers, $replace = true)
1093 {
1094 $this->headers->set('Vary', $headers, $replace);
1095
1096 return $this;
1097 }
1098
1099 /**
1100 * Determines if the Response validators (ETag, Last-Modified) match
1101 * a conditional value specified in the Request.
1102 *
1103 * If the Response is not modified, it sets the status code to 304 and
1104 * removes the actual content by calling the setNotModified() method.
1105 *
1106 * @return bool true if the Response validators match the Request, false otherwise
1107 *
1108 * @final since version 3.3
1109 */
1110 public function isNotModified(Request $request)
1111 {
1112 if (!$request->isMethodCacheable()) {
1113 return false;
1114 }
1115
1116 $notModified = false;
1117 $lastModified = $this->headers->get('Last-Modified');
1118 $modifiedSince = $request->headers->get('If-Modified-Since');
1119
1120 if ($etags = $request->getETags()) {
1121 $notModified = \in_array($this->getEtag(), $etags) || \in_array('*', $etags);
1122 }
1123
1124 if ($modifiedSince && $lastModified) {
1125 $notModified = strtotime($modifiedSince) >= strtotime($lastModified) && (!$etags || $notModified);
1126 }
1127
1128 if ($notModified) {
1129 $this->setNotModified();
1130 }
1131
1132 return $notModified;
1133 }
1134
1135 /**
1136 * Is response invalid?
1137 *
1138 * @return bool
1139 *
1140 * @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
1141 *
1142 * @final since version 3.2
1143 */
1144 public function isInvalid()
1145 {
1146 return $this->statusCode < 100 || $this->statusCode >= 600;
1147 }
1148
1149 /**
1150 * Is response informative?
1151 *
1152 * @return bool
1153 *
1154 * @final since version 3.3
1155 */
1156 public function isInformational()
1157 {
1158 return $this->statusCode >= 100 && $this->statusCode < 200;
1159 }
1160
1161 /**
1162 * Is response successful?
1163 *
1164 * @return bool
1165 *
1166 * @final since version 3.2
1167 */
1168 public function isSuccessful()
1169 {
1170 return $this->statusCode >= 200 && $this->statusCode < 300;
1171 }
1172
1173 /**
1174 * Is the response a redirect?
1175 *
1176 * @return bool
1177 *
1178 * @final since version 3.2
1179 */
1180 public function isRedirection()
1181 {
1182 return $this->statusCode >= 300 && $this->statusCode < 400;
1183 }
1184
1185 /**
1186 * Is there a client error?
1187 *
1188 * @return bool
1189 *
1190 * @final since version 3.2
1191 */
1192 public function isClientError()
1193 {
1194 return $this->statusCode >= 400 && $this->statusCode < 500;
1195 }
1196
1197 /**
1198 * Was there a server side error?
1199 *
1200 * @return bool
1201 *
1202 * @final since version 3.3
1203 */
1204 public function isServerError()
1205 {
1206 return $this->statusCode >= 500 && $this->statusCode < 600;
1207 }
1208
1209 /**
1210 * Is the response OK?
1211 *
1212 * @return bool
1213 *
1214 * @final since version 3.2
1215 */
1216 public function isOk()
1217 {
1218 return 200 === $this->statusCode;
1219 }
1220
1221 /**
1222 * Is the response forbidden?
1223 *
1224 * @return bool
1225 *
1226 * @final since version 3.2
1227 */
1228 public function isForbidden()
1229 {
1230 return 403 === $this->statusCode;
1231 }
1232
1233 /**
1234 * Is the response a not found error?
1235 *
1236 * @return bool
1237 *
1238 * @final since version 3.2
1239 */
1240 public function isNotFound()
1241 {
1242 return 404 === $this->statusCode;
1243 }
1244
1245 /**
1246 * Is the response a redirect of some form?
1247 *
1248 * @param string $location
1249 *
1250 * @return bool
1251 *
1252 * @final since version 3.2
1253 */
1254 public function isRedirect($location = null)
1255 {
1256 return \in_array($this->statusCode, [201, 301, 302, 303, 307, 308]) && (null === $location ?: $location == $this->headers->get('Location'));
1257 }
1258
1259 /**
1260 * Is the response empty?
1261 *
1262 * @return bool
1263 *
1264 * @final since version 3.2
1265 */
1266 public function isEmpty()
1267 {
1268 return \in_array($this->statusCode, [204, 304]);
1269 }
1270
1271 /**
1272 * Cleans or flushes output buffers up to target level.
1273 *
1274 * Resulting level can be greater than target level if a non-removable buffer has been encountered.
1275 *
1276 * @param int $targetLevel The target output buffering level
1277 * @param bool $flush Whether to flush or clean the buffers
1278 *
1279 * @final since version 3.3
1280 */
1281 public static function closeOutputBuffers($targetLevel, $flush)
1282 {
1283 $status = ob_get_status(true);
1284 $level = \count($status);
1285 // PHP_OUTPUT_HANDLER_* are not defined on HHVM 3.3
1286 $flags = \defined('PHP_OUTPUT_HANDLER_REMOVABLE') ? \PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? \PHP_OUTPUT_HANDLER_FLUSHABLE : \PHP_OUTPUT_HANDLER_CLEANABLE) : -1;
1287
1288 while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) {
1289 if ($flush) {
1290 ob_end_flush();
1291 } else {
1292 ob_end_clean();
1293 }
1294 }
1295 }
1296
1297 /**
1298 * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9.
1299 *
1300 * @see http://support.microsoft.com/kb/323308
1301 *
1302 * @final since version 3.3
1303 */
1304 protected function ensureIEOverSSLCompatibility(Request $request)
1305 {
1306 if (false !== stripos($this->headers->get('Content-Disposition'), 'attachment') && 1 == preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT'), $match) && true === $request->isSecure()) {
1307 if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) {
1308 $this->headers->remove('Cache-Control');
1309 }
1310 }
1311 }
1312 }
1313