1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
18:
19: App::uses('File', 'Utility');
20:
21: 22: 23: 24: 25: 26: 27: 28:
29: class CakeResponse {
30:
31: 32: 33: 34: 35:
36: protected $_statusCodes = array(
37: 100 => 'Continue',
38: 101 => 'Switching Protocols',
39: 200 => 'OK',
40: 201 => 'Created',
41: 202 => 'Accepted',
42: 203 => 'Non-Authoritative Information',
43: 204 => 'No Content',
44: 205 => 'Reset Content',
45: 206 => 'Partial Content',
46: 300 => 'Multiple Choices',
47: 301 => 'Moved Permanently',
48: 302 => 'Found',
49: 303 => 'See Other',
50: 304 => 'Not Modified',
51: 305 => 'Use Proxy',
52: 307 => 'Temporary Redirect',
53: 400 => 'Bad Request',
54: 401 => 'Unauthorized',
55: 402 => 'Payment Required',
56: 403 => 'Forbidden',
57: 404 => 'Not Found',
58: 405 => 'Method Not Allowed',
59: 406 => 'Not Acceptable',
60: 407 => 'Proxy Authentication Required',
61: 408 => 'Request Time-out',
62: 409 => 'Conflict',
63: 410 => 'Gone',
64: 411 => 'Length Required',
65: 412 => 'Precondition Failed',
66: 413 => 'Request Entity Too Large',
67: 414 => 'Request-URI Too Large',
68: 415 => 'Unsupported Media Type',
69: 416 => 'Requested range not satisfiable',
70: 417 => 'Expectation Failed',
71: 500 => 'Internal Server Error',
72: 501 => 'Not Implemented',
73: 502 => 'Bad Gateway',
74: 503 => 'Service Unavailable',
75: 504 => 'Gateway Time-out',
76: 505 => 'Unsupported Version'
77: );
78:
79: 80: 81: 82: 83:
84: protected $_mimeTypes = array(
85: 'html' => array('text/html', '*/*'),
86: 'json' => 'application/json',
87: 'xml' => array('application/xml', 'text/xml'),
88: 'rss' => 'application/rss+xml',
89: 'ai' => 'application/postscript',
90: 'bcpio' => 'application/x-bcpio',
91: 'bin' => 'application/octet-stream',
92: 'ccad' => 'application/clariscad',
93: 'cdf' => 'application/x-netcdf',
94: 'class' => 'application/octet-stream',
95: 'cpio' => 'application/x-cpio',
96: 'cpt' => 'application/mac-compactpro',
97: 'csh' => 'application/x-csh',
98: 'csv' => array('text/csv', 'application/vnd.ms-excel', 'text/plain'),
99: 'dcr' => 'application/x-director',
100: 'dir' => 'application/x-director',
101: 'dms' => 'application/octet-stream',
102: 'doc' => 'application/msword',
103: 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
104: 'drw' => 'application/drafting',
105: 'dvi' => 'application/x-dvi',
106: 'dwg' => 'application/acad',
107: 'dxf' => 'application/dxf',
108: 'dxr' => 'application/x-director',
109: 'eot' => 'application/vnd.ms-fontobject',
110: 'eps' => 'application/postscript',
111: 'exe' => 'application/octet-stream',
112: 'ez' => 'application/andrew-inset',
113: 'flv' => 'video/x-flv',
114: 'gtar' => 'application/x-gtar',
115: 'gz' => 'application/x-gzip',
116: 'bz2' => 'application/x-bzip',
117: '7z' => 'application/x-7z-compressed',
118: 'hdf' => 'application/x-hdf',
119: 'hqx' => 'application/mac-binhex40',
120: 'ico' => 'image/x-icon',
121: 'ips' => 'application/x-ipscript',
122: 'ipx' => 'application/x-ipix',
123: 'js' => 'application/javascript',
124: 'latex' => 'application/x-latex',
125: 'lha' => 'application/octet-stream',
126: 'lsp' => 'application/x-lisp',
127: 'lzh' => 'application/octet-stream',
128: 'man' => 'application/x-troff-man',
129: 'me' => 'application/x-troff-me',
130: 'mif' => 'application/vnd.mif',
131: 'ms' => 'application/x-troff-ms',
132: 'nc' => 'application/x-netcdf',
133: 'oda' => 'application/oda',
134: 'otf' => 'font/otf',
135: 'pdf' => 'application/pdf',
136: 'pgn' => 'application/x-chess-pgn',
137: 'pot' => 'application/vnd.ms-powerpoint',
138: 'pps' => 'application/vnd.ms-powerpoint',
139: 'ppt' => 'application/vnd.ms-powerpoint',
140: 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
141: 'ppz' => 'application/vnd.ms-powerpoint',
142: 'pre' => 'application/x-freelance',
143: 'prt' => 'application/pro_eng',
144: 'ps' => 'application/postscript',
145: 'roff' => 'application/x-troff',
146: 'scm' => 'application/x-lotusscreencam',
147: 'set' => 'application/set',
148: 'sh' => 'application/x-sh',
149: 'shar' => 'application/x-shar',
150: 'sit' => 'application/x-stuffit',
151: 'skd' => 'application/x-koan',
152: 'skm' => 'application/x-koan',
153: 'skp' => 'application/x-koan',
154: 'skt' => 'application/x-koan',
155: 'smi' => 'application/smil',
156: 'smil' => 'application/smil',
157: 'sol' => 'application/solids',
158: 'spl' => 'application/x-futuresplash',
159: 'src' => 'application/x-wais-source',
160: 'step' => 'application/STEP',
161: 'stl' => 'application/SLA',
162: 'stp' => 'application/STEP',
163: 'sv4cpio' => 'application/x-sv4cpio',
164: 'sv4crc' => 'application/x-sv4crc',
165: 'svg' => 'image/svg+xml',
166: 'svgz' => 'image/svg+xml',
167: 'swf' => 'application/x-shockwave-flash',
168: 't' => 'application/x-troff',
169: 'tar' => 'application/x-tar',
170: 'tcl' => 'application/x-tcl',
171: 'tex' => 'application/x-tex',
172: 'texi' => 'application/x-texinfo',
173: 'texinfo' => 'application/x-texinfo',
174: 'tr' => 'application/x-troff',
175: 'tsp' => 'application/dsptype',
176: 'ttc' => 'font/ttf',
177: 'ttf' => 'font/ttf',
178: 'unv' => 'application/i-deas',
179: 'ustar' => 'application/x-ustar',
180: 'vcd' => 'application/x-cdlink',
181: 'vda' => 'application/vda',
182: 'xlc' => 'application/vnd.ms-excel',
183: 'xll' => 'application/vnd.ms-excel',
184: 'xlm' => 'application/vnd.ms-excel',
185: 'xls' => 'application/vnd.ms-excel',
186: 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
187: 'xlw' => 'application/vnd.ms-excel',
188: 'zip' => 'application/zip',
189: 'aif' => 'audio/x-aiff',
190: 'aifc' => 'audio/x-aiff',
191: 'aiff' => 'audio/x-aiff',
192: 'au' => 'audio/basic',
193: 'kar' => 'audio/midi',
194: 'mid' => 'audio/midi',
195: 'midi' => 'audio/midi',
196: 'mp2' => 'audio/mpeg',
197: 'mp3' => 'audio/mpeg',
198: 'mpga' => 'audio/mpeg',
199: 'ogg' => 'audio/ogg',
200: 'oga' => 'audio/ogg',
201: 'spx' => 'audio/ogg',
202: 'ra' => 'audio/x-realaudio',
203: 'ram' => 'audio/x-pn-realaudio',
204: 'rm' => 'audio/x-pn-realaudio',
205: 'rpm' => 'audio/x-pn-realaudio-plugin',
206: 'snd' => 'audio/basic',
207: 'tsi' => 'audio/TSP-audio',
208: 'wav' => 'audio/x-wav',
209: 'aac' => 'audio/aac',
210: 'asc' => 'text/plain',
211: 'c' => 'text/plain',
212: 'cc' => 'text/plain',
213: 'css' => 'text/css',
214: 'etx' => 'text/x-setext',
215: 'f' => 'text/plain',
216: 'f90' => 'text/plain',
217: 'h' => 'text/plain',
218: 'hh' => 'text/plain',
219: 'htm' => array('text/html', '*/*'),
220: 'ics' => 'text/calendar',
221: 'm' => 'text/plain',
222: 'rtf' => 'text/rtf',
223: 'rtx' => 'text/richtext',
224: 'sgm' => 'text/sgml',
225: 'sgml' => 'text/sgml',
226: 'tsv' => 'text/tab-separated-values',
227: 'tpl' => 'text/template',
228: 'txt' => 'text/plain',
229: 'text' => 'text/plain',
230: 'avi' => 'video/x-msvideo',
231: 'fli' => 'video/x-fli',
232: 'mov' => 'video/quicktime',
233: 'movie' => 'video/x-sgi-movie',
234: 'mpe' => 'video/mpeg',
235: 'mpeg' => 'video/mpeg',
236: 'mpg' => 'video/mpeg',
237: 'qt' => 'video/quicktime',
238: 'viv' => 'video/vnd.vivo',
239: 'vivo' => 'video/vnd.vivo',
240: 'ogv' => 'video/ogg',
241: 'webm' => 'video/webm',
242: 'mp4' => 'video/mp4',
243: 'm4v' => 'video/mp4',
244: 'f4v' => 'video/mp4',
245: 'f4p' => 'video/mp4',
246: 'm4a' => 'audio/mp4',
247: 'f4a' => 'audio/mp4',
248: 'f4b' => 'audio/mp4',
249: 'gif' => 'image/gif',
250: 'ief' => 'image/ief',
251: 'jpg' => 'image/jpeg',
252: 'jpeg' => 'image/jpeg',
253: 'jpe' => 'image/jpeg',
254: 'pbm' => 'image/x-portable-bitmap',
255: 'pgm' => 'image/x-portable-graymap',
256: 'png' => 'image/png',
257: 'pnm' => 'image/x-portable-anymap',
258: 'ppm' => 'image/x-portable-pixmap',
259: 'ras' => 'image/cmu-raster',
260: 'rgb' => 'image/x-rgb',
261: 'tif' => 'image/tiff',
262: 'tiff' => 'image/tiff',
263: 'xbm' => 'image/x-xbitmap',
264: 'xpm' => 'image/x-xpixmap',
265: 'xwd' => 'image/x-xwindowdump',
266: 'ice' => 'x-conference/x-cooltalk',
267: 'iges' => 'model/iges',
268: 'igs' => 'model/iges',
269: 'mesh' => 'model/mesh',
270: 'msh' => 'model/mesh',
271: 'silo' => 'model/mesh',
272: 'vrml' => 'model/vrml',
273: 'wrl' => 'model/vrml',
274: 'mime' => 'www/mime',
275: 'pdb' => 'chemical/x-pdb',
276: 'xyz' => 'chemical/x-pdb',
277: 'javascript' => 'application/javascript',
278: 'form' => 'application/x-www-form-urlencoded',
279: 'file' => 'multipart/form-data',
280: 'xhtml' => array('application/xhtml+xml', 'application/xhtml', 'text/xhtml'),
281: 'xhtml-mobile' => 'application/vnd.wap.xhtml+xml',
282: 'atom' => 'application/atom+xml',
283: 'amf' => 'application/x-amf',
284: 'wap' => array('text/vnd.wap.wml', 'text/vnd.wap.wmlscript', 'image/vnd.wap.wbmp'),
285: 'wml' => 'text/vnd.wap.wml',
286: 'wmlscript' => 'text/vnd.wap.wmlscript',
287: 'wbmp' => 'image/vnd.wap.wbmp',
288: 'woff' => 'application/x-font-woff',
289: 'webp' => 'image/webp',
290: 'appcache' => 'text/cache-manifest',
291: 'manifest' => 'text/cache-manifest',
292: 'htc' => 'text/x-component',
293: 'rdf' => 'application/xml',
294: 'crx' => 'application/x-chrome-extension',
295: 'oex' => 'application/x-opera-extension',
296: 'xpi' => 'application/x-xpinstall',
297: 'safariextz' => 'application/octet-stream',
298: 'webapp' => 'application/x-web-app-manifest+json',
299: 'vcf' => 'text/x-vcard',
300: 'vtt' => 'text/vtt',
301: 'mkv' => 'video/x-matroska',
302: 'pkpass' => 'application/vnd.apple.pkpass'
303: );
304:
305: 306: 307: 308: 309:
310: protected $_protocol = 'HTTP/1.1';
311:
312: 313: 314: 315: 316:
317: protected $_status = 200;
318:
319: 320: 321: 322: 323: 324:
325: protected $_contentType = 'text/html';
326:
327: 328: 329: 330: 331:
332: protected $_headers = array();
333:
334: 335: 336: 337: 338:
339: protected $_body = null;
340:
341: 342: 343: 344: 345:
346: protected $_file = null;
347:
348: 349: 350: 351: 352:
353: protected $_fileRange = null;
354:
355: 356: 357: 358: 359:
360: protected $_charset = 'UTF-8';
361:
362: 363: 364: 365: 366: 367:
368: protected $_cacheDirectives = array();
369:
370: 371: 372: 373: 374:
375: protected $_cookies = array();
376:
377: 378: 379: 380: 381: 382: 383: 384: 385: 386:
387: public function __construct(array $options = array()) {
388: if (isset($options['body'])) {
389: $this->body($options['body']);
390: }
391: if (isset($options['statusCodes'])) {
392: $this->httpCodes($options['statusCodes']);
393: }
394: if (isset($options['status'])) {
395: $this->statusCode($options['status']);
396: }
397: if (isset($options['type'])) {
398: $this->type($options['type']);
399: }
400: if (!isset($options['charset'])) {
401: $options['charset'] = Configure::read('App.encoding');
402: }
403: $this->charset($options['charset']);
404: }
405:
406: 407: 408: 409: 410: 411:
412: public function send() {
413: if (isset($this->_headers['Location']) && $this->_status === 200) {
414: $this->statusCode(302);
415: }
416:
417: $codeMessage = $this->_statusCodes[$this->_status];
418: $this->_setCookies();
419: $this->_sendHeader("{$this->_protocol} {$this->_status} {$codeMessage}");
420: $this->_setContent();
421: $this->_setContentLength();
422: $this->_setContentType();
423: foreach ($this->_headers as $header => $values) {
424: foreach ((array)$values as $value) {
425: $this->_sendHeader($header, $value);
426: }
427: }
428: if ($this->_file) {
429: $this->_sendFile($this->_file, $this->_fileRange);
430: $this->_file = $this->_fileRange = null;
431: } else {
432: $this->_sendContent($this->_body);
433: }
434: }
435:
436: 437: 438: 439: 440: 441: 442:
443: protected function _setCookies() {
444: foreach ($this->_cookies as $name => $c) {
445: setcookie(
446: $name, $c['value'], $c['expire'], $c['path'],
447: $c['domain'], $c['secure'], $c['httpOnly']
448: );
449: }
450: }
451:
452: 453: 454: 455: 456: 457:
458: protected function _setContentType() {
459: if (in_array($this->_status, array(304, 204))) {
460: return;
461: }
462: $whitelist = array(
463: 'application/javascript', 'application/json', 'application/xml', 'application/rss+xml'
464: );
465:
466: $charset = false;
467: if ($this->_charset &&
468: (strpos($this->_contentType, 'text/') === 0 || in_array($this->_contentType, $whitelist))
469: ) {
470: $charset = true;
471: }
472:
473: if ($charset) {
474: $this->header('Content-Type', "{$this->_contentType}; charset={$this->_charset}");
475: } else {
476: $this->header('Content-Type', "{$this->_contentType}");
477: }
478: }
479:
480: 481: 482: 483: 484:
485: protected function _setContent() {
486: if (in_array($this->_status, array(304, 204))) {
487: $this->body('');
488: }
489: }
490:
491: 492: 493: 494: 495: 496:
497: protected function _setContentLength() {
498: $shouldSetLength = !isset($this->_headers['Content-Length']) && !in_array($this->_status, range(301, 307));
499: if (isset($this->_headers['Content-Length']) && $this->_headers['Content-Length'] === false) {
500: unset($this->_headers['Content-Length']);
501: return;
502: }
503: if ($shouldSetLength && !$this->outputCompressed()) {
504: $offset = ob_get_level() ? ob_get_length() : 0;
505: if (ini_get('mbstring.func_overload') & 2 && function_exists('mb_strlen')) {
506: $this->length($offset + mb_strlen($this->_body, '8bit'));
507: } else {
508: $this->length($this->_headers['Content-Length'] = $offset + strlen($this->_body));
509: }
510: }
511: }
512:
513: 514: 515: 516: 517: 518: 519:
520: protected function _sendHeader($name, $value = null) {
521: if (!headers_sent()) {
522: if ($value === null) {
523: header($name);
524: } else {
525: header("{$name}: {$value}");
526: }
527: }
528: }
529:
530: 531: 532: 533: 534: 535:
536: protected function _sendContent($content) {
537: echo $content;
538: }
539:
540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556: 557: 558: 559: 560: 561: 562: 563: 564: 565: 566:
567: public function header($header = null, $value = null) {
568: if ($header === null) {
569: return $this->_headers;
570: }
571: $headers = is_array($header) ? $header : array($header => $value);
572: foreach ($headers as $header => $value) {
573: if (is_numeric($header)) {
574: list($header, $value) = array($value, null);
575: }
576: if ($value === null) {
577: list($header, $value) = explode(':', $header, 2);
578: }
579: $this->_headers[$header] = is_array($value) ? array_map('trim', $value) : trim($value);
580: }
581: return $this->_headers;
582: }
583:
584: 585: 586: 587: 588: 589: 590: 591: 592:
593: public function location($url = null) {
594: if ($url === null) {
595: $headers = $this->header();
596: return isset($headers['Location']) ? $headers['Location'] : null;
597: }
598: $this->header('Location', $url);
599: return null;
600: }
601:
602: 603: 604: 605: 606: 607: 608:
609: public function body($content = null) {
610: if ($content === null) {
611: return $this->_body;
612: }
613: return $this->_body = $content;
614: }
615:
616: 617: 618: 619: 620: 621: 622: 623:
624: public function statusCode($code = null) {
625: if ($code === null) {
626: return $this->_status;
627: }
628: if (!isset($this->_statusCodes[$code])) {
629: throw new CakeException(__d('cake_dev', 'Unknown status code'));
630: }
631: return $this->_status = $code;
632: }
633:
634: 635: 636: 637: 638: 639: 640: 641: 642: 643: 644: 645: 646: 647: 648: 649: 650: 651: 652: 653: 654: 655: 656: 657: 658: 659: 660: 661: 662: 663: 664:
665: public function httpCodes($code = null) {
666: if (empty($code)) {
667: return $this->_statusCodes;
668: }
669: if (is_array($code)) {
670: $codes = array_keys($code);
671: $min = min($codes);
672: if (!is_int($min) || $min < 100 || max($codes) > 999) {
673: throw new CakeException(__d('cake_dev', 'Invalid status code'));
674: }
675: $this->_statusCodes = $code + $this->_statusCodes;
676: return true;
677: }
678: if (!isset($this->_statusCodes[$code])) {
679: return null;
680: }
681: return array($code => $this->_statusCodes[$code]);
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: public function type($contentType = null) {
710: if ($contentType === null) {
711: return $this->_contentType;
712: }
713: if (is_array($contentType)) {
714: foreach ($contentType as $type => $definition) {
715: $this->_mimeTypes[$type] = $definition;
716: }
717: return $this->_contentType;
718: }
719: if (isset($this->_mimeTypes[$contentType])) {
720: $contentType = $this->_mimeTypes[$contentType];
721: $contentType = is_array($contentType) ? current($contentType) : $contentType;
722: }
723: if (strpos($contentType, '/') === false) {
724: return false;
725: }
726: return $this->_contentType = $contentType;
727: }
728:
729: 730: 731: 732: 733: 734: 735: 736:
737: public function getMimeType($alias) {
738: if (isset($this->_mimeTypes[$alias])) {
739: return $this->_mimeTypes[$alias];
740: }
741: return false;
742: }
743:
744: 745: 746: 747: 748: 749: 750: 751:
752: public function mapType($ctype) {
753: if (is_array($ctype)) {
754: return array_map(array($this, 'mapType'), $ctype);
755: }
756:
757: foreach ($this->_mimeTypes as $alias => $types) {
758: if (in_array($ctype, (array)$types)) {
759: return $alias;
760: }
761: }
762: return null;
763: }
764:
765: 766: 767: 768: 769: 770: 771:
772: public function charset($charset = null) {
773: if ($charset === null) {
774: return $this->_charset;
775: }
776: return $this->_charset = $charset;
777: }
778:
779: 780: 781: 782: 783:
784: public function disableCache() {
785: $this->header(array(
786: 'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT',
787: 'Last-Modified' => gmdate("D, d M Y H:i:s") . " GMT",
788: 'Cache-Control' => 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'
789: ));
790: }
791:
792: 793: 794: 795: 796: 797: 798:
799: public function cache($since, $time = '+1 day') {
800: if (!is_int($time)) {
801: $time = strtotime($time);
802: }
803: $this->header(array(
804: 'Date' => gmdate("D, j M Y G:i:s ", time()) . 'GMT'
805: ));
806: $this->modified($since);
807: $this->expires($time);
808: $this->sharable(true);
809: $this->maxAge($time - time());
810: }
811:
812: 813: 814: 815: 816: 817: 818: 819: 820: 821: 822:
823: public function sharable($public = null, $time = null) {
824: if ($public === null) {
825: $public = array_key_exists('public', $this->_cacheDirectives);
826: $private = array_key_exists('private', $this->_cacheDirectives);
827: $noCache = array_key_exists('no-cache', $this->_cacheDirectives);
828: if (!$public && !$private && !$noCache) {
829: return null;
830: }
831: $sharable = $public || ! ($private || $noCache);
832: return $sharable;
833: }
834: if ($public) {
835: $this->_cacheDirectives['public'] = true;
836: unset($this->_cacheDirectives['private']);
837: } else {
838: $this->_cacheDirectives['private'] = true;
839: unset($this->_cacheDirectives['public']);
840: }
841:
842: $this->maxAge($time);
843: if (!$time) {
844: $this->_setCacheControl();
845: }
846: return (bool)$public;
847: }
848:
849: 850: 851: 852: 853: 854: 855: 856: 857:
858: public function sharedMaxAge($seconds = null) {
859: if ($seconds !== null) {
860: $this->_cacheDirectives['s-maxage'] = $seconds;
861: $this->_setCacheControl();
862: }
863: if (isset($this->_cacheDirectives['s-maxage'])) {
864: return $this->_cacheDirectives['s-maxage'];
865: }
866: return null;
867: }
868:
869: 870: 871: 872: 873: 874: 875: 876: 877:
878: public function maxAge($seconds = null) {
879: if ($seconds !== null) {
880: $this->_cacheDirectives['max-age'] = $seconds;
881: $this->_setCacheControl();
882: }
883: if (isset($this->_cacheDirectives['max-age'])) {
884: return $this->_cacheDirectives['max-age'];
885: }
886: return null;
887: }
888:
889: 890: 891: 892: 893: 894: 895: 896: 897: 898: 899:
900: public function mustRevalidate($enable = null) {
901: if ($enable !== null) {
902: if ($enable) {
903: $this->_cacheDirectives['must-revalidate'] = true;
904: } else {
905: unset($this->_cacheDirectives['must-revalidate']);
906: }
907: $this->_setCacheControl();
908: }
909: return array_key_exists('must-revalidate', $this->_cacheDirectives);
910: }
911:
912: 913: 914: 915: 916: 917:
918: protected function _setCacheControl() {
919: $control = '';
920: foreach ($this->_cacheDirectives as $key => $val) {
921: $control .= $val === true ? $key : sprintf('%s=%s', $key, $val);
922: $control .= ', ';
923: }
924: $control = rtrim($control, ', ');
925: $this->header('Cache-Control', $control);
926: }
927:
928: 929: 930: 931: 932: 933: 934: 935: 936: 937: 938: 939: 940:
941: public function expires($time = null) {
942: if ($time !== null) {
943: $date = $this->_getUTCDate($time);
944: $this->_headers['Expires'] = $date->format('D, j M Y H:i:s') . ' GMT';
945: }
946: if (isset($this->_headers['Expires'])) {
947: return $this->_headers['Expires'];
948: }
949: return null;
950: }
951:
952: 953: 954: 955: 956: 957: 958: 959: 960: 961: 962: 963: 964:
965: public function modified($time = null) {
966: if ($time !== null) {
967: $date = $this->_getUTCDate($time);
968: $this->_headers['Last-Modified'] = $date->format('D, j M Y H:i:s') . ' GMT';
969: }
970: if (isset($this->_headers['Last-Modified'])) {
971: return $this->_headers['Last-Modified'];
972: }
973: return null;
974: }
975:
976: 977: 978: 979: 980: 981: 982:
983: public function notModified() {
984: $this->statusCode(304);
985: $this->body('');
986: $remove = array(
987: 'Allow',
988: 'Content-Encoding',
989: 'Content-Language',
990: 'Content-Length',
991: 'Content-MD5',
992: 'Content-Type',
993: 'Last-Modified'
994: );
995: foreach ($remove as $header) {
996: unset($this->_headers[$header]);
997: }
998: }
999:
1000: 1001: 1002: 1003: 1004: 1005: 1006: 1007: 1008: 1009:
1010: public function vary($cacheVariances = null) {
1011: if ($cacheVariances !== null) {
1012: $cacheVariances = (array)$cacheVariances;
1013: $this->_headers['Vary'] = implode(', ', $cacheVariances);
1014: }
1015: if (isset($this->_headers['Vary'])) {
1016: return explode(', ', $this->_headers['Vary']);
1017: }
1018: return null;
1019: }
1020:
1021: 1022: 1023: 1024: 1025: 1026: 1027: 1028: 1029: 1030: 1031: 1032: 1033: 1034: 1035: 1036: 1037: 1038: 1039: 1040: 1041:
1042: public function etag($tag = null, $weak = false) {
1043: if ($tag !== null) {
1044: $this->_headers['Etag'] = sprintf('%s"%s"', ($weak) ? 'W/' : null, $tag);
1045: }
1046: if (isset($this->_headers['Etag'])) {
1047: return $this->_headers['Etag'];
1048: }
1049: return null;
1050: }
1051:
1052: 1053: 1054: 1055: 1056: 1057: 1058:
1059: protected function _getUTCDate($time = null) {
1060: if ($time instanceof DateTime) {
1061: $result = clone $time;
1062: } elseif (is_int($time)) {
1063: $result = new DateTime(date('Y-m-d H:i:s', $time));
1064: } else {
1065: $result = new DateTime($time);
1066: }
1067: $result->setTimeZone(new DateTimeZone('UTC'));
1068: return $result;
1069: }
1070:
1071: 1072: 1073: 1074: 1075: 1076:
1077: public function compress() {
1078: $compressionEnabled = ini_get("zlib.output_compression") !== '1' &&
1079: extension_loaded("zlib") &&
1080: (strpos(env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false);
1081: return $compressionEnabled && ob_start('ob_gzhandler');
1082: }
1083:
1084: 1085: 1086: 1087: 1088:
1089: public function outputCompressed() {
1090: return strpos(env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false
1091: && (ini_get("zlib.output_compression") === '1' || in_array('ob_gzhandler', ob_list_handlers()));
1092: }
1093:
1094: 1095: 1096: 1097: 1098: 1099:
1100: public function download($filename) {
1101: $this->header('Content-Disposition', 'attachment; filename="' . $filename . '"');
1102: }
1103:
1104: 1105: 1106: 1107: 1108: 1109: 1110:
1111: public function protocol($protocol = null) {
1112: if ($protocol !== null) {
1113: $this->_protocol = $protocol;
1114: }
1115: return $this->_protocol;
1116: }
1117:
1118: 1119: 1120: 1121: 1122: 1123: 1124:
1125: public function length($bytes = null) {
1126: if ($bytes !== null) {
1127: $this->_headers['Content-Length'] = $bytes;
1128: }
1129: if (isset($this->_headers['Content-Length'])) {
1130: return $this->_headers['Content-Length'];
1131: }
1132: return null;
1133: }
1134:
1135: 1136: 1137: 1138: 1139: 1140: 1141: 1142: 1143: 1144: 1145: 1146: 1147:
1148: public function checkNotModified(CakeRequest $request) {
1149: $etags = preg_split('/\s*,\s*/', $request->header('If-None-Match'), null, PREG_SPLIT_NO_EMPTY);
1150: $modifiedSince = $request->header('If-Modified-Since');
1151: if ($responseTag = $this->etag()) {
1152: $etagMatches = in_array('*', $etags) || in_array($responseTag, $etags);
1153: }
1154: if ($modifiedSince) {
1155: $timeMatches = strtotime($this->modified()) === strtotime($modifiedSince);
1156: }
1157: $checks = compact('etagMatches', 'timeMatches');
1158: if (empty($checks)) {
1159: return false;
1160: }
1161: $notModified = !in_array(false, $checks, true);
1162: if ($notModified) {
1163: $this->notModified();
1164: }
1165: return $notModified;
1166: }
1167:
1168: 1169: 1170: 1171: 1172: 1173:
1174: public function __toString() {
1175: return (string)$this->_body;
1176: }
1177:
1178: 1179: 1180: 1181: 1182: 1183: 1184: 1185: 1186: 1187: 1188: 1189: 1190: 1191: 1192: 1193: 1194: 1195: 1196: 1197: 1198: 1199: 1200: 1201: 1202: 1203: 1204: 1205: 1206: 1207: 1208: 1209: 1210: 1211: 1212: 1213: 1214: 1215: 1216: 1217:
1218: public function cookie($options = null) {
1219: if ($options === null) {
1220: return $this->_cookies;
1221: }
1222:
1223: if (is_string($options)) {
1224: if (!isset($this->_cookies[$options])) {
1225: return null;
1226: }
1227: return $this->_cookies[$options];
1228: }
1229:
1230: $defaults = array(
1231: 'name' => 'CakeCookie[default]',
1232: 'value' => '',
1233: 'expire' => 0,
1234: 'path' => '/',
1235: 'domain' => '',
1236: 'secure' => false,
1237: 'httpOnly' => false
1238: );
1239: $options += $defaults;
1240:
1241: $this->_cookies[$options['name']] = $options;
1242: }
1243:
1244: 1245: 1246: 1247: 1248: 1249: 1250: 1251: 1252: 1253: 1254: 1255: 1256: 1257: 1258: 1259: 1260: 1261: 1262: 1263: 1264: 1265: 1266: 1267: 1268: 1269:
1270: public function cors(CakeRequest $request, $allowedDomains, $allowedMethods = array(), $allowedHeaders = array()) {
1271: $origin = $request->header('Origin');
1272: if (!$origin) {
1273: return;
1274: }
1275:
1276: $allowedDomains = $this->_normalizeCorsDomains((array)$allowedDomains, $request->is('ssl'));
1277: foreach ($allowedDomains as $domain) {
1278: if (!preg_match($domain['preg'], $origin)) {
1279: continue;
1280: }
1281: $this->header('Access-Control-Allow-Origin', $domain['original'] === '*' ? '*' : $origin);
1282: $allowedMethods && $this->header('Access-Control-Allow-Methods', implode(', ', (array)$allowedMethods));
1283: $allowedHeaders && $this->header('Access-Control-Allow-Headers', implode(', ', (array)$allowedHeaders));
1284: break;
1285: }
1286: }
1287:
1288: 1289: 1290: 1291: 1292: 1293: 1294:
1295: protected function _normalizeCorsDomains($domains, $requestIsSSL = false) {
1296: $result = array();
1297: foreach ($domains as $domain) {
1298: if ($domain === '*') {
1299: $result[] = array('preg' => '@.@', 'original' => '*');
1300: continue;
1301: }
1302:
1303: $original = $preg = $domain;
1304: if (strpos($domain, '://') === false) {
1305: $preg = ($requestIsSSL ? 'https://' : 'http://') . $domain;
1306: }
1307: $preg = '@' . str_replace('*', '.*', $domain) . '@';
1308: $result[] = compact('original', 'preg');
1309: }
1310: return $result;
1311: }
1312:
1313: 1314: 1315: 1316: 1317: 1318: 1319: 1320: 1321: 1322: 1323: 1324: 1325: 1326: 1327: 1328: 1329:
1330: public function file($path, $options = array()) {
1331: $options += array(
1332: 'name' => null,
1333: 'download' => null
1334: );
1335:
1336: if (strpos($path, '..') !== false) {
1337: throw new NotFoundException(__d(
1338: 'cake_dev',
1339: 'The requested file contains `..` and will not be read.'
1340: ));
1341: }
1342:
1343: if (!is_file($path)) {
1344: $path = APP . $path;
1345: }
1346:
1347: $file = new File($path);
1348: if (!$file->exists() || !$file->readable()) {
1349: if (Configure::read('debug')) {
1350: throw new NotFoundException(__d('cake_dev', 'The requested file %s was not found or not readable', $path));
1351: }
1352: throw new NotFoundException(__d('cake', 'The requested file was not found'));
1353: }
1354:
1355: $extension = strtolower($file->ext());
1356: $download = $options['download'];
1357: if ((!$extension || $this->type($extension) === false) && $download === null) {
1358: $download = true;
1359: }
1360:
1361: $fileSize = $file->size();
1362: if ($download) {
1363: $agent = env('HTTP_USER_AGENT');
1364:
1365: if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent)) {
1366: $contentType = 'application/octet-stream';
1367: } elseif (preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) {
1368: $contentType = 'application/force-download';
1369: }
1370:
1371: if (!empty($contentType)) {
1372: $this->type($contentType);
1373: }
1374: if ($options['name'] === null) {
1375: $name = $file->name;
1376: } else {
1377: $name = $options['name'];
1378: }
1379: $this->download($name);
1380: $this->header('Accept-Ranges', 'bytes');
1381: $this->header('Content-Transfer-Encoding', 'binary');
1382:
1383: $httpRange = env('HTTP_RANGE');
1384: if (isset($httpRange)) {
1385: $this->_fileRange($file, $httpRange);
1386: } else {
1387: $this->header('Content-Length', $fileSize);
1388: }
1389: } else {
1390: $this->header('Content-Length', $fileSize);
1391: }
1392: $this->_clearBuffer();
1393: $this->_file = $file;
1394: }
1395:
1396: 1397: 1398: 1399: 1400: 1401: 1402: 1403: 1404: 1405:
1406: protected function _fileRange($file, $httpRange) {
1407: list(, $range) = explode('=', $httpRange);
1408: list($start, $end) = explode('-', $range);
1409:
1410: $fileSize = $file->size();
1411: $lastByte = $fileSize - 1;
1412:
1413: if ($start === '') {
1414: $start = $fileSize - $end;
1415: $end = $lastByte;
1416: }
1417: if ($end === '') {
1418: $end = $lastByte;
1419: }
1420:
1421: if ($start > $end || $end > $lastByte || $start > $lastByte) {
1422: $this->statusCode(416);
1423: $this->header(array(
1424: 'Content-Range' => 'bytes 0-' . $lastByte . '/' . $fileSize
1425: ));
1426: return;
1427: }
1428:
1429: $this->header(array(
1430: 'Content-Length' => $end - $start + 1,
1431: 'Content-Range' => 'bytes ' . $start . '-' . $end . '/' . $fileSize
1432: ));
1433:
1434: $this->statusCode(206);
1435: $this->_fileRange = array($start, $end);
1436: }
1437:
1438: 1439: 1440: 1441: 1442: 1443: 1444:
1445: protected function _sendFile($file, $range) {
1446: $compress = $this->outputCompressed();
1447: $file->open('rb');
1448:
1449: $end = $start = false;
1450: if ($range) {
1451: list($start, $end) = $range;
1452: }
1453: if ($start !== false) {
1454: $file->offset($start);
1455: }
1456:
1457: $bufferSize = 8192;
1458: set_time_limit(0);
1459: session_write_close();
1460: while (!feof($file->handle)) {
1461: if (!$this->_isActive()) {
1462: $file->close();
1463: return false;
1464: }
1465: $offset = $file->offset();
1466: if ($end && $offset >= $end) {
1467: break;
1468: }
1469: if ($end && $offset + $bufferSize >= $end) {
1470: $bufferSize = $end - $offset + 1;
1471: }
1472: echo fread($file->handle, $bufferSize);
1473: if (!$compress) {
1474: $this->_flushBuffer();
1475: }
1476: }
1477: $file->close();
1478: return true;
1479: }
1480:
1481: 1482: 1483: 1484: 1485:
1486: protected function _isActive() {
1487: return connection_status() === CONNECTION_NORMAL && !connection_aborted();
1488: }
1489:
1490: 1491: 1492: 1493: 1494:
1495: protected function _clearBuffer() {
1496:
1497: return @ob_end_clean();
1498:
1499: }
1500:
1501: 1502: 1503: 1504: 1505:
1506: protected function _flushBuffer() {
1507:
1508: @flush();
1509: if (ob_get_level()) {
1510: @ob_flush();
1511: }
1512:
1513: }
1514:
1515: }
1516: