[ Index ]

PHP Cross Reference of phpBB-3.3.0-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 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 https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml 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 = [
 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 = [])
 204      {
 205          $this->headers = new ResponseHeaderBag($headers);
 206          $this->setContent($content);
 207          $this->setStatusCode($status);
 208          $this->setProtocolVersion('1.0');
 209      }
 210  
 211      /**
 212       * Factory method for chainability.
 213       *
 214       * Example:
 215       *
 216       *     return Response::create($body, 200)
 217       *         ->setSharedMaxAge(300);
 218       *
 219       * @param mixed $content The response content, see setContent()
 220       * @param int   $status  The response status code
 221       * @param array $headers An array of response headers
 222       *
 223       * @return static
 224       */
 225      public static function create($content = '', $status = 200, $headers = [])
 226      {
 227          return new static($content, $status, $headers);
 228      }
 229  
 230      /**
 231       * Returns the Response as an HTTP string.
 232       *
 233       * The string representation of the Response is the same as the
 234       * one that will be sent to the client only if the prepare() method
 235       * has been called before.
 236       *
 237       * @return string The Response as an HTTP string
 238       *
 239       * @see prepare()
 240       */
 241      public function __toString()
 242      {
 243          return
 244              sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n".
 245              $this->headers."\r\n".
 246              $this->getContent();
 247      }
 248  
 249      /**
 250       * Clones the current Response instance.
 251       */
 252      public function __clone()
 253      {
 254          $this->headers = clone $this->headers;
 255      }
 256  
 257      /**
 258       * Prepares the Response before it is sent to the client.
 259       *
 260       * This method tweaks the Response to ensure that it is
 261       * compliant with RFC 2616. Most of the changes are based on
 262       * the Request that is "associated" with this Response.
 263       *
 264       * @return $this
 265       */
 266      public function prepare(Request $request)
 267      {
 268          $headers = $this->headers;
 269  
 270          if ($this->isInformational() || $this->isEmpty()) {
 271              $this->setContent(null);
 272              $headers->remove('Content-Type');
 273              $headers->remove('Content-Length');
 274          } else {
 275              // Content-type based on the Request
 276              if (!$headers->has('Content-Type')) {
 277                  $format = $request->getRequestFormat();
 278                  if (null !== $format && $mimeType = $request->getMimeType($format)) {
 279                      $headers->set('Content-Type', $mimeType);
 280                  }
 281              }
 282  
 283              // Fix Content-Type
 284              $charset = $this->charset ?: 'UTF-8';
 285              if (!$headers->has('Content-Type')) {
 286                  $headers->set('Content-Type', 'text/html; charset='.$charset);
 287              } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) {
 288                  // add the charset
 289                  $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset);
 290              }
 291  
 292              // Fix Content-Length
 293              if ($headers->has('Transfer-Encoding')) {
 294                  $headers->remove('Content-Length');
 295              }
 296  
 297              if ($request->isMethod('HEAD')) {
 298                  // cf. RFC2616 14.13
 299                  $length = $headers->get('Content-Length');
 300                  $this->setContent(null);
 301                  if ($length) {
 302                      $headers->set('Content-Length', $length);
 303                  }
 304              }
 305          }
 306  
 307          // Fix protocol
 308          if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) {
 309              $this->setProtocolVersion('1.1');
 310          }
 311  
 312          // Check if we need to send extra expire info headers
 313          if ('1.0' == $this->getProtocolVersion() && false !== strpos($headers->get('Cache-Control'), 'no-cache')) {
 314              $headers->set('pragma', 'no-cache');
 315              $headers->set('expires', -1);
 316          }
 317  
 318          $this->ensureIEOverSSLCompatibility($request);
 319  
 320          return $this;
 321      }
 322  
 323      /**
 324       * Sends HTTP headers.
 325       *
 326       * @return $this
 327       */
 328      public function sendHeaders()
 329      {
 330          // headers have already been sent by the developer
 331          if (headers_sent()) {
 332              return $this;
 333          }
 334  
 335          // headers
 336          foreach ($this->headers->allPreserveCaseWithoutCookies() as $name => $values) {
 337              $replace = 0 === strcasecmp($name, 'Content-Type');
 338              foreach ($values as $value) {
 339                  header($name.': '.$value, $replace, $this->statusCode);
 340              }
 341          }
 342  
 343          // cookies
 344          foreach ($this->headers->getCookies() as $cookie) {
 345              header('Set-Cookie: '.$cookie, false, $this->statusCode);
 346          }
 347  
 348          // status
 349          header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);
 350  
 351          return $this;
 352      }
 353  
 354      /**
 355       * Sends content for the current web response.
 356       *
 357       * @return $this
 358       */
 359      public function sendContent()
 360      {
 361          echo $this->content;
 362  
 363          return $this;
 364      }
 365  
 366      /**
 367       * Sends HTTP headers and content.
 368       *
 369       * @return $this
 370       */
 371      public function send()
 372      {
 373          $this->sendHeaders();
 374          $this->sendContent();
 375  
 376          if (\function_exists('fastcgi_finish_request')) {
 377              fastcgi_finish_request();
 378          } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
 379              static::closeOutputBuffers(0, true);
 380          }
 381  
 382          return $this;
 383      }
 384  
 385      /**
 386       * Sets the response content.
 387       *
 388       * Valid types are strings, numbers, null, and objects that implement a __toString() method.
 389       *
 390       * @param mixed $content Content that can be cast to string
 391       *
 392       * @return $this
 393       *
 394       * @throws \UnexpectedValueException
 395       */
 396      public function setContent($content)
 397      {
 398          if (null !== $content && !\is_string($content) && !is_numeric($content) && !\is_callable([$content, '__toString'])) {
 399              throw new \UnexpectedValueException(sprintf('The Response content must be a string or object implementing __toString(), "%s" given.', \gettype($content)));
 400          }
 401  
 402          $this->content = (string) $content;
 403  
 404          return $this;
 405      }
 406  
 407      /**
 408       * Gets the current response content.
 409       *
 410       * @return string|false
 411       */
 412      public function getContent()
 413      {
 414          return $this->content;
 415      }
 416  
 417      /**
 418       * Sets the HTTP protocol version (1.0 or 1.1).
 419       *
 420       * @param string $version The HTTP protocol version
 421       *
 422       * @return $this
 423       *
 424       * @final since version 3.2
 425       */
 426      public function setProtocolVersion($version)
 427      {
 428          $this->version = $version;
 429  
 430          return $this;
 431      }
 432  
 433      /**
 434       * Gets the HTTP protocol version.
 435       *
 436       * @return string The HTTP protocol version
 437       *
 438       * @final since version 3.2
 439       */
 440      public function getProtocolVersion()
 441      {
 442          return $this->version;
 443      }
 444  
 445      /**
 446       * Sets the response status code.
 447       *
 448       * If the status text is null it will be automatically populated for the known
 449       * status codes and left empty otherwise.
 450       *
 451       * @param int   $code HTTP status code
 452       * @param mixed $text HTTP status text
 453       *
 454       * @return $this
 455       *
 456       * @throws \InvalidArgumentException When the HTTP status code is not valid
 457       *
 458       * @final since version 3.2
 459       */
 460      public function setStatusCode($code, $text = null)
 461      {
 462          $this->statusCode = $code = (int) $code;
 463          if ($this->isInvalid()) {
 464              throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code));
 465          }
 466  
 467          if (null === $text) {
 468              $this->statusText = isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : 'unknown status';
 469  
 470              return $this;
 471          }
 472  
 473          if (false === $text) {
 474              $this->statusText = '';
 475  
 476              return $this;
 477          }
 478  
 479          $this->statusText = $text;
 480  
 481          return $this;
 482      }
 483  
 484      /**
 485       * Retrieves the status code for the current web response.
 486       *
 487       * @return int Status code
 488       *
 489       * @final since version 3.2
 490       */
 491      public function getStatusCode()
 492      {
 493          return $this->statusCode;
 494      }
 495  
 496      /**
 497       * Sets the response charset.
 498       *
 499       * @param string $charset Character set
 500       *
 501       * @return $this
 502       *
 503       * @final since version 3.2
 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       * @final since version 3.2
 518       */
 519      public function getCharset()
 520      {
 521          return $this->charset;
 522      }
 523  
 524      /**
 525       * Returns true if the response may safely be kept in a shared (surrogate) cache.
 526       *
 527       * Responses marked "private" with an explicit Cache-Control directive are
 528       * considered uncacheable.
 529       *
 530       * Responses with neither a freshness lifetime (Expires, max-age) nor cache
 531       * validator (Last-Modified, ETag) are considered uncacheable because there is
 532       * no way to tell when or how to remove them from the cache.
 533       *
 534       * Note that RFC 7231 and RFC 7234 possibly allow for a more permissive implementation,
 535       * for example "status codes that are defined as cacheable by default [...]
 536       * can be reused by a cache with heuristic expiration unless otherwise indicated"
 537       * (https://tools.ietf.org/html/rfc7231#section-6.1)
 538       *
 539       * @return bool true if the response is worth caching, false otherwise
 540       *
 541       * @final since version 3.3
 542       */
 543      public function isCacheable()
 544      {
 545          if (!\in_array($this->statusCode, [200, 203, 300, 301, 302, 404, 410])) {
 546              return false;
 547          }
 548  
 549          if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) {
 550              return false;
 551          }
 552  
 553          return $this->isValidateable() || $this->isFresh();
 554      }
 555  
 556      /**
 557       * Returns true if the response is "fresh".
 558       *
 559       * Fresh responses may be served from cache without any interaction with the
 560       * origin. A response is considered fresh when it includes a Cache-Control/max-age
 561       * indicator or Expires header and the calculated age is less than the freshness lifetime.
 562       *
 563       * @return bool true if the response is fresh, false otherwise
 564       *
 565       * @final since version 3.3
 566       */
 567      public function isFresh()
 568      {
 569          return $this->getTtl() > 0;
 570      }
 571  
 572      /**
 573       * Returns true if the response includes headers that can be used to validate
 574       * the response with the origin server using a conditional GET request.
 575       *
 576       * @return bool true if the response is validateable, false otherwise
 577       *
 578       * @final since version 3.3
 579       */
 580      public function isValidateable()
 581      {
 582          return $this->headers->has('Last-Modified') || $this->headers->has('ETag');
 583      }
 584  
 585      /**
 586       * Marks the response as "private".
 587       *
 588       * It makes the response ineligible for serving other clients.
 589       *
 590       * @return $this
 591       *
 592       * @final since version 3.2
 593       */
 594      public function setPrivate()
 595      {
 596          $this->headers->removeCacheControlDirective('public');
 597          $this->headers->addCacheControlDirective('private');
 598  
 599          return $this;
 600      }
 601  
 602      /**
 603       * Marks the response as "public".
 604       *
 605       * It makes the response eligible for serving other clients.
 606       *
 607       * @return $this
 608       *
 609       * @final since version 3.2
 610       */
 611      public function setPublic()
 612      {
 613          $this->headers->addCacheControlDirective('public');
 614          $this->headers->removeCacheControlDirective('private');
 615  
 616          return $this;
 617      }
 618  
 619      /**
 620       * Marks the response as "immutable".
 621       *
 622       * @param bool $immutable enables or disables the immutable directive
 623       *
 624       * @return $this
 625       *
 626       * @final
 627       */
 628      public function setImmutable($immutable = true)
 629      {
 630          if ($immutable) {
 631              $this->headers->addCacheControlDirective('immutable');
 632          } else {
 633              $this->headers->removeCacheControlDirective('immutable');
 634          }
 635  
 636          return $this;
 637      }
 638  
 639      /**
 640       * Returns true if the response is marked as "immutable".
 641       *
 642       * @return bool returns true if the response is marked as "immutable"; otherwise false
 643       *
 644       * @final
 645       */
 646      public function isImmutable()
 647      {
 648          return $this->headers->hasCacheControlDirective('immutable');
 649      }
 650  
 651      /**
 652       * Returns true if the response must be revalidated by caches.
 653       *
 654       * This method indicates that the response must not be served stale by a
 655       * cache in any circumstance without first revalidating with the origin.
 656       * When present, the TTL of the response should not be overridden to be
 657       * greater than the value provided by the origin.
 658       *
 659       * @return bool true if the response must be revalidated by a cache, false otherwise
 660       *
 661       * @final since version 3.3
 662       */
 663      public function mustRevalidate()
 664      {
 665          return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->hasCacheControlDirective('proxy-revalidate');
 666      }
 667  
 668      /**
 669       * Returns the Date header as a DateTime instance.
 670       *
 671       * @return \DateTime A \DateTime instance
 672       *
 673       * @throws \RuntimeException When the header is not parseable
 674       *
 675       * @final since version 3.2
 676       */
 677      public function getDate()
 678      {
 679          return $this->headers->getDate('Date');
 680      }
 681  
 682      /**
 683       * Sets the Date header.
 684       *
 685       * @return $this
 686       *
 687       * @final since version 3.2
 688       */
 689      public function setDate(\DateTime $date)
 690      {
 691          $date->setTimezone(new \DateTimeZone('UTC'));
 692          $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT');
 693  
 694          return $this;
 695      }
 696  
 697      /**
 698       * Returns the age of the response.
 699       *
 700       * @return int The age of the response in seconds
 701       *
 702       * @final since version 3.2
 703       */
 704      public function getAge()
 705      {
 706          if (null !== $age = $this->headers->get('Age')) {
 707              return (int) $age;
 708          }
 709  
 710          return max(time() - (int) $this->getDate()->format('U'), 0);
 711      }
 712  
 713      /**
 714       * Marks the response stale by setting the Age header to be equal to the maximum age of the response.
 715       *
 716       * @return $this
 717       */
 718      public function expire()
 719      {
 720          if ($this->isFresh()) {
 721              $this->headers->set('Age', $this->getMaxAge());
 722              $this->headers->remove('Expires');
 723          }
 724  
 725          return $this;
 726      }
 727  
 728      /**
 729       * Returns the value of the Expires header as a DateTime instance.
 730       *
 731       * @return \DateTime|null A DateTime instance or null if the header does not exist
 732       *
 733       * @final since version 3.2
 734       */
 735      public function getExpires()
 736      {
 737          try {
 738              return $this->headers->getDate('Expires');
 739          } catch (\RuntimeException $e) {
 740              // according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past
 741              return \DateTime::createFromFormat(DATE_RFC2822, 'Sat, 01 Jan 00 00:00:00 +0000');
 742          }
 743      }
 744  
 745      /**
 746       * Sets the Expires HTTP header with a DateTime instance.
 747       *
 748       * Passing null as value will remove the header.
 749       *
 750       * @param \DateTime|null $date A \DateTime instance or null to remove the header
 751       *
 752       * @return $this
 753       *
 754       * @final since version 3.2
 755       */
 756      public function setExpires(\DateTime $date = null)
 757      {
 758          if (null === $date) {
 759              $this->headers->remove('Expires');
 760          } else {
 761              $date = clone $date;
 762              $date->setTimezone(new \DateTimeZone('UTC'));
 763              $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT');
 764          }
 765  
 766          return $this;
 767      }
 768  
 769      /**
 770       * Returns the number of seconds after the time specified in the response's Date
 771       * header when the response should no longer be considered fresh.
 772       *
 773       * First, it checks for a s-maxage directive, then a max-age directive, and then it falls
 774       * back on an expires header. It returns null when no maximum age can be established.
 775       *
 776       * @return int|null Number of seconds
 777       *
 778       * @final since version 3.2
 779       */
 780      public function getMaxAge()
 781      {
 782          if ($this->headers->hasCacheControlDirective('s-maxage')) {
 783              return (int) $this->headers->getCacheControlDirective('s-maxage');
 784          }
 785  
 786          if ($this->headers->hasCacheControlDirective('max-age')) {
 787              return (int) $this->headers->getCacheControlDirective('max-age');
 788          }
 789  
 790          if (null !== $this->getExpires()) {
 791              return (int) $this->getExpires()->format('U') - (int) $this->getDate()->format('U');
 792          }
 793  
 794          return null;
 795      }
 796  
 797      /**
 798       * Sets the number of seconds after which the response should no longer be considered fresh.
 799       *
 800       * This methods sets the Cache-Control max-age directive.
 801       *
 802       * @param int $value Number of seconds
 803       *
 804       * @return $this
 805       *
 806       * @final since version 3.2
 807       */
 808      public function setMaxAge($value)
 809      {
 810          $this->headers->addCacheControlDirective('max-age', $value);
 811  
 812          return $this;
 813      }
 814  
 815      /**
 816       * Sets the number of seconds after which the response should no longer be considered fresh by shared caches.
 817       *
 818       * This methods sets the Cache-Control s-maxage directive.
 819       *
 820       * @param int $value Number of seconds
 821       *
 822       * @return $this
 823       *
 824       * @final since version 3.2
 825       */
 826      public function setSharedMaxAge($value)
 827      {
 828          $this->setPublic();
 829          $this->headers->addCacheControlDirective('s-maxage', $value);
 830  
 831          return $this;
 832      }
 833  
 834      /**
 835       * Returns the response's time-to-live in seconds.
 836       *
 837       * It returns null when no freshness information is present in the response.
 838       *
 839       * When the responses TTL is <= 0, the response may not be served from cache without first
 840       * revalidating with the origin.
 841       *
 842       * @return int|null The TTL in seconds
 843       *
 844       * @final since version 3.2
 845       */
 846      public function getTtl()
 847      {
 848          if (null !== $maxAge = $this->getMaxAge()) {
 849              return $maxAge - $this->getAge();
 850          }
 851  
 852          return null;
 853      }
 854  
 855      /**
 856       * Sets the response's time-to-live for shared caches.
 857       *
 858       * This method adjusts the Cache-Control/s-maxage directive.
 859       *
 860       * @param int $seconds Number of seconds
 861       *
 862       * @return $this
 863       *
 864       * @final since version 3.2
 865       */
 866      public function setTtl($seconds)
 867      {
 868          $this->setSharedMaxAge($this->getAge() + $seconds);
 869  
 870          return $this;
 871      }
 872  
 873      /**
 874       * Sets the response's time-to-live for private/client caches.
 875       *
 876       * This method adjusts the Cache-Control/max-age directive.
 877       *
 878       * @param int $seconds Number of seconds
 879       *
 880       * @return $this
 881       *
 882       * @final since version 3.2
 883       */
 884      public function setClientTtl($seconds)
 885      {
 886          $this->setMaxAge($this->getAge() + $seconds);
 887  
 888          return $this;
 889      }
 890  
 891      /**
 892       * Returns the Last-Modified HTTP header as a DateTime instance.
 893       *
 894       * @return \DateTime|null A DateTime instance or null if the header does not exist
 895       *
 896       * @throws \RuntimeException When the HTTP header is not parseable
 897       *
 898       * @final since version 3.2
 899       */
 900      public function getLastModified()
 901      {
 902          return $this->headers->getDate('Last-Modified');
 903      }
 904  
 905      /**
 906       * Sets the Last-Modified HTTP header with a DateTime instance.
 907       *
 908       * Passing null as value will remove the header.
 909       *
 910       * @param \DateTime|null $date A \DateTime instance or null to remove the header
 911       *
 912       * @return $this
 913       *
 914       * @final since version 3.2
 915       */
 916      public function setLastModified(\DateTime $date = null)
 917      {
 918          if (null === $date) {
 919              $this->headers->remove('Last-Modified');
 920          } else {
 921              $date = clone $date;
 922              $date->setTimezone(new \DateTimeZone('UTC'));
 923              $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT');
 924          }
 925  
 926          return $this;
 927      }
 928  
 929      /**
 930       * Returns the literal value of the ETag HTTP header.
 931       *
 932       * @return string|null The ETag HTTP header or null if it does not exist
 933       *
 934       * @final since version 3.2
 935       */
 936      public function getEtag()
 937      {
 938          return $this->headers->get('ETag');
 939      }
 940  
 941      /**
 942       * Sets the ETag value.
 943       *
 944       * @param string|null $etag The ETag unique identifier or null to remove the header
 945       * @param bool        $weak Whether you want a weak ETag or not
 946       *
 947       * @return $this
 948       *
 949       * @final since version 3.2
 950       */
 951      public function setEtag($etag = null, $weak = false)
 952      {
 953          if (null === $etag) {
 954              $this->headers->remove('Etag');
 955          } else {
 956              if (0 !== strpos($etag, '"')) {
 957                  $etag = '"'.$etag.'"';
 958              }
 959  
 960              $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag);
 961          }
 962  
 963          return $this;
 964      }
 965  
 966      /**
 967       * Sets the response's cache headers (validation and/or expiration).
 968       *
 969       * Available options are: etag, last_modified, max_age, s_maxage, private, public and immutable.
 970       *
 971       * @param array $options An array of cache options
 972       *
 973       * @return $this
 974       *
 975       * @throws \InvalidArgumentException
 976       *
 977       * @final since version 3.3
 978       */
 979      public function setCache(array $options)
 980      {
 981          if ($diff = array_diff(array_keys($options), ['etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public', 'immutable'])) {
 982              throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', $diff)));
 983          }
 984  
 985          if (isset($options['etag'])) {
 986              $this->setEtag($options['etag']);
 987          }
 988  
 989          if (isset($options['last_modified'])) {
 990              $this->setLastModified($options['last_modified']);
 991          }
 992  
 993          if (isset($options['max_age'])) {
 994              $this->setMaxAge($options['max_age']);
 995          }
 996  
 997          if (isset($options['s_maxage'])) {
 998              $this->setSharedMaxAge($options['s_maxage']);
 999          }
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  }


Generated: Tue Apr 7 19:44:41 2020 Cross-referenced by PHPXref 0.7.1