CakeResponse.php 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345
  1. <?php
  2. /**
  3. * CakeResponse
  4. *
  5. * PHP 5
  6. *
  7. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * For full copyright and license information, please see the LICENSE.txt
  12. * Redistributions of files must retain the above copyright notice.
  13. *
  14. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  15. * @link http://cakephp.org CakePHP(tm) Project
  16. * @package Cake.Network
  17. * @since CakePHP(tm) v 2.0
  18. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  19. */
  20. App::uses('File', 'Utility');
  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. * Holds HTTP response statuses
  32. *
  33. * @var array
  34. */
  35. protected $_statusCodes = array(
  36. 100 => 'Continue',
  37. 101 => 'Switching Protocols',
  38. 200 => 'OK',
  39. 201 => 'Created',
  40. 202 => 'Accepted',
  41. 203 => 'Non-Authoritative Information',
  42. 204 => 'No Content',
  43. 205 => 'Reset Content',
  44. 206 => 'Partial Content',
  45. 300 => 'Multiple Choices',
  46. 301 => 'Moved Permanently',
  47. 302 => 'Found',
  48. 303 => 'See Other',
  49. 304 => 'Not Modified',
  50. 305 => 'Use Proxy',
  51. 307 => 'Temporary Redirect',
  52. 400 => 'Bad Request',
  53. 401 => 'Unauthorized',
  54. 402 => 'Payment Required',
  55. 403 => 'Forbidden',
  56. 404 => 'Not Found',
  57. 405 => 'Method Not Allowed',
  58. 406 => 'Not Acceptable',
  59. 407 => 'Proxy Authentication Required',
  60. 408 => 'Request Time-out',
  61. 409 => 'Conflict',
  62. 410 => 'Gone',
  63. 411 => 'Length Required',
  64. 412 => 'Precondition Failed',
  65. 413 => 'Request Entity Too Large',
  66. 414 => 'Request-URI Too Large',
  67. 415 => 'Unsupported Media Type',
  68. 416 => 'Requested range not satisfiable',
  69. 417 => 'Expectation Failed',
  70. 500 => 'Internal Server Error',
  71. 501 => 'Not Implemented',
  72. 502 => 'Bad Gateway',
  73. 503 => 'Service Unavailable',
  74. 504 => 'Gateway Time-out'
  75. );
  76. /**
  77. * Holds known mime type mappings
  78. *
  79. * @var array
  80. */
  81. protected $_mimeTypes = array(
  82. 'html' => array('text/html', '*/*'),
  83. 'json' => 'application/json',
  84. 'xml' => array('application/xml', 'text/xml'),
  85. 'rss' => 'application/rss+xml',
  86. 'ai' => 'application/postscript',
  87. 'bcpio' => 'application/x-bcpio',
  88. 'bin' => 'application/octet-stream',
  89. 'ccad' => 'application/clariscad',
  90. 'cdf' => 'application/x-netcdf',
  91. 'class' => 'application/octet-stream',
  92. 'cpio' => 'application/x-cpio',
  93. 'cpt' => 'application/mac-compactpro',
  94. 'csh' => 'application/x-csh',
  95. 'csv' => array('text/csv', 'application/vnd.ms-excel', 'text/plain'),
  96. 'dcr' => 'application/x-director',
  97. 'dir' => 'application/x-director',
  98. 'dms' => 'application/octet-stream',
  99. 'doc' => 'application/msword',
  100. 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  101. 'drw' => 'application/drafting',
  102. 'dvi' => 'application/x-dvi',
  103. 'dwg' => 'application/acad',
  104. 'dxf' => 'application/dxf',
  105. 'dxr' => 'application/x-director',
  106. 'eot' => 'application/vnd.ms-fontobject',
  107. 'eps' => 'application/postscript',
  108. 'exe' => 'application/octet-stream',
  109. 'ez' => 'application/andrew-inset',
  110. 'flv' => 'video/x-flv',
  111. 'gtar' => 'application/x-gtar',
  112. 'gz' => 'application/x-gzip',
  113. 'bz2' => 'application/x-bzip',
  114. '7z' => 'application/x-7z-compressed',
  115. 'hdf' => 'application/x-hdf',
  116. 'hqx' => 'application/mac-binhex40',
  117. 'ico' => 'image/x-icon',
  118. 'ips' => 'application/x-ipscript',
  119. 'ipx' => 'application/x-ipix',
  120. 'js' => 'application/javascript',
  121. 'latex' => 'application/x-latex',
  122. 'lha' => 'application/octet-stream',
  123. 'lsp' => 'application/x-lisp',
  124. 'lzh' => 'application/octet-stream',
  125. 'man' => 'application/x-troff-man',
  126. 'me' => 'application/x-troff-me',
  127. 'mif' => 'application/vnd.mif',
  128. 'ms' => 'application/x-troff-ms',
  129. 'nc' => 'application/x-netcdf',
  130. 'oda' => 'application/oda',
  131. 'otf' => 'font/otf',
  132. 'pdf' => 'application/pdf',
  133. 'pgn' => 'application/x-chess-pgn',
  134. 'pot' => 'application/vnd.ms-powerpoint',
  135. 'pps' => 'application/vnd.ms-powerpoint',
  136. 'ppt' => 'application/vnd.ms-powerpoint',
  137. 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  138. 'ppz' => 'application/vnd.ms-powerpoint',
  139. 'pre' => 'application/x-freelance',
  140. 'prt' => 'application/pro_eng',
  141. 'ps' => 'application/postscript',
  142. 'roff' => 'application/x-troff',
  143. 'scm' => 'application/x-lotusscreencam',
  144. 'set' => 'application/set',
  145. 'sh' => 'application/x-sh',
  146. 'shar' => 'application/x-shar',
  147. 'sit' => 'application/x-stuffit',
  148. 'skd' => 'application/x-koan',
  149. 'skm' => 'application/x-koan',
  150. 'skp' => 'application/x-koan',
  151. 'skt' => 'application/x-koan',
  152. 'smi' => 'application/smil',
  153. 'smil' => 'application/smil',
  154. 'sol' => 'application/solids',
  155. 'spl' => 'application/x-futuresplash',
  156. 'src' => 'application/x-wais-source',
  157. 'step' => 'application/STEP',
  158. 'stl' => 'application/SLA',
  159. 'stp' => 'application/STEP',
  160. 'sv4cpio' => 'application/x-sv4cpio',
  161. 'sv4crc' => 'application/x-sv4crc',
  162. 'svg' => 'image/svg+xml',
  163. 'svgz' => 'image/svg+xml',
  164. 'swf' => 'application/x-shockwave-flash',
  165. 't' => 'application/x-troff',
  166. 'tar' => 'application/x-tar',
  167. 'tcl' => 'application/x-tcl',
  168. 'tex' => 'application/x-tex',
  169. 'texi' => 'application/x-texinfo',
  170. 'texinfo' => 'application/x-texinfo',
  171. 'tr' => 'application/x-troff',
  172. 'tsp' => 'application/dsptype',
  173. 'ttc' => 'font/ttf',
  174. 'ttf' => 'font/ttf',
  175. 'unv' => 'application/i-deas',
  176. 'ustar' => 'application/x-ustar',
  177. 'vcd' => 'application/x-cdlink',
  178. 'vda' => 'application/vda',
  179. 'xlc' => 'application/vnd.ms-excel',
  180. 'xll' => 'application/vnd.ms-excel',
  181. 'xlm' => 'application/vnd.ms-excel',
  182. 'xls' => 'application/vnd.ms-excel',
  183. 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
  184. 'xlw' => 'application/vnd.ms-excel',
  185. 'zip' => 'application/zip',
  186. 'aif' => 'audio/x-aiff',
  187. 'aifc' => 'audio/x-aiff',
  188. 'aiff' => 'audio/x-aiff',
  189. 'au' => 'audio/basic',
  190. 'kar' => 'audio/midi',
  191. 'mid' => 'audio/midi',
  192. 'midi' => 'audio/midi',
  193. 'mp2' => 'audio/mpeg',
  194. 'mp3' => 'audio/mpeg',
  195. 'mpga' => 'audio/mpeg',
  196. 'ogg' => 'audio/ogg',
  197. 'oga' => 'audio/ogg',
  198. 'spx' => 'audio/ogg',
  199. 'ra' => 'audio/x-realaudio',
  200. 'ram' => 'audio/x-pn-realaudio',
  201. 'rm' => 'audio/x-pn-realaudio',
  202. 'rpm' => 'audio/x-pn-realaudio-plugin',
  203. 'snd' => 'audio/basic',
  204. 'tsi' => 'audio/TSP-audio',
  205. 'wav' => 'audio/x-wav',
  206. 'aac' => 'audio/aac',
  207. 'asc' => 'text/plain',
  208. 'c' => 'text/plain',
  209. 'cc' => 'text/plain',
  210. 'css' => 'text/css',
  211. 'etx' => 'text/x-setext',
  212. 'f' => 'text/plain',
  213. 'f90' => 'text/plain',
  214. 'h' => 'text/plain',
  215. 'hh' => 'text/plain',
  216. 'htm' => array('text/html', '*/*'),
  217. 'ics' => 'text/calendar',
  218. 'm' => 'text/plain',
  219. 'rtf' => 'text/rtf',
  220. 'rtx' => 'text/richtext',
  221. 'sgm' => 'text/sgml',
  222. 'sgml' => 'text/sgml',
  223. 'tsv' => 'text/tab-separated-values',
  224. 'tpl' => 'text/template',
  225. 'txt' => 'text/plain',
  226. 'text' => 'text/plain',
  227. 'avi' => 'video/x-msvideo',
  228. 'fli' => 'video/x-fli',
  229. 'mov' => 'video/quicktime',
  230. 'movie' => 'video/x-sgi-movie',
  231. 'mpe' => 'video/mpeg',
  232. 'mpeg' => 'video/mpeg',
  233. 'mpg' => 'video/mpeg',
  234. 'qt' => 'video/quicktime',
  235. 'viv' => 'video/vnd.vivo',
  236. 'vivo' => 'video/vnd.vivo',
  237. 'ogv' => 'video/ogg',
  238. 'webm' => 'video/webm',
  239. 'mp4' => 'video/mp4',
  240. 'm4v' => 'video/mp4',
  241. 'f4v' => 'video/mp4',
  242. 'f4p' => 'video/mp4',
  243. 'm4a' => 'audio/mp4',
  244. 'f4a' => 'audio/mp4',
  245. 'f4b' => 'audio/mp4',
  246. 'gif' => 'image/gif',
  247. 'ief' => 'image/ief',
  248. 'jpe' => 'image/jpeg',
  249. 'jpeg' => 'image/jpeg',
  250. 'jpg' => 'image/jpeg',
  251. 'pbm' => 'image/x-portable-bitmap',
  252. 'pgm' => 'image/x-portable-graymap',
  253. 'png' => 'image/png',
  254. 'pnm' => 'image/x-portable-anymap',
  255. 'ppm' => 'image/x-portable-pixmap',
  256. 'ras' => 'image/cmu-raster',
  257. 'rgb' => 'image/x-rgb',
  258. 'tif' => 'image/tiff',
  259. 'tiff' => 'image/tiff',
  260. 'xbm' => 'image/x-xbitmap',
  261. 'xpm' => 'image/x-xpixmap',
  262. 'xwd' => 'image/x-xwindowdump',
  263. 'ice' => 'x-conference/x-cooltalk',
  264. 'iges' => 'model/iges',
  265. 'igs' => 'model/iges',
  266. 'mesh' => 'model/mesh',
  267. 'msh' => 'model/mesh',
  268. 'silo' => 'model/mesh',
  269. 'vrml' => 'model/vrml',
  270. 'wrl' => 'model/vrml',
  271. 'mime' => 'www/mime',
  272. 'pdb' => 'chemical/x-pdb',
  273. 'xyz' => 'chemical/x-pdb',
  274. 'javascript' => 'application/javascript',
  275. 'form' => 'application/x-www-form-urlencoded',
  276. 'file' => 'multipart/form-data',
  277. 'xhtml' => array('application/xhtml+xml', 'application/xhtml', 'text/xhtml'),
  278. 'xhtml-mobile' => 'application/vnd.wap.xhtml+xml',
  279. 'atom' => 'application/atom+xml',
  280. 'amf' => 'application/x-amf',
  281. 'wap' => array('text/vnd.wap.wml', 'text/vnd.wap.wmlscript', 'image/vnd.wap.wbmp'),
  282. 'wml' => 'text/vnd.wap.wml',
  283. 'wmlscript' => 'text/vnd.wap.wmlscript',
  284. 'wbmp' => 'image/vnd.wap.wbmp',
  285. 'woff' => 'application/x-font-woff',
  286. 'webp' => 'image/webp',
  287. 'appcache' => 'text/cache-manifest',
  288. 'manifest' => 'text/cache-manifest',
  289. 'htc' => 'text/x-component',
  290. 'rdf' => 'application/xml',
  291. 'crx' => 'application/x-chrome-extension',
  292. 'oex' => 'application/x-opera-extension',
  293. 'xpi' => 'application/x-xpinstall',
  294. 'safariextz' => 'application/octet-stream',
  295. 'webapp' => 'application/x-web-app-manifest+json',
  296. 'vcf' => 'text/x-vcard',
  297. 'vtt' => 'text/vtt',
  298. );
  299. /**
  300. * Protocol header to send to the client
  301. *
  302. * @var string
  303. */
  304. protected $_protocol = 'HTTP/1.1';
  305. /**
  306. * Status code to send to the client
  307. *
  308. * @var integer
  309. */
  310. protected $_status = 200;
  311. /**
  312. * Content type to send. This can be an 'extension' that will be transformed using the $_mimetypes array
  313. * or a complete mime-type
  314. *
  315. * @var integer
  316. */
  317. protected $_contentType = 'text/html';
  318. /**
  319. * Buffer list of headers
  320. *
  321. * @var array
  322. */
  323. protected $_headers = array();
  324. /**
  325. * Buffer string for response message
  326. *
  327. * @var string
  328. */
  329. protected $_body = null;
  330. /**
  331. * File object for file to be read out as response
  332. *
  333. * @var File
  334. */
  335. protected $_file = null;
  336. /**
  337. * The charset the response body is encoded with
  338. *
  339. * @var string
  340. */
  341. protected $_charset = 'UTF-8';
  342. /**
  343. * Holds all the cache directives that will be converted
  344. * into headers when sending the request
  345. *
  346. * @var string
  347. */
  348. protected $_cacheDirectives = array();
  349. /**
  350. * Holds cookies to be sent to the client
  351. *
  352. * @var array
  353. */
  354. protected $_cookies = array();
  355. /**
  356. * Class constructor
  357. *
  358. * @param array $options list of parameters to setup the response. Possible values are:
  359. * - body: the response text that should be sent to the client
  360. * - status: the HTTP status code to respond with
  361. * - type: a complete mime-type string or an extension mapped in this class
  362. * - charset: the charset for the response body
  363. */
  364. public function __construct(array $options = array()) {
  365. if (isset($options['body'])) {
  366. $this->body($options['body']);
  367. }
  368. if (isset($options['status'])) {
  369. $this->statusCode($options['status']);
  370. }
  371. if (isset($options['type'])) {
  372. $this->type($options['type']);
  373. }
  374. if (!isset($options['charset'])) {
  375. $options['charset'] = Configure::read('App.encoding');
  376. }
  377. $this->charset($options['charset']);
  378. }
  379. /**
  380. * Sends the complete response to the client including headers and message body.
  381. * Will echo out the content in the response body.
  382. *
  383. * @return void
  384. */
  385. public function send() {
  386. if (isset($this->_headers['Location']) && $this->_status === 200) {
  387. $this->statusCode(302);
  388. }
  389. $codeMessage = $this->_statusCodes[$this->_status];
  390. $this->_setCookies();
  391. $this->_sendHeader("{$this->_protocol} {$this->_status} {$codeMessage}");
  392. $this->_setContent();
  393. $this->_setContentLength();
  394. $this->_setContentType();
  395. foreach ($this->_headers as $header => $value) {
  396. $this->_sendHeader($header, $value);
  397. }
  398. if ($this->_file) {
  399. $this->_sendFile($this->_file);
  400. $this->_file = null;
  401. } else {
  402. $this->_sendContent($this->_body);
  403. }
  404. }
  405. /**
  406. * Sets the cookies that have been added via static method CakeResponse::addCookie()
  407. * before any other output is sent to the client.
  408. * Will set the cookies in the order they have been set.
  409. *
  410. * @return void
  411. */
  412. protected function _setCookies() {
  413. foreach ($this->_cookies as $name => $c) {
  414. setcookie(
  415. $name, $c['value'], $c['expire'], $c['path'],
  416. $c['domain'], $c['secure'], $c['httpOnly']
  417. );
  418. }
  419. }
  420. /**
  421. * Formats the Content-Type header based on the configured contentType and charset
  422. * the charset will only be set in the header if the response is of type text/*
  423. *
  424. * @return void
  425. */
  426. protected function _setContentType() {
  427. if (in_array($this->_status, array(304, 204))) {
  428. return;
  429. }
  430. $whitelist = array(
  431. 'application/javascript', 'application/json', 'application/xml', 'application/rss+xml'
  432. );
  433. $charset = false;
  434. if (
  435. $this->_charset &&
  436. (strpos($this->_contentType, 'text/') === 0 || in_array($this->_contentType, $whitelist))
  437. ) {
  438. $charset = true;
  439. }
  440. if ($charset) {
  441. $this->header('Content-Type', "{$this->_contentType}; charset={$this->_charset}");
  442. } else {
  443. $this->header('Content-Type', "{$this->_contentType}");
  444. }
  445. }
  446. /**
  447. * Sets the response body to an empty text if the status code is 204 or 304
  448. *
  449. * @return void
  450. */
  451. protected function _setContent() {
  452. if (in_array($this->_status, array(304, 204))) {
  453. $this->body('');
  454. }
  455. }
  456. /**
  457. * Calculates the correct Content-Length and sets it as a header in the response
  458. * Will not set the value if already set or if the output is compressed.
  459. *
  460. * @return void
  461. */
  462. protected function _setContentLength() {
  463. $shouldSetLength = !isset($this->_headers['Content-Length']) && !in_array($this->_status, range(301, 307));
  464. if (isset($this->_headers['Content-Length']) && $this->_headers['Content-Length'] === false) {
  465. unset($this->_headers['Content-Length']);
  466. return;
  467. }
  468. if ($shouldSetLength && !$this->outputCompressed()) {
  469. $offset = ob_get_level() ? ob_get_length() : 0;
  470. if (ini_get('mbstring.func_overload') & 2 && function_exists('mb_strlen')) {
  471. $this->length($offset + mb_strlen($this->_body, '8bit'));
  472. } else {
  473. $this->length($this->_headers['Content-Length'] = $offset + strlen($this->_body));
  474. }
  475. }
  476. }
  477. /**
  478. * Sends a header to the client.
  479. *
  480. * @param string $name the header name
  481. * @param string $value the header value
  482. * @return void
  483. */
  484. protected function _sendHeader($name, $value = null) {
  485. if (!headers_sent()) {
  486. if (is_null($value)) {
  487. header($name);
  488. } else {
  489. header("{$name}: {$value}");
  490. }
  491. }
  492. }
  493. /**
  494. * Sends a content string to the client.
  495. *
  496. * @param string $content string to send as response body
  497. * @return void
  498. */
  499. protected function _sendContent($content) {
  500. echo $content;
  501. }
  502. /**
  503. * Buffers a header string to be sent
  504. * Returns the complete list of buffered headers
  505. *
  506. * ### Single header
  507. * e.g `header('Location', 'http://example.com');`
  508. *
  509. * ### Multiple headers
  510. * e.g `header(array('Location' => 'http://example.com', 'X-Extra' => 'My header'));`
  511. *
  512. * ### String header
  513. * e.g `header('WWW-Authenticate: Negotiate');`
  514. *
  515. * ### Array of string headers
  516. * e.g `header(array('WWW-Authenticate: Negotiate', 'Content-type: application/pdf'));`
  517. *
  518. * Multiple calls for setting the same header name will have the same effect as setting the header once
  519. * with the last value sent for it
  520. * e.g `header('WWW-Authenticate: Negotiate'); header('WWW-Authenticate: Not-Negotiate');`
  521. * will have the same effect as only doing `header('WWW-Authenticate: Not-Negotiate');`
  522. *
  523. * @param string|array $header. An array of header strings or a single header string
  524. * - an associative array of "header name" => "header value" is also accepted
  525. * - an array of string headers is also accepted
  526. * @param string $value. The header value.
  527. * @return array list of headers to be sent
  528. */
  529. public function header($header = null, $value = null) {
  530. if (is_null($header)) {
  531. return $this->_headers;
  532. }
  533. if (is_array($header)) {
  534. foreach ($header as $h => $v) {
  535. if (is_numeric($h)) {
  536. $this->header($v);
  537. continue;
  538. }
  539. $this->_headers[$h] = trim($v);
  540. }
  541. return $this->_headers;
  542. }
  543. if (!is_null($value)) {
  544. $this->_headers[$header] = $value;
  545. return $this->_headers;
  546. }
  547. list($header, $value) = explode(':', $header, 2);
  548. $this->_headers[$header] = trim($value);
  549. return $this->_headers;
  550. }
  551. /**
  552. * Buffers the response message to be sent
  553. * if $content is null the current buffer is returned
  554. *
  555. * @param string $content the string message to be sent
  556. * @return string current message buffer if $content param is passed as null
  557. */
  558. public function body($content = null) {
  559. if (is_null($content)) {
  560. return $this->_body;
  561. }
  562. return $this->_body = $content;
  563. }
  564. /**
  565. * Sets the HTTP status code to be sent
  566. * if $code is null the current code is returned
  567. *
  568. * @param integer $code
  569. * @return integer current status code
  570. * @throws CakeException When an unknown status code is reached.
  571. */
  572. public function statusCode($code = null) {
  573. if (is_null($code)) {
  574. return $this->_status;
  575. }
  576. if (!isset($this->_statusCodes[$code])) {
  577. throw new CakeException(__d('cake_dev', 'Unknown status code'));
  578. }
  579. return $this->_status = $code;
  580. }
  581. /**
  582. * Queries & sets valid HTTP response codes & messages.
  583. *
  584. * @param integer|array $code If $code is an integer, then the corresponding code/message is
  585. * returned if it exists, null if it does not exist. If $code is an array,
  586. * then the 'code' and 'message' keys of each nested array are added to the default
  587. * HTTP codes. Example:
  588. *
  589. * httpCodes(404); // returns array(404 => 'Not Found')
  590. *
  591. * httpCodes(array(
  592. * 701 => 'Unicorn Moved',
  593. * 800 => 'Unexpected Minotaur'
  594. * )); // sets these new values, and returns true
  595. *
  596. * @return mixed associative array of the HTTP codes as keys, and the message
  597. * strings as values, or null of the given $code does not exist.
  598. */
  599. public function httpCodes($code = null) {
  600. if (empty($code)) {
  601. return $this->_statusCodes;
  602. }
  603. if (is_array($code)) {
  604. $this->_statusCodes = $code + $this->_statusCodes;
  605. return true;
  606. }
  607. if (!isset($this->_statusCodes[$code])) {
  608. return null;
  609. }
  610. return array($code => $this->_statusCodes[$code]);
  611. }
  612. /**
  613. * Sets the response content type. It can be either a file extension
  614. * which will be mapped internally to a mime-type or a string representing a mime-type
  615. * if $contentType is null the current content type is returned
  616. * if $contentType is an associative array, content type definitions will be stored/replaced
  617. *
  618. * ### Setting the content type
  619. *
  620. * e.g `type('jpg');`
  621. *
  622. * ### Returning the current content type
  623. *
  624. * e.g `type();`
  625. *
  626. * ### Storing content type definitions
  627. *
  628. * e.g `type(array('keynote' => 'application/keynote', 'bat' => 'application/bat'));`
  629. *
  630. * ### Replacing a content type definition
  631. *
  632. * e.g `type(array('jpg' => 'text/plain'));`
  633. *
  634. * @param string $contentType
  635. * @return mixed current content type or false if supplied an invalid content type
  636. */
  637. public function type($contentType = null) {
  638. if (is_null($contentType)) {
  639. return $this->_contentType;
  640. }
  641. if (is_array($contentType)) {
  642. foreach ($contentType as $type => $definition) {
  643. $this->_mimeTypes[$type] = $definition;
  644. }
  645. return $this->_contentType;
  646. }
  647. if (isset($this->_mimeTypes[$contentType])) {
  648. $contentType = $this->_mimeTypes[$contentType];
  649. $contentType = is_array($contentType) ? current($contentType) : $contentType;
  650. }
  651. if (strpos($contentType, '/') === false) {
  652. return false;
  653. }
  654. return $this->_contentType = $contentType;
  655. }
  656. /**
  657. * Returns the mime type definition for an alias
  658. *
  659. * e.g `getMimeType('pdf'); // returns 'application/pdf'`
  660. *
  661. * @param string $alias the content type alias to map
  662. * @return mixed string mapped mime type or false if $alias is not mapped
  663. */
  664. public function getMimeType($alias) {
  665. if (isset($this->_mimeTypes[$alias])) {
  666. return $this->_mimeTypes[$alias];
  667. }
  668. return false;
  669. }
  670. /**
  671. * Maps a content-type back to an alias
  672. *
  673. * e.g `mapType('application/pdf'); // returns 'pdf'`
  674. *
  675. * @param string|array $ctype Either a string content type to map, or an array of types.
  676. * @return mixed Aliases for the types provided.
  677. */
  678. public function mapType($ctype) {
  679. if (is_array($ctype)) {
  680. return array_map(array($this, 'mapType'), $ctype);
  681. }
  682. foreach ($this->_mimeTypes as $alias => $types) {
  683. if (is_array($types) && in_array($ctype, $types)) {
  684. return $alias;
  685. } elseif (is_string($types) && $types == $ctype) {
  686. return $alias;
  687. }
  688. }
  689. return null;
  690. }
  691. /**
  692. * Sets the response charset
  693. * if $charset is null the current charset is returned
  694. *
  695. * @param string $charset
  696. * @return string current charset
  697. */
  698. public function charset($charset = null) {
  699. if (is_null($charset)) {
  700. return $this->_charset;
  701. }
  702. return $this->_charset = $charset;
  703. }
  704. /**
  705. * Sets the correct headers to instruct the client to not cache the response
  706. *
  707. * @return void
  708. */
  709. public function disableCache() {
  710. $this->header(array(
  711. 'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT',
  712. 'Last-Modified' => gmdate("D, d M Y H:i:s") . " GMT",
  713. 'Cache-Control' => 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'
  714. ));
  715. }
  716. /**
  717. * Sets the correct headers to instruct the client to cache the response.
  718. *
  719. * @param string $since a valid time since the response text has not been modified
  720. * @param string $time a valid time for cache expiry
  721. * @return void
  722. */
  723. public function cache($since, $time = '+1 day') {
  724. if (!is_int($time)) {
  725. $time = strtotime($time);
  726. }
  727. $this->header(array(
  728. 'Date' => gmdate("D, j M Y G:i:s ", time()) . 'GMT'
  729. ));
  730. $this->modified($since);
  731. $this->expires($time);
  732. $this->sharable(true);
  733. $this->maxAge($time - time());
  734. }
  735. /**
  736. * Sets whether a response is eligible to be cached by intermediate proxies
  737. * This method controls the `public` or `private` directive in the Cache-Control
  738. * header
  739. *
  740. * @param boolean $public if set to true, the Cache-Control header will be set as public
  741. * if set to false, the response will be set to private
  742. * if no value is provided, it will return whether the response is sharable or not
  743. * @param integer $time time in seconds after which the response should no longer be considered fresh
  744. * @return boolean
  745. */
  746. public function sharable($public = null, $time = null) {
  747. if ($public === null) {
  748. $public = array_key_exists('public', $this->_cacheDirectives);
  749. $private = array_key_exists('private', $this->_cacheDirectives);
  750. $noCache = array_key_exists('no-cache', $this->_cacheDirectives);
  751. if (!$public && !$private && !$noCache) {
  752. return null;
  753. }
  754. $sharable = $public || ! ($private || $noCache);
  755. return $sharable;
  756. }
  757. if ($public) {
  758. $this->_cacheDirectives['public'] = true;
  759. unset($this->_cacheDirectives['private']);
  760. $this->sharedMaxAge($time);
  761. } else {
  762. $this->_cacheDirectives['private'] = true;
  763. unset($this->_cacheDirectives['public']);
  764. $this->maxAge($time);
  765. }
  766. if (!$time) {
  767. $this->_setCacheControl();
  768. }
  769. return (bool)$public;
  770. }
  771. /**
  772. * Sets the Cache-Control s-maxage directive.
  773. * The max-age is the number of seconds after which the response should no longer be considered
  774. * a good candidate to be fetched from a shared cache (like in a proxy server).
  775. * If called with no parameters, this function will return the current max-age value if any
  776. *
  777. * @param integer $seconds if null, the method will return the current s-maxage value
  778. * @return int
  779. */
  780. public function sharedMaxAge($seconds = null) {
  781. if ($seconds !== null) {
  782. $this->_cacheDirectives['s-maxage'] = $seconds;
  783. $this->_setCacheControl();
  784. }
  785. if (isset($this->_cacheDirectives['s-maxage'])) {
  786. return $this->_cacheDirectives['s-maxage'];
  787. }
  788. return null;
  789. }
  790. /**
  791. * Sets the Cache-Control max-age directive.
  792. * The max-age is the number of seconds after which the response should no longer be considered
  793. * a good candidate to be fetched from the local (client) cache.
  794. * If called with no parameters, this function will return the current max-age value if any
  795. *
  796. * @param integer $seconds if null, the method will return the current max-age value
  797. * @return int
  798. */
  799. public function maxAge($seconds = null) {
  800. if ($seconds !== null) {
  801. $this->_cacheDirectives['max-age'] = $seconds;
  802. $this->_setCacheControl();
  803. }
  804. if (isset($this->_cacheDirectives['max-age'])) {
  805. return $this->_cacheDirectives['max-age'];
  806. }
  807. return null;
  808. }
  809. /**
  810. * Sets the Cache-Control must-revalidate directive.
  811. * must-revalidate indicates that the response should not be served
  812. * stale by a cache under any circumstance without first revalidating
  813. * with the origin.
  814. * If called with no parameters, this function will return whether must-revalidate is present.
  815. *
  816. * @param integer $seconds if null, the method will return the current
  817. * must-revalidate value
  818. * @return boolean
  819. */
  820. public function mustRevalidate($enable = null) {
  821. if ($enable !== null) {
  822. if ($enable) {
  823. $this->_cacheDirectives['must-revalidate'] = true;
  824. } else {
  825. unset($this->_cacheDirectives['must-revalidate']);
  826. }
  827. $this->_setCacheControl();
  828. }
  829. return array_key_exists('must-revalidate', $this->_cacheDirectives);
  830. }
  831. /**
  832. * Helper method to generate a valid Cache-Control header from the options set
  833. * in other methods
  834. *
  835. * @return void
  836. */
  837. protected function _setCacheControl() {
  838. $control = '';
  839. foreach ($this->_cacheDirectives as $key => $val) {
  840. $control .= $val === true ? $key : sprintf('%s=%s', $key, $val);
  841. $control .= ', ';
  842. }
  843. $control = rtrim($control, ', ');
  844. $this->header('Cache-Control', $control);
  845. }
  846. /**
  847. * Sets the Expires header for the response by taking an expiration time
  848. * If called with no parameters it will return the current Expires value
  849. *
  850. * ## Examples:
  851. *
  852. * `$response->expires('now')` Will Expire the response cache now
  853. * `$response->expires(new DateTime('+1 day'))` Will set the expiration in next 24 hours
  854. * `$response->expires()` Will return the current expiration header value
  855. *
  856. * @param string|DateTime $time
  857. * @return string
  858. */
  859. public function expires($time = null) {
  860. if ($time !== null) {
  861. $date = $this->_getUTCDate($time);
  862. $this->_headers['Expires'] = $date->format('D, j M Y H:i:s') . ' GMT';
  863. }
  864. if (isset($this->_headers['Expires'])) {
  865. return $this->_headers['Expires'];
  866. }
  867. return null;
  868. }
  869. /**
  870. * Sets the Last-Modified header for the response by taking an modification time
  871. * If called with no parameters it will return the current Last-Modified value
  872. *
  873. * ## Examples:
  874. *
  875. * `$response->modified('now')` Will set the Last-Modified to the current time
  876. * `$response->modified(new DateTime('+1 day'))` Will set the modification date in the past 24 hours
  877. * `$response->modified()` Will return the current Last-Modified header value
  878. *
  879. * @param string|DateTime $time
  880. * @return string
  881. */
  882. public function modified($time = null) {
  883. if ($time !== null) {
  884. $date = $this->_getUTCDate($time);
  885. $this->_headers['Last-Modified'] = $date->format('D, j M Y H:i:s') . ' GMT';
  886. }
  887. if (isset($this->_headers['Last-Modified'])) {
  888. return $this->_headers['Last-Modified'];
  889. }
  890. return null;
  891. }
  892. /**
  893. * Sets the response as Not Modified by removing any body contents
  894. * setting the status code to "304 Not Modified" and removing all
  895. * conflicting headers
  896. *
  897. * @return void
  898. */
  899. public function notModified() {
  900. $this->statusCode(304);
  901. $this->body('');
  902. $remove = array(
  903. 'Allow',
  904. 'Content-Encoding',
  905. 'Content-Language',
  906. 'Content-Length',
  907. 'Content-MD5',
  908. 'Content-Type',
  909. 'Last-Modified'
  910. );
  911. foreach ($remove as $header) {
  912. unset($this->_headers[$header]);
  913. }
  914. }
  915. /**
  916. * Sets the Vary header for the response, if an array is passed,
  917. * values will be imploded into a comma separated string. If no
  918. * parameters are passed, then an array with the current Vary header
  919. * value is returned
  920. *
  921. * @param string|array $cacheVariances a single Vary string or a array
  922. * containing the list for variances.
  923. * @return array
  924. */
  925. public function vary($cacheVariances = null) {
  926. if ($cacheVariances !== null) {
  927. $cacheVariances = (array)$cacheVariances;
  928. $this->_headers['Vary'] = implode(', ', $cacheVariances);
  929. }
  930. if (isset($this->_headers['Vary'])) {
  931. return explode(', ', $this->_headers['Vary']);
  932. }
  933. return null;
  934. }
  935. /**
  936. * Sets the response Etag, Etags are a strong indicative that a response
  937. * can be cached by a HTTP client. A bad way of generating Etags is
  938. * creating a hash of the response output, instead generate a unique
  939. * hash of the unique components that identifies a request, such as a
  940. * modification time, a resource Id, and anything else you consider it
  941. * makes it unique.
  942. *
  943. * Second parameter is used to instruct clients that the content has
  944. * changed, but sematicallly, it can be used as the same thing. Think
  945. * for instance of a page with a hit counter, two different page views
  946. * are equivalent, but they differ by a few bytes. This leaves off to
  947. * the Client the decision of using or not the cached page.
  948. *
  949. * If no parameters are passed, current Etag header is returned.
  950. *
  951. * @param string $hash the unique has that identifies this response
  952. * @param boolean $weak whether the response is semantically the same as
  953. * other with the same hash or not
  954. * @return string
  955. */
  956. public function etag($tag = null, $weak = false) {
  957. if ($tag !== null) {
  958. $this->_headers['Etag'] = sprintf('%s"%s"', ($weak) ? 'W/' : null, $tag);
  959. }
  960. if (isset($this->_headers['Etag'])) {
  961. return $this->_headers['Etag'];
  962. }
  963. return null;
  964. }
  965. /**
  966. * Returns a DateTime object initialized at the $time param and using UTC
  967. * as timezone
  968. *
  969. * @param string|integer|DateTime $time
  970. * @return DateTime
  971. */
  972. protected function _getUTCDate($time = null) {
  973. if ($time instanceof DateTime) {
  974. $result = clone $time;
  975. } elseif (is_int($time)) {
  976. $result = new DateTime(date('Y-m-d H:i:s', $time));
  977. } else {
  978. $result = new DateTime($time);
  979. }
  980. $result->setTimeZone(new DateTimeZone('UTC'));
  981. return $result;
  982. }
  983. /**
  984. * Sets the correct output buffering handler to send a compressed response. Responses will
  985. * be compressed with zlib, if the extension is available.
  986. *
  987. * @return boolean false if client does not accept compressed responses or no handler is available, true otherwise
  988. */
  989. public function compress() {
  990. $compressionEnabled = ini_get("zlib.output_compression") !== '1' &&
  991. extension_loaded("zlib") &&
  992. (strpos(env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false);
  993. return $compressionEnabled && ob_start('ob_gzhandler');
  994. }
  995. /**
  996. * Returns whether the resulting output will be compressed by PHP
  997. *
  998. * @return boolean
  999. */
  1000. public function outputCompressed() {
  1001. return strpos(env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false
  1002. && (ini_get("zlib.output_compression") === '1' || in_array('ob_gzhandler', ob_list_handlers()));
  1003. }
  1004. /**
  1005. * Sets the correct headers to instruct the browser to download the response as a file.
  1006. *
  1007. * @param string $filename the name of the file as the browser will download the response
  1008. * @return void
  1009. */
  1010. public function download($filename) {
  1011. $this->header('Content-Disposition', 'attachment; filename="' . $filename . '"');
  1012. }
  1013. /**
  1014. * Sets the protocol to be used when sending the response. Defaults to HTTP/1.1
  1015. * If called with no arguments, it will return the current configured protocol
  1016. *
  1017. * @param string protocol to be used for sending response
  1018. * @return string protocol currently set
  1019. */
  1020. public function protocol($protocol = null) {
  1021. if ($protocol !== null) {
  1022. $this->_protocol = $protocol;
  1023. }
  1024. return $this->_protocol;
  1025. }
  1026. /**
  1027. * Sets the Content-Length header for the response
  1028. * If called with no arguments returns the last Content-Length set
  1029. *
  1030. * @param integer $bytes Number of bytes
  1031. * @return integer|null
  1032. */
  1033. public function length($bytes = null) {
  1034. if ($bytes !== null) {
  1035. $this->_headers['Content-Length'] = $bytes;
  1036. }
  1037. if (isset($this->_headers['Content-Length'])) {
  1038. return $this->_headers['Content-Length'];
  1039. }
  1040. return null;
  1041. }
  1042. /**
  1043. * Checks whether a response has not been modified according to the 'If-None-Match'
  1044. * (Etags) and 'If-Modified-Since' (last modification date) request
  1045. * headers headers. If the response is detected to be not modified, it
  1046. * is marked as so accordingly so the client can be informed of that.
  1047. *
  1048. * In order to mark a response as not modified, you need to set at least
  1049. * the Last-Modified etag response header before calling this method. Otherwise
  1050. * a comparison will not be possible.
  1051. *
  1052. * @param CakeRequest $request Request object
  1053. * @return boolean whether the response was marked as not modified or not.
  1054. */
  1055. public function checkNotModified(CakeRequest $request) {
  1056. $etags = preg_split('/\s*,\s*/', $request->header('If-None-Match'), null, PREG_SPLIT_NO_EMPTY);
  1057. $modifiedSince = $request->header('If-Modified-Since');
  1058. if ($responseTag = $this->etag()) {
  1059. $etagMatches = in_array('*', $etags) || in_array($responseTag, $etags);
  1060. }
  1061. if ($modifiedSince) {
  1062. $timeMatches = strtotime($this->modified()) == strtotime($modifiedSince);
  1063. }
  1064. $checks = compact('etagMatches', 'timeMatches');
  1065. if (empty($checks)) {
  1066. return false;
  1067. }
  1068. $notModified = !in_array(false, $checks, true);
  1069. if ($notModified) {
  1070. $this->notModified();
  1071. }
  1072. return $notModified;
  1073. }
  1074. /**
  1075. * String conversion. Fetches the response body as a string.
  1076. * Does *not* send headers.
  1077. *
  1078. * @return string
  1079. */
  1080. public function __toString() {
  1081. return (string)$this->_body;
  1082. }
  1083. /**
  1084. * Getter/Setter for cookie configs
  1085. *
  1086. * This method acts as a setter/getter depending on the type of the argument.
  1087. * If the method is called with no arguments, it returns all configurations.
  1088. *
  1089. * If the method is called with a string as argument, it returns either the
  1090. * given configuration if it is set, or null, if it's not set.
  1091. *
  1092. * If the method is called with an array as argument, it will set the cookie
  1093. * configuration to the cookie container.
  1094. *
  1095. * @param array $options Either null to get all cookies, string for a specific cookie
  1096. * or array to set cookie.
  1097. *
  1098. * ### Options (when setting a configuration)
  1099. * - name: The Cookie name
  1100. * - value: Value of the cookie
  1101. * - expire: Time the cookie expires in
  1102. * - path: Path the cookie applies to
  1103. * - domain: Domain the cookie is for.
  1104. * - secure: Is the cookie https?
  1105. * - httpOnly: Is the cookie available in the client?
  1106. *
  1107. * ## Examples
  1108. *
  1109. * ### Getting all cookies
  1110. *
  1111. * `$this->cookie()`
  1112. *
  1113. * ### Getting a certain cookie configuration
  1114. *
  1115. * `$this->cookie('MyCookie')`
  1116. *
  1117. * ### Setting a cookie configuration
  1118. *
  1119. * `$this->cookie((array) $options)`
  1120. *
  1121. * @return mixed
  1122. */
  1123. public function cookie($options = null) {
  1124. if ($options === null) {
  1125. return $this->_cookies;
  1126. }
  1127. if (is_string($options)) {
  1128. if (!isset($this->_cookies[$options])) {
  1129. return null;
  1130. }
  1131. return $this->_cookies[$options];
  1132. }
  1133. $defaults = array(
  1134. 'name' => 'CakeCookie[default]',
  1135. 'value' => '',
  1136. 'expire' => 0,
  1137. 'path' => '/',
  1138. 'domain' => '',
  1139. 'secure' => false,
  1140. 'httpOnly' => false
  1141. );
  1142. $options += $defaults;
  1143. $this->_cookies[$options['name']] = $options;
  1144. }
  1145. /**
  1146. * Setup for display or download the given file
  1147. *
  1148. * @param string $path Path to file
  1149. * @param array $options Options
  1150. * ### Options keys
  1151. * - name: Alternate download name
  1152. * - download: If `true` sets download header and forces file to be downloaded rather than displayed in browser
  1153. * @return void
  1154. * @throws NotFoundException
  1155. */
  1156. public function file($path, $options = array()) {
  1157. $options += array(
  1158. 'name' => null,
  1159. 'download' => null
  1160. );
  1161. if (!is_file($path)) {
  1162. $path = APP . $path;
  1163. }
  1164. $file = new File($path);
  1165. if (!$file->exists() || !$file->readable()) {
  1166. if (Configure::read('debug')) {
  1167. throw new NotFoundException(__d('cake_dev', 'The requested file %s was not found or not readable', $path));
  1168. }
  1169. throw new NotFoundException(__d('cake', 'The requested file was not found'));
  1170. }
  1171. $extension = strtolower($file->ext());
  1172. $download = $options['download'];
  1173. if ((!$extension || $this->type($extension) === false) && is_null($download)) {
  1174. $download = true;
  1175. }
  1176. $fileSize = $file->size();
  1177. if ($download) {
  1178. $agent = env('HTTP_USER_AGENT');
  1179. if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent)) {
  1180. $contentType = 'application/octetstream';
  1181. } elseif (preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) {
  1182. $contentType = 'application/force-download';
  1183. }
  1184. if (!empty($contentType)) {
  1185. $this->type($contentType);
  1186. }
  1187. if (is_null($options['name'])) {
  1188. $name = $file->name;
  1189. } else {
  1190. $name = $options['name'];
  1191. }
  1192. $this->download($name);
  1193. $this->header('Accept-Ranges', 'bytes');
  1194. $httpRange = env('HTTP_RANGE');
  1195. if (isset($httpRange)) {
  1196. list(, $range) = explode('=', $httpRange);
  1197. $size = $fileSize - 1;
  1198. $length = $fileSize - $range;
  1199. $this->header(array(
  1200. 'Content-Length' => $length,
  1201. 'Content-Range' => 'bytes ' . $range . $size . '/' . $fileSize
  1202. ));
  1203. $this->statusCode(206);
  1204. $file->open('rb', true);
  1205. $file->offset($range);
  1206. } else {
  1207. $this->header('Content-Length', $fileSize);
  1208. }
  1209. } else {
  1210. $this->header('Content-Length', $fileSize);
  1211. }
  1212. $this->_clearBuffer();
  1213. $this->_file = $file;
  1214. }
  1215. /**
  1216. * Reads out a file, and echos the content to the client.
  1217. *
  1218. * @param File $file File object
  1219. * @return boolean True is whole file is echoed successfully or false if client connection is lost in between
  1220. */
  1221. protected function _sendFile($file) {
  1222. $compress = $this->outputCompressed();
  1223. $file->open('rb');
  1224. while (!feof($file->handle)) {
  1225. if (!$this->_isActive()) {
  1226. $file->close();
  1227. return false;
  1228. }
  1229. set_time_limit(0);
  1230. echo fread($file->handle, 8192);
  1231. if (!$compress) {
  1232. $this->_flushBuffer();
  1233. }
  1234. }
  1235. $file->close();
  1236. return true;
  1237. }
  1238. /**
  1239. * Returns true if connection is still active
  1240. *
  1241. * @return boolean
  1242. */
  1243. protected function _isActive() {
  1244. return connection_status() === CONNECTION_NORMAL && !connection_aborted();
  1245. }
  1246. /**
  1247. * Clears the contents of the topmost output buffer and discards them
  1248. *
  1249. * @return boolean
  1250. */
  1251. protected function _clearBuffer() {
  1252. //@codingStandardsIgnoreStart
  1253. return @ob_end_clean();
  1254. //@codingStandardsIgnoreEnd
  1255. }
  1256. /**
  1257. * Flushes the contents of the output buffer
  1258. *
  1259. * @return void
  1260. */
  1261. protected function _flushBuffer() {
  1262. //@codingStandardsIgnoreStart
  1263. @flush();
  1264. @ob_flush();
  1265. //@codingStandardsIgnoreEnd
  1266. }
  1267. }