Verzeichnisstruktur phpBB-3.3.16


Veröffentlicht
27.04.2026

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.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

      (Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

Response.php

Zuletzt modifiziert: 01.05.2026, 11:26 - Dateigröße: 37.47 KiB


 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