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