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: 429 => 'Too Many Requests',
72: 500 => 'Internal Server Error',
73: 501 => 'Not Implemented',
74: 502 => 'Bad Gateway',
75: 503 => 'Service Unavailable',
76: 504 => 'Gateway Time-out',
77: 505 => 'Unsupported Version'
78: );
79:
80: 81: 82: 83: 84:
85: protected $_mimeTypes = array(
86: 'html' => array('text/html', '*/*'),
87: 'json' => 'application/json',
88: 'xml' => array('application/xml', 'text/xml'),
89: 'rss' => 'application/rss+xml',
90: 'ai' => 'application/postscript',
91: 'bcpio' => 'application/x-bcpio',
92: 'bin' => 'application/octet-stream',
93: 'ccad' => 'application/clariscad',
94: 'cdf' => 'application/x-netcdf',
95: 'class' => 'application/octet-stream',
96: 'cpio' => 'application/x-cpio',
97: 'cpt' => 'application/mac-compactpro',
98: 'csh' => 'application/x-csh',
99: 'csv' => array('text/csv', 'application/vnd.ms-excel'),
100: 'dcr' => 'application/x-director',
101: 'dir' => 'application/x-director',
102: 'dms' => 'application/octet-stream',
103: 'doc' => 'application/msword',
104: 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
105: 'drw' => 'application/drafting',
106: 'dvi' => 'application/x-dvi',
107: 'dwg' => 'application/acad',
108: 'dxf' => 'application/dxf',
109: 'dxr' => 'application/x-director',
110: 'eot' => 'application/vnd.ms-fontobject',
111: 'eps' => 'application/postscript',
112: 'exe' => 'application/octet-stream',
113: 'ez' => 'application/andrew-inset',
114: 'flv' => 'video/x-flv',
115: 'gtar' => 'application/x-gtar',
116: 'gz' => 'application/x-gzip',
117: 'bz2' => 'application/x-bzip',
118: '7z' => 'application/x-7z-compressed',
119: 'hdf' => 'application/x-hdf',
120: 'hqx' => 'application/mac-binhex40',
121: 'ico' => 'image/x-icon',
122: 'ips' => 'application/x-ipscript',
123: 'ipx' => 'application/x-ipix',
124: 'js' => 'application/javascript',
125: 'jsonapi' => 'application/vnd.api+json',
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: 'jpg' => 'image/jpeg',
254: 'jpeg' => 'image/jpeg',
255: 'jpe' => '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: 'psd' => array(
269: 'application/photoshop',
270: 'application/psd',
271: 'image/psd',
272: 'image/x-photoshop',
273: 'image/photoshop',
274: 'zz-application/zz-winassoc-psd'
275: ),
276: 'ice' => 'x-conference/x-cooltalk',
277: 'iges' => 'model/iges',
278: 'igs' => 'model/iges',
279: 'mesh' => 'model/mesh',
280: 'msh' => 'model/mesh',
281: 'silo' => 'model/mesh',
282: 'vrml' => 'model/vrml',
283: 'wrl' => 'model/vrml',
284: 'mime' => 'www/mime',
285: 'pdb' => 'chemical/x-pdb',
286: 'xyz' => 'chemical/x-pdb',
287: 'javascript' => 'application/javascript',
288: 'form' => 'application/x-www-form-urlencoded',
289: 'file' => 'multipart/form-data',
290: 'xhtml' => array('application/xhtml+xml', 'application/xhtml', 'text/xhtml'),
291: 'xhtml-mobile' => 'application/vnd.wap.xhtml+xml',
292: 'atom' => 'application/atom+xml',
293: 'amf' => 'application/x-amf',
294: 'wap' => array('text/vnd.wap.wml', 'text/vnd.wap.wmlscript', 'image/vnd.wap.wbmp'),
295: 'wml' => 'text/vnd.wap.wml',
296: 'wmlscript' => 'text/vnd.wap.wmlscript',
297: 'wbmp' => 'image/vnd.wap.wbmp',
298: 'woff' => 'application/x-font-woff',
299: 'webp' => 'image/webp',
300: 'appcache' => 'text/cache-manifest',
301: 'manifest' => 'text/cache-manifest',
302: 'htc' => 'text/x-component',
303: 'rdf' => 'application/xml',
304: 'crx' => 'application/x-chrome-extension',
305: 'oex' => 'application/x-opera-extension',
306: 'xpi' => 'application/x-xpinstall',
307: 'safariextz' => 'application/octet-stream',
308: 'webapp' => 'application/x-web-app-manifest+json',
309: 'vcf' => 'text/x-vcard',
310: 'vtt' => 'text/vtt',
311: 'mkv' => 'video/x-matroska',
312: 'pkpass' => 'application/vnd.apple.pkpass',
313: 'ajax' => 'text/html'
314: );
315:
316: 317: 318: 319: 320:
321: protected $_protocol = 'HTTP/1.1';
322:
323: 324: 325: 326: 327:
328: protected $_status = 200;
329:
330: 331: 332: 333: 334: 335:
336: protected $_contentType = 'text/html';
337:
338: 339: 340: 341: 342:
343: protected $_headers = array();
344:
345: 346: 347: 348: 349:
350: protected $_body = null;
351:
352: 353: 354: 355: 356:
357: protected $_file = null;
358:
359: 360: 361: 362: 363:
364: protected $_fileRange = null;
365:
366: 367: 368: 369: 370:
371: protected $_charset = 'UTF-8';
372:
373: 374: 375: 376: 377: 378:
379: protected $_cacheDirectives = array();
380:
381: 382: 383: 384: 385:
386: protected $_cookies = array();
387:
388: 389: 390: 391: 392: 393: 394: 395: 396: 397:
398: public function __construct(array $options = array()) {
399: if (isset($options['body'])) {
400: $this->body($options['body']);
401: }
402: if (isset($options['statusCodes'])) {
403: $this->httpCodes($options['statusCodes']);
404: }
405: if (isset($options['status'])) {
406: $this->statusCode($options['status']);
407: }
408: if (isset($options['type'])) {
409: $this->type($options['type']);
410: }
411: if (!isset($options['charset'])) {
412: $options['charset'] = Configure::read('App.encoding');
413: }
414: $this->charset($options['charset']);
415: }
416:
417: 418: 419: 420: 421: 422:
423: public function send() {
424: if (isset($this->_headers['Location']) && $this->_status === 200) {
425: $this->statusCode(302);
426: }
427:
428: $codeMessage = $this->_statusCodes[$this->_status];
429: $this->_setCookies();
430: $this->_sendHeader("{$this->_protocol} {$this->_status} {$codeMessage}");
431: $this->_setContent();
432: $this->_setContentLength();
433: $this->_setContentType();
434: foreach ($this->_headers as $header => $values) {
435: foreach ((array)$values as $value) {
436: $this->_sendHeader($header, $value);
437: }
438: }
439: if ($this->_file) {
440: $this->_sendFile($this->_file, $this->_fileRange);
441: $this->_file = $this->_fileRange = null;
442: } else {
443: $this->_sendContent($this->_body);
444: }
445: }
446:
447: 448: 449: 450: 451: 452: 453:
454: protected function _setCookies() {
455: foreach ($this->_cookies as $name => $c) {
456: setcookie(
457: $name, $c['value'], $c['expire'], $c['path'],
458: $c['domain'], $c['secure'], $c['httpOnly']
459: );
460: }
461: }
462:
463: 464: 465: 466: 467: 468:
469: protected function _setContentType() {
470: if (in_array($this->_status, array(304, 204))) {
471: return;
472: }
473: $whitelist = array(
474: 'application/javascript', 'application/json', 'application/xml', 'application/rss+xml'
475: );
476:
477: $charset = false;
478: if ($this->_charset &&
479: (strpos($this->_contentType, 'text/') === 0 || in_array($this->_contentType, $whitelist))
480: ) {
481: $charset = true;
482: }
483:
484: if ($charset) {
485: $this->header('Content-Type', "{$this->_contentType}; charset={$this->_charset}");
486: } else {
487: $this->header('Content-Type', "{$this->_contentType}");
488: }
489: }
490:
491: 492: 493: 494: 495:
496: protected function _setContent() {
497: if (in_array($this->_status, array(304, 204))) {
498: $this->body('');
499: }
500: }
501:
502: 503: 504: 505: 506: 507:
508: protected function _setContentLength() {
509: $shouldSetLength = !isset($this->_headers['Content-Length']) && !in_array($this->_status, range(301, 307));
510: if (isset($this->_headers['Content-Length']) && $this->_headers['Content-Length'] === false) {
511: unset($this->_headers['Content-Length']);
512: return;
513: }
514: if ($shouldSetLength && !$this->outputCompressed()) {
515: $offset = ob_get_level() ? ob_get_length() : 0;
516: if (ini_get('mbstring.func_overload') & 2 && function_exists('mb_strlen')) {
517: $this->length($offset + mb_strlen($this->_body, '8bit'));
518: } else {
519: $this->length($this->_headers['Content-Length'] = $offset + strlen($this->_body));
520: }
521: }
522: }
523:
524: 525: 526: 527: 528: 529: 530: 531: 532:
533: protected function _sendHeader($name, $value = null) {
534: if (headers_sent($filename, $linenum)) {
535: return;
536: }
537: if ($value === null) {
538: header($name);
539: } else {
540: header("{$name}: {$value}");
541: }
542: }
543:
544: 545: 546: 547: 548: 549:
550: protected function _sendContent($content) {
551: echo $content;
552: }
553:
554: 555: 556: 557: 558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580:
581: public function header($header = null, $value = null) {
582: if ($header === null) {
583: return $this->_headers;
584: }
585: $headers = is_array($header) ? $header : array($header => $value);
586: foreach ($headers as $header => $value) {
587: if (is_numeric($header)) {
588: list($header, $value) = array($value, null);
589: }
590: if ($value === null && strpos($header, ':') !== false) {
591: list($header, $value) = explode(':', $header, 2);
592: }
593: $this->_headers[$header] = is_array($value) ? array_map('trim', $value) : trim($value);
594: }
595: return $this->_headers;
596: }
597:
598: 599: 600: 601: 602: 603: 604: 605: 606:
607: public function location($url = null) {
608: if ($url === null) {
609: $headers = $this->header();
610: return isset($headers['Location']) ? $headers['Location'] : null;
611: }
612: $this->header('Location', $url);
613: return null;
614: }
615:
616: 617: 618: 619: 620: 621: 622:
623: public function body($content = null) {
624: if ($content === null) {
625: return $this->_body;
626: }
627: return $this->_body = $content;
628: }
629:
630: 631: 632: 633: 634: 635: 636: 637:
638: public function statusCode($code = null) {
639: if ($code === null) {
640: return $this->_status;
641: }
642: if (!isset($this->_statusCodes[$code])) {
643: throw new CakeException(__d('cake_dev', 'Unknown status code'));
644: }
645: return $this->_status = $code;
646: }
647:
648: 649: 650: 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:
679: public function httpCodes($code = null) {
680: if (empty($code)) {
681: return $this->_statusCodes;
682: }
683: if (is_array($code)) {
684: $codes = array_keys($code);
685: $min = min($codes);
686: if (!is_int($min) || $min < 100 || max($codes) > 999) {
687: throw new CakeException(__d('cake_dev', 'Invalid status code'));
688: }
689: $this->_statusCodes = $code + $this->_statusCodes;
690: return true;
691: }
692: if (!isset($this->_statusCodes[$code])) {
693: return null;
694: }
695: return array($code => $this->_statusCodes[$code]);
696: }
697:
698: 699: 700: 701: 702: 703: 704: 705: 706: 707: 708: 709: 710: 711: 712: 713: 714: 715: 716: 717: 718: 719: 720: 721: 722:
723: public function type($contentType = null) {
724: if ($contentType === null) {
725: return $this->_contentType;
726: }
727: if (is_array($contentType)) {
728: foreach ($contentType as $type => $definition) {
729: $this->_mimeTypes[$type] = $definition;
730: }
731: return $this->_contentType;
732: }
733: if (isset($this->_mimeTypes[$contentType])) {
734: $contentType = $this->_mimeTypes[$contentType];
735: $contentType = is_array($contentType) ? current($contentType) : $contentType;
736: }
737: if (strpos($contentType, '/') === false) {
738: return false;
739: }
740: return $this->_contentType = $contentType;
741: }
742:
743: 744: 745: 746: 747: 748: 749: 750:
751: public function getMimeType($alias) {
752: if (isset($this->_mimeTypes[$alias])) {
753: return $this->_mimeTypes[$alias];
754: }
755: return false;
756: }
757:
758: 759: 760: 761: 762: 763: 764: 765:
766: public function mapType($ctype) {
767: if (is_array($ctype)) {
768: return array_map(array($this, 'mapType'), $ctype);
769: }
770:
771: foreach ($this->_mimeTypes as $alias => $types) {
772: if (in_array($ctype, (array)$types)) {
773: return $alias;
774: }
775: }
776: return null;
777: }
778:
779: 780: 781: 782: 783: 784: 785:
786: public function charset($charset = null) {
787: if ($charset === null) {
788: return $this->_charset;
789: }
790: return $this->_charset = $charset;
791: }
792:
793: 794: 795: 796: 797:
798: public function disableCache() {
799: $this->header(array(
800: 'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT',
801: 'Last-Modified' => gmdate("D, d M Y H:i:s") . " GMT",
802: 'Cache-Control' => 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'
803: ));
804: }
805:
806: 807: 808: 809: 810: 811: 812:
813: public function cache($since, $time = '+1 day') {
814: if (!is_int($time)) {
815: $time = strtotime($time);
816: }
817: $this->header(array(
818: 'Date' => gmdate("D, j M Y G:i:s ", time()) . 'GMT'
819: ));
820: $this->modified($since);
821: $this->expires($time);
822: $this->sharable(true);
823: $this->maxAge($time - time());
824: }
825:
826: 827: 828: 829: 830: 831: 832: 833: 834: 835: 836:
837: public function sharable($public = null, $time = null) {
838: if ($public === null) {
839: $public = array_key_exists('public', $this->_cacheDirectives);
840: $private = array_key_exists('private', $this->_cacheDirectives);
841: $noCache = array_key_exists('no-cache', $this->_cacheDirectives);
842: if (!$public && !$private && !$noCache) {
843: return null;
844: }
845: $sharable = $public || !($private || $noCache);
846: return $sharable;
847: }
848: if ($public) {
849: $this->_cacheDirectives['public'] = true;
850: unset($this->_cacheDirectives['private']);
851: } else {
852: $this->_cacheDirectives['private'] = true;
853: unset($this->_cacheDirectives['public']);
854: }
855:
856: $this->maxAge($time);
857: if (!$time) {
858: $this->_setCacheControl();
859: }
860: return (bool)$public;
861: }
862:
863: 864: 865: 866: 867: 868: 869: 870: 871:
872: public function sharedMaxAge($seconds = null) {
873: if ($seconds !== null) {
874: $this->_cacheDirectives['s-maxage'] = $seconds;
875: $this->_setCacheControl();
876: }
877: if (isset($this->_cacheDirectives['s-maxage'])) {
878: return $this->_cacheDirectives['s-maxage'];
879: }
880: return null;
881: }
882:
883: 884: 885: 886: 887: 888: 889: 890: 891:
892: public function maxAge($seconds = null) {
893: if ($seconds !== null) {
894: $this->_cacheDirectives['max-age'] = $seconds;
895: $this->_setCacheControl();
896: }
897: if (isset($this->_cacheDirectives['max-age'])) {
898: return $this->_cacheDirectives['max-age'];
899: }
900: return null;
901: }
902:
903: 904: 905: 906: 907: 908: 909: 910: 911: 912: 913:
914: public function mustRevalidate($enable = null) {
915: if ($enable !== null) {
916: if ($enable) {
917: $this->_cacheDirectives['must-revalidate'] = true;
918: } else {
919: unset($this->_cacheDirectives['must-revalidate']);
920: }
921: $this->_setCacheControl();
922: }
923: return array_key_exists('must-revalidate', $this->_cacheDirectives);
924: }
925:
926: 927: 928: 929: 930: 931:
932: protected function _setCacheControl() {
933: $control = '';
934: foreach ($this->_cacheDirectives as $key => $val) {
935: $control .= $val === true ? $key : sprintf('%s=%s', $key, $val);
936: $control .= ', ';
937: }
938: $control = rtrim($control, ', ');
939: $this->header('Cache-Control', $control);
940: }
941:
942: 943: 944: 945: 946: 947: 948: 949: 950: 951: 952: 953: 954:
955: public function expires($time = null) {
956: if ($time !== null) {
957: $date = $this->_getUTCDate($time);
958: $this->_headers['Expires'] = $date->format('D, j M Y H:i:s') . ' GMT';
959: }
960: if (isset($this->_headers['Expires'])) {
961: return $this->_headers['Expires'];
962: }
963: return null;
964: }
965:
966: 967: 968: 969: 970: 971: 972: 973: 974: 975: 976: 977: 978:
979: public function modified($time = null) {
980: if ($time !== null) {
981: $date = $this->_getUTCDate($time);
982: $this->_headers['Last-Modified'] = $date->format('D, j M Y H:i:s') . ' GMT';
983: }
984: if (isset($this->_headers['Last-Modified'])) {
985: return $this->_headers['Last-Modified'];
986: }
987: return null;
988: }
989:
990: 991: 992: 993: 994: 995: 996:
997: public function notModified() {
998: $this->statusCode(304);
999: $this->body('');
1000: $remove = array(
1001: 'Allow',
1002: 'Content-Encoding',
1003: 'Content-Language',
1004: 'Content-Length',
1005: 'Content-MD5',
1006: 'Content-Type',
1007: 'Last-Modified'
1008: );
1009: foreach ($remove as $header) {
1010: unset($this->_headers[$header]);
1011: }
1012: }
1013:
1014: 1015: 1016: 1017: 1018: 1019: 1020: 1021: 1022: 1023:
1024: public function vary($cacheVariances = null) {
1025: if ($cacheVariances !== null) {
1026: $cacheVariances = (array)$cacheVariances;
1027: $this->_headers['Vary'] = implode(', ', $cacheVariances);
1028: }
1029: if (isset($this->_headers['Vary'])) {
1030: return explode(', ', $this->_headers['Vary']);
1031: }
1032: return null;
1033: }
1034:
1035: 1036: 1037: 1038: 1039: 1040: 1041: 1042: 1043: 1044: 1045: 1046: 1047: 1048: 1049: 1050: 1051: 1052: 1053: 1054: 1055:
1056: public function etag($tag = null, $weak = false) {
1057: if ($tag !== null) {
1058: $this->_headers['Etag'] = sprintf('%s"%s"', ($weak) ? 'W/' : null, $tag);
1059: }
1060: if (isset($this->_headers['Etag'])) {
1061: return $this->_headers['Etag'];
1062: }
1063: return null;
1064: }
1065:
1066: 1067: 1068: 1069: 1070: 1071: 1072:
1073: protected function _getUTCDate($time = null) {
1074: if ($time instanceof DateTime) {
1075: $result = clone $time;
1076: } elseif (is_int($time)) {
1077: $result = new DateTime(date('Y-m-d H:i:s', $time));
1078: } else {
1079: $result = new DateTime($time);
1080: }
1081: $result->setTimeZone(new DateTimeZone('UTC'));
1082: return $result;
1083: }
1084:
1085: 1086: 1087: 1088: 1089: 1090:
1091: public function compress() {
1092: $compressionEnabled = ini_get("zlib.output_compression") !== '1' &&
1093: extension_loaded("zlib") &&
1094: (strpos(env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false);
1095: return $compressionEnabled && ob_start('ob_gzhandler');
1096: }
1097:
1098: 1099: 1100: 1101: 1102:
1103: public function outputCompressed() {
1104: return strpos(env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false
1105: && (ini_get("zlib.output_compression") === '1' || in_array('ob_gzhandler', ob_list_handlers()));
1106: }
1107:
1108: 1109: 1110: 1111: 1112: 1113:
1114: public function download($filename) {
1115: $this->header('Content-Disposition', 'attachment; filename="' . $filename . '"');
1116: }
1117:
1118: 1119: 1120: 1121: 1122: 1123: 1124:
1125: public function protocol($protocol = null) {
1126: if ($protocol !== null) {
1127: $this->_protocol = $protocol;
1128: }
1129: return $this->_protocol;
1130: }
1131:
1132: 1133: 1134: 1135: 1136: 1137: 1138:
1139: public function length($bytes = null) {
1140: if ($bytes !== null) {
1141: $this->_headers['Content-Length'] = $bytes;
1142: }
1143: if (isset($this->_headers['Content-Length'])) {
1144: return $this->_headers['Content-Length'];
1145: }
1146: return null;
1147: }
1148:
1149: 1150: 1151: 1152: 1153: 1154: 1155: 1156: 1157: 1158: 1159: 1160: 1161:
1162: public function checkNotModified(CakeRequest $request) {
1163: $etags = preg_split('/\s*,\s*/', $request->header('If-None-Match'), null, PREG_SPLIT_NO_EMPTY);
1164: $modifiedSince = $request->header('If-Modified-Since');
1165: if ($responseTag = $this->etag()) {
1166: $etagMatches = in_array('*', $etags) || in_array($responseTag, $etags);
1167: }
1168: if ($modifiedSince) {
1169: $timeMatches = strtotime($this->modified()) === strtotime($modifiedSince);
1170: }
1171: $checks = compact('etagMatches', 'timeMatches');
1172: if (empty($checks)) {
1173: return false;
1174: }
1175: $notModified = !in_array(false, $checks, true);
1176: if ($notModified) {
1177: $this->notModified();
1178: }
1179: return $notModified;
1180: }
1181:
1182: 1183: 1184: 1185: 1186: 1187:
1188: public function __toString() {
1189: return (string)$this->_body;
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: 1219: 1220: 1221: 1222: 1223: 1224: 1225: 1226: 1227: 1228: 1229: 1230:
1231: public function cookie($options = null) {
1232: if ($options === null) {
1233: return $this->_cookies;
1234: }
1235:
1236: if (is_string($options)) {
1237: if (!isset($this->_cookies[$options])) {
1238: return null;
1239: }
1240: return $this->_cookies[$options];
1241: }
1242:
1243: $defaults = array(
1244: 'name' => 'CakeCookie[default]',
1245: 'value' => '',
1246: 'expire' => 0,
1247: 'path' => '/',
1248: 'domain' => '',
1249: 'secure' => false,
1250: 'httpOnly' => false
1251: );
1252: $options += $defaults;
1253:
1254: $this->_cookies[$options['name']] = $options;
1255: }
1256:
1257: 1258: 1259: 1260: 1261: 1262: 1263: 1264: 1265: 1266: 1267: 1268: 1269: 1270: 1271: 1272: 1273: 1274: 1275: 1276: 1277: 1278: 1279: 1280: 1281: 1282:
1283: public function cors(CakeRequest $request, $allowedDomains, $allowedMethods = array(), $allowedHeaders = array()) {
1284: $origin = $request->header('Origin');
1285: if (!$origin) {
1286: return;
1287: }
1288:
1289: $allowedDomains = $this->_normalizeCorsDomains((array)$allowedDomains, $request->is('ssl'));
1290: foreach ($allowedDomains as $domain) {
1291: if (!preg_match($domain['preg'], $origin)) {
1292: continue;
1293: }
1294: $this->header('Access-Control-Allow-Origin', $domain['original'] === '*' ? '*' : $origin);
1295: $allowedMethods && $this->header('Access-Control-Allow-Methods', implode(', ', (array)$allowedMethods));
1296: $allowedHeaders && $this->header('Access-Control-Allow-Headers', implode(', ', (array)$allowedHeaders));
1297: break;
1298: }
1299: }
1300:
1301: 1302: 1303: 1304: 1305: 1306: 1307:
1308: protected function _normalizeCorsDomains($domains, $requestIsSSL = false) {
1309: $result = array();
1310: foreach ($domains as $domain) {
1311: if ($domain === '*') {
1312: $result[] = array('preg' => '@.@', 'original' => '*');
1313: continue;
1314: }
1315:
1316: $original = $preg = $domain;
1317: if (strpos($domain, '://') === false) {
1318: $preg = ($requestIsSSL ? 'https://' : 'http://') . $domain;
1319: }
1320: $preg = '@' . str_replace('*', '.*', $domain) . '@';
1321: $result[] = compact('original', 'preg');
1322: }
1323: return $result;
1324: }
1325:
1326: 1327: 1328: 1329: 1330: 1331: 1332: 1333: 1334: 1335: 1336: 1337: 1338: 1339: 1340: 1341: 1342:
1343: public function file($path, $options = array()) {
1344: $options += array(
1345: 'name' => null,
1346: 'download' => null
1347: );
1348:
1349: if (strpos($path, '../') !== false || strpos($path, '..\\') !== false) {
1350: throw new NotFoundException(__d(
1351: 'cake_dev',
1352: 'The requested file contains `..` and will not be read.'
1353: ));
1354: }
1355:
1356: if (!is_file($path)) {
1357: $path = APP . $path;
1358: }
1359:
1360: $file = new File($path);
1361: if (!$file->exists() || !$file->readable()) {
1362: if (Configure::read('debug')) {
1363: throw new NotFoundException(__d('cake_dev', 'The requested file %s was not found or not readable', $path));
1364: }
1365: throw new NotFoundException(__d('cake', 'The requested file was not found'));
1366: }
1367:
1368: $extension = strtolower($file->ext());
1369: $download = $options['download'];
1370: if ((!$extension || $this->type($extension) === false) && $download === null) {
1371: $download = true;
1372: }
1373:
1374: $fileSize = $file->size();
1375: if ($download) {
1376: $agent = env('HTTP_USER_AGENT');
1377:
1378: if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent)) {
1379: $contentType = 'application/octet-stream';
1380: } elseif (preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) {
1381: $contentType = 'application/force-download';
1382: }
1383:
1384: if (!empty($contentType)) {
1385: $this->type($contentType);
1386: }
1387: if ($options['name'] === null) {
1388: $name = $file->name;
1389: } else {
1390: $name = $options['name'];
1391: }
1392: $this->download($name);
1393: $this->header('Content-Transfer-Encoding', 'binary');
1394: }
1395:
1396: $this->header('Accept-Ranges', 'bytes');
1397: $httpRange = env('HTTP_RANGE');
1398: if (isset($httpRange)) {
1399: $this->_fileRange($file, $httpRange);
1400: } else {
1401: $this->header('Content-Length', $fileSize);
1402: }
1403:
1404: $this->_clearBuffer();
1405: $this->_file = $file;
1406: }
1407:
1408: 1409: 1410: 1411: 1412: 1413: 1414: 1415: 1416: 1417:
1418: protected function _fileRange($file, $httpRange) {
1419: $fileSize = $file->size();
1420: $lastByte = $fileSize - 1;
1421: $start = 0;
1422: $end = $lastByte;
1423:
1424: preg_match('/^bytes\s*=\s*(\d+)?\s*-\s*(\d+)?$/', $httpRange, $matches);
1425: if ($matches) {
1426: $start = $matches[1];
1427: $end = isset($matches[2]) ? $matches[2] : '';
1428: }
1429:
1430: if ($start === '') {
1431: $start = $fileSize - $end;
1432: $end = $lastByte;
1433: }
1434: if ($end === '') {
1435: $end = $lastByte;
1436: }
1437:
1438: if ($start > $end || $end > $lastByte || $start > $lastByte) {
1439: $this->statusCode(416);
1440: $this->header(array(
1441: 'Content-Range' => 'bytes 0-' . $lastByte . '/' . $fileSize
1442: ));
1443: return;
1444: }
1445:
1446: $this->header(array(
1447: 'Content-Length' => $end - $start + 1,
1448: 'Content-Range' => 'bytes ' . $start . '-' . $end . '/' . $fileSize
1449: ));
1450:
1451: $this->statusCode(206);
1452: $this->_fileRange = array($start, $end);
1453: }
1454:
1455: 1456: 1457: 1458: 1459: 1460: 1461:
1462: protected function _sendFile($file, $range) {
1463: $compress = $this->outputCompressed();
1464: $file->open('rb');
1465:
1466: $end = $start = false;
1467: if ($range) {
1468: list($start, $end) = $range;
1469: }
1470: if ($start !== false) {
1471: $file->offset($start);
1472: }
1473:
1474: $bufferSize = 8192;
1475: set_time_limit(0);
1476: session_write_close();
1477: while (!feof($file->handle)) {
1478: if (!$this->_isActive()) {
1479: $file->close();
1480: return false;
1481: }
1482: $offset = $file->offset();
1483: if ($end && $offset >= $end) {
1484: break;
1485: }
1486: if ($end && $offset + $bufferSize >= $end) {
1487: $bufferSize = $end - $offset + 1;
1488: }
1489: echo fread($file->handle, $bufferSize);
1490: if (!$compress) {
1491: $this->_flushBuffer();
1492: }
1493: }
1494: $file->close();
1495: return true;
1496: }
1497:
1498: 1499: 1500: 1501: 1502:
1503: protected function _isActive() {
1504: return connection_status() === CONNECTION_NORMAL && !connection_aborted();
1505: }
1506:
1507: 1508: 1509: 1510: 1511:
1512: protected function _clearBuffer() {
1513: if (ob_get_length()) {
1514: return ob_end_clean();
1515: }
1516: return true;
1517: }
1518:
1519: 1520: 1521: 1522: 1523:
1524: protected function _flushBuffer() {
1525:
1526: @flush();
1527: if (ob_get_level()) {
1528: @ob_flush();
1529: }
1530:
1531: }
1532:
1533: }
1534: