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