[ Index ]

PHP Cross Reference of phpBB-3.2.11-deutsch

title

Body

[close]

/vendor/symfony/http-foundation/ -> Response.php (source)

   1  <?php
   2  
   3  /*
   4   * This file is part of the Symfony package.
   5   *
   6   * (c) Fabien Potencier <fabien@symfony.com>
   7   *
   8   * For the full copyright and license information, please view the LICENSE
   9   * file that was distributed with this source code.
  10   */
  11  
  12  namespace Symfony\Component\HttpFoundation;
  13  
  14  /**
  15   * Response represents an HTTP response.
  16   *
  17   * @author Fabien Potencier <fabien@symfony.com>
  18   */
  19  class Response
  20  {
  21      const HTTP_CONTINUE = 100;
  22      const HTTP_SWITCHING_PROTOCOLS = 101;
  23      const HTTP_PROCESSING = 102;            // RFC2518
  24      const HTTP_EARLY_HINTS = 103;           // RFC8297
  25      const HTTP_OK = 200;
  26      const HTTP_CREATED = 201;
  27      const HTTP_ACCEPTED = 202;
  28      const HTTP_NON_AUTHORITATIVE_INFORMATION = 203;
  29      const HTTP_NO_CONTENT = 204;
  30      const HTTP_RESET_CONTENT = 205;
  31      const HTTP_PARTIAL_CONTENT = 206;
  32      const HTTP_MULTI_STATUS = 207;          // RFC4918
  33      const HTTP_ALREADY_REPORTED = 208;      // RFC5842
  34      const HTTP_IM_USED = 226;               // RFC3229
  35      const HTTP_MULTIPLE_CHOICES = 300;
  36      const HTTP_MOVED_PERMANENTLY = 301;
  37      const HTTP_FOUND = 302;
  38      const HTTP_SEE_OTHER = 303;
  39      const HTTP_NOT_MODIFIED = 304;
  40      const HTTP_USE_PROXY = 305;
  41      const HTTP_RESERVED = 306;
  42      const HTTP_TEMPORARY_REDIRECT = 307;
  43      const HTTP_PERMANENTLY_REDIRECT = 308;  // RFC7238
  44      const HTTP_BAD_REQUEST = 400;
  45      const HTTP_UNAUTHORIZED = 401;
  46      const HTTP_PAYMENT_REQUIRED = 402;
  47      const HTTP_FORBIDDEN = 403;
  48      const HTTP_NOT_FOUND = 404;
  49      const HTTP_METHOD_NOT_ALLOWED = 405;
  50      const HTTP_NOT_ACCEPTABLE = 406;
  51      const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407;
  52      const HTTP_REQUEST_TIMEOUT = 408;
  53      const HTTP_CONFLICT = 409;
  54      const HTTP_GONE = 410;
  55      const HTTP_LENGTH_REQUIRED = 411;
  56      const HTTP_PRECONDITION_FAILED = 412;
  57      const HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
  58      const HTTP_REQUEST_URI_TOO_LONG = 414;
  59      const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
  60      const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
  61      const HTTP_EXPECTATION_FAILED = 417;
  62      const HTTP_I_AM_A_TEAPOT = 418;                                               // RFC2324
  63      const HTTP_MISDIRECTED_REQUEST = 421;                                         // RFC7540
  64      const HTTP_UNPROCESSABLE_ENTITY = 422;                                        // RFC4918
  65      const HTTP_LOCKED = 423;                                                      // RFC4918
  66      const HTTP_FAILED_DEPENDENCY = 424;                                           // RFC4918
  67  
  68      /**
  69       * @deprecated
  70       */
  71      const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425;   // RFC2817
  72      const HTTP_TOO_EARLY = 425;                                                   // RFC-ietf-httpbis-replay-04
  73      const HTTP_UPGRADE_REQUIRED = 426;                                            // RFC2817
  74      const HTTP_PRECONDITION_REQUIRED = 428;                                       // RFC6585
  75      const HTTP_TOO_MANY_REQUESTS = 429;                                           // RFC6585
  76      const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431;                             // RFC6585
  77      const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451;
  78      const HTTP_INTERNAL_SERVER_ERROR = 500;
  79      const HTTP_NOT_IMPLEMENTED = 501;
  80      const HTTP_BAD_GATEWAY = 502;
  81      const HTTP_SERVICE_UNAVAILABLE = 503;
  82      const HTTP_GATEWAY_TIMEOUT = 504;
  83      const HTTP_VERSION_NOT_SUPPORTED = 505;
  84      const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506;                        // RFC2295
  85      const HTTP_INSUFFICIENT_STORAGE = 507;                                        // RFC4918
  86      const HTTP_LOOP_DETECTED = 508;                                               // RFC5842
  87      const HTTP_NOT_EXTENDED = 510;                                                // RFC2774
  88      const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511;                             // RFC6585
  89  
  90      /**
  91       * @var \Symfony\Component\HttpFoundation\ResponseHeaderBag
  92       */
  93      public $headers;
  94  
  95      /**
  96       * @var string
  97       */
  98      protected $content;
  99  
 100      /**
 101       * @var string
 102       */
 103      protected $version;
 104  
 105      /**
 106       * @var int
 107       */
 108      protected $statusCode;
 109  
 110      /**
 111       * @var string
 112       */
 113      protected $statusText;
 114  
 115      /**
 116       * @var string
 117       */
 118      protected $charset;
 119  
 120      /**
 121       * Status codes translation table.
 122       *
 123       * The list of codes is complete according to the
 124       * {@link http://www.iana.org/assignments/http-status-codes/ Hypertext Transfer Protocol (HTTP) Status Code Registry}
 125       * (last updated 2016-03-01).
 126       *
 127       * Unless otherwise noted, the status code is defined in RFC2616.
 128       *
 129       * @var array
 130       */
 131      public static $statusTexts = array(
 132          100 => 'Continue',
 133          101 => 'Switching Protocols',
 134          102 => 'Processing',            // RFC2518
 135          103 => 'Early Hints',
 136          200 => 'OK',
 137          201 => 'Created',
 138          202 => 'Accepted',
 139          203 => 'Non-Authoritative Information',
 140          204 => 'No Content',
 141          205 => 'Reset Content',
 142          206 => 'Partial Content',
 143          207 => 'Multi-Status',          // RFC4918
 144          208 => 'Already Reported',      // RFC5842
 145          226 => 'IM Used',               // RFC3229
 146          300 => 'Multiple Choices',
 147          301 => 'Moved Permanently',
 148          302 => 'Found',
 149          303 => 'See Other',
 150          304 => 'Not Modified',
 151          305 => 'Use Proxy',
 152          307 => 'Temporary Redirect',
 153          308 => 'Permanent Redirect',    // RFC7238
 154          400 => 'Bad Request',
 155          401 => 'Unauthorized',
 156          402 => 'Payment Required',
 157          403 => 'Forbidden',
 158          404 => 'Not Found',
 159          405 => 'Method Not Allowed',
 160          406 => 'Not Acceptable',
 161          407 => 'Proxy Authentication Required',
 162          408 => 'Request Timeout',
 163          409 => 'Conflict',
 164          410 => 'Gone',
 165          411 => 'Length Required',
 166          412 => 'Precondition Failed',
 167          413 => 'Payload Too Large',
 168          414 => 'URI Too Long',
 169          415 => 'Unsupported Media Type',
 170          416 => 'Range Not Satisfiable',
 171          417 => 'Expectation Failed',
 172          418 => 'I\'m a teapot',                                               // RFC2324
 173          421 => 'Misdirected Request',                                         // RFC7540
 174          422 => 'Unprocessable Entity',                                        // RFC4918
 175          423 => 'Locked',                                                      // RFC4918
 176          424 => 'Failed Dependency',                                           // RFC4918
 177          425 => 'Too Early',                                                   // RFC-ietf-httpbis-replay-04
 178          426 => 'Upgrade Required',                                            // RFC2817
 179          428 => 'Precondition Required',                                       // RFC6585
 180          429 => 'Too Many Requests',                                           // RFC6585
 181          431 => 'Request Header Fields Too Large',                             // RFC6585
 182          451 => 'Unavailable For Legal Reasons',                               // RFC7725
 183          500 => 'Internal Server Error',
 184          501 => 'Not Implemented',
 185          502 => 'Bad Gateway',
 186          503 => 'Service Unavailable',
 187          504 => 'Gateway Timeout',
 188          505 => 'HTTP Version Not Supported',
 189          506 => 'Variant Also Negotiates',                                     // RFC2295
 190          507 => 'Insufficient Storage',                                        // RFC4918
 191          508 => 'Loop Detected',                                               // RFC5842
 192          510 => 'Not Extended',                                                // RFC2774
 193          511 => 'Network Authentication Required',                             // RFC6585
 194      );
 195  
 196      /**
 197       * @param mixed $content The response content, see setContent()
 198       * @param int   $status  The response status code
 199       * @param array $headers An array of response headers
 200       *
 201       * @throws \InvalidArgumentException When the HTTP status code is not valid
 202       */
 203      public function __construct($content = '', $status = 200, $headers = array())
 204      {
 205          $this->headers = new ResponseHeaderBag($headers);
 206          $this->setContent($content);
 207          $this->setStatusCode($status);
 208          $this->setProtocolVersion('1.0');
 209  
 210          /* RFC2616 - 14.18 says all Responses need to have a Date */
 211          if (!$this->headers->has('Date')) {
 212              $this->setDate(\DateTime::createFromFormat('U', time()));
 213          }
 214      }
 215  
 216      /**
 217       * Factory method for chainability.
 218       *
 219       * Example:
 220       *
 221       *     return Response::create($body, 200)
 222       *         ->setSharedMaxAge(300);
 223       *
 224       * @param mixed $content The response content, see setContent()
 225       * @param int   $status  The response status code
 226       * @param array $headers An array of response headers
 227       *
 228       * @return static
 229       */
 230      public static function create($content = '', $status = 200, $headers = array())
 231      {
 232          return new static($content, $status, $headers);
 233      }
 234  
 235      /**
 236       * Returns the Response as an HTTP string.
 237       *
 238       * The string representation of the Response is the same as the
 239       * one that will be sent to the client only if the prepare() method
 240       * has been called before.
 241       *
 242       * @return string The Response as an HTTP string
 243       *
 244       * @see prepare()
 245       */
 246      public function __toString()
 247      {
 248          return
 249              sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n".
 250              $this->headers."\r\n".
 251              $this->getContent();
 252      }
 253  
 254      /**
 255       * Clones the current Response instance.
 256       */
 257      public function __clone()
 258      {
 259          $this->headers = clone $this->headers;
 260      }
 261  
 262      /**
 263       * Prepares the Response before it is sent to the client.
 264       *
 265       * This method tweaks the Response to ensure that it is
 266       * compliant with RFC 2616. Most of the changes are based on
 267       * the Request that is "associated" with this Response.
 268       *
 269       * @return $this
 270       */
 271      public function prepare(Request $request)
 272      {
 273          $headers = $this->headers;
 274  
 275          if ($this->isInformational() || $this->isEmpty()) {
 276              $this->setContent(null);
 277              $headers->remove('Content-Type');
 278              $headers->remove('Content-Length');
 279          } else {
 280              // Content-type based on the Request
 281              if (!$headers->has('Content-Type')) {
 282                  $format = $request->getRequestFormat();
 283                  if (null !== $format && $mimeType = $request->getMimeType($format)) {
 284                      $headers->set('Content-Type', $mimeType);
 285                  }
 286              }
 287  
 288              // Fix Content-Type
 289              $charset = $this->charset ?: 'UTF-8';
 290              if (!$headers->has('Content-Type')) {
 291                  $headers->set('Content-Type', 'text/html; charset='.$charset);
 292              } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) {
 293                  // add the charset
 294                  $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset);
 295              }
 296  
 297              // Fix Content-Length
 298              if ($headers->has('Transfer-Encoding')) {
 299                  $headers->remove('Content-Length');
 300              }
 301  
 302              if ($request->isMethod('HEAD')) {
 303                  // cf. RFC2616 14.13
 304                  $length = $headers->get('Content-Length');
 305                  $this->setContent(null);
 306                  if ($length) {
 307                      $headers->set('Content-Length', $length);
 308                  }
 309              }
 310          }
 311  
 312          // Fix protocol
 313          if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) {
 314              $this->setProtocolVersion('1.1');
 315          }
 316  
 317          // Check if we need to send extra expire info headers
 318          if ('1.0' == $this->getProtocolVersion() && 'no-cache' == $this->headers->get('Cache-Control')) {
 319              $this->headers->set('pragma', 'no-cache');
 320              $this->headers->set('expires', -1);
 321          }
 322  
 323          $this->ensureIEOverSSLCompatibility($request);
 324  
 325          return $this;
 326      }
 327  
 328      /**
 329       * Sends HTTP headers.
 330       *
 331       * @return $this
 332       */
 333      public function sendHeaders()
 334      {
 335          // headers have already been sent by the developer
 336          if (headers_sent()) {
 337              return $this;
 338          }
 339  
 340          /* RFC2616 - 14.18 says all Responses need to have a Date */
 341          if (!$this->headers->has('Date')) {
 342              $this->setDate(\DateTime::createFromFormat('U', time()));
 343          }
 344  
 345          // headers
 346          foreach ($this->headers->allPreserveCase() as $name => $values) {
 347              $replace = 0 === strcasecmp($name, 'Content-Type');
 348              foreach ($values as $value) {
 349                  header($name.': '.$value, $replace, $this->statusCode);
 350              }
 351          }
 352  
 353          // status
 354          header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
 355  
 356          // cookies
 357          foreach ($this->headers->getCookies() as $cookie) {
 358              setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly());
 359          }
 360  
 361          return $this;
 362      }
 363  
 364      /**
 365       * Sends content for the current web response.
 366       *
 367       * @return $this
 368       */
 369      public function sendContent()
 370      {
 371          echo $this->content;
 372  
 373          return $this;
 374      }
 375  
 376      /**
 377       * Sends HTTP headers and content.
 378       *
 379       * @return $this
 380       */
 381      public function send()
 382      {
 383          $this->sendHeaders();
 384          $this->sendContent();
 385  
 386          if (\function_exists('fastcgi_finish_request')) {
 387              fastcgi_finish_request();
 388          } elseif (!\in_array(\PHP_SAPI, array('cli', 'phpdbg'), true)) {
 389              static::closeOutputBuffers(0, true);
 390          }
 391  
 392          return $this;
 393      }
 394  
 395      /**
 396       * Sets the response content.
 397       *
 398       * Valid types are strings, numbers, null, and objects that implement a __toString() method.
 399       *
 400       * @param mixed $content Content that can be cast to string
 401       *
 402       * @return $this
 403       *
 404       * @throws \UnexpectedValueException
 405       */
 406      public function setContent($content)
 407      {
 408          if (null !== $content && !\is_string($content) && !is_numeric($content) && !\is_callable(array($content, '__toString'))) {
 409              throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', \gettype($content)));
 410          }
 411  
 412          $this->content = (string) $content;
 413  
 414          return $this;
 415      }
 416  
 417      /**
 418       * Gets the current response content.
 419       *
 420       * @return string Content
 421       */
 422      public function getContent()
 423      {
 424          return $this->content;
 425      }
 426  
 427      /**
 428       * Sets the HTTP protocol version (1.0 or 1.1).
 429       *
 430       * @param string $version The HTTP protocol version
 431       *
 432       * @return $this
 433       */
 434      public function setProtocolVersion($version)
 435      {
 436          $this->version = $version;
 437  
 438          return $this;
 439      }
 440  
 441      /**
 442       * Gets the HTTP protocol version.
 443       *
 444       * @return string The HTTP protocol version
 445       */
 446      public function getProtocolVersion()
 447      {
 448          return $this->version;
 449      }
 450  
 451      /**
 452       * Sets the response status code.
 453       *
 454       * If the status text is null it will be automatically populated for the known
 455       * status codes and left empty otherwise.
 456       *
 457       * @param int   $code HTTP status code
 458       * @param mixed $text HTTP status text
 459       *
 460       * @return $this
 461       *
 462       * @throws \InvalidArgumentException When the HTTP status code is not valid
 463       */
 464      public function setStatusCode($code, $text = null)
 465      {
 466          $this->statusCode = $code = (int) $code;
 467          if ($this->isInvalid()) {
 468              throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code));
 469          }
 470  
 471          if (null === $text) {
 472              $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : 'unknown status';
 473  
 474              return $this;
 475          }
 476  
 477          if (false === $text) {
 478              $this->statusText = '';
 479  
 480              return $this;
 481          }
 482  
 483          $this->statusText = $text;
 484  
 485          return $this;
 486      }
 487  
 488      /**
 489       * Retrieves the status code for the current web response.
 490       *
 491       * @return int Status code
 492       */
 493      public function getStatusCode()
 494      {
 495          return $this->statusCode;
 496      }
 497  
 498      /**
 499       * Sets the response charset.
 500       *
 501       * @param string $charset Character set
 502       *
 503       * @return $this
 504       */
 505      public function setCharset($charset)
 506      {
 507          $this->charset = $charset;
 508  
 509          return $this;
 510      }
 511  
 512      /**
 513       * Retrieves the response charset.
 514       *
 515       * @return string Character set
 516       */
 517      public function getCharset()
 518      {
 519          return $this->charset;
 520      }
 521  
 522      /**
 523       * Returns true if the response may safely be kept in a shared (surrogate) cache.
 524       *
 525       * Responses marked "private" with an explicit Cache-Control directive are
 526       * considered uncacheable.
 527       *
 528       * Responses with neither a freshness lifetime (Expires, max-age) nor cache
 529       * validator (Last-Modified, ETag) are considered uncacheable because there is
 530       * no way to tell when or how to remove them from the cache.
 531       *
 532       * Note that RFC 7231 and RFC 7234 possibly allow for a more permissive implementation,
 533       * for example "status codes that are defined as cacheable by default [...]
 534       * can be reused by a cache with heuristic expiration unless otherwise indicated"
 535       * (https://tools.ietf.org/html/rfc7231#section-6.1)
 536       *
 537       * @return bool true if the response is worth caching, false otherwise
 538       */
 539      public function isCacheable()
 540      {
 541          if (!\in_array($this->statusCode, array(200, 203, 300, 301, 302, 404, 410))) {
 542              return false;
 543          }
 544  
 545          if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) {
 546              return false;
 547          }
 548  
 549          return $this->isValidateable() || $this->isFresh();
 550      }
 551  
 552      /**
 553       * Returns true if the response is "fresh".
 554       *
 555       * Fresh responses may be served from cache without any interaction with the
 556       * origin. A response is considered fresh when it includes a Cache-Control/max-age
 557       * indicator or Expires header and the calculated age is less than the freshness lifetime.
 558       *
 559       * @return bool true if the response is fresh, false otherwise
 560       */
 561      public function isFresh()
 562      {
 563          return $this->getTtl() > 0;
 564      }
 565  
 566      /**
 567       * Returns true if the response includes headers that can be used to validate
 568       * the response with the origin server using a conditional GET request.
 569       *
 570       * @return bool true if the response is validateable, false otherwise
 571       */
 572      public function isValidateable()
 573      {
 574          return $this->headers->has('Last-Modified') || $this->headers->has('ETag');
 575      }
 576  
 577      /**
 578       * Marks the response as "private".
 579       *
 580       * It makes the response ineligible for serving other clients.
 581       *
 582       * @return $this
 583       */
 584      public function setPrivate()
 585      {
 586          $this->headers->removeCacheControlDirective('public');
 587          $this->headers->addCacheControlDirective('private');
 588  
 589          return $this;
 590      }
 591  
 592      /**
 593       * Marks the response as "public".
 594       *
 595       * It makes the response eligible for serving other clients.
 596       *
 597       * @return $this
 598       */
 599      public function setPublic()
 600      {
 601          $this->headers->addCacheControlDirective('public');
 602          $this->headers->removeCacheControlDirective('private');
 603  
 604          return $this;
 605      }
 606  
 607      /**
 608       * Returns true if the response must be revalidated by caches.
 609       *
 610       * This method indicates that the response must not be served stale by a
 611       * cache in any circumstance without first revalidating with the origin.
 612       * When present, the TTL of the response should not be overridden to be
 613       * greater than the value provided by the origin.
 614       *
 615       * @return bool true if the response must be revalidated by a cache, false otherwise
 616       */
 617      public function mustRevalidate()
 618      {
 619          return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate');
 620      }
 621  
 622      /**
 623       * Returns the Date header as a DateTime instance.
 624       *
 625       * @return \DateTime A \DateTime instance
 626       *
 627       * @throws \RuntimeException When the header is not parseable
 628       */
 629      public function getDate()
 630      {
 631          /*
 632              RFC2616 - 14.18 says all Responses need to have a Date.
 633              Make sure we provide one even if it the header
 634              has been removed in the meantime.
 635           */
 636          if (!$this->headers->has('Date')) {
 637              $this->setDate(\DateTime::createFromFormat('U', time()));
 638          }
 639  
 640          return $this->headers->getDate('Date');
 641      }
 642  
 643      /**
 644       * Sets the Date header.
 645       *
 646       * @return $this
 647       */
 648      public function setDate(\DateTime $date)
 649      {
 650          $date->setTimezone(new \DateTimeZone('UTC'));
 651          $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT');
 652  
 653          return $this;
 654      }
 655  
 656      /**
 657       * Returns the age of the response.
 658       *
 659       * @return int The age of the response in seconds
 660       */
 661      public function getAge()
 662      {
 663          if (null !== $age = $this->headers->get('Age')) {
 664              return (int) $age;
 665          }
 666  
 667          return max(time() - $this->getDate()->format('U'), 0);
 668      }
 669  
 670      /**
 671       * Marks the response stale by setting the Age header to be equal to the maximum age of the response.
 672       *
 673       * @return $this
 674       */
 675      public function expire()
 676      {
 677          if ($this->isFresh()) {
 678              $this->headers->set('Age', $this->getMaxAge());
 679              $this->headers->remove('Expires');
 680          }
 681  
 682          return $this;
 683      }
 684  
 685      /**
 686       * Returns the value of the Expires header as a DateTime instance.
 687       *
 688       * @return \DateTime|null A DateTime instance or null if the header does not exist
 689       */
 690      public function getExpires()
 691      {
 692          try {
 693              return $this->headers->getDate('Expires');
 694          } catch (\RuntimeException $e) {
 695              // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past
 696              return \DateTime::createFromFormat(DATE_RFC2822, 'Sat, 01 Jan 00 00:00:00 +0000');
 697          }
 698      }
 699  
 700      /**
 701       * Sets the Expires HTTP header with a DateTime instance.
 702       *
 703       * Passing null as value will remove the header.
 704       *
 705       * @param \DateTime|null $date A \DateTime instance or null to remove the header
 706       *
 707       * @return $this
 708       */
 709      public function setExpires(\DateTime $date = null)
 710      {
 711          if (null === $date) {
 712              $this->headers->remove('Expires');
 713          } else {
 714              $date = clone $date;
 715              $date->setTimezone(new \DateTimeZone('UTC'));
 716              $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT');
 717          }
 718  
 719          return $this;
 720      }
 721  
 722      /**
 723       * Returns the number of seconds after the time specified in the response's Date
 724       * header when the response should no longer be considered fresh.
 725       *
 726       * First, it checks for a s-maxage directive, then a max-age directive, and then it falls
 727       * back on an expires header. It returns null when no maximum age can be established.
 728       *
 729       * @return int|null Number of seconds
 730       */
 731      public function getMaxAge()
 732      {
 733          if ($this->headers->hasCacheControlDirective('s-maxage')) {
 734              return (int) $this->headers->getCacheControlDirective('s-maxage');
 735          }
 736  
 737          if ($this->headers->hasCacheControlDirective('max-age')) {
 738              return (int) $this->headers->getCacheControlDirective('max-age');
 739          }
 740  
 741          if (null !== $this->getExpires()) {
 742              return $this->getExpires()->format('U') - $this->getDate()->format('U');
 743          }
 744      }
 745  
 746      /**
 747       * Sets the number of seconds after which the response should no longer be considered fresh.
 748       *
 749       * This methods sets the Cache-Control max-age directive.
 750       *
 751       * @param int $value Number of seconds
 752       *
 753       * @return $this
 754       */
 755      public function setMaxAge($value)
 756      {
 757          $this->headers->addCacheControlDirective('max-age', $value);
 758  
 759          return $this;
 760      }
 761  
 762      /**
 763       * Sets the number of seconds after which the response should no longer be considered fresh by shared caches.
 764       *
 765       * This methods sets the Cache-Control s-maxage directive.
 766       *
 767       * @param int $value Number of seconds
 768       *
 769       * @return $this
 770       */
 771      public function setSharedMaxAge($value)
 772      {
 773          $this->setPublic();
 774          $this->headers->addCacheControlDirective('s-maxage', $value);
 775  
 776          return $this;
 777      }
 778  
 779      /**
 780       * Returns the response's time-to-live in seconds.
 781       *
 782       * It returns null when no freshness information is present in the response.
 783       *
 784       * When the responses TTL is <= 0, the response may not be served from cache without first
 785       * revalidating with the origin.
 786       *
 787       * @return int|null The TTL in seconds
 788       */
 789      public function getTtl()
 790      {
 791          if (null !== $maxAge = $this->getMaxAge()) {
 792              return $maxAge - $this->getAge();
 793          }
 794      }
 795  
 796      /**
 797       * Sets the response's time-to-live for shared caches.
 798       *
 799       * This method adjusts the Cache-Control/s-maxage directive.
 800       *
 801       * @param int $seconds Number of seconds
 802       *
 803       * @return $this
 804       */
 805      public function setTtl($seconds)
 806      {
 807          $this->setSharedMaxAge($this->getAge() + $seconds);
 808  
 809          return $this;
 810      }
 811  
 812      /**
 813       * Sets the response's time-to-live for private/client caches.
 814       *
 815       * This method adjusts the Cache-Control/max-age directive.
 816       *
 817       * @param int $seconds Number of seconds
 818       *
 819       * @return $this
 820       */
 821      public function setClientTtl($seconds)
 822      {
 823          $this->setMaxAge($this->getAge() + $seconds);
 824  
 825          return $this;
 826      }
 827  
 828      /**
 829       * Returns the Last-Modified HTTP header as a DateTime instance.
 830       *
 831       * @return \DateTime|null A DateTime instance or null if the header does not exist
 832       *
 833       * @throws \RuntimeException When the HTTP header is not parseable
 834       */
 835      public function getLastModified()
 836      {
 837          return $this->headers->getDate('Last-Modified');
 838      }
 839  
 840      /**
 841       * Sets the Last-Modified HTTP header with a DateTime instance.
 842       *
 843       * Passing null as value will remove the header.
 844       *
 845       * @param \DateTime|null $date A \DateTime instance or null to remove the header
 846       *
 847       * @return $this
 848       */
 849      public function setLastModified(\DateTime $date = null)
 850      {
 851          if (null === $date) {
 852              $this->headers->remove('Last-Modified');
 853          } else {
 854              $date = clone $date;
 855              $date->setTimezone(new \DateTimeZone('UTC'));
 856              $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT');
 857          }
 858  
 859          return $this;
 860      }
 861  
 862      /**
 863       * Returns the literal value of the ETag HTTP header.
 864       *
 865       * @return string|null The ETag HTTP header or null if it does not exist
 866       */
 867      public function getEtag()
 868      {
 869          return $this->headers->get('ETag');
 870      }
 871  
 872      /**
 873       * Sets the ETag value.
 874       *
 875       * @param string|null $etag The ETag unique identifier or null to remove the header
 876       * @param bool        $weak Whether you want a weak ETag or not
 877       *
 878       * @return $this
 879       */
 880      public function setEtag($etag = null, $weak = false)
 881      {
 882          if (null === $etag) {
 883              $this->headers->remove('Etag');
 884          } else {
 885              if (0 !== strpos($etag, '"')) {
 886                  $etag = '"'.$etag.'"';
 887              }
 888  
 889              $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag);
 890          }
 891  
 892          return $this;
 893      }
 894  
 895      /**
 896       * Sets the response's cache headers (validation and/or expiration).
 897       *
 898       * Available options are: etag, last_modified, max_age, s_maxage, private, and public.
 899       *
 900       * @param array $options An array of cache options
 901       *
 902       * @return $this
 903       *
 904       * @throws \InvalidArgumentException
 905       */
 906      public function setCache(array $options)
 907      {
 908          if ($diff = array_diff(array_keys($options), array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public'))) {
 909              throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', array_values($diff))));
 910          }
 911  
 912          if (isset($options['etag'])) {
 913              $this->setEtag($options['etag']);
 914          }
 915  
 916          if (isset($options['last_modified'])) {
 917              $this->setLastModified($options['last_modified']);
 918          }
 919  
 920          if (isset($options['max_age'])) {
 921              $this->setMaxAge($options['max_age']);
 922          }
 923  
 924          if (isset($options['s_maxage'])) {
 925              $this->setSharedMaxAge($options['s_maxage']);
 926          }
 927  
 928          if (isset($options['public'])) {
 929              if ($options['public']) {
 930                  $this->setPublic();
 931              } else {
 932                  $this->setPrivate();
 933              }
 934          }
 935  
 936          if (isset($options['private'])) {
 937              if ($options['private']) {
 938                  $this->setPrivate();
 939              } else {
 940                  $this->setPublic();
 941              }
 942          }
 943  
 944          return $this;
 945      }
 946  
 947      /**
 948       * Modifies the response so that it conforms to the rules defined for a 304 status code.
 949       *
 950       * This sets the status, removes the body, and discards any headers
 951       * that MUST NOT be included in 304 responses.
 952       *
 953       * @return $this
 954       *
 955       * @see http://tools.ietf.org/html/rfc2616#section-10.3.5
 956       */
 957      public function setNotModified()
 958      {
 959          $this->setStatusCode(304);
 960          $this->setContent(null);
 961  
 962          // remove headers that MUST NOT be included with 304 Not Modified responses
 963          foreach (array('Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified') as $header) {
 964              $this->headers->remove($header);
 965          }
 966  
 967          return $this;
 968      }
 969  
 970      /**
 971       * Returns true if the response includes a Vary header.
 972       *
 973       * @return bool true if the response includes a Vary header, false otherwise
 974       */
 975      public function hasVary()
 976      {
 977          return null !== $this->headers->get('Vary');
 978      }
 979  
 980      /**
 981       * Returns an array of header names given in the Vary header.
 982       *
 983       * @return array An array of Vary names
 984       */
 985      public function getVary()
 986      {
 987          if (!$vary = $this->headers->get('Vary', null, false)) {
 988              return array();
 989          }
 990  
 991          $ret = array();
 992          foreach ($vary as $item) {
 993              $ret = array_merge($ret, preg_split('/[\s,]+/', $item));
 994          }
 995  
 996          return $ret;
 997      }
 998  
 999      /**
1000       * Sets the Vary header.
1001       *
1002       * @param string|array $headers
1003       * @param bool         $replace Whether to replace the actual value or not (true by default)
1004       *
1005       * @return $this
1006       */
1007      public function setVary($headers, $replace = true)
1008      {
1009          $this->headers->set('Vary', $headers, $replace);
1010  
1011          return $this;
1012      }
1013  
1014      /**
1015       * Determines if the Response validators (ETag, Last-Modified) match
1016       * a conditional value specified in the Request.
1017       *
1018       * If the Response is not modified, it sets the status code to 304 and
1019       * removes the actual content by calling the setNotModified() method.
1020       *
1021       * @return bool true if the Response validators match the Request, false otherwise
1022       */
1023      public function isNotModified(Request $request)
1024      {
1025          if (!$request->isMethodCacheable()) {
1026              return false;
1027          }
1028  
1029          $notModified = false;
1030          $lastModified = $this->headers->get('Last-Modified');
1031          $modifiedSince = $request->headers->get('If-Modified-Since');
1032  
1033          if ($etags = $request->getETags()) {
1034              $notModified = \in_array($this->getEtag(), $etags) || \in_array('*', $etags);
1035          }
1036  
1037          if ($modifiedSince && $lastModified) {
1038              $notModified = strtotime($modifiedSince) >= strtotime($lastModified) && (!$etags || $notModified);
1039          }
1040  
1041          if ($notModified) {
1042              $this->setNotModified();
1043          }
1044  
1045          return $notModified;
1046      }
1047  
1048      /**
1049       * Is response invalid?
1050       *
1051       * @return bool
1052       *
1053       * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
1054       */
1055      public function isInvalid()
1056      {
1057          return $this->statusCode < 100 || $this->statusCode >= 600;
1058      }
1059  
1060      /**
1061       * Is response informative?
1062       *
1063       * @return bool
1064       */
1065      public function isInformational()
1066      {
1067          return $this->statusCode >= 100 && $this->statusCode < 200;
1068      }
1069  
1070      /**
1071       * Is response successful?
1072       *
1073       * @return bool
1074       */
1075      public function isSuccessful()
1076      {
1077          return $this->statusCode >= 200 && $this->statusCode < 300;
1078      }
1079  
1080      /**
1081       * Is the response a redirect?
1082       *
1083       * @return bool
1084       */
1085      public function isRedirection()
1086      {
1087          return $this->statusCode >= 300 && $this->statusCode < 400;
1088      }
1089  
1090      /**
1091       * Is there a client error?
1092       *
1093       * @return bool
1094       */
1095      public function isClientError()
1096      {
1097          return $this->statusCode >= 400 && $this->statusCode < 500;
1098      }
1099  
1100      /**
1101       * Was there a server side error?
1102       *
1103       * @return bool
1104       */
1105      public function isServerError()
1106      {
1107          return $this->statusCode >= 500 && $this->statusCode < 600;
1108      }
1109  
1110      /**
1111       * Is the response OK?
1112       *
1113       * @return bool
1114       */
1115      public function isOk()
1116      {
1117          return 200 === $this->statusCode;
1118      }
1119  
1120      /**
1121       * Is the response forbidden?
1122       *
1123       * @return bool
1124       */
1125      public function isForbidden()
1126      {
1127          return 403 === $this->statusCode;
1128      }
1129  
1130      /**
1131       * Is the response a not found error?
1132       *
1133       * @return bool
1134       */
1135      public function isNotFound()
1136      {
1137          return 404 === $this->statusCode;
1138      }
1139  
1140      /**
1141       * Is the response a redirect of some form?
1142       *
1143       * @param string $location
1144       *
1145       * @return bool
1146       */
1147      public function isRedirect($location = null)
1148      {
1149          return \in_array($this->statusCode, array(201, 301, 302, 303, 307, 308)) && (null === $location ?: $location == $this->headers->get('Location'));
1150      }
1151  
1152      /**
1153       * Is the response empty?
1154       *
1155       * @return bool
1156       */
1157      public function isEmpty()
1158      {
1159          return \in_array($this->statusCode, array(204, 304));
1160      }
1161  
1162      /**
1163       * Cleans or flushes output buffers up to target level.
1164       *
1165       * Resulting level can be greater than target level if a non-removable buffer has been encountered.
1166       *
1167       * @param int  $targetLevel The target output buffering level
1168       * @param bool $flush       Whether to flush or clean the buffers
1169       */
1170      public static function closeOutputBuffers($targetLevel, $flush)
1171      {
1172          $status = ob_get_status(true);
1173          $level = \count($status);
1174          $flags = \defined('PHP_OUTPUT_HANDLER_REMOVABLE') ? PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE) : -1;
1175  
1176          while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || ($s['flags'] & $flags) === $flags : $s['del'])) {
1177              if ($flush) {
1178                  ob_end_flush();
1179              } else {
1180                  ob_end_clean();
1181              }
1182          }
1183      }
1184  
1185      /**
1186       * Checks if we need to remove Cache-Control for SSL encrypted downloads when using IE < 9.
1187       *
1188       * @see http://support.microsoft.com/kb/323308
1189       */
1190      protected function ensureIEOverSSLCompatibility(Request $request)
1191      {
1192          if (false !== stripos($this->headers->get('Content-Disposition'), 'attachment') && 1 == preg_match('/MSIE (.*?);/i', $request->server->get('HTTP_USER_AGENT'), $match) && true === $request->isSecure()) {
1193              if ((int) preg_replace('/(MSIE )(.*?);/', '$2', $match[0]) < 9) {
1194                  $this->headers->remove('Cache-Control');
1195              }
1196          }
1197      }
1198  }


Generated: Wed Nov 11 20:33:01 2020 Cross-referenced by PHPXref 0.7.1