1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
15: namespace Cake\Http;
16:
17: use Cake\Core\Configure;
18: use Cake\Filesystem\File;
19: use Cake\Filesystem\Folder;
20: use Cake\Http\Cookie\Cookie;
21: use Cake\Http\Cookie\CookieCollection;
22: use Cake\Http\Cookie\CookieInterface;
23: use Cake\Http\CorsBuilder;
24: use Cake\Http\Exception\NotFoundException;
25: use Cake\Log\Log;
26: use DateTime;
27: use DateTimeInterface;
28: use DateTimeZone;
29: use InvalidArgumentException;
30: use Psr\Http\Message\ResponseInterface;
31: use Psr\Http\Message\StreamInterface;
32: use Zend\Diactoros\MessageTrait;
33: use Zend\Diactoros\Stream;
34:
35: 36: 37: 38: 39: 40: 41: 42: 43: 44:
45: class Response implements ResponseInterface
46: {
47: use MessageTrait;
48:
49: 50: 51: 52: 53:
54: protected $_statusCodes = [
55: 100 => 'Continue',
56: 101 => 'Switching Protocols',
57: 102 => 'Processing',
58: 200 => 'OK',
59: 201 => 'Created',
60: 202 => 'Accepted',
61: 203 => 'Non-Authoritative Information',
62: 204 => 'No Content',
63: 205 => 'Reset Content',
64: 206 => 'Partial Content',
65: 207 => 'Multi-status',
66: 208 => 'Already Reported',
67: 226 => 'IM used',
68: 300 => 'Multiple Choices',
69: 301 => 'Moved Permanently',
70: 302 => 'Found',
71: 303 => 'See Other',
72: 304 => 'Not Modified',
73: 305 => 'Use Proxy',
74: 306 => '(Unused)',
75: 307 => 'Temporary Redirect',
76: 308 => 'Permanent Redirect',
77: 400 => 'Bad Request',
78: 401 => 'Unauthorized',
79: 402 => 'Payment Required',
80: 403 => 'Forbidden',
81: 404 => 'Not Found',
82: 405 => 'Method Not Allowed',
83: 406 => 'Not Acceptable',
84: 407 => 'Proxy Authentication Required',
85: 408 => 'Request Timeout',
86: 409 => 'Conflict',
87: 410 => 'Gone',
88: 411 => 'Length Required',
89: 412 => 'Precondition Failed',
90: 413 => 'Request Entity Too Large',
91: 414 => 'Request-URI Too Large',
92: 415 => 'Unsupported Media Type',
93: 416 => 'Requested range not satisfiable',
94: 417 => 'Expectation Failed',
95: 418 => 'I\'m a teapot',
96: 421 => 'Misdirected Request',
97: 422 => 'Unprocessable Entity',
98: 423 => 'Locked',
99: 424 => 'Failed Dependency',
100: 425 => 'Unordered Collection',
101: 426 => 'Upgrade Required',
102: 428 => 'Precondition Required',
103: 429 => 'Too Many Requests',
104: 431 => 'Request Header Fields Too Large',
105: 444 => 'Connection Closed Without Response',
106: 451 => 'Unavailable For Legal Reasons',
107: 499 => 'Client Closed Request',
108: 500 => 'Internal Server Error',
109: 501 => 'Not Implemented',
110: 502 => 'Bad Gateway',
111: 503 => 'Service Unavailable',
112: 504 => 'Gateway Timeout',
113: 505 => 'Unsupported Version',
114: 506 => 'Variant Also Negotiates',
115: 507 => 'Insufficient Storage',
116: 508 => 'Loop Detected',
117: 510 => 'Not Extended',
118: 511 => 'Network Authentication Required',
119: 599 => 'Network Connect Timeout Error',
120: ];
121:
122: 123: 124: 125: 126:
127: protected $_mimeTypes = [
128: 'html' => ['text/html', '*/*'],
129: 'json' => 'application/json',
130: 'xml' => ['application/xml', 'text/xml'],
131: 'xhtml' => ['application/xhtml+xml', 'application/xhtml', 'text/xhtml'],
132: 'webp' => 'image/webp',
133: 'rss' => 'application/rss+xml',
134: 'ai' => 'application/postscript',
135: 'bcpio' => 'application/x-bcpio',
136: 'bin' => 'application/octet-stream',
137: 'ccad' => 'application/clariscad',
138: 'cdf' => 'application/x-netcdf',
139: 'class' => 'application/octet-stream',
140: 'cpio' => 'application/x-cpio',
141: 'cpt' => 'application/mac-compactpro',
142: 'csh' => 'application/x-csh',
143: 'csv' => ['text/csv', 'application/vnd.ms-excel'],
144: 'dcr' => 'application/x-director',
145: 'dir' => 'application/x-director',
146: 'dms' => 'application/octet-stream',
147: 'doc' => 'application/msword',
148: 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
149: 'drw' => 'application/drafting',
150: 'dvi' => 'application/x-dvi',
151: 'dwg' => 'application/acad',
152: 'dxf' => 'application/dxf',
153: 'dxr' => 'application/x-director',
154: 'eot' => 'application/vnd.ms-fontobject',
155: 'eps' => 'application/postscript',
156: 'exe' => 'application/octet-stream',
157: 'ez' => 'application/andrew-inset',
158: 'flv' => 'video/x-flv',
159: 'gtar' => 'application/x-gtar',
160: 'gz' => 'application/x-gzip',
161: 'bz2' => 'application/x-bzip',
162: '7z' => 'application/x-7z-compressed',
163: 'hdf' => 'application/x-hdf',
164: 'hqx' => 'application/mac-binhex40',
165: 'ico' => 'image/x-icon',
166: 'ips' => 'application/x-ipscript',
167: 'ipx' => 'application/x-ipix',
168: 'js' => 'application/javascript',
169: 'jsonapi' => 'application/vnd.api+json',
170: 'latex' => 'application/x-latex',
171: 'lha' => 'application/octet-stream',
172: 'lsp' => 'application/x-lisp',
173: 'lzh' => 'application/octet-stream',
174: 'man' => 'application/x-troff-man',
175: 'me' => 'application/x-troff-me',
176: 'mif' => 'application/vnd.mif',
177: 'ms' => 'application/x-troff-ms',
178: 'nc' => 'application/x-netcdf',
179: 'oda' => 'application/oda',
180: 'otf' => 'font/otf',
181: 'pdf' => 'application/pdf',
182: 'pgn' => 'application/x-chess-pgn',
183: 'pot' => 'application/vnd.ms-powerpoint',
184: 'pps' => 'application/vnd.ms-powerpoint',
185: 'ppt' => 'application/vnd.ms-powerpoint',
186: 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
187: 'ppz' => 'application/vnd.ms-powerpoint',
188: 'pre' => 'application/x-freelance',
189: 'prt' => 'application/pro_eng',
190: 'ps' => 'application/postscript',
191: 'roff' => 'application/x-troff',
192: 'scm' => 'application/x-lotusscreencam',
193: 'set' => 'application/set',
194: 'sh' => 'application/x-sh',
195: 'shar' => 'application/x-shar',
196: 'sit' => 'application/x-stuffit',
197: 'skd' => 'application/x-koan',
198: 'skm' => 'application/x-koan',
199: 'skp' => 'application/x-koan',
200: 'skt' => 'application/x-koan',
201: 'smi' => 'application/smil',
202: 'smil' => 'application/smil',
203: 'sol' => 'application/solids',
204: 'spl' => 'application/x-futuresplash',
205: 'src' => 'application/x-wais-source',
206: 'step' => 'application/STEP',
207: 'stl' => 'application/SLA',
208: 'stp' => 'application/STEP',
209: 'sv4cpio' => 'application/x-sv4cpio',
210: 'sv4crc' => 'application/x-sv4crc',
211: 'svg' => 'image/svg+xml',
212: 'svgz' => 'image/svg+xml',
213: 'swf' => 'application/x-shockwave-flash',
214: 't' => 'application/x-troff',
215: 'tar' => 'application/x-tar',
216: 'tcl' => 'application/x-tcl',
217: 'tex' => 'application/x-tex',
218: 'texi' => 'application/x-texinfo',
219: 'texinfo' => 'application/x-texinfo',
220: 'tr' => 'application/x-troff',
221: 'tsp' => 'application/dsptype',
222: 'ttc' => 'font/ttf',
223: 'ttf' => 'font/ttf',
224: 'unv' => 'application/i-deas',
225: 'ustar' => 'application/x-ustar',
226: 'vcd' => 'application/x-cdlink',
227: 'vda' => 'application/vda',
228: 'xlc' => 'application/vnd.ms-excel',
229: 'xll' => 'application/vnd.ms-excel',
230: 'xlm' => 'application/vnd.ms-excel',
231: 'xls' => 'application/vnd.ms-excel',
232: 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
233: 'xlw' => 'application/vnd.ms-excel',
234: 'zip' => 'application/zip',
235: 'aif' => 'audio/x-aiff',
236: 'aifc' => 'audio/x-aiff',
237: 'aiff' => 'audio/x-aiff',
238: 'au' => 'audio/basic',
239: 'kar' => 'audio/midi',
240: 'mid' => 'audio/midi',
241: 'midi' => 'audio/midi',
242: 'mp2' => 'audio/mpeg',
243: 'mp3' => 'audio/mpeg',
244: 'mpga' => 'audio/mpeg',
245: 'ogg' => 'audio/ogg',
246: 'oga' => 'audio/ogg',
247: 'spx' => 'audio/ogg',
248: 'ra' => 'audio/x-realaudio',
249: 'ram' => 'audio/x-pn-realaudio',
250: 'rm' => 'audio/x-pn-realaudio',
251: 'rpm' => 'audio/x-pn-realaudio-plugin',
252: 'snd' => 'audio/basic',
253: 'tsi' => 'audio/TSP-audio',
254: 'wav' => 'audio/x-wav',
255: 'aac' => 'audio/aac',
256: 'asc' => 'text/plain',
257: 'c' => 'text/plain',
258: 'cc' => 'text/plain',
259: 'css' => 'text/css',
260: 'etx' => 'text/x-setext',
261: 'f' => 'text/plain',
262: 'f90' => 'text/plain',
263: 'h' => 'text/plain',
264: 'hh' => 'text/plain',
265: 'htm' => ['text/html', '*/*'],
266: 'ics' => 'text/calendar',
267: 'm' => 'text/plain',
268: 'rtf' => 'text/rtf',
269: 'rtx' => 'text/richtext',
270: 'sgm' => 'text/sgml',
271: 'sgml' => 'text/sgml',
272: 'tsv' => 'text/tab-separated-values',
273: 'tpl' => 'text/template',
274: 'txt' => 'text/plain',
275: 'text' => 'text/plain',
276: 'avi' => 'video/x-msvideo',
277: 'fli' => 'video/x-fli',
278: 'mov' => 'video/quicktime',
279: 'movie' => 'video/x-sgi-movie',
280: 'mpe' => 'video/mpeg',
281: 'mpeg' => 'video/mpeg',
282: 'mpg' => 'video/mpeg',
283: 'qt' => 'video/quicktime',
284: 'viv' => 'video/vnd.vivo',
285: 'vivo' => 'video/vnd.vivo',
286: 'ogv' => 'video/ogg',
287: 'webm' => 'video/webm',
288: 'mp4' => 'video/mp4',
289: 'm4v' => 'video/mp4',
290: 'f4v' => 'video/mp4',
291: 'f4p' => 'video/mp4',
292: 'm4a' => 'audio/mp4',
293: 'f4a' => 'audio/mp4',
294: 'f4b' => 'audio/mp4',
295: 'gif' => 'image/gif',
296: 'ief' => 'image/ief',
297: 'jpg' => 'image/jpeg',
298: 'jpeg' => 'image/jpeg',
299: 'jpe' => 'image/jpeg',
300: 'pbm' => 'image/x-portable-bitmap',
301: 'pgm' => 'image/x-portable-graymap',
302: 'png' => 'image/png',
303: 'pnm' => 'image/x-portable-anymap',
304: 'ppm' => 'image/x-portable-pixmap',
305: 'ras' => 'image/cmu-raster',
306: 'rgb' => 'image/x-rgb',
307: 'tif' => 'image/tiff',
308: 'tiff' => 'image/tiff',
309: 'xbm' => 'image/x-xbitmap',
310: 'xpm' => 'image/x-xpixmap',
311: 'xwd' => 'image/x-xwindowdump',
312: 'psd' => ['application/photoshop', 'application/psd', 'image/psd', 'image/x-photoshop', 'image/photoshop', 'zz-application/zz-winassoc-psd'],
313: 'ice' => 'x-conference/x-cooltalk',
314: 'iges' => 'model/iges',
315: 'igs' => 'model/iges',
316: 'mesh' => 'model/mesh',
317: 'msh' => 'model/mesh',
318: 'silo' => 'model/mesh',
319: 'vrml' => 'model/vrml',
320: 'wrl' => 'model/vrml',
321: 'mime' => 'www/mime',
322: 'pdb' => 'chemical/x-pdb',
323: 'xyz' => 'chemical/x-pdb',
324: 'javascript' => 'application/javascript',
325: 'form' => 'application/x-www-form-urlencoded',
326: 'file' => 'multipart/form-data',
327: 'xhtml-mobile' => 'application/vnd.wap.xhtml+xml',
328: 'atom' => 'application/atom+xml',
329: 'amf' => 'application/x-amf',
330: 'wap' => ['text/vnd.wap.wml', 'text/vnd.wap.wmlscript', 'image/vnd.wap.wbmp'],
331: 'wml' => 'text/vnd.wap.wml',
332: 'wmlscript' => 'text/vnd.wap.wmlscript',
333: 'wbmp' => 'image/vnd.wap.wbmp',
334: 'woff' => 'application/x-font-woff',
335: 'appcache' => 'text/cache-manifest',
336: 'manifest' => 'text/cache-manifest',
337: 'htc' => 'text/x-component',
338: 'rdf' => 'application/xml',
339: 'crx' => 'application/x-chrome-extension',
340: 'oex' => 'application/x-opera-extension',
341: 'xpi' => 'application/x-xpinstall',
342: 'safariextz' => 'application/octet-stream',
343: 'webapp' => 'application/x-web-app-manifest+json',
344: 'vcf' => 'text/x-vcard',
345: 'vtt' => 'text/vtt',
346: 'mkv' => 'video/x-matroska',
347: 'pkpass' => 'application/vnd.apple.pkpass',
348: 'ajax' => 'text/html',
349: 'bmp' => 'image/bmp'
350: ];
351:
352: 353: 354: 355: 356:
357: protected $_protocol = 'HTTP/1.1';
358:
359: 360: 361: 362: 363:
364: protected $_status = 200;
365:
366: 367: 368: 369: 370: 371:
372: protected $_contentType = 'text/html';
373:
374: 375: 376: 377: 378:
379: protected $_file;
380:
381: 382: 383: 384: 385:
386: protected $_fileRange = [];
387:
388: 389: 390: 391: 392:
393: protected $_charset = 'UTF-8';
394:
395: 396: 397: 398: 399: 400:
401: protected $_cacheDirectives = [];
402:
403: 404: 405: 406: 407:
408: protected $_cookies = null;
409:
410: 411: 412: 413: 414:
415: protected $_reasonPhrase = 'OK';
416:
417: 418: 419: 420: 421:
422: protected $_streamMode = 'wb+';
423:
424: 425: 426: 427: 428:
429: protected $_streamTarget = 'php://memory';
430:
431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441:
442: public function __construct(array $options = [])
443: {
444: if (isset($options['streamTarget'])) {
445: $this->_streamTarget = $options['streamTarget'];
446: }
447: if (isset($options['streamMode'])) {
448: $this->_streamMode = $options['streamMode'];
449: }
450: if (isset($options['stream'])) {
451: if (!$options['stream'] instanceof StreamInterface) {
452: throw new InvalidArgumentException('Stream option must be an object that implements StreamInterface');
453: }
454: $this->stream = $options['stream'];
455: } else {
456: $this->_createStream();
457: }
458: if (isset($options['body'])) {
459: $this->stream->write($options['body']);
460: }
461: if (isset($options['statusCodes'])) {
462: $this->httpCodes($options['statusCodes']);
463: }
464: if (isset($options['status'])) {
465: $this->_setStatus($options['status']);
466: }
467: if (!isset($options['charset'])) {
468: $options['charset'] = Configure::read('App.encoding');
469: }
470: $this->_charset = $options['charset'];
471: if (isset($options['type'])) {
472: $this->_contentType = $this->resolveType($options['type']);
473: }
474: $this->_setContentType();
475: $this->_cookies = new CookieCollection();
476: }
477:
478: 479: 480: 481: 482:
483: protected function _createStream()
484: {
485: $this->stream = new Stream($this->_streamTarget, $this->_streamMode);
486: }
487:
488: 489: 490: 491: 492: 493: 494:
495: public function send()
496: {
497: deprecationWarning('Response::send() will be removed in 4.0.0');
498:
499: if ($this->hasHeader('Location') && $this->_status === 200) {
500: $this->statusCode(302);
501: }
502:
503: $this->_setContent();
504: $this->sendHeaders();
505:
506: if ($this->_file) {
507: $this->_sendFile($this->_file, $this->_fileRange);
508: $this->_file = null;
509: $this->_fileRange = [];
510: } else {
511: $this->_sendContent($this->body());
512: }
513:
514: if (function_exists('fastcgi_finish_request')) {
515: fastcgi_finish_request();
516: }
517: }
518:
519: 520: 521: 522: 523: 524:
525: public function sendHeaders()
526: {
527: deprecationWarning(
528: 'Will be removed in 4.0.0'
529: );
530:
531: $file = $line = null;
532: if (headers_sent($file, $line)) {
533: Log::warning("Headers already sent in {$file}:{$line}");
534:
535: return;
536: }
537:
538: $codeMessage = $this->_statusCodes[$this->_status];
539: $this->_setCookies();
540: $this->_sendHeader("{$this->_protocol} {$this->_status} {$codeMessage}");
541: $this->_setContentType();
542:
543: foreach ($this->headers as $header => $values) {
544: foreach ((array)$values as $value) {
545: $this->_sendHeader($header, $value);
546: }
547: }
548: }
549:
550: 551: 552: 553: 554: 555: 556: 557:
558: protected function _setCookies()
559: {
560: deprecationWarning(
561: 'Will be removed in 4.0.0'
562: );
563:
564: foreach ($this->_cookies as $cookie) {
565: setcookie(
566: $cookie->getName(),
567: $cookie->getValue(),
568: $cookie->getExpiresTimestamp(),
569: $cookie->getPath(),
570: $cookie->getDomain(),
571: $cookie->isSecure(),
572: $cookie->isHttpOnly()
573: );
574: }
575: }
576:
577: 578: 579: 580: 581: 582:
583: protected function _setContentType()
584: {
585: if (in_array($this->_status, [304, 204])) {
586: $this->_clearHeader('Content-Type');
587:
588: return;
589: }
590: $whitelist = [
591: 'application/javascript', 'application/xml', 'application/rss+xml'
592: ];
593:
594: $charset = false;
595: if ($this->_charset &&
596: (strpos($this->_contentType, 'text/') === 0 || in_array($this->_contentType, $whitelist))
597: ) {
598: $charset = true;
599: }
600:
601: if ($charset) {
602: $this->_setHeader('Content-Type', "{$this->_contentType}; charset={$this->_charset}");
603: } else {
604: $this->_setHeader('Content-Type', (string)$this->_contentType);
605: }
606: }
607:
608: 609: 610: 611: 612: 613:
614: protected function _setContent()
615: {
616: deprecationWarning(
617: 'Will be removed in 4.0.0'
618: );
619:
620: if (in_array($this->_status, [304, 204])) {
621: $this->body('');
622: }
623: }
624:
625: 626: 627: 628: 629: 630: 631: 632:
633: protected function _sendHeader($name, $value = null)
634: {
635: deprecationWarning(
636: 'Will be removed in 4.0.0'
637: );
638:
639: if ($value === null) {
640: header($name);
641: } else {
642: header("{$name}: {$value}");
643: }
644: }
645:
646: 647: 648: 649: 650: 651: 652: 653: 654: 655: 656:
657: protected function _sendContent($content)
658: {
659: deprecationWarning(
660: 'Will be removed in 4.0.0'
661: );
662:
663: if (!is_string($content) && is_callable($content)) {
664: $content = $content();
665: }
666:
667: echo $content;
668: }
669:
670: 671: 672: 673: 674: 675: 676: 677: 678: 679: 680: 681: 682: 683: 684: 685: 686: 687: 688: 689: 690: 691: 692: 693: 694: 695: 696: 697: 698: 699: 700: 701: 702: 703: 704: 705: 706: 707: 708: 709: 710: 711:
712: public function header($header = null, $value = null)
713: {
714: deprecationWarning(
715: 'Response::header() is deprecated. ' .
716: 'Use `withHeader()`, `getHeaderLine()` and `getHeaders()` instead.'
717: );
718:
719: if ($header === null) {
720: return $this->getSimpleHeaders();
721: }
722:
723: $headers = is_array($header) ? $header : [$header => $value];
724: foreach ($headers as $header => $value) {
725: if (is_numeric($header)) {
726: list($header, $value) = [$value, null];
727: }
728: if ($value === null) {
729: list($header, $value) = explode(':', $header, 2);
730: }
731:
732: $lower = strtolower($header);
733: if (array_key_exists($lower, $this->headerNames)) {
734: $header = $this->headerNames[$lower];
735: } else {
736: $this->headerNames[$lower] = $header;
737: }
738:
739: $this->headers[$header] = is_array($value) ? array_map('trim', $value) : [trim($value)];
740: }
741:
742: return $this->getSimpleHeaders();
743: }
744:
745: 746: 747: 748: 749: 750: 751: 752:
753: protected function getSimpleHeaders()
754: {
755: $out = [];
756: foreach ($this->headers as $key => $values) {
757: $header = $this->headerNames[strtolower($key)];
758: if (count($values) === 1) {
759: $values = $values[0];
760: }
761: $out[$header] = $values;
762: }
763:
764: return $out;
765: }
766:
767: 768: 769: 770: 771: 772: 773: 774: 775: 776: 777:
778: public function location($url = null)
779: {
780: deprecationWarning(
781: 'Response::location() is deprecated. ' .
782: 'Mutable responses are deprecated. Use `withLocation()` and `getHeaderLine()` instead.'
783: );
784:
785: if ($url === null) {
786: $result = $this->getHeaderLine('Location');
787: if (!$result) {
788: return null;
789: }
790:
791: return $result;
792: }
793: if ($this->_status === 200) {
794: $this->_status = 302;
795: }
796: $this->_setHeader('Location', $url);
797:
798: return null;
799: }
800:
801: 802: 803: 804: 805: 806: 807: 808: 809:
810: public function withLocation($url)
811: {
812: $new = $this->withHeader('Location', $url);
813: if ($new->_status === 200) {
814: $new->_status = 302;
815: }
816:
817: return $new;
818: }
819:
820: 821: 822: 823: 824: 825: 826:
827: protected function _setHeader($header, $value)
828: {
829: $normalized = strtolower($header);
830: $this->headerNames[$normalized] = $header;
831: $this->headers[$header] = [$value];
832: }
833:
834: 835: 836: 837: 838: 839:
840: protected function _clearHeader($header)
841: {
842: $normalized = strtolower($header);
843: if (!isset($this->headerNames[$normalized])) {
844: return;
845: }
846: $original = $this->headerNames[$normalized];
847: unset($this->headerNames[$normalized], $this->headers[$original]);
848: }
849:
850: 851: 852: 853: 854: 855: 856: 857:
858: public function body($content = null)
859: {
860: deprecationWarning(
861: 'Response::body() is deprecated. ' .
862: 'Mutable response methods are deprecated. Use `withBody()` and `getBody()` instead.'
863: );
864:
865: if ($content === null) {
866: if ($this->stream->isSeekable()) {
867: $this->stream->rewind();
868: }
869: $result = $this->stream->getContents();
870: if (strlen($result) === 0) {
871: return null;
872: }
873:
874: return $result;
875: }
876:
877:
878: if (!is_string($content) && is_callable($content)) {
879: $this->stream = new CallbackStream($content);
880: } else {
881: $this->_createStream();
882: $this->stream->write($content);
883: }
884:
885: return $content;
886: }
887:
888: 889: 890: 891: 892: 893:
894: protected function _handleCallableBody(callable $content)
895: {
896: ob_start();
897: $result1 = $content();
898: $result2 = ob_get_contents();
899: ob_get_clean();
900:
901: if ($result1) {
902: return $result1;
903: }
904:
905: return $result2;
906: }
907:
908: 909: 910: 911: 912: 913: 914: 915: 916: 917: 918: 919:
920: public function statusCode($code = null)
921: {
922: deprecationWarning(
923: 'Response::statusCode() is deprecated. ' .
924: 'Use `getStatusCode()` and `withStatus()` instead.'
925: );
926:
927: if ($code === null) {
928: return $this->_status;
929: }
930: if (!isset($this->_statusCodes[$code])) {
931: throw new InvalidArgumentException('Unknown status code');
932: }
933: $this->_setStatus($code);
934:
935: return $code;
936: }
937:
938: 939: 940: 941: 942: 943: 944: 945:
946: public function getStatusCode()
947: {
948: return $this->_status;
949: }
950:
951: 952: 953: 954: 955: 956: 957: 958: 959: 960: 961: 962: 963: 964: 965: 966: 967: 968: 969: 970: 971: 972: 973: 974: 975: 976: 977: 978: 979:
980: public function withStatus($code, $reasonPhrase = '')
981: {
982: $new = clone $this;
983: $new->_setStatus($code, $reasonPhrase);
984:
985: return $new;
986: }
987:
988: 989: 990: 991: 992: 993: 994: 995:
996: protected function _setStatus($code, $reasonPhrase = '')
997: {
998: if (!isset($this->_statusCodes[$code])) {
999: throw new InvalidArgumentException(sprintf(
1000: 'Invalid status code: %s. Use a valid HTTP status code in range 1xx - 5xx.',
1001: $code
1002: ));
1003: }
1004:
1005: $this->_status = $code;
1006: if (empty($reasonPhrase)) {
1007: $reasonPhrase = $this->_statusCodes[$code];
1008: }
1009: $this->_reasonPhrase = $reasonPhrase;
1010: $this->_setContentType();
1011: }
1012:
1013: 1014: 1015: 1016: 1017: 1018: 1019: 1020: 1021: 1022: 1023: 1024: 1025:
1026: public function getReasonPhrase()
1027: {
1028: return $this->_reasonPhrase;
1029: }
1030:
1031: 1032: 1033: 1034: 1035: 1036: 1037: 1038: 1039: 1040: 1041: 1042: 1043: 1044: 1045: 1046: 1047: 1048: 1049: 1050: 1051: 1052: 1053: 1054: 1055: 1056: 1057: 1058: 1059: 1060: 1061: 1062:
1063: public function httpCodes($code = null)
1064: {
1065: deprecationWarning('Response::httpCodes(). Will be removed in 4.0.0');
1066:
1067: if (empty($code)) {
1068: return $this->_statusCodes;
1069: }
1070: if (is_array($code)) {
1071: $codes = array_keys($code);
1072: $min = min($codes);
1073: if (!is_int($min) || $min < 100 || max($codes) > 999) {
1074: throw new InvalidArgumentException('Invalid status code');
1075: }
1076: $this->_statusCodes = $code + $this->_statusCodes;
1077:
1078: return true;
1079: }
1080: if (!isset($this->_statusCodes[$code])) {
1081: return null;
1082: }
1083:
1084: return [$code => $this->_statusCodes[$code]];
1085: }
1086:
1087: 1088: 1089: 1090: 1091: 1092: 1093: 1094: 1095: 1096: 1097: 1098: 1099: 1100: 1101: 1102: 1103: 1104: 1105: 1106: 1107: 1108: 1109: 1110: 1111: 1112: 1113: 1114: 1115: 1116: 1117: 1118: 1119: 1120: 1121: 1122: 1123:
1124: public function type($contentType = null)
1125: {
1126: deprecationWarning(
1127: 'Response::type() is deprecated. ' .
1128: 'Use setTypeMap(), getType() or withType() instead.'
1129: );
1130:
1131: if ($contentType === null) {
1132: return $this->getType();
1133: }
1134: if (is_array($contentType)) {
1135: foreach ($contentType as $type => $definition) {
1136: $this->_mimeTypes[$type] = $definition;
1137: }
1138:
1139: return $this->getType();
1140: }
1141: if (isset($this->_mimeTypes[$contentType])) {
1142: $contentType = $this->_mimeTypes[$contentType];
1143: $contentType = is_array($contentType) ? current($contentType) : $contentType;
1144: }
1145: if (strpos($contentType, '/') === false) {
1146: return false;
1147: }
1148: $this->_contentType = $contentType;
1149: $this->_setContentType();
1150:
1151: return $contentType;
1152: }
1153:
1154: 1155: 1156: 1157: 1158: 1159: 1160: 1161: 1162: 1163: 1164:
1165: public function setTypeMap($type, $mimeType)
1166: {
1167: $this->_mimeTypes[$type] = $mimeType;
1168: }
1169:
1170: 1171: 1172: 1173: 1174:
1175: public function getType()
1176: {
1177: return $this->_contentType;
1178: }
1179:
1180: 1181: 1182: 1183: 1184: 1185: 1186: 1187: 1188:
1189: public function withType($contentType)
1190: {
1191: $mappedType = $this->resolveType($contentType);
1192: $new = clone $this;
1193: $new->_contentType = $mappedType;
1194: $new->_setContentType();
1195:
1196: return $new;
1197: }
1198:
1199: 1200: 1201: 1202: 1203: 1204: 1205:
1206: protected function resolveType($contentType)
1207: {
1208: $mapped = $this->getMimeType($contentType);
1209: if ($mapped) {
1210: return is_array($mapped) ? current($mapped) : $mapped;
1211: }
1212: if (strpos($contentType, '/') === false) {
1213: throw new InvalidArgumentException(sprintf('"%s" is an invalid content type.', $contentType));
1214: }
1215:
1216: return $contentType;
1217: }
1218:
1219: 1220: 1221: 1222: 1223: 1224: 1225: 1226:
1227: public function getMimeType($alias)
1228: {
1229: if (isset($this->_mimeTypes[$alias])) {
1230: return $this->_mimeTypes[$alias];
1231: }
1232:
1233: return false;
1234: }
1235:
1236: 1237: 1238: 1239: 1240: 1241: 1242: 1243:
1244: public function mapType($ctype)
1245: {
1246: if (is_array($ctype)) {
1247: return array_map([$this, 'mapType'], $ctype);
1248: }
1249:
1250: foreach ($this->_mimeTypes as $alias => $types) {
1251: if (in_array($ctype, (array)$types)) {
1252: return $alias;
1253: }
1254: }
1255:
1256: return null;
1257: }
1258:
1259: 1260: 1261: 1262: 1263: 1264: 1265: 1266:
1267: public function charset($charset = null)
1268: {
1269: deprecationWarning(
1270: 'Response::charset() is deprecated. ' .
1271: 'Use getCharset()/withCharset() instead.'
1272: );
1273:
1274: if ($charset === null) {
1275: return $this->_charset;
1276: }
1277: $this->_charset = $charset;
1278: $this->_setContentType();
1279:
1280: return $this->_charset;
1281: }
1282:
1283: 1284: 1285: 1286: 1287:
1288: public function getCharset()
1289: {
1290: return $this->_charset;
1291: }
1292:
1293: 1294: 1295: 1296: 1297: 1298:
1299: public function withCharset($charset)
1300: {
1301: $new = clone $this;
1302: $new->_charset = $charset;
1303: $new->_setContentType();
1304:
1305: return $new;
1306: }
1307:
1308: 1309: 1310: 1311: 1312: 1313:
1314: public function disableCache()
1315: {
1316: deprecationWarning(
1317: 'Response::disableCache() is deprecated. ' .
1318: 'Use withDisabledCache() instead.'
1319: );
1320:
1321: $this->_setHeader('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT');
1322: $this->_setHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT');
1323: $this->_setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
1324: }
1325:
1326: 1327: 1328: 1329: 1330:
1331: public function withDisabledCache()
1332: {
1333: return $this->withHeader('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT')
1334: ->withHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT')
1335: ->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
1336: }
1337:
1338: 1339: 1340: 1341: 1342: 1343: 1344: 1345: 1346:
1347: public function cache($since, $time = '+1 day')
1348: {
1349: deprecationWarning(
1350: 'Response::cache() is deprecated. ' .
1351: 'Use withCache() instead.'
1352: );
1353:
1354: if (!is_int($time)) {
1355: $time = strtotime($time);
1356: if ($time === false) {
1357: throw new InvalidArgumentException('Invalid time parameter. Ensure your time value can be parsed by strtotime');
1358: }
1359: }
1360:
1361: $this->_setHeader('Date', gmdate('D, j M Y G:i:s ', time()) . 'GMT');
1362:
1363: $this->modified($since);
1364: $this->expires($time);
1365: $this->sharable(true);
1366: $this->maxAge($time - time());
1367: }
1368:
1369: 1370: 1371: 1372: 1373: 1374: 1375: 1376:
1377: public function withCache($since, $time = '+1 day')
1378: {
1379: if (!is_int($time)) {
1380: $time = strtotime($time);
1381: if ($time === false) {
1382: throw new InvalidArgumentException('Invalid time parameter. Ensure your time value can be parsed by strtotime');
1383: }
1384: }
1385:
1386: return $this->withHeader('Date', gmdate('D, j M Y G:i:s ', time()) . 'GMT')
1387: ->withModified($since)
1388: ->withExpires($time)
1389: ->withSharable(true)
1390: ->withMaxAge($time - time());
1391: }
1392:
1393: 1394: 1395: 1396: 1397: 1398: 1399: 1400: 1401: 1402: 1403:
1404: public function sharable($public = null, $time = null)
1405: {
1406: deprecationWarning(
1407: 'Response::sharable() is deprecated. ' .
1408: 'Use withSharable() instead.'
1409: );
1410: if ($public === null) {
1411: $public = array_key_exists('public', $this->_cacheDirectives);
1412: $private = array_key_exists('private', $this->_cacheDirectives);
1413: $noCache = array_key_exists('no-cache', $this->_cacheDirectives);
1414: if (!$public && !$private && !$noCache) {
1415: return null;
1416: }
1417:
1418: return $public || !($private || $noCache);
1419: }
1420: if ($public) {
1421: $this->_cacheDirectives['public'] = true;
1422: unset($this->_cacheDirectives['private']);
1423: } else {
1424: $this->_cacheDirectives['private'] = true;
1425: unset($this->_cacheDirectives['public']);
1426: }
1427:
1428: $this->maxAge($time);
1429: if (!$time) {
1430: $this->_setCacheControl();
1431: }
1432:
1433: return (bool)$public;
1434: }
1435:
1436: 1437: 1438: 1439: 1440: 1441: 1442: 1443:
1444: public function withSharable($public, $time = null)
1445: {
1446: $new = clone $this;
1447: unset($new->_cacheDirectives['private'], $new->_cacheDirectives['public']);
1448:
1449: $key = $public ? 'public' : 'private';
1450: $new->_cacheDirectives[$key] = true;
1451:
1452: if ($time !== null) {
1453: $new->_cacheDirectives['max-age'] = $time;
1454: }
1455: $new->_setCacheControl();
1456:
1457: return $new;
1458: }
1459:
1460: 1461: 1462: 1463: 1464: 1465: 1466: 1467: 1468: 1469: 1470:
1471: public function sharedMaxAge($seconds = null)
1472: {
1473: deprecationWarning(
1474: 'Response::sharedMaxAge() is deprecated. ' .
1475: 'Use withSharedMaxAge() instead.'
1476: );
1477: if ($seconds !== null) {
1478: $this->_cacheDirectives['s-maxage'] = $seconds;
1479: $this->_setCacheControl();
1480: }
1481: if (isset($this->_cacheDirectives['s-maxage'])) {
1482: return $this->_cacheDirectives['s-maxage'];
1483: }
1484:
1485: return null;
1486: }
1487:
1488: 1489: 1490: 1491: 1492: 1493: 1494: 1495: 1496:
1497: public function withSharedMaxAge($seconds)
1498: {
1499: $new = clone $this;
1500: $new->_cacheDirectives['s-maxage'] = $seconds;
1501: $new->_setCacheControl();
1502:
1503: return $new;
1504: }
1505:
1506: 1507: 1508: 1509: 1510: 1511: 1512: 1513: 1514: 1515:
1516: public function maxAge($seconds = null)
1517: {
1518: deprecationWarning(
1519: 'Response::maxAge() is deprecated. ' .
1520: 'Use withMaxAge() instead.'
1521: );
1522: if ($seconds !== null) {
1523: $this->_cacheDirectives['max-age'] = $seconds;
1524: $this->_setCacheControl();
1525: }
1526: if (isset($this->_cacheDirectives['max-age'])) {
1527: return $this->_cacheDirectives['max-age'];
1528: }
1529:
1530: return null;
1531: }
1532:
1533: 1534: 1535: 1536: 1537: 1538: 1539: 1540: 1541:
1542: public function withMaxAge($seconds)
1543: {
1544: $new = clone $this;
1545: $new->_cacheDirectives['max-age'] = $seconds;
1546: $new->_setCacheControl();
1547:
1548: return $new;
1549: }
1550:
1551: 1552: 1553: 1554: 1555: 1556: 1557: 1558: 1559: 1560: 1561: 1562:
1563: public function mustRevalidate($enable = null)
1564: {
1565: deprecationWarning(
1566: 'Response::mustRevalidate() is deprecated. ' .
1567: 'Use withMustRevalidate() instead.'
1568: );
1569:
1570: if ($enable !== null) {
1571: if ($enable) {
1572: $this->_cacheDirectives['must-revalidate'] = true;
1573: } else {
1574: unset($this->_cacheDirectives['must-revalidate']);
1575: }
1576: $this->_setCacheControl();
1577: }
1578:
1579: return array_key_exists('must-revalidate', $this->_cacheDirectives);
1580: }
1581:
1582: 1583: 1584: 1585: 1586: 1587: 1588: 1589: 1590: 1591: 1592:
1593: public function withMustRevalidate($enable)
1594: {
1595: $new = clone $this;
1596: if ($enable) {
1597: $new->_cacheDirectives['must-revalidate'] = true;
1598: } else {
1599: unset($new->_cacheDirectives['must-revalidate']);
1600: }
1601: $new->_setCacheControl();
1602:
1603: return $new;
1604: }
1605:
1606: 1607: 1608: 1609: 1610: 1611:
1612: protected function _setCacheControl()
1613: {
1614: $control = '';
1615: foreach ($this->_cacheDirectives as $key => $val) {
1616: $control .= $val === true ? $key : sprintf('%s=%s', $key, $val);
1617: $control .= ', ';
1618: }
1619: $control = rtrim($control, ', ');
1620: $this->_setHeader('Cache-Control', $control);
1621: }
1622:
1623: 1624: 1625: 1626: 1627: 1628: 1629: 1630: 1631: 1632: 1633: 1634: 1635: 1636:
1637: public function expires($time = null)
1638: {
1639: deprecationWarning(
1640: 'Response::expires() is deprecated. ' .
1641: 'Use withExpires() instead.'
1642: );
1643:
1644: if ($time !== null) {
1645: $date = $this->_getUTCDate($time);
1646: $this->_setHeader('Expires', $date->format('D, j M Y H:i:s') . ' GMT');
1647: }
1648:
1649: if ($this->hasHeader('Expires')) {
1650: return $this->getHeaderLine('Expires');
1651: }
1652:
1653: return null;
1654: }
1655:
1656: 1657: 1658: 1659: 1660: 1661: 1662: 1663: 1664: 1665: 1666: 1667: 1668: 1669: 1670: 1671:
1672: public function withExpires($time)
1673: {
1674: $date = $this->_getUTCDate($time);
1675:
1676: return $this->withHeader('Expires', $date->format('D, j M Y H:i:s') . ' GMT');
1677: }
1678:
1679: 1680: 1681: 1682: 1683: 1684: 1685: 1686: 1687: 1688: 1689: 1690: 1691: 1692:
1693: public function modified($time = null)
1694: {
1695: deprecationWarning(
1696: 'Response::modified() is deprecated. ' .
1697: 'Use withModified() or getHeaderLine("Last-Modified") instead.'
1698: );
1699:
1700: if ($time !== null) {
1701: $date = $this->_getUTCDate($time);
1702: $this->_setHeader('Last-Modified', $date->format('D, j M Y H:i:s') . ' GMT');
1703: }
1704:
1705: if ($this->hasHeader('Last-Modified')) {
1706: return $this->getHeaderLine('Last-Modified');
1707: }
1708:
1709: return null;
1710: }
1711:
1712: 1713: 1714: 1715: 1716: 1717: 1718: 1719: 1720: 1721: 1722: 1723: 1724: 1725: 1726: 1727:
1728: public function withModified($time)
1729: {
1730: $date = $this->_getUTCDate($time);
1731:
1732: return $this->withHeader('Last-Modified', $date->format('D, j M Y H:i:s') . ' GMT');
1733: }
1734:
1735: 1736: 1737: 1738: 1739: 1740: 1741: 1742: 1743:
1744: public function notModified()
1745: {
1746: $this->_createStream();
1747: $this->_setStatus(304);
1748:
1749: $remove = [
1750: 'Allow',
1751: 'Content-Encoding',
1752: 'Content-Language',
1753: 'Content-Length',
1754: 'Content-MD5',
1755: 'Content-Type',
1756: 'Last-Modified'
1757: ];
1758: foreach ($remove as $header) {
1759: $this->_clearHeader($header);
1760: }
1761: }
1762:
1763: 1764: 1765: 1766: 1767: 1768: 1769: 1770: 1771:
1772: public function withNotModified()
1773: {
1774: $new = $this->withStatus(304);
1775: $new->_createStream();
1776: $remove = [
1777: 'Allow',
1778: 'Content-Encoding',
1779: 'Content-Language',
1780: 'Content-Length',
1781: 'Content-MD5',
1782: 'Content-Type',
1783: 'Last-Modified'
1784: ];
1785: foreach ($remove as $header) {
1786: $new = $new->withoutHeader($header);
1787: }
1788:
1789: return $new;
1790: }
1791:
1792: 1793: 1794: 1795: 1796: 1797: 1798: 1799: 1800: 1801: 1802:
1803: public function vary($cacheVariances = null)
1804: {
1805: deprecationWarning(
1806: 'Response::vary() is deprecated. ' .
1807: 'Use withVary() instead.'
1808: );
1809:
1810: if ($cacheVariances !== null) {
1811: $cacheVariances = (array)$cacheVariances;
1812: $this->_setHeader('Vary', implode(', ', $cacheVariances));
1813: }
1814:
1815: if ($this->hasHeader('Vary')) {
1816: return explode(', ', $this->getHeaderLine('Vary'));
1817: }
1818:
1819: return null;
1820: }
1821:
1822: 1823: 1824: 1825: 1826: 1827: 1828: 1829: 1830: 1831: 1832:
1833: public function withVary($cacheVariances)
1834: {
1835: return $this->withHeader('Vary', (array)$cacheVariances);
1836: }
1837:
1838: 1839: 1840: 1841: 1842: 1843: 1844: 1845: 1846: 1847: 1848: 1849: 1850: 1851: 1852: 1853: 1854: 1855: 1856: 1857: 1858: 1859:
1860: public function etag($hash = null, $weak = false)
1861: {
1862: deprecationWarning(
1863: 'Response::etag() is deprecated. ' .
1864: 'Use withEtag() or getHeaderLine("Etag") instead.'
1865: );
1866:
1867: if ($hash !== null) {
1868: $this->_setHeader('Etag', sprintf('%s"%s"', $weak ? 'W/' : null, $hash));
1869: }
1870:
1871: if ($this->hasHeader('Etag')) {
1872: return $this->getHeaderLine('Etag');
1873: }
1874:
1875: return null;
1876: }
1877:
1878: 1879: 1880: 1881: 1882: 1883: 1884: 1885: 1886: 1887: 1888: 1889: 1890: 1891: 1892: 1893: 1894: 1895: 1896: 1897: 1898:
1899: public function withEtag($hash, $weak = false)
1900: {
1901: $hash = sprintf('%s"%s"', $weak ? 'W/' : null, $hash);
1902:
1903: return $this->withHeader('Etag', $hash);
1904: }
1905:
1906: 1907: 1908: 1909: 1910: 1911: 1912:
1913: protected function _getUTCDate($time = null)
1914: {
1915: if ($time instanceof DateTimeInterface) {
1916: $result = clone $time;
1917: } elseif (is_int($time)) {
1918: $result = new DateTime(date('Y-m-d H:i:s', $time));
1919: } else {
1920: $result = new DateTime($time);
1921: }
1922:
1923: return $result->setTimezone(new DateTimeZone('UTC'));
1924: }
1925:
1926: 1927: 1928: 1929: 1930: 1931:
1932: public function compress()
1933: {
1934: $compressionEnabled = ini_get('zlib.output_compression') !== '1' &&
1935: extension_loaded('zlib') &&
1936: (strpos(env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false);
1937:
1938: return $compressionEnabled && ob_start('ob_gzhandler');
1939: }
1940:
1941: 1942: 1943: 1944: 1945:
1946: public function outputCompressed()
1947: {
1948: return strpos(env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false
1949: && (ini_get('zlib.output_compression') === '1' || in_array('ob_gzhandler', ob_list_handlers(), true));
1950: }
1951:
1952: 1953: 1954: 1955: 1956: 1957: 1958:
1959: public function download($filename)
1960: {
1961: deprecationWarning(
1962: 'Response::download() is deprecated. ' .
1963: 'Use withDownload() instead.'
1964: );
1965:
1966: $this->header('Content-Disposition', 'attachment; filename="' . $filename . '"');
1967: }
1968:
1969: 1970: 1971: 1972: 1973: 1974:
1975: public function withDownload($filename)
1976: {
1977: return $this->withHeader('Content-Disposition', 'attachment; filename="' . $filename . '"');
1978: }
1979:
1980: 1981: 1982: 1983: 1984: 1985: 1986: 1987:
1988: public function protocol($protocol = null)
1989: {
1990: deprecationWarning(
1991: 'Response::protocol() is deprecated. ' .
1992: 'Use getProtocolVersion() instead.'
1993: );
1994:
1995: if ($protocol !== null) {
1996: $this->_protocol = $protocol;
1997: }
1998:
1999: return $this->_protocol;
2000: }
2001:
2002: 2003: 2004: 2005: 2006: 2007: 2008: 2009:
2010: public function length($bytes = null)
2011: {
2012: deprecationWarning(
2013: 'Response::length() is deprecated. ' .
2014: 'Use withLength() instead.'
2015: );
2016:
2017: if ($bytes !== null) {
2018: $this->_setHeader('Content-Length', $bytes);
2019: }
2020:
2021: if ($this->hasHeader('Content-Length')) {
2022: return $this->getHeaderLine('Content-Length');
2023: }
2024:
2025: return null;
2026: }
2027:
2028: 2029: 2030: 2031: 2032: 2033:
2034: public function withLength($bytes)
2035: {
2036: return $this->withHeader('Content-Length', (string)$bytes);
2037: }
2038:
2039: 2040: 2041: 2042: 2043: 2044: 2045: 2046: 2047: 2048: 2049: 2050: 2051: 2052: 2053: 2054: 2055: 2056: 2057: 2058: 2059: 2060:
2061: public function withAddedLink($url, $options = [])
2062: {
2063: $params = [];
2064: foreach ($options as $key => $option) {
2065: $params[] = $key . '="' . $option . '"';
2066: }
2067:
2068: $param = '';
2069: if ($params) {
2070: $param = '; ' . implode('; ', $params);
2071: }
2072:
2073: return $this->withAddedHeader('Link', '<' . $url . '>' . $param);
2074: }
2075:
2076: 2077: 2078: 2079: 2080: 2081: 2082: 2083: 2084: 2085: 2086: 2087: 2088: 2089: 2090:
2091: public function checkNotModified(ServerRequest $request)
2092: {
2093: $etags = preg_split('/\s*,\s*/', (string)$request->getHeaderLine('If-None-Match'), 0, PREG_SPLIT_NO_EMPTY);
2094: $responseTag = $this->getHeaderLine('Etag');
2095: $etagMatches = null;
2096: if ($responseTag) {
2097: $etagMatches = in_array('*', $etags, true) || in_array($responseTag, $etags, true);
2098: }
2099:
2100: $modifiedSince = $request->getHeaderLine('If-Modified-Since');
2101: $timeMatches = null;
2102: if ($modifiedSince && $this->hasHeader('Last-Modified')) {
2103: $timeMatches = strtotime($this->getHeaderLine('Last-Modified')) === strtotime($modifiedSince);
2104: }
2105: if ($etagMatches === null && $timeMatches === null) {
2106: return false;
2107: }
2108: $notModified = $etagMatches !== false && $timeMatches !== false;
2109: if ($notModified) {
2110: $this->notModified();
2111: }
2112:
2113: return $notModified;
2114: }
2115:
2116: 2117: 2118: 2119: 2120: 2121: 2122:
2123: public function __toString()
2124: {
2125: $this->stream->rewind();
2126:
2127: return (string)$this->stream->getContents();
2128: }
2129:
2130: 2131: 2132: 2133: 2134: 2135: 2136: 2137: 2138: 2139: 2140: 2141: 2142: 2143: 2144: 2145: 2146: 2147: 2148: 2149: 2150: 2151: 2152: 2153: 2154: 2155: 2156: 2157: 2158: 2159: 2160: 2161: 2162: 2163: 2164: 2165: 2166: 2167: 2168: 2169:
2170: public function cookie($options = null)
2171: {
2172: deprecationWarning(
2173: 'Response::cookie() is deprecated. ' .
2174: 'Use getCookie(), getCookies() and withCookie() instead.'
2175: );
2176:
2177: if ($options === null) {
2178: return $this->getCookies();
2179: }
2180:
2181: if (is_string($options)) {
2182: if (!$this->_cookies->has($options)) {
2183: return null;
2184: }
2185:
2186: $cookie = $this->_cookies->get($options);
2187:
2188: return $this->convertCookieToArray($cookie);
2189: }
2190:
2191: $options += [
2192: 'name' => 'CakeCookie[default]',
2193: 'value' => '',
2194: 'expire' => 0,
2195: 'path' => '/',
2196: 'domain' => '',
2197: 'secure' => false,
2198: 'httpOnly' => false
2199: ];
2200: $expires = $options['expire'] ? new DateTime('@' . $options['expire']) : null;
2201: $cookie = new Cookie(
2202: $options['name'],
2203: $options['value'],
2204: $expires,
2205: $options['path'],
2206: $options['domain'],
2207: $options['secure'],
2208: $options['httpOnly']
2209: );
2210: $this->_cookies = $this->_cookies->add($cookie);
2211: }
2212:
2213: 2214: 2215: 2216: 2217: 2218: 2219: 2220: 2221: 2222: 2223: 2224: 2225: 2226: 2227: 2228: 2229: 2230: 2231: 2232: 2233: 2234: 2235: 2236: 2237: 2238: 2239: 2240: 2241:
2242: public function withCookie($name, $data = '')
2243: {
2244: if ($name instanceof Cookie) {
2245: $cookie = $name;
2246: } else {
2247: deprecationWarning(
2248: get_called_class() . '::withCookie(string $name, array $data) is deprecated. ' .
2249: 'Pass an instance of \Cake\Http\Cookie\Cookie instead.'
2250: );
2251:
2252: if (!is_array($data)) {
2253: $data = ['value' => $data];
2254: }
2255: $data += [
2256: 'value' => '',
2257: 'expire' => 0,
2258: 'path' => '/',
2259: 'domain' => '',
2260: 'secure' => false,
2261: 'httpOnly' => false
2262: ];
2263: $expires = $data['expire'] ? new DateTime('@' . $data['expire']) : null;
2264: $cookie = new Cookie(
2265: $name,
2266: $data['value'],
2267: $expires,
2268: $data['path'],
2269: $data['domain'],
2270: $data['secure'],
2271: $data['httpOnly']
2272: );
2273: }
2274:
2275: $new = clone $this;
2276: $new->_cookies = $new->_cookies->add($cookie);
2277:
2278: return $new;
2279: }
2280:
2281: 2282: 2283: 2284: 2285: 2286: 2287: 2288: 2289: 2290: 2291: 2292: 2293: 2294: 2295: 2296: 2297: 2298: 2299: 2300: 2301: 2302: 2303: 2304: 2305: 2306: 2307:
2308: public function withExpiredCookie($name, $options = [])
2309: {
2310: if ($name instanceof CookieInterface) {
2311: $cookie = $name->withExpired();
2312: } else {
2313: deprecationWarning(
2314: get_called_class() . '::withExpiredCookie(string $name, array $data) is deprecated. ' .
2315: 'Pass an instance of \Cake\Http\Cookie\Cookie instead.'
2316: );
2317:
2318: $options += [
2319: 'path' => '/',
2320: 'domain' => '',
2321: 'secure' => false,
2322: 'httpOnly' => false
2323: ];
2324:
2325: $cookie = new Cookie(
2326: $name,
2327: '',
2328: DateTime::createFromFormat('U', 1),
2329: $options['path'],
2330: $options['domain'],
2331: $options['secure'],
2332: $options['httpOnly']
2333: );
2334: }
2335:
2336: $new = clone $this;
2337: $new->_cookies = $new->_cookies->add($cookie);
2338:
2339: return $new;
2340: }
2341:
2342: 2343: 2344: 2345: 2346: 2347: 2348: 2349: 2350:
2351: public function getCookie($name)
2352: {
2353: if (!$this->_cookies->has($name)) {
2354: return null;
2355: }
2356:
2357: $cookie = $this->_cookies->get($name);
2358:
2359: return $this->convertCookieToArray($cookie);
2360: }
2361:
2362: 2363: 2364: 2365: 2366: 2367: 2368:
2369: public function getCookies()
2370: {
2371: $out = [];
2372: foreach ($this->_cookies as $cookie) {
2373: $out[$cookie->getName()] = $this->convertCookieToArray($cookie);
2374: }
2375:
2376: return $out;
2377: }
2378:
2379: 2380: 2381: 2382: 2383: 2384: 2385: 2386: 2387:
2388: protected function convertCookieToArray(CookieInterface $cookie)
2389: {
2390: return [
2391: 'name' => $cookie->getName(),
2392: 'value' => $cookie->getStringValue(),
2393: 'path' => $cookie->getPath(),
2394: 'domain' => $cookie->getDomain(),
2395: 'secure' => $cookie->isSecure(),
2396: 'httpOnly' => $cookie->isHttpOnly(),
2397: 'expire' => $cookie->getExpiresTimestamp()
2398: ];
2399: }
2400:
2401: 2402: 2403: 2404: 2405:
2406: public function getCookieCollection()
2407: {
2408: return $this->_cookies;
2409: }
2410:
2411: 2412: 2413: 2414: 2415: 2416:
2417: public function withCookieCollection(CookieCollection $cookieCollection)
2418: {
2419: $new = clone $this;
2420: $new->_cookies = $cookieCollection;
2421:
2422: return $new;
2423: }
2424:
2425: 2426: 2427: 2428: 2429: 2430: 2431: 2432: 2433: 2434: 2435: 2436: 2437: 2438: 2439: 2440: 2441: 2442: 2443: 2444: 2445: 2446: 2447: 2448: 2449: 2450: 2451: 2452: 2453: 2454: 2455: 2456: 2457: 2458: 2459: 2460: 2461: 2462: 2463: 2464:
2465: public function cors(ServerRequest $request, $allowedDomains = [], $allowedMethods = [], $allowedHeaders = [])
2466: {
2467: $origin = $request->getHeaderLine('Origin');
2468: $ssl = $request->is('ssl');
2469: $builder = new CorsBuilder($this, $origin, $ssl);
2470: if (!$origin) {
2471: return $builder;
2472: }
2473: if (empty($allowedDomains) && empty($allowedMethods) && empty($allowedHeaders)) {
2474: return $builder;
2475: }
2476: deprecationWarning(
2477: 'The $allowedDomains, $allowedMethods, and $allowedHeaders parameters of Response::cors() ' .
2478: 'are deprecated. Instead you should use the builder methods on the return of cors().'
2479: );
2480:
2481: $updated = $builder->allowOrigin($allowedDomains)
2482: ->allowMethods((array)$allowedMethods)
2483: ->allowHeaders((array)$allowedHeaders)
2484: ->build();
2485:
2486:
2487:
2488: if ($updated !== $this) {
2489: foreach ($updated->getHeaders() as $name => $values) {
2490: if (!$this->hasHeader($name)) {
2491: $this->_setHeader($name, $values[0]);
2492: }
2493: }
2494: }
2495:
2496: return $builder;
2497: }
2498:
2499: 2500: 2501: 2502: 2503: 2504: 2505: 2506: 2507: 2508: 2509: 2510: 2511: 2512: 2513: 2514: 2515: 2516:
2517: public function file($path, array $options = [])
2518: {
2519: deprecationWarning(
2520: 'Response::file() is deprecated. ' .
2521: 'Use withFile() instead.'
2522: );
2523:
2524: $file = $this->validateFile($path);
2525: $options += [
2526: 'name' => null,
2527: 'download' => null
2528: ];
2529:
2530: $extension = strtolower($file->ext());
2531: $download = $options['download'];
2532: if ((!$extension || $this->type($extension) === false) && $download === null) {
2533: $download = true;
2534: }
2535:
2536: $fileSize = $file->size();
2537: if ($download) {
2538: $agent = env('HTTP_USER_AGENT');
2539:
2540: if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent)) {
2541: $contentType = 'application/octet-stream';
2542: } elseif (preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) {
2543: $contentType = 'application/force-download';
2544: }
2545:
2546: if (!empty($contentType)) {
2547: $this->type($contentType);
2548: }
2549: if ($options['name'] === null) {
2550: $name = $file->name;
2551: } else {
2552: $name = $options['name'];
2553: }
2554: $this->download($name);
2555: $this->header('Content-Transfer-Encoding', 'binary');
2556: }
2557:
2558: $this->header('Accept-Ranges', 'bytes');
2559: $httpRange = env('HTTP_RANGE');
2560: if (isset($httpRange)) {
2561: $this->_fileRange($file, $httpRange);
2562: } else {
2563: $this->header('Content-Length', $fileSize);
2564: }
2565:
2566: $this->_file = $file;
2567: $this->stream = new Stream($file->path, 'rb');
2568: }
2569:
2570: 2571: 2572: 2573: 2574: 2575: 2576: 2577: 2578: 2579: 2580: 2581: 2582: 2583: 2584: 2585: 2586: 2587: 2588: 2589:
2590: public function withFile($path, array $options = [])
2591: {
2592: $file = $this->validateFile($path);
2593: $options += [
2594: 'name' => null,
2595: 'download' => null
2596: ];
2597:
2598: $extension = strtolower($file->ext());
2599: $mapped = $this->getMimeType($extension);
2600: if ((!$extension || !$mapped) && $options['download'] === null) {
2601: $options['download'] = true;
2602: }
2603:
2604: $new = clone $this;
2605: if ($mapped) {
2606: $new = $new->withType($extension);
2607: }
2608:
2609: $fileSize = $file->size();
2610: if ($options['download']) {
2611: $agent = env('HTTP_USER_AGENT');
2612:
2613: if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent)) {
2614: $contentType = 'application/octet-stream';
2615: } elseif (preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) {
2616: $contentType = 'application/force-download';
2617: }
2618:
2619: if (isset($contentType)) {
2620: $new = $new->withType($contentType);
2621: }
2622: $name = $options['name'] ?: $file->name;
2623: $new = $new->withDownload($name)
2624: ->withHeader('Content-Transfer-Encoding', 'binary');
2625: }
2626:
2627: $new = $new->withHeader('Accept-Ranges', 'bytes');
2628: $httpRange = env('HTTP_RANGE');
2629: if (isset($httpRange)) {
2630: $new->_fileRange($file, $httpRange);
2631: } else {
2632: $new = $new->withHeader('Content-Length', (string)$fileSize);
2633: }
2634: $new->_file = $file;
2635: $new->stream = new Stream($file->path, 'rb');
2636:
2637: return $new;
2638: }
2639:
2640: 2641: 2642: 2643: 2644: 2645:
2646: public function withStringBody($string)
2647: {
2648: $new = clone $this;
2649: $new->_createStream();
2650: $new->stream->write((string)$string);
2651:
2652: return $new;
2653: }
2654:
2655: 2656: 2657: 2658: 2659: 2660: 2661:
2662: protected function validateFile($path)
2663: {
2664: if (strpos($path, '../') !== false || strpos($path, '..\\') !== false) {
2665: throw new NotFoundException(__d('cake', 'The requested file contains `..` and will not be read.'));
2666: }
2667: if (!is_file($path)) {
2668: deprecationWarning(
2669: 'Automatic prefixing of paths with `APP` by `Response::file()` and `withFile()` is deprecated. ' .
2670: 'Use absolute paths instead.'
2671: );
2672: $path = APP . $path;
2673: }
2674: if (!Folder::isAbsolute($path)) {
2675: deprecationWarning(
2676: 'Serving files via `file()` or `withFile()` using relative paths is deprecated.' .
2677: 'Use an absolute path instead.'
2678: );
2679: }
2680:
2681: $file = new File($path);
2682: if (!$file->exists() || !$file->readable()) {
2683: if (Configure::read('debug')) {
2684: throw new NotFoundException(sprintf('The requested file %s was not found or not readable', $path));
2685: }
2686: throw new NotFoundException(__d('cake', 'The requested file was not found'));
2687: }
2688:
2689: return $file;
2690: }
2691:
2692: 2693: 2694: 2695: 2696:
2697: public function getFile()
2698: {
2699: return $this->_file;
2700: }
2701:
2702: 2703: 2704: 2705: 2706: 2707: 2708: 2709: 2710: 2711: 2712: 2713:
2714: protected function _fileRange($file, $httpRange)
2715: {
2716: $fileSize = $file->size();
2717: $lastByte = $fileSize - 1;
2718: $start = 0;
2719: $end = $lastByte;
2720:
2721: preg_match('/^bytes\s*=\s*(\d+)?\s*-\s*(\d+)?$/', $httpRange, $matches);
2722: if ($matches) {
2723: $start = $matches[1];
2724: $end = isset($matches[2]) ? $matches[2] : '';
2725: }
2726:
2727: if ($start === '') {
2728: $start = $fileSize - $end;
2729: $end = $lastByte;
2730: }
2731: if ($end === '') {
2732: $end = $lastByte;
2733: }
2734:
2735: if ($start > $end || $end > $lastByte || $start > $lastByte) {
2736: $this->_setStatus(416);
2737: $this->_setHeader('Content-Range', 'bytes 0-' . $lastByte . '/' . $fileSize);
2738:
2739: return;
2740: }
2741:
2742: $this->_setHeader('Content-Length', $end - $start + 1);
2743: $this->_setHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $fileSize);
2744: $this->_setStatus(206);
2745: $this->_fileRange = [$start, $end];
2746: }
2747:
2748: 2749: 2750: 2751: 2752: 2753: 2754: 2755:
2756: protected function _sendFile($file, $range)
2757: {
2758: deprecationWarning('Will be removed in 4.0.0');
2759:
2760: ob_implicit_flush(true);
2761:
2762: $file->open('rb');
2763:
2764: $end = $start = false;
2765: if ($range) {
2766: list($start, $end) = $range;
2767: }
2768: if ($start !== false) {
2769: $file->offset($start);
2770: }
2771:
2772: $bufferSize = 8192;
2773: if (strpos(ini_get('disable_functions'), 'set_time_limit') === false) {
2774: set_time_limit(0);
2775: }
2776: session_write_close();
2777: while (!feof($file->handle)) {
2778: if (!$this->_isActive()) {
2779: $file->close();
2780:
2781: return false;
2782: }
2783: $offset = $file->offset();
2784: if ($end && $offset >= $end) {
2785: break;
2786: }
2787: if ($end && $offset + $bufferSize >= $end) {
2788: $bufferSize = $end - $offset + 1;
2789: }
2790: echo fread($file->handle, $bufferSize);
2791: }
2792: $file->close();
2793:
2794: return true;
2795: }
2796:
2797: 2798: 2799: 2800: 2801: 2802:
2803: protected function _isActive()
2804: {
2805: deprecationWarning('Will be removed in 4.0.0');
2806:
2807: return connection_status() === CONNECTION_NORMAL && !connection_aborted();
2808: }
2809:
2810: 2811: 2812: 2813: 2814: 2815:
2816: protected function _clearBuffer()
2817: {
2818: deprecationWarning(
2819: 'This function is not needed anymore and will be removed.'
2820: );
2821:
2822:
2823: return @ob_end_clean();
2824:
2825: }
2826:
2827: 2828: 2829: 2830: 2831: 2832:
2833: protected function _flushBuffer()
2834: {
2835: deprecationWarning(
2836: 'This function is not needed anymore and will be removed.'
2837: );
2838:
2839:
2840: @flush();
2841: if (ob_get_level()) {
2842: @ob_flush();
2843: }
2844:
2845: }
2846:
2847: 2848: 2849: 2850: 2851: 2852: 2853: 2854:
2855: public function stop($status = 0)
2856: {
2857: deprecationWarning('Will be removed in 4.0.0');
2858:
2859: exit($status);
2860: }
2861:
2862: 2863: 2864: 2865: 2866: 2867:
2868: public function __debugInfo()
2869: {
2870: return [
2871: 'status' => $this->_status,
2872: 'contentType' => $this->_contentType,
2873: 'headers' => $this->headers,
2874: 'file' => $this->_file,
2875: 'fileRange' => $this->_fileRange,
2876: 'cookies' => $this->_cookies,
2877: 'cacheDirectives' => $this->_cacheDirectives,
2878: 'body' => (string)$this->getBody(),
2879: ];
2880: }
2881: }
2882:
2883:
2884: class_alias('Cake\Http\Response', 'Cake\Network\Response');
2885: