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