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