2015-08-18 00:00:26 +00:00
< ? php
/*
* This file is part of the Symfony package .
*
* ( c ) Fabien Potencier < fabien @ symfony . com >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Symfony\Component\HttpFoundation ;
/**
* Response represents an HTTP response .
*
* @ author Fabien Potencier < fabien @ symfony . com >
*
* @ api
*/
class Response
{
const HTTP_CONTINUE = 100 ;
const HTTP_SWITCHING_PROTOCOLS = 101 ;
const HTTP_PROCESSING = 102 ; // RFC2518
const HTTP_OK = 200 ;
const HTTP_CREATED = 201 ;
const HTTP_ACCEPTED = 202 ;
const HTTP_NON_AUTHORITATIVE_INFORMATION = 203 ;
const HTTP_NO_CONTENT = 204 ;
const HTTP_RESET_CONTENT = 205 ;
const HTTP_PARTIAL_CONTENT = 206 ;
const HTTP_MULTI_STATUS = 207 ; // RFC4918
const HTTP_ALREADY_REPORTED = 208 ; // RFC5842
const HTTP_IM_USED = 226 ; // RFC3229
const HTTP_MULTIPLE_CHOICES = 300 ;
const HTTP_MOVED_PERMANENTLY = 301 ;
const HTTP_FOUND = 302 ;
const HTTP_SEE_OTHER = 303 ;
const HTTP_NOT_MODIFIED = 304 ;
const HTTP_USE_PROXY = 305 ;
const HTTP_RESERVED = 306 ;
const HTTP_TEMPORARY_REDIRECT = 307 ;
const HTTP_PERMANENTLY_REDIRECT = 308 ; // RFC7238
const HTTP_BAD_REQUEST = 400 ;
const HTTP_UNAUTHORIZED = 401 ;
const HTTP_PAYMENT_REQUIRED = 402 ;
const HTTP_FORBIDDEN = 403 ;
const HTTP_NOT_FOUND = 404 ;
const HTTP_METHOD_NOT_ALLOWED = 405 ;
const HTTP_NOT_ACCEPTABLE = 406 ;
const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407 ;
const HTTP_REQUEST_TIMEOUT = 408 ;
const HTTP_CONFLICT = 409 ;
const HTTP_GONE = 410 ;
const HTTP_LENGTH_REQUIRED = 411 ;
const HTTP_PRECONDITION_FAILED = 412 ;
const HTTP_REQUEST_ENTITY_TOO_LARGE = 413 ;
const HTTP_REQUEST_URI_TOO_LONG = 414 ;
const HTTP_UNSUPPORTED_MEDIA_TYPE = 415 ;
const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416 ;
const HTTP_EXPECTATION_FAILED = 417 ;
const HTTP_I_AM_A_TEAPOT = 418 ; // RFC2324
const HTTP_UNPROCESSABLE_ENTITY = 422 ; // RFC4918
const HTTP_LOCKED = 423 ; // RFC4918
const HTTP_FAILED_DEPENDENCY = 424 ; // RFC4918
const HTTP_RESERVED_FOR_WEBDAV_ADVANCED_COLLECTIONS_EXPIRED_PROPOSAL = 425 ; // RFC2817
const HTTP_UPGRADE_REQUIRED = 426 ; // RFC2817
const HTTP_PRECONDITION_REQUIRED = 428 ; // RFC6585
const HTTP_TOO_MANY_REQUESTS = 429 ; // RFC6585
const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431 ; // RFC6585
const HTTP_INTERNAL_SERVER_ERROR = 500 ;
const HTTP_NOT_IMPLEMENTED = 501 ;
const HTTP_BAD_GATEWAY = 502 ;
const HTTP_SERVICE_UNAVAILABLE = 503 ;
const HTTP_GATEWAY_TIMEOUT = 504 ;
const HTTP_VERSION_NOT_SUPPORTED = 505 ;
const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506 ; // RFC2295
const HTTP_INSUFFICIENT_STORAGE = 507 ; // RFC4918
const HTTP_LOOP_DETECTED = 508 ; // RFC5842
const HTTP_NOT_EXTENDED = 510 ; // RFC2774
const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511 ; // RFC6585
/**
* @ var \Symfony\Component\HttpFoundation\ResponseHeaderBag
*/
public $headers ;
/**
* @ var string
*/
protected $content ;
/**
* @ var string
*/
protected $version ;
/**
* @ var int
*/
protected $statusCode ;
/**
* @ var string
*/
protected $statusText ;
/**
* @ var string
*/
protected $charset ;
/**
* Status codes translation table .
*
* The list of codes is complete according to the
* { @ link http :// www . iana . org / assignments / http - status - codes / Hypertext Transfer Protocol ( HTTP ) Status Code Registry }
* ( last updated 2012 - 02 - 13 ) .
*
* Unless otherwise noted , the status code is defined in RFC2616 .
*
* @ var array
*/
public static $statusTexts = array (
100 => 'Continue' ,
101 => 'Switching Protocols' ,
102 => 'Processing' , // RFC2518
200 => 'OK' ,
201 => 'Created' ,
202 => 'Accepted' ,
203 => 'Non-Authoritative Information' ,
204 => 'No Content' ,
205 => 'Reset Content' ,
206 => 'Partial Content' ,
207 => 'Multi-Status' , // RFC4918
208 => 'Already Reported' , // RFC5842
226 => 'IM Used' , // RFC3229
300 => 'Multiple Choices' ,
301 => 'Moved Permanently' ,
302 => 'Found' ,
303 => 'See Other' ,
304 => 'Not Modified' ,
305 => 'Use Proxy' ,
306 => 'Reserved' ,
307 => 'Temporary Redirect' ,
308 => 'Permanent Redirect' , // RFC7238
400 => 'Bad Request' ,
401 => 'Unauthorized' ,
402 => 'Payment Required' ,
403 => 'Forbidden' ,
404 => 'Not Found' ,
405 => 'Method Not Allowed' ,
406 => 'Not Acceptable' ,
407 => 'Proxy Authentication Required' ,
408 => 'Request Timeout' ,
409 => 'Conflict' ,
410 => 'Gone' ,
411 => 'Length Required' ,
412 => 'Precondition Failed' ,
413 => 'Request Entity Too Large' ,
414 => 'Request-URI Too Long' ,
415 => 'Unsupported Media Type' ,
416 => 'Requested Range Not Satisfiable' ,
417 => 'Expectation Failed' ,
418 => 'I\'m a teapot' , // RFC2324
422 => 'Unprocessable Entity' , // RFC4918
423 => 'Locked' , // RFC4918
424 => 'Failed Dependency' , // RFC4918
425 => 'Reserved for WebDAV advanced collections expired proposal' , // RFC2817
426 => 'Upgrade Required' , // RFC2817
428 => 'Precondition Required' , // RFC6585
429 => 'Too Many Requests' , // RFC6585
431 => 'Request Header Fields Too Large' , // RFC6585
500 => 'Internal Server Error' ,
501 => 'Not Implemented' ,
502 => 'Bad Gateway' ,
503 => 'Service Unavailable' ,
504 => 'Gateway Timeout' ,
505 => 'HTTP Version Not Supported' ,
506 => 'Variant Also Negotiates (Experimental)' , // RFC2295
507 => 'Insufficient Storage' , // RFC4918
508 => 'Loop Detected' , // RFC5842
510 => 'Not Extended' , // RFC2774
511 => 'Network Authentication Required' , // RFC6585
);
/**
* Constructor .
*
* @ param mixed $content The response content , see setContent ()
* @ param int $status The response status code
* @ param array $headers An array of response headers
*
* @ throws \InvalidArgumentException When the HTTP status code is not valid
*
* @ api
*/
public function __construct ( $content = '' , $status = 200 , $headers = array ())
{
$this -> headers = new ResponseHeaderBag ( $headers );
$this -> setContent ( $content );
$this -> setStatusCode ( $status );
$this -> setProtocolVersion ( '1.0' );
if ( ! $this -> headers -> has ( 'Date' )) {
$this -> setDate ( new \DateTime ( null , new \DateTimeZone ( 'UTC' )));
}
}
/**
* Factory method for chainability .
*
* Example :
*
* return Response :: create ( $body , 200 )
* -> setSharedMaxAge ( 300 );
*
* @ param mixed $content The response content , see setContent ()
* @ param int $status The response status code
* @ param array $headers An array of response headers
*
* @ return Response
*/
public static function create ( $content = '' , $status = 200 , $headers = array ())
{
return new static ( $content , $status , $headers );
}
/**
* Returns the Response as an HTTP string .
*
* The string representation of the Response is the same as the
* one that will be sent to the client only if the prepare () method
* has been called before .
*
* @ return string The Response as an HTTP string
*
* @ see prepare ()
*/
public function __toString ()
{
return
sprintf ( 'HTTP/%s %s %s' , $this -> version , $this -> statusCode , $this -> statusText ) . " \r \n " .
$this -> headers . " \r \n " .
$this -> getContent ();
}
/**
* Clones the current Response instance .
*/
public function __clone ()
{
$this -> headers = clone $this -> headers ;
}
/**
* Prepares the Response before it is sent to the client .
*
* This method tweaks the Response to ensure that it is
* compliant with RFC 2616. Most of the changes are based on
* the Request that is " associated " with this Response .
*
* @ param Request $request A Request instance
*
* @ return Response The current response .
*/
public function prepare ( Request $request )
{
$headers = $this -> headers ;
if ( $this -> isInformational () || $this -> isEmpty ()) {
$this -> setContent ( null );
$headers -> remove ( 'Content-Type' );
$headers -> remove ( 'Content-Length' );
} else {
// Content-type based on the Request
if ( ! $headers -> has ( 'Content-Type' )) {
$format = $request -> getRequestFormat ();
if ( null !== $format && $mimeType = $request -> getMimeType ( $format )) {
$headers -> set ( 'Content-Type' , $mimeType );
}
}
// Fix Content-Type
$charset = $this -> charset ? : 'UTF-8' ;
if ( ! $headers -> has ( 'Content-Type' )) {
$headers -> set ( 'Content-Type' , 'text/html; charset=' . $charset );
} elseif ( 0 === stripos ( $headers -> get ( 'Content-Type' ), 'text/' ) && false === stripos ( $headers -> get ( 'Content-Type' ), 'charset' )) {
// add the charset
$headers -> set ( 'Content-Type' , $headers -> get ( 'Content-Type' ) . '; charset=' . $charset );
}
// Fix Content-Length
if ( $headers -> has ( 'Transfer-Encoding' )) {
$headers -> remove ( 'Content-Length' );
}
if ( $request -> isMethod ( 'HEAD' )) {
// cf. RFC2616 14.13
$length = $headers -> get ( 'Content-Length' );
$this -> setContent ( null );
if ( $length ) {
$headers -> set ( 'Content-Length' , $length );
}
}
}
// Fix protocol
if ( 'HTTP/1.0' != $request -> server -> get ( 'SERVER_PROTOCOL' )) {
$this -> setProtocolVersion ( '1.1' );
}
// Check if we need to send extra expire info headers
if ( '1.0' == $this -> getProtocolVersion () && 'no-cache' == $this -> headers -> get ( 'Cache-Control' )) {
$this -> headers -> set ( 'pragma' , 'no-cache' );
$this -> headers -> set ( 'expires' , - 1 );
}
$this -> ensureIEOverSSLCompatibility ( $request );
return $this ;
}
/**
* Sends HTTP headers .
*
* @ return Response
*/
public function sendHeaders ()
{
// headers have already been sent by the developer
if ( headers_sent ()) {
return $this ;
}
// status
header ( sprintf ( 'HTTP/%s %s %s' , $this -> version , $this -> statusCode , $this -> statusText ), true , $this -> statusCode );
// headers
foreach ( $this -> headers -> allPreserveCase () as $name => $values ) {
foreach ( $values as $value ) {
header ( $name . ': ' . $value , false , $this -> statusCode );
}
}
// cookies
foreach ( $this -> headers -> getCookies () as $cookie ) {
setcookie ( $cookie -> getName (), $cookie -> getValue (), $cookie -> getExpiresTime (), $cookie -> getPath (), $cookie -> getDomain (), $cookie -> isSecure (), $cookie -> isHttpOnly ());
}
return $this ;
}
/**
* Sends content for the current web response .
*
* @ return Response
*/
public function sendContent ()
{
echo $this -> content ;
return $this ;
}
/**
* Sends HTTP headers and content .
*
* @ return Response
*
* @ api
*/
public function send ()
{
$this -> sendHeaders ();
$this -> sendContent ();
if ( function_exists ( 'fastcgi_finish_request' )) {
fastcgi_finish_request ();
} elseif ( 'cli' !== PHP_SAPI ) {
static :: closeOutputBuffers ( 0 , true );
}
return $this ;
}
/**
* Sets the response content .
*
* Valid types are strings , numbers , null , and objects that implement a __toString () method .
*
* @ param mixed $content Content that can be cast to string
*
* @ return Response
*
* @ throws \UnexpectedValueException
*
* @ api
*/
public function setContent ( $content )
{
if ( null !== $content && ! is_string ( $content ) && ! is_numeric ( $content ) && ! is_callable ( array ( $content , '__toString' ))) {
throw new \UnexpectedValueException ( sprintf ( 'The Response content must be a string or object implementing __toString(), "%s" given.' , gettype ( $content )));
}
$this -> content = ( string ) $content ;
return $this ;
}
/**
* Gets the current response content .
*
* @ return string Content
*
* @ api
*/
public function getContent ()
{
return $this -> content ;
}
/**
* Sets the HTTP protocol version ( 1.0 or 1.1 ) .
*
* @ param string $version The HTTP protocol version
*
* @ return Response
*
* @ api
*/
public function setProtocolVersion ( $version )
{
$this -> version = $version ;
return $this ;
}
/**
* Gets the HTTP protocol version .
*
* @ return string The HTTP protocol version
*
* @ api
*/
public function getProtocolVersion ()
{
return $this -> version ;
}
/**
* Sets the response status code .
*
* @ param int $code HTTP status code
* @ param mixed $text HTTP status text
*
* If the status text is null it will be automatically populated for the known
* status codes and left empty otherwise .
*
* @ return Response
*
* @ throws \InvalidArgumentException When the HTTP status code is not valid
*
* @ api
*/
public function setStatusCode ( $code , $text = null )
{
$this -> statusCode = $code = ( int ) $code ;
if ( $this -> isInvalid ()) {
throw new \InvalidArgumentException ( sprintf ( 'The HTTP status code "%s" is not valid.' , $code ));
}
if ( null === $text ) {
$this -> statusText = isset ( self :: $statusTexts [ $code ]) ? self :: $statusTexts [ $code ] : '' ;
return $this ;
}
if ( false === $text ) {
$this -> statusText = '' ;
return $this ;
}
$this -> statusText = $text ;
return $this ;
}
/**
* Retrieves the status code for the current web response .
*
* @ return int Status code
*
* @ api
*/
public function getStatusCode ()
{
return $this -> statusCode ;
}
/**
* Sets the response charset .
*
* @ param string $charset Character set
*
* @ return Response
*
* @ api
*/
public function setCharset ( $charset )
{
$this -> charset = $charset ;
return $this ;
}
/**
* Retrieves the response charset .
*
* @ return string Character set
*
* @ api
*/
public function getCharset ()
{
return $this -> charset ;
}
/**
* Returns true if the response is worth caching under any circumstance .
*
* Responses marked " private " with an explicit Cache - Control directive are
* considered uncacheable .
*
* Responses with neither a freshness lifetime ( Expires , max - age ) nor cache
* validator ( Last - Modified , ETag ) are considered uncacheable .
*
* @ return bool true if the response is worth caching , false otherwise
*
* @ api
*/
public function isCacheable ()
{
if ( ! in_array ( $this -> statusCode , array ( 200 , 203 , 300 , 301 , 302 , 404 , 410 ))) {
return false ;
}
if ( $this -> headers -> hasCacheControlDirective ( 'no-store' ) || $this -> headers -> getCacheControlDirective ( 'private' )) {
return false ;
}
return $this -> isValidateable () || $this -> isFresh ();
}
/**
* Returns true if the response is " fresh " .
*
* Fresh responses may be served from cache without any interaction with the
* origin . A response is considered fresh when it includes a Cache - Control / max - age
* indicator or Expires header and the calculated age is less than the freshness lifetime .
*
* @ return bool true if the response is fresh , false otherwise
*
* @ api
*/
public function isFresh ()
{
return $this -> getTtl () > 0 ;
}
/**
* Returns true if the response includes headers that can be used to validate
* the response with the origin server using a conditional GET request .
*
* @ return bool true if the response is validateable , false otherwise
*
* @ api
*/
public function isValidateable ()
{
return $this -> headers -> has ( 'Last-Modified' ) || $this -> headers -> has ( 'ETag' );
}
/**
* Marks the response as " private " .
*
* It makes the response ineligible for serving other clients .
*
* @ return Response
*
* @ api
*/
public function setPrivate ()
{
$this -> headers -> removeCacheControlDirective ( 'public' );
$this -> headers -> addCacheControlDirective ( 'private' );
return $this ;
}
/**
* Marks the response as " public " .
*
* It makes the response eligible for serving other clients .
*
* @ return Response
*
* @ api
*/
public function setPublic ()
{
$this -> headers -> addCacheControlDirective ( 'public' );
$this -> headers -> removeCacheControlDirective ( 'private' );
return $this ;
}
/**
* Returns true if the response must be revalidated by caches .
*
* This method indicates that the response must not be served stale by a
* cache in any circumstance without first revalidating with the origin .
* When present , the TTL of the response should not be overridden to be
* greater than the value provided by the origin .
*
* @ return bool true if the response must be revalidated by a cache , false otherwise
*
* @ api
*/
public function mustRevalidate ()
{
return $this -> headers -> hasCacheControlDirective ( 'must-revalidate' ) || $this -> headers -> has ( 'proxy-revalidate' );
}
/**
* Returns the Date header as a DateTime instance .
*
* @ return \DateTime A \DateTime instance
*
* @ throws \RuntimeException When the header is not parseable
*
* @ api
*/
public function getDate ()
{
return $this -> headers -> getDate ( 'Date' , new \DateTime ());
}
/**
* Sets the Date header .
*
* @ param \DateTime $date A \DateTime instance
*
* @ return Response
*
* @ api
*/
public function setDate ( \DateTime $date )
{
$date -> setTimezone ( new \DateTimeZone ( 'UTC' ));
$this -> headers -> set ( 'Date' , $date -> format ( 'D, d M Y H:i:s' ) . ' GMT' );
return $this ;
}
/**
* Returns the age of the response .
*
* @ return int The age of the response in seconds
*/
public function getAge ()
{
if ( null !== $age = $this -> headers -> get ( 'Age' )) {
return ( int ) $age ;
}
return max ( time () - $this -> getDate () -> format ( 'U' ), 0 );
}
/**
* Marks the response stale by setting the Age header to be equal to the maximum age of the response .
*
* @ return Response
*
* @ api
*/
public function expire ()
{
if ( $this -> isFresh ()) {
$this -> headers -> set ( 'Age' , $this -> getMaxAge ());
}
return $this ;
}
/**
* Returns the value of the Expires header as a DateTime instance .
*
* @ return \DateTime | null A DateTime instance or null if the header does not exist
*
* @ api
*/
public function getExpires ()
{
try {
return $this -> headers -> getDate ( 'Expires' );
} catch ( \RuntimeException $e ) {
// according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past
return \DateTime :: createFromFormat ( DATE_RFC2822 , 'Sat, 01 Jan 00 00:00:00 +0000' );
}
}
/**
* Sets the Expires HTTP header with a DateTime instance .
*
* Passing null as value will remove the header .
*
* @ param \DateTime | null $date A \DateTime instance or null to remove the header
*
* @ return Response
*
* @ api
*/
public function setExpires ( \DateTime $date = null )
{
if ( null === $date ) {
$this -> headers -> remove ( 'Expires' );
} else {
$date = clone $date ;
$date -> setTimezone ( new \DateTimeZone ( 'UTC' ));
$this -> headers -> set ( 'Expires' , $date -> format ( 'D, d M Y H:i:s' ) . ' GMT' );
}
return $this ;
}
/**
* Returns the number of seconds after the time specified in the response ' s Date
* header when the response should no longer be considered fresh .
*
* First , it checks for a s - maxage directive , then a max - age directive , and then it falls
* back on an expires header . It returns null when no maximum age can be established .
*
* @ return int | null Number of seconds
*
* @ api
*/
public function getMaxAge ()
{
if ( $this -> headers -> hasCacheControlDirective ( 's-maxage' )) {
return ( int ) $this -> headers -> getCacheControlDirective ( 's-maxage' );
}
if ( $this -> headers -> hasCacheControlDirective ( 'max-age' )) {
return ( int ) $this -> headers -> getCacheControlDirective ( 'max-age' );
}
if ( null !== $this -> getExpires ()) {
return $this -> getExpires () -> format ( 'U' ) - $this -> getDate () -> format ( 'U' );
}
}
/**
* Sets the number of seconds after which the response should no longer be considered fresh .
*
* This methods sets the Cache - Control max - age directive .
*
* @ param int $value Number of seconds
*
* @ return Response
*
* @ api
*/
public function setMaxAge ( $value )
{
$this -> headers -> addCacheControlDirective ( 'max-age' , $value );
return $this ;
}
/**
* Sets the number of seconds after which the response should no longer be considered fresh by shared caches .
*
* This methods sets the Cache - Control s - maxage directive .
*
* @ param int $value Number of seconds
*
* @ return Response
*
* @ api
*/
public function setSharedMaxAge ( $value )
{
$this -> setPublic ();
$this -> headers -> addCacheControlDirective ( 's-maxage' , $value );
return $this ;
}
/**
* Returns the response ' s time - to - live in seconds .
*
* It returns null when no freshness information is present in the response .
*
* When the responses TTL is <= 0 , the response may not be served from cache without first
* revalidating with the origin .
*
* @ return int | null The TTL in seconds
*
* @ api
*/
public function getTtl ()
{
if ( null !== $maxAge = $this -> getMaxAge ()) {
return $maxAge - $this -> getAge ();
}
}
/**
* Sets the response ' s time - to - live for shared caches .
*
* This method adjusts the Cache - Control / s - maxage directive .
*
* @ param int $seconds Number of seconds
*
* @ return Response
*
* @ api
*/
public function setTtl ( $seconds )
{
$this -> setSharedMaxAge ( $this -> getAge () + $seconds );
return $this ;
}
/**
* Sets the response ' s time - to - live for private / client caches .
*
* This method adjusts the Cache - Control / max - age directive .
*
* @ param int $seconds Number of seconds
*
* @ return Response
*
* @ api
*/
public function setClientTtl ( $seconds )
{
$this -> setMaxAge ( $this -> getAge () + $seconds );
return $this ;
}
/**
* Returns the Last - Modified HTTP header as a DateTime instance .
*
* @ return \DateTime | null A DateTime instance or null if the header does not exist
*
* @ throws \RuntimeException When the HTTP header is not parseable
*
* @ api
*/
public function getLastModified ()
{
return $this -> headers -> getDate ( 'Last-Modified' );
}
/**
* Sets the Last - Modified HTTP header with a DateTime instance .
*
* Passing null as value will remove the header .
*
* @ param \DateTime | null $date A \DateTime instance or null to remove the header
*
* @ return Response
*
* @ api
*/
public function setLastModified ( \DateTime $date = null )
{
if ( null === $date ) {
$this -> headers -> remove ( 'Last-Modified' );
} else {
$date = clone $date ;
$date -> setTimezone ( new \DateTimeZone ( 'UTC' ));
$this -> headers -> set ( 'Last-Modified' , $date -> format ( 'D, d M Y H:i:s' ) . ' GMT' );
}
return $this ;
}
/**
* Returns the literal value of the ETag HTTP header .
*
* @ return string | null The ETag HTTP header or null if it does not exist
*
* @ api
*/
public function getEtag ()
{
return $this -> headers -> get ( 'ETag' );
}
/**
* Sets the ETag value .
*
* @ param string | null $etag The ETag unique identifier or null to remove the header
* @ param bool $weak Whether you want a weak ETag or not
*
* @ return Response
*
* @ api
*/
public function setEtag ( $etag = null , $weak = false )
{
if ( null === $etag ) {
$this -> headers -> remove ( 'Etag' );
} else {
if ( 0 !== strpos ( $etag , '"' )) {
$etag = '"' . $etag . '"' ;
}
$this -> headers -> set ( 'ETag' , ( true === $weak ? 'W/' : '' ) . $etag );
}
return $this ;
}
/**
* Sets the response ' s cache headers ( validation and / or expiration ) .
*
* Available options are : etag , last_modified , max_age , s_maxage , private , and public .
*
* @ param array $options An array of cache options
*
* @ return Response
*
* @ throws \InvalidArgumentException
*
* @ api
*/
public function setCache ( array $options )
{
if ( $diff = array_diff ( array_keys ( $options ), array ( 'etag' , 'last_modified' , 'max_age' , 's_maxage' , 'private' , 'public' ))) {
throw new \InvalidArgumentException ( sprintf ( 'Response does not support the following options: "%s".' , implode ( '", "' , array_values ( $diff ))));
}
if ( isset ( $options [ 'etag' ])) {
$this -> setEtag ( $options [ 'etag' ]);
}
if ( isset ( $options [ 'last_modified' ])) {
$this -> setLastModified ( $options [ 'last_modified' ]);
}
if ( isset ( $options [ 'max_age' ])) {
$this -> setMaxAge ( $options [ 'max_age' ]);
}
if ( isset ( $options [ 's_maxage' ])) {
$this -> setSharedMaxAge ( $options [ 's_maxage' ]);
}
if ( isset ( $options [ 'public' ])) {
if ( $options [ 'public' ]) {
$this -> setPublic ();
} else {
$this -> setPrivate ();
}
}
if ( isset ( $options [ 'private' ])) {
if ( $options [ 'private' ]) {
$this -> setPrivate ();
} else {
$this -> setPublic ();
}
}
return $this ;
}
/**
* Modifies the response so that it conforms to the rules defined for a 304 status code .
*
* This sets the status , removes the body , and discards any headers
* that MUST NOT be included in 304 responses .
*
* @ return Response
*
* @ see http :// tools . ietf . org / html / rfc2616 #section-10.3.5
*
* @ api
*/
public function setNotModified ()
{
$this -> setStatusCode ( 304 );
$this -> setContent ( null );
// remove headers that MUST NOT be included with 304 Not Modified responses
foreach ( array ( 'Allow' , 'Content-Encoding' , 'Content-Language' , 'Content-Length' , 'Content-MD5' , 'Content-Type' , 'Last-Modified' ) as $header ) {
$this -> headers -> remove ( $header );
}
return $this ;
}
/**
* Returns true if the response includes a Vary header .
*
* @ return bool true if the response includes a Vary header , false otherwise
*
* @ api
*/
public function hasVary ()
{
return null !== $this -> headers -> get ( 'Vary' );
}
/**
* Returns an array of header names given in the Vary header .
*
* @ return array An array of Vary names
*
* @ api
*/
public function getVary ()
{
if ( ! $vary = $this -> headers -> get ( 'Vary' , null , false )) {
return array ();
}
$ret = array ();
foreach ( $vary as $item ) {
$ret = array_merge ( $ret , preg_split ( '/[\s,]+/' , $item ));
}
return $ret ;
}
/**
* Sets the Vary header .
*
* @ param string | array $headers
* @ param bool $replace Whether to replace the actual value of not ( true by default )
*
* @ return Response
*
* @ api
*/
public function setVary ( $headers , $replace = true )
{
$this -> headers -> set ( 'Vary' , $headers , $replace );
return $this ;
}
/**
* Determines if the Response validators ( ETag , Last - Modified ) match
* a conditional value specified in the Request .
*
* If the Response is not modified , it sets the status code to 304 and
* removes the actual content by calling the setNotModified () method .
*
* @ param Request $request A Request instance
*
* @ return bool true if the Response validators match the Request , false otherwise
*
* @ api
*/
public function isNotModified ( Request $request )
{
if ( ! $request -> isMethodSafe ()) {
return false ;
}
$notModified = false ;
$lastModified = $this -> headers -> get ( 'Last-Modified' );
$modifiedSince = $request -> headers -> get ( 'If-Modified-Since' );
2015-08-27 19:03:05 +00:00
if ( $etags = $request -> getETags ()) {
2015-08-18 00:00:26 +00:00
$notModified = in_array ( $this -> getEtag (), $etags ) || in_array ( '*' , $etags );
}
if ( $modifiedSince && $lastModified ) {
$notModified = strtotime ( $modifiedSince ) >= strtotime ( $lastModified ) && ( ! $etags || $notModified );
}
if ( $notModified ) {
$this -> setNotModified ();
}
return $notModified ;
}
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
/**
* Is response invalid ?
*
* @ return bool
*
* @ api
*/
public function isInvalid ()
{
return $this -> statusCode < 100 || $this -> statusCode >= 600 ;
}
/**
* Is response informative ?
*
* @ return bool
*
* @ api
*/
public function isInformational ()
{
return $this -> statusCode >= 100 && $this -> statusCode < 200 ;
}
/**
* Is response successful ?
*
* @ return bool
*
* @ api
*/
public function isSuccessful ()
{
return $this -> statusCode >= 200 && $this -> statusCode < 300 ;
}
/**
* Is the response a redirect ?
*
* @ return bool
*
* @ api
*/
public function isRedirection ()
{
return $this -> statusCode >= 300 && $this -> statusCode < 400 ;
}
/**
* Is there a client error ?
*
* @ return bool
*
* @ api
*/
public function isClientError ()
{
return $this -> statusCode >= 400 && $this -> statusCode < 500 ;
}
/**
* Was there a server side error ?
*
* @ return bool
*
* @ api
*/
public function isServerError ()
{
return $this -> statusCode >= 500 && $this -> statusCode < 600 ;
}
/**
* Is the response OK ?
*
* @ return bool
*
* @ api
*/
public function isOk ()
{
return 200 === $this -> statusCode ;
}
/**
* Is the response forbidden ?
*
* @ return bool
*
* @ api
*/
public function isForbidden ()
{
return 403 === $this -> statusCode ;
}
/**
* Is the response a not found error ?
*
* @ return bool
*
* @ api
*/
public function isNotFound ()
{
return 404 === $this -> statusCode ;
}
/**
* Is the response a redirect of some form ?
*
* @ param string $location
*
* @ return bool
*
* @ api
*/
public function isRedirect ( $location = null )
{
return in_array ( $this -> statusCode , array ( 201 , 301 , 302 , 303 , 307 , 308 )) && ( null === $location ? : $location == $this -> headers -> get ( 'Location' ));
}
/**
* Is the response empty ?
*
* @ return bool
*
* @ api
*/
public function isEmpty ()
{
return in_array ( $this -> statusCode , array ( 204 , 304 ));
}
/**
* Cleans or flushes output buffers up to target level .
*
* Resulting level can be greater than target level if a non - removable buffer has been encountered .
*
* @ param int $targetLevel The target output buffering level
* @ param bool $flush Whether to flush or clean the buffers
*/
public static function closeOutputBuffers ( $targetLevel , $flush )
{
$status = ob_get_status ( true );
$level = count ( $status );
2015-09-04 20:20:09 +00:00
$flags = defined ( 'PHP_OUTPUT_HANDLER_REMOVABLE' ) ? PHP_OUTPUT_HANDLER_REMOVABLE | ( $flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE ) : - 1 ;
2015-08-18 00:00:26 +00:00
2015-08-27 19:03:05 +00:00
while ( $level -- > $targetLevel && ( $s = $status [ $level ]) && ( ! isset ( $s [ 'del' ]) ? ! isset ( $s [ 'flags' ]) || $flags === ( $s [ 'flags' ] & $flags ) : $s [ 'del' ])) {
2015-08-18 00:00:26 +00:00
if ( $flush ) {
ob_end_flush ();
} else {
ob_end_clean ();
}
}
}
/**
* Checks if we need to remove Cache - Control for SSL encrypted downloads when using IE < 9.
*
* @ link http :// support . microsoft . com / kb / 323308
*/
protected function ensureIEOverSSLCompatibility ( Request $request )
{
if ( false !== stripos ( $this -> headers -> get ( 'Content-Disposition' ), 'attachment' ) && preg_match ( '/MSIE (.*?);/i' , $request -> server -> get ( 'HTTP_USER_AGENT' ), $match ) == 1 && true === $request -> isSecure ()) {
if (( int ) preg_replace ( '/(MSIE )(.*?);/' , '$2' , $match [ 0 ]) < 9 ) {
$this -> headers -> remove ( 'Cache-Control' );
}
}
}
}