CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Reporting Security Issues
    • Privacy Policy
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Get Involved
    • Issues (GitHub)
    • Bakery
    • Featured Resources
    • Training
    • Meetups
    • My CakePHP
    • CakeFest
    • Newsletter
    • Linkedin
    • YouTube
    • Facebook
    • Twitter
    • Mastodon
    • Help & Support
    • Forum
    • Stack Overflow
    • Slack
    • Paid Support
CakePHP

C CakePHP 2.4 API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 2.4
      • 4.2
      • 4.1
      • 4.0
      • 3.9
      • 3.8
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Packages

  • Cake
    • Cache
      • Engine
    • Configure
    • Console
      • Command
        • Task
    • Controller
      • Component
        • Acl
        • Auth
    • Core
    • Error
    • Event
    • I18n
    • Log
      • Engine
    • Model
      • Behavior
      • Datasource
        • Database
        • Session
      • Validator
    • Network
      • Email
      • Http
    • Routing
      • Filter
      • Route
    • TestSuite
      • Coverage
      • Fixture
      • Reporter
    • Utility
    • View
      • Helper

Classes

  • CakeRequest
  • CakeResponse
  • CakeSocket
   1: <?php
   2: /**
   3:  * CakeResponse
   4:  *
   5:  * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
   6:  * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
   7:  *
   8:  * Licensed under The MIT License
   9:  * For full copyright and license information, please see the LICENSE.txt
  10:  * Redistributions of files must retain the above copyright notice.
  11:  *
  12:  * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  13:  * @link          http://cakephp.org CakePHP(tm) Project
  14:  * @package       Cake.Network
  15:  * @since         CakePHP(tm) v 2.0
  16:  * @license       http://www.opensource.org/licenses/mit-license.php MIT License
  17:  */
  18: 
  19: App::uses('File', 'Utility');
  20: 
  21: /**
  22:  * CakeResponse is responsible for managing the response text, status and headers of a HTTP response.
  23:  *
  24:  * By default controllers will use this class to render their response. If you are going to use
  25:  * a custom response class it should subclass this object in order to ensure compatibility.
  26:  *
  27:  * @package       Cake.Network
  28:  */
  29: class CakeResponse {
  30: 
  31: /**
  32:  * Holds HTTP response statuses
  33:  *
  34:  * @var array
  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:  * Holds known mime type mappings
  81:  *
  82:  * @var array
  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:  * Protocol header to send to the client
 307:  *
 308:  * @var string
 309:  */
 310:     protected $_protocol = 'HTTP/1.1';
 311: 
 312: /**
 313:  * Status code to send to the client
 314:  *
 315:  * @var integer
 316:  */
 317:     protected $_status = 200;
 318: 
 319: /**
 320:  * Content type to send. This can be an 'extension' that will be transformed using the $_mimetypes array
 321:  * or a complete mime-type
 322:  *
 323:  * @var integer
 324:  */
 325:     protected $_contentType = 'text/html';
 326: 
 327: /**
 328:  * Buffer list of headers
 329:  *
 330:  * @var array
 331:  */
 332:     protected $_headers = array();
 333: 
 334: /**
 335:  * Buffer string for response message
 336:  *
 337:  * @var string
 338:  */
 339:     protected $_body = null;
 340: 
 341: /**
 342:  * File object for file to be read out as response
 343:  *
 344:  * @var File
 345:  */
 346:     protected $_file = null;
 347: 
 348: /**
 349:  * File range. Used for requesting ranges of files.
 350:  *
 351:  * @var array
 352:  */
 353:     protected $_fileRange = null;
 354: 
 355: /**
 356:  * The charset the response body is encoded with
 357:  *
 358:  * @var string
 359:  */
 360:     protected $_charset = 'UTF-8';
 361: 
 362: /**
 363:  * Holds all the cache directives that will be converted
 364:  * into headers when sending the request
 365:  *
 366:  * @var string
 367:  */
 368:     protected $_cacheDirectives = array();
 369: 
 370: /**
 371:  * Holds cookies to be sent to the client
 372:  *
 373:  * @var array
 374:  */
 375:     protected $_cookies = array();
 376: 
 377: /**
 378:  * Constructor
 379:  *
 380:  * @param array $options list of parameters to setup the response. Possible values are:
 381:  *  - body: the response text that should be sent to the client
 382:  *  - status: the HTTP status code to respond with
 383:  *  - type: a complete mime-type string or an extension mapped in this class
 384:  *  - charset: the charset for the response body
 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:  * Sends the complete response to the client including headers and message body.
 404:  * Will echo out the content in the response body.
 405:  *
 406:  * @return void
 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 => $values) {
 420:             foreach ((array)$values as $value) {
 421:                 $this->_sendHeader($header, $value);
 422:             }
 423:         }
 424:         if ($this->_file) {
 425:             $this->_sendFile($this->_file, $this->_fileRange);
 426:             $this->_file = $this->_fileRange = null;
 427:         } else {
 428:             $this->_sendContent($this->_body);
 429:         }
 430:     }
 431: 
 432: /**
 433:  * Sets the cookies that have been added via CakeResponse::cookie() before any
 434:  * other output is sent to the client. Will set the cookies in the order they
 435:  * have been set.
 436:  *
 437:  * @return void
 438:  */
 439:     protected function _setCookies() {
 440:         foreach ($this->_cookies as $name => $c) {
 441:             setcookie(
 442:                 $name, $c['value'], $c['expire'], $c['path'],
 443:                 $c['domain'], $c['secure'], $c['httpOnly']
 444:             );
 445:         }
 446:     }
 447: 
 448: /**
 449:  * Formats the Content-Type header based on the configured contentType and charset
 450:  * the charset will only be set in the header if the response is of type text/*
 451:  *
 452:  * @return void
 453:  */
 454:     protected function _setContentType() {
 455:         if (in_array($this->_status, array(304, 204))) {
 456:             return;
 457:         }
 458:         $whitelist = array(
 459:             'application/javascript', 'application/json', 'application/xml', 'application/rss+xml'
 460:         );
 461: 
 462:         $charset = false;
 463:         if (
 464:             $this->_charset &&
 465:             (strpos($this->_contentType, 'text/') === 0 || in_array($this->_contentType, $whitelist))
 466:         ) {
 467:             $charset = true;
 468:         }
 469: 
 470:         if ($charset) {
 471:             $this->header('Content-Type', "{$this->_contentType}; charset={$this->_charset}");
 472:         } else {
 473:             $this->header('Content-Type', "{$this->_contentType}");
 474:         }
 475:     }
 476: 
 477: /**
 478:  * Sets the response body to an empty text if the status code is 204 or 304
 479:  *
 480:  * @return void
 481:  */
 482:     protected function _setContent() {
 483:         if (in_array($this->_status, array(304, 204))) {
 484:             $this->body('');
 485:         }
 486:     }
 487: 
 488: /**
 489:  * Calculates the correct Content-Length and sets it as a header in the response
 490:  * Will not set the value if already set or if the output is compressed.
 491:  *
 492:  * @return void
 493:  */
 494:     protected function _setContentLength() {
 495:         $shouldSetLength = !isset($this->_headers['Content-Length']) && !in_array($this->_status, range(301, 307));
 496:         if (isset($this->_headers['Content-Length']) && $this->_headers['Content-Length'] === false) {
 497:             unset($this->_headers['Content-Length']);
 498:             return;
 499:         }
 500:         if ($shouldSetLength && !$this->outputCompressed()) {
 501:             $offset = ob_get_level() ? ob_get_length() : 0;
 502:             if (ini_get('mbstring.func_overload') & 2 && function_exists('mb_strlen')) {
 503:                 $this->length($offset + mb_strlen($this->_body, '8bit'));
 504:             } else {
 505:                 $this->length($this->_headers['Content-Length'] = $offset + strlen($this->_body));
 506:             }
 507:         }
 508:     }
 509: 
 510: /**
 511:  * Sends a header to the client.
 512:  *
 513:  * @param string $name the header name
 514:  * @param string $value the header value
 515:  * @return void
 516:  */
 517:     protected function _sendHeader($name, $value = null) {
 518:         if (!headers_sent()) {
 519:             if ($value === null) {
 520:                 header($name);
 521:             } else {
 522:                 header("{$name}: {$value}");
 523:             }
 524:         }
 525:     }
 526: 
 527: /**
 528:  * Sends a content string to the client.
 529:  *
 530:  * @param string $content string to send as response body
 531:  * @return void
 532:  */
 533:     protected function _sendContent($content) {
 534:         echo $content;
 535:     }
 536: 
 537: /**
 538:  * Buffers a header string to be sent
 539:  * Returns the complete list of buffered headers
 540:  *
 541:  * ### Single header
 542:  * e.g `header('Location', 'http://example.com');`
 543:  *
 544:  * ### Multiple headers
 545:  * e.g `header(array('Location' => 'http://example.com', 'X-Extra' => 'My header'));`
 546:  *
 547:  * ### String header
 548:  * e.g `header('WWW-Authenticate: Negotiate');`
 549:  *
 550:  * ### Array of string headers
 551:  * e.g `header(array('WWW-Authenticate: Negotiate', 'Content-type: application/pdf'));`
 552:  *
 553:  * Multiple calls for setting the same header name will have the same effect as setting the header once
 554:  * with the last value sent for it
 555:  *  e.g `header('WWW-Authenticate: Negotiate'); header('WWW-Authenticate: Not-Negotiate');`
 556:  * will have the same effect as only doing `header('WWW-Authenticate: Not-Negotiate');`
 557:  *
 558:  * @param string|array $header. An array of header strings or a single header string
 559:  *  - an associative array of "header name" => "header value" is also accepted
 560:  *  - an array of string headers is also accepted
 561:  * @param string|array $value. The header value(s)
 562:  * @return array list of headers to be sent
 563:  */
 564:     public function header($header = null, $value = null) {
 565:         if ($header === null) {
 566:             return $this->_headers;
 567:         }
 568:         $headers = is_array($header) ? $header : array($header => $value);
 569:         foreach ($headers as $header => $value) {
 570:             if (is_numeric($header)) {
 571:                 list($header, $value) = array($value, null);
 572:             }
 573:             if ($value === null) {
 574:                 list($header, $value) = explode(':', $header, 2);
 575:             }
 576:             $this->_headers[$header] = is_array($value) ? array_map('trim', $value) : trim($value);
 577:         }
 578:         return $this->_headers;
 579:     }
 580: 
 581: /**
 582:  * Acccessor for the location header.
 583:  *
 584:  * Get/Set the Location header value.
 585:  * @param null|string $url Either null to get the current location, or a string to set one.
 586:  * @return string|null When setting the location null will be returned. When reading the location
 587:  *    a string of the current location header value (if any) will be returned.
 588:  */
 589:     public function location($url = null) {
 590:         if ($url === null) {
 591:             $headers = $this->header();
 592:             return isset($headers['Location']) ? $headers['Location'] : null;
 593:         }
 594:         $this->header('Location', $url);
 595:     }
 596: 
 597: /**
 598:  * Buffers the response message to be sent
 599:  * if $content is null the current buffer is returned
 600:  *
 601:  * @param string $content the string message to be sent
 602:  * @return string current message buffer if $content param is passed as null
 603:  */
 604:     public function body($content = null) {
 605:         if ($content === null) {
 606:             return $this->_body;
 607:         }
 608:         return $this->_body = $content;
 609:     }
 610: 
 611: /**
 612:  * Sets the HTTP status code to be sent
 613:  * if $code is null the current code is returned
 614:  *
 615:  * @param integer $code the HTTP status code
 616:  * @return integer current status code
 617:  * @throws CakeException When an unknown status code is reached.
 618:  */
 619:     public function statusCode($code = null) {
 620:         if ($code === null) {
 621:             return $this->_status;
 622:         }
 623:         if (!isset($this->_statusCodes[$code])) {
 624:             throw new CakeException(__d('cake_dev', 'Unknown status code'));
 625:         }
 626:         return $this->_status = $code;
 627:     }
 628: 
 629: /**
 630:  * Queries & sets valid HTTP response codes & messages.
 631:  *
 632:  * @param integer|array $code If $code is an integer, then the corresponding code/message is
 633:  *        returned if it exists, null if it does not exist. If $code is an array, then the
 634:  *        keys are used as codes and the values as messages to add to the default HTTP
 635:  *        codes. The codes must be integers greater than 99 and less than 1000. Keep in
 636:  *        mind that the HTTP specification outlines that status codes begin with a digit
 637:  *        between 1 and 5, which defines the class of response the client is to expect.
 638:  *        Example:
 639:  *
 640:  *        httpCodes(404); // returns array(404 => 'Not Found')
 641:  *
 642:  *        httpCodes(array(
 643:  *            381 => 'Unicorn Moved',
 644:  *            555 => 'Unexpected Minotaur'
 645:  *        )); // sets these new values, and returns true
 646:  *
 647:  *        httpCodes(array(
 648:  *            0 => 'Nothing Here',
 649:  *            -1 => 'Reverse Infinity',
 650:  *            12345 => 'Universal Password',
 651:  *            'Hello' => 'World'
 652:  *        )); // throws an exception due to invalid codes
 653:  *
 654:  *        For more on HTTP status codes see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1
 655:  *
 656:  * @return mixed associative array of the HTTP codes as keys, and the message
 657:  *    strings as values, or null of the given $code does not exist.
 658:  * @throws CakeException If an attempt is made to add an invalid status code
 659:  */
 660:     public function httpCodes($code = null) {
 661:         if (empty($code)) {
 662:             return $this->_statusCodes;
 663:         }
 664:         if (is_array($code)) {
 665:             $codes = array_keys($code);
 666:             $min = min($codes);
 667:             if (!is_int($min) || $min < 100 || max($codes) > 999) {
 668:                 throw new CakeException(__d('cake_dev', 'Invalid status code'));
 669:             }
 670:             $this->_statusCodes = $code + $this->_statusCodes;
 671:             return true;
 672:         }
 673:         if (!isset($this->_statusCodes[$code])) {
 674:             return null;
 675:         }
 676:         return array($code => $this->_statusCodes[$code]);
 677:     }
 678: 
 679: /**
 680:  * Sets the response content type. It can be either a file extension
 681:  * which will be mapped internally to a mime-type or a string representing a mime-type
 682:  * if $contentType is null the current content type is returned
 683:  * if $contentType is an associative array, content type definitions will be stored/replaced
 684:  *
 685:  * ### Setting the content type
 686:  *
 687:  * e.g `type('jpg');`
 688:  *
 689:  * ### Returning the current content type
 690:  *
 691:  * e.g `type();`
 692:  *
 693:  * ### Storing content type definitions
 694:  *
 695:  * e.g `type(array('keynote' => 'application/keynote', 'bat' => 'application/bat'));`
 696:  *
 697:  * ### Replacing a content type definition
 698:  *
 699:  * e.g `type(array('jpg' => 'text/plain'));`
 700:  *
 701:  * @param string $contentType
 702:  * @return mixed current content type or false if supplied an invalid content type
 703:  */
 704:     public function type($contentType = null) {
 705:         if ($contentType === null) {
 706:             return $this->_contentType;
 707:         }
 708:         if (is_array($contentType)) {
 709:             foreach ($contentType as $type => $definition) {
 710:                 $this->_mimeTypes[$type] = $definition;
 711:             }
 712:             return $this->_contentType;
 713:         }
 714:         if (isset($this->_mimeTypes[$contentType])) {
 715:             $contentType = $this->_mimeTypes[$contentType];
 716:             $contentType = is_array($contentType) ? current($contentType) : $contentType;
 717:         }
 718:         if (strpos($contentType, '/') === false) {
 719:             return false;
 720:         }
 721:         return $this->_contentType = $contentType;
 722:     }
 723: 
 724: /**
 725:  * Returns the mime type definition for an alias
 726:  *
 727:  * e.g `getMimeType('pdf'); // returns 'application/pdf'`
 728:  *
 729:  * @param string $alias the content type alias to map
 730:  * @return mixed string mapped mime type or false if $alias is not mapped
 731:  */
 732:     public function getMimeType($alias) {
 733:         if (isset($this->_mimeTypes[$alias])) {
 734:             return $this->_mimeTypes[$alias];
 735:         }
 736:         return false;
 737:     }
 738: 
 739: /**
 740:  * Maps a content-type back to an alias
 741:  *
 742:  * e.g `mapType('application/pdf'); // returns 'pdf'`
 743:  *
 744:  * @param string|array $ctype Either a string content type to map, or an array of types.
 745:  * @return mixed Aliases for the types provided.
 746:  */
 747:     public function mapType($ctype) {
 748:         if (is_array($ctype)) {
 749:             return array_map(array($this, 'mapType'), $ctype);
 750:         }
 751: 
 752:         foreach ($this->_mimeTypes as $alias => $types) {
 753:             if (in_array($ctype, (array)$types)) {
 754:                 return $alias;
 755:             }
 756:         }
 757:         return null;
 758:     }
 759: 
 760: /**
 761:  * Sets the response charset
 762:  * if $charset is null the current charset is returned
 763:  *
 764:  * @param string $charset
 765:  * @return string current charset
 766:  */
 767:     public function charset($charset = null) {
 768:         if ($charset === null) {
 769:             return $this->_charset;
 770:         }
 771:         return $this->_charset = $charset;
 772:     }
 773: 
 774: /**
 775:  * Sets the correct headers to instruct the client to not cache the response
 776:  *
 777:  * @return void
 778:  */
 779:     public function disableCache() {
 780:         $this->header(array(
 781:             'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT',
 782:             'Last-Modified' => gmdate("D, d M Y H:i:s") . " GMT",
 783:             'Cache-Control' => 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'
 784:         ));
 785:     }
 786: 
 787: /**
 788:  * Sets the correct headers to instruct the client to cache the response.
 789:  *
 790:  * @param string $since a valid time since the response text has not been modified
 791:  * @param string $time a valid time for cache expiry
 792:  * @return void
 793:  */
 794:     public function cache($since, $time = '+1 day') {
 795:         if (!is_int($time)) {
 796:             $time = strtotime($time);
 797:         }
 798:         $this->header(array(
 799:             'Date' => gmdate("D, j M Y G:i:s ", time()) . 'GMT'
 800:         ));
 801:         $this->modified($since);
 802:         $this->expires($time);
 803:         $this->sharable(true);
 804:         $this->maxAge($time - time());
 805:     }
 806: 
 807: /**
 808:  * Sets whether a response is eligible to be cached by intermediate proxies
 809:  * This method controls the `public` or `private` directive in the Cache-Control
 810:  * header
 811:  *
 812:  * @param boolean $public If set to true, the Cache-Control header will be set as public
 813:  *   if set to false, the response will be set to private
 814:  *   if no value is provided, it will return whether the response is sharable or not
 815:  * @param integer $time time in seconds after which the response should no longer be considered fresh
 816:  * @return boolean
 817:  */
 818:     public function sharable($public = null, $time = null) {
 819:         if ($public === null) {
 820:             $public = array_key_exists('public', $this->_cacheDirectives);
 821:             $private = array_key_exists('private', $this->_cacheDirectives);
 822:             $noCache = array_key_exists('no-cache', $this->_cacheDirectives);
 823:             if (!$public && !$private && !$noCache) {
 824:                 return null;
 825:             }
 826:             $sharable = $public || ! ($private || $noCache);
 827:             return $sharable;
 828:         }
 829:         if ($public) {
 830:             $this->_cacheDirectives['public'] = true;
 831:             unset($this->_cacheDirectives['private']);
 832:             $this->sharedMaxAge($time);
 833:         } else {
 834:             $this->_cacheDirectives['private'] = true;
 835:             unset($this->_cacheDirectives['public']);
 836:             $this->maxAge($time);
 837:         }
 838:         if (!$time) {
 839:             $this->_setCacheControl();
 840:         }
 841:         return (bool)$public;
 842:     }
 843: 
 844: /**
 845:  * Sets the Cache-Control s-maxage directive.
 846:  * The max-age is the number of seconds after which the response should no longer be considered
 847:  * a good candidate to be fetched from a shared cache (like in a proxy server).
 848:  * If called with no parameters, this function will return the current max-age value if any
 849:  *
 850:  * @param integer $seconds if null, the method will return the current s-maxage value
 851:  * @return integer
 852:  */
 853:     public function sharedMaxAge($seconds = null) {
 854:         if ($seconds !== null) {
 855:             $this->_cacheDirectives['s-maxage'] = $seconds;
 856:             $this->_setCacheControl();
 857:         }
 858:         if (isset($this->_cacheDirectives['s-maxage'])) {
 859:             return $this->_cacheDirectives['s-maxage'];
 860:         }
 861:         return null;
 862:     }
 863: 
 864: /**
 865:  * Sets the Cache-Control max-age directive.
 866:  * The max-age is the number of seconds after which the response should no longer be considered
 867:  * a good candidate to be fetched from the local (client) cache.
 868:  * If called with no parameters, this function will return the current max-age value if any
 869:  *
 870:  * @param integer $seconds if null, the method will return the current max-age value
 871:  * @return integer
 872:  */
 873:     public function maxAge($seconds = null) {
 874:         if ($seconds !== null) {
 875:             $this->_cacheDirectives['max-age'] = $seconds;
 876:             $this->_setCacheControl();
 877:         }
 878:         if (isset($this->_cacheDirectives['max-age'])) {
 879:             return $this->_cacheDirectives['max-age'];
 880:         }
 881:         return null;
 882:     }
 883: 
 884: /**
 885:  * Sets the Cache-Control must-revalidate directive.
 886:  * must-revalidate indicates that the response should not be served
 887:  * stale by a cache under any circumstance without first revalidating
 888:  * with the origin.
 889:  * If called with no parameters, this function will return whether must-revalidate is present.
 890:  *
 891:  * @param integer $seconds if null, the method will return the current
 892:  *   must-revalidate value
 893:  * @return boolean
 894:  */
 895:     public function mustRevalidate($enable = null) {
 896:         if ($enable !== null) {
 897:             if ($enable) {
 898:                 $this->_cacheDirectives['must-revalidate'] = true;
 899:             } else {
 900:                 unset($this->_cacheDirectives['must-revalidate']);
 901:             }
 902:             $this->_setCacheControl();
 903:         }
 904:         return array_key_exists('must-revalidate', $this->_cacheDirectives);
 905:     }
 906: 
 907: /**
 908:  * Helper method to generate a valid Cache-Control header from the options set
 909:  * in other methods
 910:  *
 911:  * @return void
 912:  */
 913:     protected function _setCacheControl() {
 914:         $control = '';
 915:         foreach ($this->_cacheDirectives as $key => $val) {
 916:             $control .= $val === true ? $key : sprintf('%s=%s', $key, $val);
 917:             $control .= ', ';
 918:         }
 919:         $control = rtrim($control, ', ');
 920:         $this->header('Cache-Control', $control);
 921:     }
 922: 
 923: /**
 924:  * Sets the Expires header for the response by taking an expiration time
 925:  * If called with no parameters it will return the current Expires value
 926:  *
 927:  * ## Examples:
 928:  *
 929:  * `$response->expires('now')` Will Expire the response cache now
 930:  * `$response->expires(new DateTime('+1 day'))` Will set the expiration in next 24 hours
 931:  * `$response->expires()` Will return the current expiration header value
 932:  *
 933:  * @param string|DateTime $time
 934:  * @return string
 935:  */
 936:     public function expires($time = null) {
 937:         if ($time !== null) {
 938:             $date = $this->_getUTCDate($time);
 939:             $this->_headers['Expires'] = $date->format('D, j M Y H:i:s') . ' GMT';
 940:         }
 941:         if (isset($this->_headers['Expires'])) {
 942:             return $this->_headers['Expires'];
 943:         }
 944:         return null;
 945:     }
 946: 
 947: /**
 948:  * Sets the Last-Modified header for the response by taking an modification time
 949:  * If called with no parameters it will return the current Last-Modified value
 950:  *
 951:  * ## Examples:
 952:  *
 953:  * `$response->modified('now')` Will set the Last-Modified to the current time
 954:  * `$response->modified(new DateTime('+1 day'))` Will set the modification date in the past 24 hours
 955:  * `$response->modified()` Will return the current Last-Modified header value
 956:  *
 957:  * @param string|DateTime $time
 958:  * @return string
 959:  */
 960:     public function modified($time = null) {
 961:         if ($time !== null) {
 962:             $date = $this->_getUTCDate($time);
 963:             $this->_headers['Last-Modified'] = $date->format('D, j M Y H:i:s') . ' GMT';
 964:         }
 965:         if (isset($this->_headers['Last-Modified'])) {
 966:             return $this->_headers['Last-Modified'];
 967:         }
 968:         return null;
 969:     }
 970: 
 971: /**
 972:  * Sets the response as Not Modified by removing any body contents
 973:  * setting the status code to "304 Not Modified" and removing all
 974:  * conflicting headers
 975:  *
 976:  * @return void
 977:  */
 978:     public function notModified() {
 979:         $this->statusCode(304);
 980:         $this->body('');
 981:         $remove = array(
 982:             'Allow',
 983:             'Content-Encoding',
 984:             'Content-Language',
 985:             'Content-Length',
 986:             'Content-MD5',
 987:             'Content-Type',
 988:             'Last-Modified'
 989:         );
 990:         foreach ($remove as $header) {
 991:             unset($this->_headers[$header]);
 992:         }
 993:     }
 994: 
 995: /**
 996:  * Sets the Vary header for the response, if an array is passed,
 997:  * values will be imploded into a comma separated string. If no
 998:  * parameters are passed, then an array with the current Vary header
 999:  * value is returned
1000:  *
1001:  * @param string|array $cacheVariances a single Vary string or a array
1002:  *   containing the list for variances.
1003:  * @return array
1004:  */
1005:     public function vary($cacheVariances = null) {
1006:         if ($cacheVariances !== null) {
1007:             $cacheVariances = (array)$cacheVariances;
1008:             $this->_headers['Vary'] = implode(', ', $cacheVariances);
1009:         }
1010:         if (isset($this->_headers['Vary'])) {
1011:             return explode(', ', $this->_headers['Vary']);
1012:         }
1013:         return null;
1014:     }
1015: 
1016: /**
1017:  * Sets the response Etag, Etags are a strong indicative that a response
1018:  * can be cached by a HTTP client. A bad way of generating Etags is
1019:  * creating a hash of the response output, instead generate a unique
1020:  * hash of the unique components that identifies a request, such as a
1021:  * modification time, a resource Id, and anything else you consider it
1022:  * makes it unique.
1023:  *
1024:  * Second parameter is used to instruct clients that the content has
1025:  * changed, but sematicallly, it can be used as the same thing. Think
1026:  * for instance of a page with a hit counter, two different page views
1027:  * are equivalent, but they differ by a few bytes. This leaves off to
1028:  * the Client the decision of using or not the cached page.
1029:  *
1030:  * If no parameters are passed, current Etag header is returned.
1031:  *
1032:  * @param string $hash the unique has that identifies this response
1033:  * @param boolean $weak whether the response is semantically the same as
1034:  *   other with the same hash or not
1035:  * @return string
1036:  */
1037:     public function etag($tag = null, $weak = false) {
1038:         if ($tag !== null) {
1039:             $this->_headers['Etag'] = sprintf('%s"%s"', ($weak) ? 'W/' : null, $tag);
1040:         }
1041:         if (isset($this->_headers['Etag'])) {
1042:             return $this->_headers['Etag'];
1043:         }
1044:         return null;
1045:     }
1046: 
1047: /**
1048:  * Returns a DateTime object initialized at the $time param and using UTC
1049:  * as timezone
1050:  *
1051:  * @param string|integer|DateTime $time
1052:  * @return DateTime
1053:  */
1054:     protected function _getUTCDate($time = null) {
1055:         if ($time instanceof DateTime) {
1056:             $result = clone $time;
1057:         } elseif (is_int($time)) {
1058:             $result = new DateTime(date('Y-m-d H:i:s', $time));
1059:         } else {
1060:             $result = new DateTime($time);
1061:         }
1062:         $result->setTimeZone(new DateTimeZone('UTC'));
1063:         return $result;
1064:     }
1065: 
1066: /**
1067:  * Sets the correct output buffering handler to send a compressed response. Responses will
1068:  * be compressed with zlib, if the extension is available.
1069:  *
1070:  * @return boolean false if client does not accept compressed responses or no handler is available, true otherwise
1071:  */
1072:     public function compress() {
1073:         $compressionEnabled = ini_get("zlib.output_compression") !== '1' &&
1074:             extension_loaded("zlib") &&
1075:             (strpos(env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false);
1076:         return $compressionEnabled && ob_start('ob_gzhandler');
1077:     }
1078: 
1079: /**
1080:  * Returns whether the resulting output will be compressed by PHP
1081:  *
1082:  * @return boolean
1083:  */
1084:     public function outputCompressed() {
1085:         return strpos(env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false
1086:             && (ini_get("zlib.output_compression") === '1' || in_array('ob_gzhandler', ob_list_handlers()));
1087:     }
1088: 
1089: /**
1090:  * Sets the correct headers to instruct the browser to download the response as a file.
1091:  *
1092:  * @param string $filename the name of the file as the browser will download the response
1093:  * @return void
1094:  */
1095:     public function download($filename) {
1096:         $this->header('Content-Disposition', 'attachment; filename="' . $filename . '"');
1097:     }
1098: 
1099: /**
1100:  * Sets the protocol to be used when sending the response. Defaults to HTTP/1.1
1101:  * If called with no arguments, it will return the current configured protocol
1102:  *
1103:  * @param string protocol to be used for sending response
1104:  * @return string protocol currently set
1105:  */
1106:     public function protocol($protocol = null) {
1107:         if ($protocol !== null) {
1108:             $this->_protocol = $protocol;
1109:         }
1110:         return $this->_protocol;
1111:     }
1112: 
1113: /**
1114:  * Sets the Content-Length header for the response
1115:  * If called with no arguments returns the last Content-Length set
1116:  *
1117:  * @param integer $bytes Number of bytes
1118:  * @return integer|null
1119:  */
1120:     public function length($bytes = null) {
1121:         if ($bytes !== null) {
1122:             $this->_headers['Content-Length'] = $bytes;
1123:         }
1124:         if (isset($this->_headers['Content-Length'])) {
1125:             return $this->_headers['Content-Length'];
1126:         }
1127:         return null;
1128:     }
1129: 
1130: /**
1131:  * Checks whether a response has not been modified according to the 'If-None-Match'
1132:  * (Etags) and 'If-Modified-Since' (last modification date) request
1133:  * headers headers. If the response is detected to be not modified, it
1134:  * is marked as so accordingly so the client can be informed of that.
1135:  *
1136:  * In order to mark a response as not modified, you need to set at least
1137:  * the Last-Modified etag response header before calling this method. Otherwise
1138:  * a comparison will not be possible.
1139:  *
1140:  * @param CakeRequest $request Request object
1141:  * @return boolean whether the response was marked as not modified or not.
1142:  */
1143:     public function checkNotModified(CakeRequest $request) {
1144:         $etags = preg_split('/\s*,\s*/', $request->header('If-None-Match'), null, PREG_SPLIT_NO_EMPTY);
1145:         $modifiedSince = $request->header('If-Modified-Since');
1146:         if ($responseTag = $this->etag()) {
1147:             $etagMatches = in_array('*', $etags) || in_array($responseTag, $etags);
1148:         }
1149:         if ($modifiedSince) {
1150:             $timeMatches = strtotime($this->modified()) === strtotime($modifiedSince);
1151:         }
1152:         $checks = compact('etagMatches', 'timeMatches');
1153:         if (empty($checks)) {
1154:             return false;
1155:         }
1156:         $notModified = !in_array(false, $checks, true);
1157:         if ($notModified) {
1158:             $this->notModified();
1159:         }
1160:         return $notModified;
1161:     }
1162: 
1163: /**
1164:  * String conversion. Fetches the response body as a string.
1165:  * Does *not* send headers.
1166:  *
1167:  * @return string
1168:  */
1169:     public function __toString() {
1170:         return (string)$this->_body;
1171:     }
1172: 
1173: /**
1174:  * Getter/Setter for cookie configs
1175:  *
1176:  * This method acts as a setter/getter depending on the type of the argument.
1177:  * If the method is called with no arguments, it returns all configurations.
1178:  *
1179:  * If the method is called with a string as argument, it returns either the
1180:  * given configuration if it is set, or null, if it's not set.
1181:  *
1182:  * If the method is called with an array as argument, it will set the cookie
1183:  * configuration to the cookie container.
1184:  *
1185:  * @param array $options Either null to get all cookies, string for a specific cookie
1186:  *  or array to set cookie.
1187:  *
1188:  * ### Options (when setting a configuration)
1189:  *  - name: The Cookie name
1190:  *  - value: Value of the cookie
1191:  *  - expire: Time the cookie expires in
1192:  *  - path: Path the cookie applies to
1193:  *  - domain: Domain the cookie is for.
1194:  *  - secure: Is the cookie https?
1195:  *  - httpOnly: Is the cookie available in the client?
1196:  *
1197:  * ## Examples
1198:  *
1199:  * ### Getting all cookies
1200:  *
1201:  * `$this->cookie()`
1202:  *
1203:  * ### Getting a certain cookie configuration
1204:  *
1205:  * `$this->cookie('MyCookie')`
1206:  *
1207:  * ### Setting a cookie configuration
1208:  *
1209:  * `$this->cookie((array) $options)`
1210:  *
1211:  * @return mixed
1212:  */
1213:     public function cookie($options = null) {
1214:         if ($options === null) {
1215:             return $this->_cookies;
1216:         }
1217: 
1218:         if (is_string($options)) {
1219:             if (!isset($this->_cookies[$options])) {
1220:                 return null;
1221:             }
1222:             return $this->_cookies[$options];
1223:         }
1224: 
1225:         $defaults = array(
1226:             'name' => 'CakeCookie[default]',
1227:             'value' => '',
1228:             'expire' => 0,
1229:             'path' => '/',
1230:             'domain' => '',
1231:             'secure' => false,
1232:             'httpOnly' => false
1233:         );
1234:         $options += $defaults;
1235: 
1236:         $this->_cookies[$options['name']] = $options;
1237:     }
1238: 
1239: /**
1240:  * Setup for display or download the given file.
1241:  *
1242:  * If $_SERVER['HTTP_RANGE'] is set a slice of the file will be
1243:  * returned instead of the entire file.
1244:  *
1245:  * ### Options keys
1246:  *
1247:  * - name: Alternate download name
1248:  * - download: If `true` sets download header and forces file to be downloaded rather than displayed in browser
1249:  *
1250:  * @param string $path Path to file. If the path is not an absolute path that resolves
1251:  *   to a file, `APP` will be prepended to the path.
1252:  * @param array $options Options See above.
1253:  * @return void
1254:  * @throws NotFoundException
1255:  */
1256:     public function file($path, $options = array()) {
1257:         $options += array(
1258:             'name' => null,
1259:             'download' => null
1260:         );
1261: 
1262:         if (strpos($path, '..') !== false) {
1263:             throw new NotFoundException(__d(
1264:                 'cake_dev',
1265:                 'The requested file contains `..` and will not be read.'
1266:             ));
1267:         }
1268: 
1269:         if (!is_file($path)) {
1270:             $path = APP . $path;
1271:         }
1272: 
1273:         $file = new File($path);
1274:         if (!$file->exists() || !$file->readable()) {
1275:             if (Configure::read('debug')) {
1276:                 throw new NotFoundException(__d('cake_dev', 'The requested file %s was not found or not readable', $path));
1277:             }
1278:             throw new NotFoundException(__d('cake', 'The requested file was not found'));
1279:         }
1280: 
1281:         $extension = strtolower($file->ext());
1282:         $download = $options['download'];
1283:         if ((!$extension || $this->type($extension) === false) && $download === null) {
1284:             $download = true;
1285:         }
1286: 
1287:         $fileSize = $file->size();
1288:         if ($download) {
1289:             $agent = env('HTTP_USER_AGENT');
1290: 
1291:             if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent)) {
1292:                 $contentType = 'application/octet-stream';
1293:             } elseif (preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) {
1294:                 $contentType = 'application/force-download';
1295:             }
1296: 
1297:             if (!empty($contentType)) {
1298:                 $this->type($contentType);
1299:             }
1300:             if ($options['name'] === null) {
1301:                 $name = $file->name;
1302:             } else {
1303:                 $name = $options['name'];
1304:             }
1305:             $this->download($name);
1306:             $this->header('Accept-Ranges', 'bytes');
1307:             $this->header('Content-Transfer-Encoding', 'binary');
1308: 
1309:             $httpRange = env('HTTP_RANGE');
1310:             if (isset($httpRange)) {
1311:                 $this->_fileRange($file, $httpRange);
1312:             } else {
1313:                 $this->header('Content-Length', $fileSize);
1314:             }
1315:         } else {
1316:             $this->header('Content-Length', $fileSize);
1317:         }
1318:         $this->_clearBuffer();
1319:         $this->_file = $file;
1320:     }
1321: 
1322: /**
1323:  * Apply a file range to a file and set the end offset.
1324:  *
1325:  * If an invalid range is requested a 416 Status code will be used
1326:  * in the response.
1327:  *
1328:  * @param File $file The file to set a range on.
1329:  * @param string $httpRange The range to use.
1330:  * @return void
1331:  */
1332:     protected function _fileRange($file, $httpRange) {
1333:         list(, $range) = explode('=', $httpRange);
1334:         list($start, $end) = explode('-', $range);
1335: 
1336:         $fileSize = $file->size();
1337:         $lastByte = $fileSize - 1;
1338: 
1339:         if ($start === '') {
1340:             $start = $fileSize - $end;
1341:             $end = $lastByte;
1342:         }
1343:         if ($end === '') {
1344:             $end = $lastByte;
1345:         }
1346: 
1347:         if ($start > $end || $end > $lastByte || $start > $lastByte) {
1348:             $this->statusCode(416);
1349:             $this->header(array(
1350:                 'Content-Range' => 'bytes 0-' . $lastByte . '/' . $fileSize
1351:             ));
1352:             return;
1353:         }
1354: 
1355:         $this->header(array(
1356:             'Content-Length' => $end - $start + 1,
1357:             'Content-Range' => 'bytes ' . $start . '-' . $end . '/' . $fileSize
1358:         ));
1359: 
1360:         $this->statusCode(206);
1361:         $this->_fileRange = array($start, $end);
1362:     }
1363: 
1364: /**
1365:  * Reads out a file, and echos the content to the client.
1366:  *
1367:  * @param File $file File object
1368:  * @param array $range The range to read out of the file.
1369:  * @return boolean True is whole file is echoed successfully or false if client connection is lost in between
1370:  */
1371:     protected function _sendFile($file, $range) {
1372:         $compress = $this->outputCompressed();
1373:         $file->open('rb');
1374: 
1375:         $end = $start = false;
1376:         if ($range) {
1377:             list($start, $end) = $range;
1378:         }
1379:         if ($start !== false) {
1380:             $file->offset($start);
1381:         }
1382: 
1383:         $bufferSize = 8192;
1384:         set_time_limit(0);
1385:         session_write_close();
1386:         while (!feof($file->handle)) {
1387:             if (!$this->_isActive()) {
1388:                 $file->close();
1389:                 return false;
1390:             }
1391:             $offset = $file->offset();
1392:             if ($end && $offset >= $end) {
1393:                 break;
1394:             }
1395:             if ($end && $offset + $bufferSize >= $end) {
1396:                 $bufferSize = $end - $offset + 1;
1397:             }
1398:             echo fread($file->handle, $bufferSize);
1399:             if (!$compress) {
1400:                 $this->_flushBuffer();
1401:             }
1402:         }
1403:         $file->close();
1404:         return true;
1405:     }
1406: 
1407: /**
1408:  * Returns true if connection is still active
1409:  *
1410:  * @return boolean
1411:  */
1412:     protected function _isActive() {
1413:         return connection_status() === CONNECTION_NORMAL && !connection_aborted();
1414:     }
1415: 
1416: /**
1417:  * Clears the contents of the topmost output buffer and discards them
1418:  *
1419:  * @return boolean
1420:  */
1421:     protected function _clearBuffer() {
1422:         //@codingStandardsIgnoreStart
1423:         return @ob_end_clean();
1424:         //@codingStandardsIgnoreEnd
1425:     }
1426: 
1427: /**
1428:  * Flushes the contents of the output buffer
1429:  *
1430:  * @return void
1431:  */
1432:     protected function _flushBuffer() {
1433:         //@codingStandardsIgnoreStart
1434:         @flush();
1435:         @ob_flush();
1436:         //@codingStandardsIgnoreEnd
1437:     }
1438: 
1439: }
1440: 
OpenHub
Rackspace
Rackspace
  • Business Solutions
  • Showcase
  • Documentation
  • Book
  • API
  • Videos
  • Reporting Security Issues
  • Privacy Policy
  • Logos & Trademarks
  • Community
  • Get Involved
  • Issues (GitHub)
  • Bakery
  • Featured Resources
  • Training
  • Meetups
  • My CakePHP
  • CakeFest
  • Newsletter
  • Linkedin
  • YouTube
  • Facebook
  • Twitter
  • Mastodon
  • Help & Support
  • Forum
  • Stack Overflow
  • Slack
  • Paid Support

Generated using CakePHP API Docs