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