1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org) |
4
|
|
|
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
5
|
|
|
* |
6
|
|
|
* Licensed under The MIT License |
7
|
|
|
* For full copyright and license information, please see the LICENSE.txt |
8
|
|
|
* Redistributions of files must retain the above copyright notice. |
9
|
|
|
* |
10
|
|
|
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) |
11
|
|
|
* @link https://cakephp.org CakePHP(tm) Project |
12
|
|
|
* @since 2.0.0 |
13
|
|
|
* @license https://opensource.org/licenses/mit-license.php MIT License |
14
|
|
|
*/ |
15
|
|
|
namespace Cake\Http; |
16
|
|
|
|
17
|
|
|
use Cake\Core\Configure; |
18
|
|
|
use Cake\Filesystem\File; |
19
|
|
|
use Cake\Filesystem\Folder; |
20
|
|
|
use Cake\Http\Cookie\Cookie; |
21
|
|
|
use Cake\Http\Cookie\CookieCollection; |
22
|
|
|
use Cake\Http\Cookie\CookieInterface; |
23
|
|
|
use Cake\Http\CorsBuilder; |
24
|
|
|
use Cake\Http\Exception\NotFoundException; |
25
|
|
|
use Cake\Log\Log; |
26
|
|
|
use DateTime; |
27
|
|
|
use DateTimeInterface; |
28
|
|
|
use DateTimeZone; |
29
|
|
|
use InvalidArgumentException; |
30
|
|
|
use Psr\Http\Message\ResponseInterface; |
31
|
|
|
use Psr\Http\Message\StreamInterface; |
32
|
|
|
use Zend\Diactoros\MessageTrait; |
33
|
|
|
use Zend\Diactoros\Stream; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Responses contain the response text, status and headers of a HTTP response. |
37
|
|
|
* |
38
|
|
|
* There are external packages such as `fig/http-message-util` that provide HTTP |
39
|
|
|
* status code constants. These can be used with any method that accepts or |
40
|
|
|
* returns a status code integer. Keep in mind that these consants might |
41
|
|
|
* include status codes that are now allowed which will throw an |
42
|
|
|
* `\InvalidArgumentException`. |
43
|
|
|
* |
44
|
|
|
*/ |
45
|
|
|
class Response implements ResponseInterface |
46
|
|
|
{ |
47
|
|
|
use MessageTrait; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Allowed HTTP status codes and their default description. |
51
|
|
|
* |
52
|
|
|
* @var string[] |
53
|
|
|
*/ |
54
|
|
|
protected $_statusCodes = [ |
55
|
|
|
100 => 'Continue', |
56
|
|
|
101 => 'Switching Protocols', |
57
|
|
|
102 => 'Processing', |
58
|
|
|
200 => 'OK', |
59
|
|
|
201 => 'Created', |
60
|
|
|
202 => 'Accepted', |
61
|
|
|
203 => 'Non-Authoritative Information', |
62
|
|
|
204 => 'No Content', |
63
|
|
|
205 => 'Reset Content', |
64
|
|
|
206 => 'Partial Content', |
65
|
|
|
207 => 'Multi-status', |
66
|
|
|
208 => 'Already Reported', |
67
|
|
|
226 => 'IM used', |
68
|
|
|
300 => 'Multiple Choices', |
69
|
|
|
301 => 'Moved Permanently', |
70
|
|
|
302 => 'Found', |
71
|
|
|
303 => 'See Other', |
72
|
|
|
304 => 'Not Modified', |
73
|
|
|
305 => 'Use Proxy', |
74
|
|
|
306 => '(Unused)', |
75
|
|
|
307 => 'Temporary Redirect', |
76
|
|
|
308 => 'Permanent Redirect', |
77
|
|
|
400 => 'Bad Request', |
78
|
|
|
401 => 'Unauthorized', |
79
|
|
|
402 => 'Payment Required', |
80
|
|
|
403 => 'Forbidden', |
81
|
|
|
404 => 'Not Found', |
82
|
|
|
405 => 'Method Not Allowed', |
83
|
|
|
406 => 'Not Acceptable', |
84
|
|
|
407 => 'Proxy Authentication Required', |
85
|
|
|
408 => 'Request Timeout', |
86
|
|
|
409 => 'Conflict', |
87
|
|
|
410 => 'Gone', |
88
|
|
|
411 => 'Length Required', |
89
|
|
|
412 => 'Precondition Failed', |
90
|
|
|
413 => 'Request Entity Too Large', |
91
|
|
|
414 => 'Request-URI Too Large', |
92
|
|
|
415 => 'Unsupported Media Type', |
93
|
|
|
416 => 'Requested range not satisfiable', |
94
|
|
|
417 => 'Expectation Failed', |
95
|
|
|
418 => 'I\'m a teapot', |
96
|
|
|
421 => 'Misdirected Request', |
97
|
|
|
422 => 'Unprocessable Entity', |
98
|
|
|
423 => 'Locked', |
99
|
|
|
424 => 'Failed Dependency', |
100
|
|
|
425 => 'Unordered Collection', |
101
|
|
|
426 => 'Upgrade Required', |
102
|
|
|
428 => 'Precondition Required', |
103
|
|
|
429 => 'Too Many Requests', |
104
|
|
|
431 => 'Request Header Fields Too Large', |
105
|
|
|
444 => 'Connection Closed Without Response', |
106
|
|
|
451 => 'Unavailable For Legal Reasons', |
107
|
|
|
499 => 'Client Closed Request', |
108
|
|
|
500 => 'Internal Server Error', |
109
|
|
|
501 => 'Not Implemented', |
110
|
|
|
502 => 'Bad Gateway', |
111
|
|
|
503 => 'Service Unavailable', |
112
|
|
|
504 => 'Gateway Timeout', |
113
|
|
|
505 => 'Unsupported Version', |
114
|
|
|
506 => 'Variant Also Negotiates', |
115
|
|
|
507 => 'Insufficient Storage', |
116
|
|
|
508 => 'Loop Detected', |
117
|
|
|
510 => 'Not Extended', |
118
|
|
|
511 => 'Network Authentication Required', |
119
|
|
|
599 => 'Network Connect Timeout Error', |
120
|
|
|
]; |
121
|
|
|
|
122
|
|
|
/** |
123
|
|
|
* Holds type key to mime type mappings for known mime types. |
124
|
|
|
* |
125
|
|
|
* @var array |
126
|
|
|
*/ |
127
|
|
|
protected $_mimeTypes = [ |
128
|
|
|
'html' => ['text/html', '*/*'], |
129
|
|
|
'json' => 'application/json', |
130
|
|
|
'xml' => ['application/xml', 'text/xml'], |
131
|
|
|
'xhtml' => ['application/xhtml+xml', 'application/xhtml', 'text/xhtml'], |
132
|
|
|
'webp' => 'image/webp', |
133
|
|
|
'rss' => 'application/rss+xml', |
134
|
|
|
'ai' => 'application/postscript', |
135
|
|
|
'bcpio' => 'application/x-bcpio', |
136
|
|
|
'bin' => 'application/octet-stream', |
137
|
|
|
'ccad' => 'application/clariscad', |
138
|
|
|
'cdf' => 'application/x-netcdf', |
139
|
|
|
'class' => 'application/octet-stream', |
140
|
|
|
'cpio' => 'application/x-cpio', |
141
|
|
|
'cpt' => 'application/mac-compactpro', |
142
|
|
|
'csh' => 'application/x-csh', |
143
|
|
|
'csv' => ['text/csv', 'application/vnd.ms-excel'], |
144
|
|
|
'dcr' => 'application/x-director', |
145
|
|
|
'dir' => 'application/x-director', |
146
|
|
|
'dms' => 'application/octet-stream', |
147
|
|
|
'doc' => 'application/msword', |
148
|
|
|
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', |
149
|
|
|
'drw' => 'application/drafting', |
150
|
|
|
'dvi' => 'application/x-dvi', |
151
|
|
|
'dwg' => 'application/acad', |
152
|
|
|
'dxf' => 'application/dxf', |
153
|
|
|
'dxr' => 'application/x-director', |
154
|
|
|
'eot' => 'application/vnd.ms-fontobject', |
155
|
|
|
'eps' => 'application/postscript', |
156
|
|
|
'exe' => 'application/octet-stream', |
157
|
|
|
'ez' => 'application/andrew-inset', |
158
|
|
|
'flv' => 'video/x-flv', |
159
|
|
|
'gtar' => 'application/x-gtar', |
160
|
|
|
'gz' => 'application/x-gzip', |
161
|
|
|
'bz2' => 'application/x-bzip', |
162
|
|
|
'7z' => 'application/x-7z-compressed', |
163
|
|
|
'hdf' => 'application/x-hdf', |
164
|
|
|
'hqx' => 'application/mac-binhex40', |
165
|
|
|
'ico' => 'image/x-icon', |
166
|
|
|
'ips' => 'application/x-ipscript', |
167
|
|
|
'ipx' => 'application/x-ipix', |
168
|
|
|
'js' => 'application/javascript', |
169
|
|
|
'jsonapi' => 'application/vnd.api+json', |
170
|
|
|
'latex' => 'application/x-latex', |
171
|
|
|
'lha' => 'application/octet-stream', |
172
|
|
|
'lsp' => 'application/x-lisp', |
173
|
|
|
'lzh' => 'application/octet-stream', |
174
|
|
|
'man' => 'application/x-troff-man', |
175
|
|
|
'me' => 'application/x-troff-me', |
176
|
|
|
'mif' => 'application/vnd.mif', |
177
|
|
|
'ms' => 'application/x-troff-ms', |
178
|
|
|
'nc' => 'application/x-netcdf', |
179
|
|
|
'oda' => 'application/oda', |
180
|
|
|
'otf' => 'font/otf', |
181
|
|
|
'pdf' => 'application/pdf', |
182
|
|
|
'pgn' => 'application/x-chess-pgn', |
183
|
|
|
'pot' => 'application/vnd.ms-powerpoint', |
184
|
|
|
'pps' => 'application/vnd.ms-powerpoint', |
185
|
|
|
'ppt' => 'application/vnd.ms-powerpoint', |
186
|
|
|
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', |
187
|
|
|
'ppz' => 'application/vnd.ms-powerpoint', |
188
|
|
|
'pre' => 'application/x-freelance', |
189
|
|
|
'prt' => 'application/pro_eng', |
190
|
|
|
'ps' => 'application/postscript', |
191
|
|
|
'roff' => 'application/x-troff', |
192
|
|
|
'scm' => 'application/x-lotusscreencam', |
193
|
|
|
'set' => 'application/set', |
194
|
|
|
'sh' => 'application/x-sh', |
195
|
|
|
'shar' => 'application/x-shar', |
196
|
|
|
'sit' => 'application/x-stuffit', |
197
|
|
|
'skd' => 'application/x-koan', |
198
|
|
|
'skm' => 'application/x-koan', |
199
|
|
|
'skp' => 'application/x-koan', |
200
|
|
|
'skt' => 'application/x-koan', |
201
|
|
|
'smi' => 'application/smil', |
202
|
|
|
'smil' => 'application/smil', |
203
|
|
|
'sol' => 'application/solids', |
204
|
|
|
'spl' => 'application/x-futuresplash', |
205
|
|
|
'src' => 'application/x-wais-source', |
206
|
|
|
'step' => 'application/STEP', |
207
|
|
|
'stl' => 'application/SLA', |
208
|
|
|
'stp' => 'application/STEP', |
209
|
|
|
'sv4cpio' => 'application/x-sv4cpio', |
210
|
|
|
'sv4crc' => 'application/x-sv4crc', |
211
|
|
|
'svg' => 'image/svg+xml', |
212
|
|
|
'svgz' => 'image/svg+xml', |
213
|
|
|
'swf' => 'application/x-shockwave-flash', |
214
|
|
|
't' => 'application/x-troff', |
215
|
|
|
'tar' => 'application/x-tar', |
216
|
|
|
'tcl' => 'application/x-tcl', |
217
|
|
|
'tex' => 'application/x-tex', |
218
|
|
|
'texi' => 'application/x-texinfo', |
219
|
|
|
'texinfo' => 'application/x-texinfo', |
220
|
|
|
'tr' => 'application/x-troff', |
221
|
|
|
'tsp' => 'application/dsptype', |
222
|
|
|
'ttc' => 'font/ttf', |
223
|
|
|
'ttf' => 'font/ttf', |
224
|
|
|
'unv' => 'application/i-deas', |
225
|
|
|
'ustar' => 'application/x-ustar', |
226
|
|
|
'vcd' => 'application/x-cdlink', |
227
|
|
|
'vda' => 'application/vda', |
228
|
|
|
'xlc' => 'application/vnd.ms-excel', |
229
|
|
|
'xll' => 'application/vnd.ms-excel', |
230
|
|
|
'xlm' => 'application/vnd.ms-excel', |
231
|
|
|
'xls' => 'application/vnd.ms-excel', |
232
|
|
|
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', |
233
|
|
|
'xlw' => 'application/vnd.ms-excel', |
234
|
|
|
'zip' => 'application/zip', |
235
|
|
|
'aif' => 'audio/x-aiff', |
236
|
|
|
'aifc' => 'audio/x-aiff', |
237
|
|
|
'aiff' => 'audio/x-aiff', |
238
|
|
|
'au' => 'audio/basic', |
239
|
|
|
'kar' => 'audio/midi', |
240
|
|
|
'mid' => 'audio/midi', |
241
|
|
|
'midi' => 'audio/midi', |
242
|
|
|
'mp2' => 'audio/mpeg', |
243
|
|
|
'mp3' => 'audio/mpeg', |
244
|
|
|
'mpga' => 'audio/mpeg', |
245
|
|
|
'ogg' => 'audio/ogg', |
246
|
|
|
'oga' => 'audio/ogg', |
247
|
|
|
'spx' => 'audio/ogg', |
248
|
|
|
'ra' => 'audio/x-realaudio', |
249
|
|
|
'ram' => 'audio/x-pn-realaudio', |
250
|
|
|
'rm' => 'audio/x-pn-realaudio', |
251
|
|
|
'rpm' => 'audio/x-pn-realaudio-plugin', |
252
|
|
|
'snd' => 'audio/basic', |
253
|
|
|
'tsi' => 'audio/TSP-audio', |
254
|
|
|
'wav' => 'audio/x-wav', |
255
|
|
|
'aac' => 'audio/aac', |
256
|
|
|
'asc' => 'text/plain', |
257
|
|
|
'c' => 'text/plain', |
258
|
|
|
'cc' => 'text/plain', |
259
|
|
|
'css' => 'text/css', |
260
|
|
|
'etx' => 'text/x-setext', |
261
|
|
|
'f' => 'text/plain', |
262
|
|
|
'f90' => 'text/plain', |
263
|
|
|
'h' => 'text/plain', |
264
|
|
|
'hh' => 'text/plain', |
265
|
|
|
'htm' => ['text/html', '*/*'], |
266
|
|
|
'ics' => 'text/calendar', |
267
|
|
|
'm' => 'text/plain', |
268
|
|
|
'rtf' => 'text/rtf', |
269
|
|
|
'rtx' => 'text/richtext', |
270
|
|
|
'sgm' => 'text/sgml', |
271
|
|
|
'sgml' => 'text/sgml', |
272
|
|
|
'tsv' => 'text/tab-separated-values', |
273
|
|
|
'tpl' => 'text/template', |
274
|
|
|
'txt' => 'text/plain', |
275
|
|
|
'text' => 'text/plain', |
276
|
|
|
'avi' => 'video/x-msvideo', |
277
|
|
|
'fli' => 'video/x-fli', |
278
|
|
|
'mov' => 'video/quicktime', |
279
|
|
|
'movie' => 'video/x-sgi-movie', |
280
|
|
|
'mpe' => 'video/mpeg', |
281
|
|
|
'mpeg' => 'video/mpeg', |
282
|
|
|
'mpg' => 'video/mpeg', |
283
|
|
|
'qt' => 'video/quicktime', |
284
|
|
|
'viv' => 'video/vnd.vivo', |
285
|
|
|
'vivo' => 'video/vnd.vivo', |
286
|
|
|
'ogv' => 'video/ogg', |
287
|
|
|
'webm' => 'video/webm', |
288
|
|
|
'mp4' => 'video/mp4', |
289
|
|
|
'm4v' => 'video/mp4', |
290
|
|
|
'f4v' => 'video/mp4', |
291
|
|
|
'f4p' => 'video/mp4', |
292
|
|
|
'm4a' => 'audio/mp4', |
293
|
|
|
'f4a' => 'audio/mp4', |
294
|
|
|
'f4b' => 'audio/mp4', |
295
|
|
|
'gif' => 'image/gif', |
296
|
|
|
'ief' => 'image/ief', |
297
|
|
|
'jpg' => 'image/jpeg', |
298
|
|
|
'jpeg' => 'image/jpeg', |
299
|
|
|
'jpe' => 'image/jpeg', |
300
|
|
|
'pbm' => 'image/x-portable-bitmap', |
301
|
|
|
'pgm' => 'image/x-portable-graymap', |
302
|
|
|
'png' => 'image/png', |
303
|
|
|
'pnm' => 'image/x-portable-anymap', |
304
|
|
|
'ppm' => 'image/x-portable-pixmap', |
305
|
|
|
'ras' => 'image/cmu-raster', |
306
|
|
|
'rgb' => 'image/x-rgb', |
307
|
|
|
'tif' => 'image/tiff', |
308
|
|
|
'tiff' => 'image/tiff', |
309
|
|
|
'xbm' => 'image/x-xbitmap', |
310
|
|
|
'xpm' => 'image/x-xpixmap', |
311
|
|
|
'xwd' => 'image/x-xwindowdump', |
312
|
|
|
'psd' => ['application/photoshop', 'application/psd', 'image/psd', 'image/x-photoshop', 'image/photoshop', 'zz-application/zz-winassoc-psd'], |
313
|
|
|
'ice' => 'x-conference/x-cooltalk', |
314
|
|
|
'iges' => 'model/iges', |
315
|
|
|
'igs' => 'model/iges', |
316
|
|
|
'mesh' => 'model/mesh', |
317
|
|
|
'msh' => 'model/mesh', |
318
|
|
|
'silo' => 'model/mesh', |
319
|
|
|
'vrml' => 'model/vrml', |
320
|
|
|
'wrl' => 'model/vrml', |
321
|
|
|
'mime' => 'www/mime', |
322
|
|
|
'pdb' => 'chemical/x-pdb', |
323
|
|
|
'xyz' => 'chemical/x-pdb', |
324
|
|
|
'javascript' => 'application/javascript', |
325
|
|
|
'form' => 'application/x-www-form-urlencoded', |
326
|
|
|
'file' => 'multipart/form-data', |
327
|
|
|
'xhtml-mobile' => 'application/vnd.wap.xhtml+xml', |
328
|
|
|
'atom' => 'application/atom+xml', |
329
|
|
|
'amf' => 'application/x-amf', |
330
|
|
|
'wap' => ['text/vnd.wap.wml', 'text/vnd.wap.wmlscript', 'image/vnd.wap.wbmp'], |
331
|
|
|
'wml' => 'text/vnd.wap.wml', |
332
|
|
|
'wmlscript' => 'text/vnd.wap.wmlscript', |
333
|
|
|
'wbmp' => 'image/vnd.wap.wbmp', |
334
|
|
|
'woff' => 'application/x-font-woff', |
335
|
|
|
'appcache' => 'text/cache-manifest', |
336
|
|
|
'manifest' => 'text/cache-manifest', |
337
|
|
|
'htc' => 'text/x-component', |
338
|
|
|
'rdf' => 'application/xml', |
339
|
|
|
'crx' => 'application/x-chrome-extension', |
340
|
|
|
'oex' => 'application/x-opera-extension', |
341
|
|
|
'xpi' => 'application/x-xpinstall', |
342
|
|
|
'safariextz' => 'application/octet-stream', |
343
|
|
|
'webapp' => 'application/x-web-app-manifest+json', |
344
|
|
|
'vcf' => 'text/x-vcard', |
345
|
|
|
'vtt' => 'text/vtt', |
346
|
|
|
'mkv' => 'video/x-matroska', |
347
|
|
|
'pkpass' => 'application/vnd.apple.pkpass', |
348
|
|
|
'ajax' => 'text/html', |
349
|
|
|
'bmp' => 'image/bmp', |
350
|
|
|
]; |
351
|
|
|
|
352
|
|
|
/** |
353
|
|
|
* Protocol header to send to the client |
354
|
|
|
* |
355
|
|
|
* @var string |
356
|
|
|
*/ |
357
|
|
|
protected $_protocol = 'HTTP/1.1'; |
358
|
|
|
|
359
|
|
|
/** |
360
|
|
|
* Status code to send to the client |
361
|
|
|
* |
362
|
|
|
* @var int |
363
|
|
|
*/ |
364
|
|
|
protected $_status = 200; |
365
|
|
|
|
366
|
|
|
/** |
367
|
|
|
* Content type to send. This can be an 'extension' that will be transformed using the $_mimetypes array |
368
|
|
|
* or a complete mime-type |
369
|
|
|
* |
370
|
|
|
* @var string |
371
|
|
|
*/ |
372
|
|
|
protected $_contentType = 'text/html'; |
373
|
|
|
|
374
|
|
|
/** |
375
|
|
|
* File object for file to be read out as response |
376
|
|
|
* |
377
|
|
|
* @var \Cake\Filesystem\File|null |
378
|
|
|
*/ |
379
|
|
|
protected $_file; |
380
|
|
|
|
381
|
|
|
/** |
382
|
|
|
* File range. Used for requesting ranges of files. |
383
|
|
|
* |
384
|
|
|
* @var array |
385
|
|
|
*/ |
386
|
|
|
protected $_fileRange = []; |
387
|
|
|
|
388
|
|
|
/** |
389
|
|
|
* The charset the response body is encoded with |
390
|
|
|
* |
391
|
|
|
* @var string |
392
|
|
|
*/ |
393
|
|
|
protected $_charset = 'UTF-8'; |
394
|
|
|
|
395
|
|
|
/** |
396
|
|
|
* Holds all the cache directives that will be converted |
397
|
|
|
* into headers when sending the request |
398
|
|
|
* |
399
|
|
|
* @var array |
400
|
|
|
*/ |
401
|
|
|
protected $_cacheDirectives = []; |
402
|
|
|
|
403
|
|
|
/** |
404
|
|
|
* Collection of cookies to send to the client |
405
|
|
|
* |
406
|
|
|
* @var \Cake\Http\Cookie\CookieCollection |
407
|
|
|
*/ |
408
|
|
|
protected $_cookies = null; |
409
|
|
|
|
410
|
|
|
/** |
411
|
|
|
* Reason Phrase |
412
|
|
|
* |
413
|
|
|
* @var string |
414
|
|
|
*/ |
415
|
|
|
protected $_reasonPhrase = 'OK'; |
416
|
|
|
|
417
|
|
|
/** |
418
|
|
|
* Stream mode options. |
419
|
|
|
* |
420
|
|
|
* @var string |
421
|
|
|
*/ |
422
|
|
|
protected $_streamMode = 'wb+'; |
423
|
|
|
|
424
|
|
|
/** |
425
|
|
|
* Stream target or resource object. |
426
|
|
|
* |
427
|
|
|
* @var string|resource |
428
|
|
|
*/ |
429
|
|
|
protected $_streamTarget = 'php://memory'; |
430
|
|
|
|
431
|
|
|
/** |
432
|
|
|
* Constructor |
433
|
|
|
* |
434
|
|
|
* @param array $options list of parameters to setup the response. Possible values are: |
435
|
|
|
* - body: the response text that should be sent to the client |
436
|
|
|
* - statusCodes: additional allowable response codes |
437
|
|
|
* - status: the HTTP status code to respond with |
438
|
|
|
* - type: a complete mime-type string or an extension mapped in this class |
439
|
|
|
* - charset: the charset for the response body |
440
|
|
|
* @throws \InvalidArgumentException |
441
|
|
|
*/ |
442
|
|
|
public function __construct(array $options = []) |
443
|
|
|
{ |
444
|
|
|
if (isset($options['streamTarget'])) { |
445
|
|
|
$this->_streamTarget = $options['streamTarget']; |
446
|
|
|
} |
447
|
|
|
if (isset($options['streamMode'])) { |
448
|
|
|
$this->_streamMode = $options['streamMode']; |
449
|
|
|
} |
450
|
|
|
if (isset($options['stream'])) { |
451
|
|
|
if (!$options['stream'] instanceof StreamInterface) { |
452
|
|
|
throw new InvalidArgumentException('Stream option must be an object that implements StreamInterface'); |
453
|
|
|
} |
454
|
|
|
$this->stream = $options['stream']; |
455
|
|
|
} else { |
456
|
|
|
$this->_createStream(); |
457
|
|
|
} |
458
|
|
|
if (isset($options['body'])) { |
459
|
|
|
$this->stream->write($options['body']); |
460
|
|
|
} |
461
|
|
|
if (isset($options['statusCodes'])) { |
462
|
|
|
$this->httpCodes($options['statusCodes']); |
|
|
|
|
463
|
|
|
} |
464
|
|
|
if (isset($options['status'])) { |
465
|
|
|
$this->_setStatus($options['status']); |
466
|
|
|
} |
467
|
|
|
if (!isset($options['charset'])) { |
468
|
|
|
$options['charset'] = Configure::read('App.encoding'); |
469
|
|
|
} |
470
|
|
|
$this->_charset = $options['charset']; |
471
|
|
|
if (isset($options['type'])) { |
472
|
|
|
$this->_contentType = $this->resolveType($options['type']); |
473
|
|
|
} |
474
|
|
|
$this->_setContentType(); |
475
|
|
|
$this->_cookies = new CookieCollection(); |
476
|
|
|
} |
477
|
|
|
|
478
|
|
|
/** |
479
|
|
|
* Creates the stream object. |
480
|
|
|
* |
481
|
|
|
* @return void |
482
|
|
|
*/ |
483
|
|
|
protected function _createStream() |
484
|
|
|
{ |
485
|
|
|
$this->stream = new Stream($this->_streamTarget, $this->_streamMode); |
486
|
|
|
} |
487
|
|
|
|
488
|
|
|
/** |
489
|
|
|
* Sends the complete response to the client including headers and message body. |
490
|
|
|
* Will echo out the content in the response body. |
491
|
|
|
* |
492
|
|
|
* @return void |
493
|
|
|
* @deprecated 3.4.0 Will be removed in 4.0.0 |
494
|
|
|
*/ |
495
|
|
|
public function send() |
496
|
|
|
{ |
497
|
|
|
deprecationWarning('Response::send() will be removed in 4.0.0'); |
498
|
|
|
|
499
|
|
|
if ($this->hasHeader('Location') && $this->_status === 200) { |
500
|
|
|
$this->statusCode(302); |
|
|
|
|
501
|
|
|
} |
502
|
|
|
|
503
|
|
|
$this->_setContent(); |
|
|
|
|
504
|
|
|
$this->sendHeaders(); |
|
|
|
|
505
|
|
|
|
506
|
|
|
if ($this->_file) { |
507
|
|
|
$this->_sendFile($this->_file, $this->_fileRange); |
|
|
|
|
508
|
|
|
$this->_file = null; |
509
|
|
|
$this->_fileRange = []; |
510
|
|
|
} else { |
511
|
|
|
$this->_sendContent($this->body()); |
|
|
|
|
512
|
|
|
} |
513
|
|
|
|
514
|
|
|
if (function_exists('fastcgi_finish_request')) { |
515
|
|
|
fastcgi_finish_request(); |
516
|
|
|
} |
517
|
|
|
} |
518
|
|
|
|
519
|
|
|
/** |
520
|
|
|
* Sends the HTTP headers and cookies. |
521
|
|
|
* |
522
|
|
|
* @return void |
523
|
|
|
* @deprecated 3.4.0 Will be removed in 4.0.0 |
524
|
|
|
*/ |
525
|
|
|
public function sendHeaders() |
526
|
|
|
{ |
527
|
|
|
deprecationWarning( |
528
|
|
|
'Will be removed in 4.0.0' |
529
|
|
|
); |
530
|
|
|
|
531
|
|
|
$file = $line = null; |
532
|
|
|
if (headers_sent($file, $line)) { |
533
|
|
|
Log::warning("Headers already sent in {$file}:{$line}"); |
534
|
|
|
|
535
|
|
|
return; |
536
|
|
|
} |
537
|
|
|
|
538
|
|
|
$codeMessage = $this->_statusCodes[$this->_status]; |
539
|
|
|
$this->_setCookies(); |
|
|
|
|
540
|
|
|
$this->_sendHeader("{$this->_protocol} {$this->_status} {$codeMessage}"); |
|
|
|
|
541
|
|
|
$this->_setContentType(); |
542
|
|
|
|
543
|
|
|
foreach ($this->headers as $header => $values) { |
544
|
|
|
foreach ((array)$values as $value) { |
545
|
|
|
$this->_sendHeader($header, $value); |
|
|
|
|
546
|
|
|
} |
547
|
|
|
} |
548
|
|
|
} |
549
|
|
|
|
550
|
|
|
/** |
551
|
|
|
* Sets the cookies that have been added via Cake\Http\Response::cookie() before any |
552
|
|
|
* other output is sent to the client. Will set the cookies in the order they |
553
|
|
|
* have been set. |
554
|
|
|
* |
555
|
|
|
* @return void |
556
|
|
|
* @deprecated 3.4.0 Will be removed in 4.0.0 |
557
|
|
|
*/ |
558
|
|
|
protected function _setCookies() |
559
|
|
|
{ |
560
|
|
|
deprecationWarning( |
561
|
|
|
'Will be removed in 4.0.0' |
562
|
|
|
); |
563
|
|
|
|
564
|
|
|
foreach ($this->_cookies as $cookie) { |
565
|
|
|
setcookie( |
566
|
|
|
$cookie->getName(), |
567
|
|
|
$cookie->getValue(), |
568
|
|
|
$cookie->getExpiresTimestamp(), |
569
|
|
|
$cookie->getPath(), |
570
|
|
|
$cookie->getDomain(), |
571
|
|
|
$cookie->isSecure(), |
572
|
|
|
$cookie->isHttpOnly() |
573
|
|
|
); |
574
|
|
|
} |
575
|
|
|
} |
576
|
|
|
|
577
|
|
|
/** |
578
|
|
|
* Formats the Content-Type header based on the configured contentType and charset |
579
|
|
|
* the charset will only be set in the header if the response is of type text/* |
580
|
|
|
* |
581
|
|
|
* @return void |
582
|
|
|
*/ |
583
|
|
|
protected function _setContentType() |
584
|
|
|
{ |
585
|
|
|
if (in_array($this->_status, [304, 204])) { |
586
|
|
|
$this->_clearHeader('Content-Type'); |
587
|
|
|
|
588
|
|
|
return; |
589
|
|
|
} |
590
|
|
|
$whitelist = [ |
591
|
|
|
'application/javascript', 'application/xml', 'application/rss+xml', |
592
|
|
|
]; |
593
|
|
|
|
594
|
|
|
$charset = false; |
595
|
|
|
if ( |
596
|
|
|
$this->_charset && |
597
|
|
|
(strpos($this->_contentType, 'text/') === 0 || in_array($this->_contentType, $whitelist)) |
598
|
|
|
) { |
599
|
|
|
$charset = true; |
600
|
|
|
} |
601
|
|
|
|
602
|
|
|
if ($charset) { |
603
|
|
|
$this->_setHeader('Content-Type', "{$this->_contentType}; charset={$this->_charset}"); |
604
|
|
|
} else { |
605
|
|
|
$this->_setHeader('Content-Type', (string)$this->_contentType); |
606
|
|
|
} |
607
|
|
|
} |
608
|
|
|
|
609
|
|
|
/** |
610
|
|
|
* Sets the response body to an empty text if the status code is 204 or 304 |
611
|
|
|
* |
612
|
|
|
* @return void |
613
|
|
|
* @deprecated 3.4.0 Will be removed in 4.0.0 |
614
|
|
|
*/ |
615
|
|
|
protected function _setContent() |
616
|
|
|
{ |
617
|
|
|
deprecationWarning( |
618
|
|
|
'Will be removed in 4.0.0' |
619
|
|
|
); |
620
|
|
|
|
621
|
|
|
if (in_array($this->_status, [304, 204])) { |
622
|
|
|
$this->body(''); |
|
|
|
|
623
|
|
|
} |
624
|
|
|
} |
625
|
|
|
|
626
|
|
|
/** |
627
|
|
|
* Sends a header to the client. |
628
|
|
|
* |
629
|
|
|
* @param string $name the header name |
630
|
|
|
* @param string|null $value the header value |
631
|
|
|
* @return void |
632
|
|
|
* @deprecated 3.4.0 Will be removed in 4.0.0 |
633
|
|
|
*/ |
634
|
|
|
protected function _sendHeader($name, $value = null) |
635
|
|
|
{ |
636
|
|
|
deprecationWarning( |
637
|
|
|
'Will be removed in 4.0.0' |
638
|
|
|
); |
639
|
|
|
|
640
|
|
|
if ($value === null) { |
641
|
|
|
header($name); |
642
|
|
|
} else { |
643
|
|
|
header("{$name}: {$value}"); |
644
|
|
|
} |
645
|
|
|
} |
646
|
|
|
|
647
|
|
|
/** |
648
|
|
|
* Sends a content string to the client. |
649
|
|
|
* |
650
|
|
|
* If the content is a callable, it is invoked. The callable should either |
651
|
|
|
* return a string or output content directly and have no return value. |
652
|
|
|
* |
653
|
|
|
* @param string|callable $content String to send as response body or callable |
654
|
|
|
* which returns/outputs content. |
655
|
|
|
* @return void |
656
|
|
|
* @deprecated 3.4.0 Will be removed in 4.0.0 |
657
|
|
|
*/ |
658
|
|
|
protected function _sendContent($content) |
659
|
|
|
{ |
660
|
|
|
deprecationWarning( |
661
|
|
|
'Will be removed in 4.0.0' |
662
|
|
|
); |
663
|
|
|
|
664
|
|
|
if (!is_string($content) && is_callable($content)) { |
665
|
|
|
$content = $content(); |
666
|
|
|
} |
667
|
|
|
|
668
|
|
|
echo $content; |
669
|
|
|
} |
670
|
|
|
|
671
|
|
|
/** |
672
|
|
|
* Buffers a header string to be sent |
673
|
|
|
* Returns the complete list of buffered headers |
674
|
|
|
* |
675
|
|
|
* ### Single header |
676
|
|
|
* ``` |
677
|
|
|
* header('Location', 'http://example.com'); |
678
|
|
|
* ``` |
679
|
|
|
* |
680
|
|
|
* ### Multiple headers |
681
|
|
|
* ``` |
682
|
|
|
* header(['Location' => 'http://example.com', 'X-Extra' => 'My header']); |
683
|
|
|
* ``` |
684
|
|
|
* |
685
|
|
|
* ### String header |
686
|
|
|
* ``` |
687
|
|
|
* header('WWW-Authenticate: Negotiate'); |
688
|
|
|
* ``` |
689
|
|
|
* |
690
|
|
|
* ### Array of string headers |
691
|
|
|
* ``` |
692
|
|
|
* header(['WWW-Authenticate: Negotiate', 'Content-type: application/pdf']); |
693
|
|
|
* ``` |
694
|
|
|
* |
695
|
|
|
* Multiple calls for setting the same header name will have the same effect as setting the header once |
696
|
|
|
* with the last value sent for it |
697
|
|
|
* ``` |
698
|
|
|
* header('WWW-Authenticate: Negotiate'); |
699
|
|
|
* header('WWW-Authenticate: Not-Negotiate'); |
700
|
|
|
* ``` |
701
|
|
|
* will have the same effect as only doing |
702
|
|
|
* ``` |
703
|
|
|
* header('WWW-Authenticate: Not-Negotiate'); |
704
|
|
|
* ``` |
705
|
|
|
* |
706
|
|
|
* @param string|array|null $header An array of header strings or a single header string |
707
|
|
|
* - an associative array of "header name" => "header value" is also accepted |
708
|
|
|
* - an array of string headers is also accepted |
709
|
|
|
* @param string|array|null $value The header value(s) |
710
|
|
|
* @return array List of headers to be sent |
711
|
|
|
* @deprecated 3.4.0 Use `withHeader()`, `getHeaderLine()` and `getHeaders()` instead. |
712
|
|
|
*/ |
713
|
|
|
public function header($header = null, $value = null) |
714
|
|
|
{ |
715
|
|
|
deprecationWarning( |
716
|
|
|
'Response::header() is deprecated. ' . |
717
|
|
|
'Use `withHeader()`, `getHeaderLine()` and `getHeaders()` instead.' |
718
|
|
|
); |
719
|
|
|
|
720
|
|
|
if ($header === null) { |
721
|
|
|
return $this->getSimpleHeaders(); |
722
|
|
|
} |
723
|
|
|
|
724
|
|
|
$headers = is_array($header) ? $header : [$header => $value]; |
725
|
|
|
foreach ($headers as $header => $value) { |
726
|
|
|
if (is_numeric($header)) { |
727
|
|
|
list($header, $value) = [$value, null]; |
728
|
|
|
} |
729
|
|
|
if ($value === null) { |
730
|
|
|
list($header, $value) = explode(':', $header, 2); |
731
|
|
|
} |
732
|
|
|
|
733
|
|
|
$lower = strtolower($header); |
734
|
|
|
if (array_key_exists($lower, $this->headerNames)) { |
735
|
|
|
$header = $this->headerNames[$lower]; |
736
|
|
|
} else { |
737
|
|
|
$this->headerNames[$lower] = $header; |
738
|
|
|
} |
739
|
|
|
|
740
|
|
|
$this->headers[$header] = is_array($value) ? array_map('trim', $value) : [trim($value)]; |
741
|
|
|
} |
742
|
|
|
|
743
|
|
|
return $this->getSimpleHeaders(); |
744
|
|
|
} |
745
|
|
|
|
746
|
|
|
/** |
747
|
|
|
* Backwards compatibility helper for getting flattened headers. |
748
|
|
|
* |
749
|
|
|
* Previously CakePHP would store headers as a simple dictionary, now that |
750
|
|
|
* we're supporting PSR7, the internal storage has each header as an array. |
751
|
|
|
* |
752
|
|
|
* @return array |
753
|
|
|
*/ |
754
|
|
|
protected function getSimpleHeaders() |
755
|
|
|
{ |
756
|
|
|
$out = []; |
757
|
|
|
foreach ($this->headers as $key => $values) { |
758
|
|
|
$header = $this->headerNames[strtolower($key)]; |
759
|
|
|
if (count($values) === 1) { |
760
|
|
|
$values = $values[0]; |
761
|
|
|
} |
762
|
|
|
$out[$header] = $values; |
763
|
|
|
} |
764
|
|
|
|
765
|
|
|
return $out; |
766
|
|
|
} |
767
|
|
|
|
768
|
|
|
/** |
769
|
|
|
* Accessor for the location header. |
770
|
|
|
* |
771
|
|
|
* Get/Set the Location header value. |
772
|
|
|
* |
773
|
|
|
* @param string|null $url Either null to get the current location, or a string to set one. |
774
|
|
|
* @return string|null When setting the location null will be returned. When reading the location |
775
|
|
|
* a string of the current location header value (if any) will be returned. |
776
|
|
|
* @deprecated 3.4.0 Mutable responses are deprecated. Use `withLocation()` and `getHeaderLine()` |
777
|
|
|
* instead. |
778
|
|
|
*/ |
779
|
|
|
public function location($url = null) |
780
|
|
|
{ |
781
|
|
|
deprecationWarning( |
782
|
|
|
'Response::location() is deprecated. ' . |
783
|
|
|
'Mutable responses are deprecated. Use `withLocation()` and `getHeaderLine()` instead.' |
784
|
|
|
); |
785
|
|
|
|
786
|
|
|
if ($url === null) { |
787
|
|
|
$result = $this->getHeaderLine('Location'); |
788
|
|
|
if (!$result) { |
789
|
|
|
return null; |
790
|
|
|
} |
791
|
|
|
|
792
|
|
|
return $result; |
793
|
|
|
} |
794
|
|
|
if ($this->_status === 200) { |
795
|
|
|
$this->_status = 302; |
796
|
|
|
} |
797
|
|
|
$this->_setHeader('Location', $url); |
798
|
|
|
|
799
|
|
|
return null; |
800
|
|
|
} |
801
|
|
|
|
802
|
|
|
/** |
803
|
|
|
* Return an instance with an updated location header. |
804
|
|
|
* |
805
|
|
|
* If the current status code is 200, it will be replaced |
806
|
|
|
* with 302. |
807
|
|
|
* |
808
|
|
|
* @param string $url The location to redirect to. |
809
|
|
|
* @return static A new response with the Location header set. |
810
|
|
|
*/ |
811
|
|
|
public function withLocation($url) |
812
|
|
|
{ |
813
|
|
|
$new = $this->withHeader('Location', $url); |
814
|
|
|
if ($new->_status === 200) { |
815
|
|
|
$new->_status = 302; |
816
|
|
|
} |
817
|
|
|
|
818
|
|
|
return $new; |
819
|
|
|
} |
820
|
|
|
|
821
|
|
|
/** |
822
|
|
|
* Sets a header. |
823
|
|
|
* |
824
|
|
|
* @param string $header Header key. |
825
|
|
|
* @param string $value Header value. |
826
|
|
|
* @return void |
827
|
|
|
*/ |
828
|
|
|
protected function _setHeader($header, $value) |
829
|
|
|
{ |
830
|
|
|
$normalized = strtolower($header); |
831
|
|
|
$this->headerNames[$normalized] = $header; |
832
|
|
|
$this->headers[$header] = [$value]; |
833
|
|
|
} |
834
|
|
|
|
835
|
|
|
/** |
836
|
|
|
* Clear header |
837
|
|
|
* |
838
|
|
|
* @param string $header Header key. |
839
|
|
|
* @return void |
840
|
|
|
*/ |
841
|
|
|
protected function _clearHeader($header) |
842
|
|
|
{ |
843
|
|
|
$normalized = strtolower($header); |
844
|
|
|
if (!isset($this->headerNames[$normalized])) { |
845
|
|
|
return; |
846
|
|
|
} |
847
|
|
|
$original = $this->headerNames[$normalized]; |
848
|
|
|
unset($this->headerNames[$normalized], $this->headers[$original]); |
849
|
|
|
} |
850
|
|
|
|
851
|
|
|
/** |
852
|
|
|
* Buffers the response message to be sent |
853
|
|
|
* if $content is null the current buffer is returned |
854
|
|
|
* |
855
|
|
|
* @param string|callable|null $content the string or callable message to be sent |
856
|
|
|
* @return string|null Current message buffer if $content param is passed as null |
857
|
|
|
* @deprecated 3.4.0 Mutable response methods are deprecated. Use `withBody()`/`withStringBody()` and `getBody()` instead. |
858
|
|
|
*/ |
859
|
|
|
public function body($content = null) |
860
|
|
|
{ |
861
|
|
|
deprecationWarning( |
862
|
|
|
'Response::body() is deprecated. ' . |
863
|
|
|
'Mutable response methods are deprecated. Use `withBody()` and `getBody()` instead.' |
864
|
|
|
); |
865
|
|
|
|
866
|
|
|
if ($content === null) { |
867
|
|
|
if ($this->stream->isSeekable()) { |
868
|
|
|
$this->stream->rewind(); |
869
|
|
|
} |
870
|
|
|
$result = $this->stream->getContents(); |
871
|
|
|
if (strlen($result) === 0) { |
872
|
|
|
return null; |
873
|
|
|
} |
874
|
|
|
|
875
|
|
|
return $result; |
876
|
|
|
} |
877
|
|
|
|
878
|
|
|
// Compatibility with closure/streaming responses |
879
|
|
|
if (!is_string($content) && is_callable($content)) { |
880
|
|
|
$this->stream = new CallbackStream($content); |
881
|
|
|
} else { |
882
|
|
|
$this->_createStream(); |
883
|
|
|
$this->stream->write($content); |
884
|
|
|
} |
885
|
|
|
|
886
|
|
|
return $content; |
887
|
|
|
} |
888
|
|
|
|
889
|
|
|
/** |
890
|
|
|
* Handles the callable body for backward compatibility reasons. |
891
|
|
|
* |
892
|
|
|
* @param callable $content Callable content. |
893
|
|
|
* @return string |
894
|
|
|
*/ |
895
|
|
|
protected function _handleCallableBody(callable $content) |
896
|
|
|
{ |
897
|
|
|
ob_start(); |
898
|
|
|
$result1 = $content(); |
899
|
|
|
$result2 = ob_get_contents(); |
900
|
|
|
ob_get_clean(); |
901
|
|
|
|
902
|
|
|
if ($result1) { |
903
|
|
|
return $result1; |
904
|
|
|
} |
905
|
|
|
|
906
|
|
|
return $result2; |
907
|
|
|
} |
908
|
|
|
|
909
|
|
|
/** |
910
|
|
|
* Sets the HTTP status code to be sent. |
911
|
|
|
* If $code is null the current code is returned |
912
|
|
|
* |
913
|
|
|
* If the status code is 304 or 204, the existing Content-Type header |
914
|
|
|
* will be cleared, as these response codes have no body. |
915
|
|
|
* |
916
|
|
|
* @param int|null $code the HTTP status code |
917
|
|
|
* @return int Current status code |
918
|
|
|
* @throws \InvalidArgumentException When an unknown status code is reached. |
919
|
|
|
* @deprecated 3.4.0 Use `getStatusCode()` and `withStatus()` instead. |
920
|
|
|
*/ |
921
|
|
|
public function statusCode($code = null) |
922
|
|
|
{ |
923
|
|
|
deprecationWarning( |
924
|
|
|
'Response::statusCode() is deprecated. ' . |
925
|
|
|
'Use `getStatusCode()` and `withStatus()` instead.' |
926
|
|
|
); |
927
|
|
|
|
928
|
|
|
if ($code === null) { |
929
|
|
|
return $this->_status; |
930
|
|
|
} |
931
|
|
|
if (!isset($this->_statusCodes[$code])) { |
932
|
|
|
throw new InvalidArgumentException('Unknown status code'); |
933
|
|
|
} |
934
|
|
|
$this->_setStatus($code); |
935
|
|
|
|
936
|
|
|
return $code; |
937
|
|
|
} |
938
|
|
|
|
939
|
|
|
/** |
940
|
|
|
* Gets the response status code. |
941
|
|
|
* |
942
|
|
|
* The status code is a 3-digit integer result code of the server's attempt |
943
|
|
|
* to understand and satisfy the request. |
944
|
|
|
* |
945
|
|
|
* @return int Status code. |
946
|
|
|
*/ |
947
|
|
|
public function getStatusCode() |
948
|
|
|
{ |
949
|
|
|
return $this->_status; |
950
|
|
|
} |
951
|
|
|
|
952
|
|
|
/** |
953
|
|
|
* Return an instance with the specified status code and, optionally, reason phrase. |
954
|
|
|
* |
955
|
|
|
* If no reason phrase is specified, implementations MAY choose to default |
956
|
|
|
* to the RFC 7231 or IANA recommended reason phrase for the response's |
957
|
|
|
* status code. |
958
|
|
|
* |
959
|
|
|
* This method MUST be implemented in such a way as to retain the |
960
|
|
|
* immutability of the message, and MUST return an instance that has the |
961
|
|
|
* updated status and reason phrase. |
962
|
|
|
* |
963
|
|
|
* If the status code is 304 or 204, the existing Content-Type header |
964
|
|
|
* will be cleared, as these response codes have no body. |
965
|
|
|
* |
966
|
|
|
* There are external packages such as `fig/http-message-util` that provide HTTP |
967
|
|
|
* status code constants. These can be used with any method that accepts or |
968
|
|
|
* returns a status code integer. However, keep in mind that these consants |
969
|
|
|
* might include status codes that are now allowed which will throw an |
970
|
|
|
* `\InvalidArgumentException`. |
971
|
|
|
* |
972
|
|
|
* @link https://tools.ietf.org/html/rfc7231#section-6 |
973
|
|
|
* @link https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml |
974
|
|
|
* @param int $code The 3-digit integer status code to set. |
975
|
|
|
* @param string $reasonPhrase The reason phrase to use with the |
976
|
|
|
* provided status code; if none is provided, implementations MAY |
977
|
|
|
* use the defaults as suggested in the HTTP specification. |
978
|
|
|
* @return static |
979
|
|
|
* @throws \InvalidArgumentException For invalid status code arguments. |
980
|
|
|
*/ |
981
|
|
|
public function withStatus($code, $reasonPhrase = '') |
982
|
|
|
{ |
983
|
|
|
$new = clone $this; |
984
|
|
|
$new->_setStatus($code, $reasonPhrase); |
985
|
|
|
|
986
|
|
|
return $new; |
987
|
|
|
} |
988
|
|
|
|
989
|
|
|
/** |
990
|
|
|
* Modifier for response status |
991
|
|
|
* |
992
|
|
|
* @param int $code The status code to set. |
993
|
|
|
* @param string $reasonPhrase The response reason phrase. |
994
|
|
|
* @return void |
995
|
|
|
* @throws \InvalidArgumentException For invalid status code arguments. |
996
|
|
|
*/ |
997
|
|
|
protected function _setStatus($code, $reasonPhrase = '') |
998
|
|
|
{ |
999
|
|
|
if (!isset($this->_statusCodes[$code])) { |
1000
|
|
|
throw new InvalidArgumentException(sprintf( |
1001
|
|
|
'Invalid status code: %s. Use a valid HTTP status code in range 1xx - 5xx.', |
1002
|
|
|
$code |
1003
|
|
|
)); |
1004
|
|
|
} |
1005
|
|
|
|
1006
|
|
|
$this->_status = $code; |
1007
|
|
|
if (empty($reasonPhrase)) { |
1008
|
|
|
$reasonPhrase = $this->_statusCodes[$code]; |
1009
|
|
|
} |
1010
|
|
|
$this->_reasonPhrase = $reasonPhrase; |
1011
|
|
|
$this->_setContentType(); |
1012
|
|
|
} |
1013
|
|
|
|
1014
|
|
|
/** |
1015
|
|
|
* Gets the response reason phrase associated with the status code. |
1016
|
|
|
* |
1017
|
|
|
* Because a reason phrase is not a required element in a response |
1018
|
|
|
* status line, the reason phrase value MAY be null. Implementations MAY |
1019
|
|
|
* choose to return the default RFC 7231 recommended reason phrase (or those |
1020
|
|
|
* listed in the IANA HTTP Status Code Registry) for the response's |
1021
|
|
|
* status code. |
1022
|
|
|
* |
1023
|
|
|
* @link https://tools.ietf.org/html/rfc7231#section-6 |
1024
|
|
|
* @link http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml |
1025
|
|
|
* @return string Reason phrase; must return an empty string if none present. |
1026
|
|
|
*/ |
1027
|
|
|
public function getReasonPhrase() |
1028
|
|
|
{ |
1029
|
|
|
return $this->_reasonPhrase; |
1030
|
|
|
} |
1031
|
|
|
|
1032
|
|
|
/** |
1033
|
|
|
* Queries & sets valid HTTP response codes & messages. |
1034
|
|
|
* |
1035
|
|
|
* @param int|array|null $code If $code is an integer, then the corresponding code/message is |
1036
|
|
|
* returned if it exists, null if it does not exist. If $code is an array, then the |
1037
|
|
|
* keys are used as codes and the values as messages to add to the default HTTP |
1038
|
|
|
* codes. The codes must be integers greater than 99 and less than 1000. Keep in |
1039
|
|
|
* mind that the HTTP specification outlines that status codes begin with a digit |
1040
|
|
|
* between 1 and 5, which defines the class of response the client is to expect. |
1041
|
|
|
* Example: |
1042
|
|
|
* |
1043
|
|
|
* httpCodes(404); // returns [404 => 'Not Found'] |
1044
|
|
|
* |
1045
|
|
|
* httpCodes([ |
1046
|
|
|
* 381 => 'Unicorn Moved', |
1047
|
|
|
* 555 => 'Unexpected Minotaur' |
1048
|
|
|
* ]); // sets these new values, and returns true |
1049
|
|
|
* |
1050
|
|
|
* httpCodes([ |
1051
|
|
|
* 0 => 'Nothing Here', |
1052
|
|
|
* -1 => 'Reverse Infinity', |
1053
|
|
|
* 12345 => 'Universal Password', |
1054
|
|
|
* 'Hello' => 'World' |
1055
|
|
|
* ]); // throws an exception due to invalid codes |
1056
|
|
|
* |
1057
|
|
|
* For more on HTTP status codes see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1 |
1058
|
|
|
* |
1059
|
|
|
* @return mixed Associative array of the HTTP codes as keys, and the message |
1060
|
|
|
* strings as values, or null of the given $code does not exist. |
1061
|
|
|
* @throws \InvalidArgumentException If an attempt is made to add an invalid status code |
1062
|
|
|
* @deprecated 3.4.0 Will be removed in 4.0.0 |
1063
|
|
|
*/ |
1064
|
|
|
public function httpCodes($code = null) |
1065
|
|
|
{ |
1066
|
|
|
deprecationWarning('Response::httpCodes(). Will be removed in 4.0.0'); |
1067
|
|
|
|
1068
|
|
|
if (empty($code)) { |
1069
|
|
|
return $this->_statusCodes; |
1070
|
|
|
} |
1071
|
|
|
if (is_array($code)) { |
1072
|
|
|
$codes = array_keys($code); |
1073
|
|
|
$min = min($codes); |
1074
|
|
|
if (!is_int($min) || $min < 100 || max($codes) > 999) { |
1075
|
|
|
throw new InvalidArgumentException('Invalid status code'); |
1076
|
|
|
} |
1077
|
|
|
$this->_statusCodes = $code + $this->_statusCodes; |
1078
|
|
|
|
1079
|
|
|
return true; |
1080
|
|
|
} |
1081
|
|
|
if (!isset($this->_statusCodes[$code])) { |
1082
|
|
|
return null; |
1083
|
|
|
} |
1084
|
|
|
|
1085
|
|
|
return [$code => $this->_statusCodes[$code]]; |
1086
|
|
|
} |
1087
|
|
|
|
1088
|
|
|
/** |
1089
|
|
|
* Sets the response content type. It can be either a file extension |
1090
|
|
|
* which will be mapped internally to a mime-type or a string representing a mime-type |
1091
|
|
|
* if $contentType is null the current content type is returned |
1092
|
|
|
* if $contentType is an associative array, content type definitions will be stored/replaced |
1093
|
|
|
* |
1094
|
|
|
* ### Setting the content type |
1095
|
|
|
* |
1096
|
|
|
* ``` |
1097
|
|
|
* type('jpg'); |
1098
|
|
|
* ``` |
1099
|
|
|
* |
1100
|
|
|
* If you attempt to set the type on a 304 or 204 status code response, the |
1101
|
|
|
* content type will not take effect as these status codes do not have content-types. |
1102
|
|
|
* |
1103
|
|
|
* ### Returning the current content type |
1104
|
|
|
* |
1105
|
|
|
* ``` |
1106
|
|
|
* type(); |
1107
|
|
|
* ``` |
1108
|
|
|
* |
1109
|
|
|
* ### Storing content type definitions |
1110
|
|
|
* |
1111
|
|
|
* ``` |
1112
|
|
|
* type(['keynote' => 'application/keynote', 'bat' => 'application/bat']); |
1113
|
|
|
* ``` |
1114
|
|
|
* |
1115
|
|
|
* ### Replacing a content type definition |
1116
|
|
|
* |
1117
|
|
|
* ``` |
1118
|
|
|
* type(['jpg' => 'text/plain']); |
1119
|
|
|
* ``` |
1120
|
|
|
* |
1121
|
|
|
* @param string|array|null $contentType Content type key. |
1122
|
|
|
* @return mixed Current content type or false if supplied an invalid content type. |
1123
|
|
|
* @deprecated 3.5.5 Use getType() or withType() instead. |
1124
|
|
|
*/ |
1125
|
|
|
public function type($contentType = null) |
1126
|
|
|
{ |
1127
|
|
|
deprecationWarning( |
1128
|
|
|
'Response::type() is deprecated. ' . |
1129
|
|
|
'Use setTypeMap(), getType() or withType() instead.' |
1130
|
|
|
); |
1131
|
|
|
|
1132
|
|
|
if ($contentType === null) { |
1133
|
|
|
return $this->getType(); |
1134
|
|
|
} |
1135
|
|
|
if (is_array($contentType)) { |
1136
|
|
|
foreach ($contentType as $type => $definition) { |
1137
|
|
|
$this->_mimeTypes[$type] = $definition; |
1138
|
|
|
} |
1139
|
|
|
|
1140
|
|
|
return $this->getType(); |
1141
|
|
|
} |
1142
|
|
|
if (isset($this->_mimeTypes[$contentType])) { |
1143
|
|
|
$contentType = $this->_mimeTypes[$contentType]; |
1144
|
|
|
$contentType = is_array($contentType) ? current($contentType) : $contentType; |
1145
|
|
|
} |
1146
|
|
|
if (strpos($contentType, '/') === false) { |
1147
|
|
|
return false; |
1148
|
|
|
} |
1149
|
|
|
$this->_contentType = $contentType; |
1150
|
|
|
$this->_setContentType(); |
1151
|
|
|
|
1152
|
|
|
return $contentType; |
1153
|
|
|
} |
1154
|
|
|
|
1155
|
|
|
/** |
1156
|
|
|
* Sets a content type definition into the map. |
1157
|
|
|
* |
1158
|
|
|
* E.g.: setTypeMap('xhtml', ['application/xhtml+xml', 'application/xhtml']) |
1159
|
|
|
* |
1160
|
|
|
* This is needed for RequestHandlerComponent and recognition of types. |
1161
|
|
|
* |
1162
|
|
|
* @param string $type Content type. |
1163
|
|
|
* @param string|array $mimeType Definition of the mime type. |
1164
|
|
|
* @return void |
1165
|
|
|
*/ |
1166
|
|
|
public function setTypeMap($type, $mimeType) |
1167
|
|
|
{ |
1168
|
|
|
$this->_mimeTypes[$type] = $mimeType; |
1169
|
|
|
} |
1170
|
|
|
|
1171
|
|
|
/** |
1172
|
|
|
* Returns the current content type. |
1173
|
|
|
* |
1174
|
|
|
* @return string |
1175
|
|
|
*/ |
1176
|
|
|
public function getType() |
1177
|
|
|
{ |
1178
|
|
|
return $this->_contentType; |
1179
|
|
|
} |
1180
|
|
|
|
1181
|
|
|
/** |
1182
|
|
|
* Get an updated response with the content type set. |
1183
|
|
|
* |
1184
|
|
|
* If you attempt to set the type on a 304 or 204 status code response, the |
1185
|
|
|
* content type will not take effect as these status codes do not have content-types. |
1186
|
|
|
* |
1187
|
|
|
* @param string $contentType Either a file extension which will be mapped to a mime-type or a concrete mime-type. |
1188
|
|
|
* @return static |
1189
|
|
|
*/ |
1190
|
|
|
public function withType($contentType) |
1191
|
|
|
{ |
1192
|
|
|
$mappedType = $this->resolveType($contentType); |
1193
|
|
|
$new = clone $this; |
1194
|
|
|
$new->_contentType = $mappedType; |
1195
|
|
|
$new->_setContentType(); |
1196
|
|
|
|
1197
|
|
|
return $new; |
1198
|
|
|
} |
1199
|
|
|
|
1200
|
|
|
/** |
1201
|
|
|
* Translate and validate content-types. |
1202
|
|
|
* |
1203
|
|
|
* @param string $contentType The content-type or type alias. |
1204
|
|
|
* @return string The resolved content-type |
1205
|
|
|
* @throws \InvalidArgumentException When an invalid content-type or alias is used. |
1206
|
|
|
*/ |
1207
|
|
|
protected function resolveType($contentType) |
1208
|
|
|
{ |
1209
|
|
|
$mapped = $this->getMimeType($contentType); |
1210
|
|
|
if ($mapped) { |
1211
|
|
|
return is_array($mapped) ? current($mapped) : $mapped; |
1212
|
|
|
} |
1213
|
|
|
if (strpos($contentType, '/') === false) { |
1214
|
|
|
throw new InvalidArgumentException(sprintf('"%s" is an invalid content type.', $contentType)); |
1215
|
|
|
} |
1216
|
|
|
|
1217
|
|
|
return $contentType; |
1218
|
|
|
} |
1219
|
|
|
|
1220
|
|
|
/** |
1221
|
|
|
* Returns the mime type definition for an alias |
1222
|
|
|
* |
1223
|
|
|
* e.g `getMimeType('pdf'); // returns 'application/pdf'` |
1224
|
|
|
* |
1225
|
|
|
* @param string $alias the content type alias to map |
1226
|
|
|
* @return string|array|false String mapped mime type or false if $alias is not mapped |
1227
|
|
|
*/ |
1228
|
|
|
public function getMimeType($alias) |
1229
|
|
|
{ |
1230
|
|
|
if (isset($this->_mimeTypes[$alias])) { |
1231
|
|
|
return $this->_mimeTypes[$alias]; |
1232
|
|
|
} |
1233
|
|
|
|
1234
|
|
|
return false; |
1235
|
|
|
} |
1236
|
|
|
|
1237
|
|
|
/** |
1238
|
|
|
* Maps a content-type back to an alias |
1239
|
|
|
* |
1240
|
|
|
* e.g `mapType('application/pdf'); // returns 'pdf'` |
1241
|
|
|
* |
1242
|
|
|
* @param string|array $ctype Either a string content type to map, or an array of types. |
1243
|
|
|
* @return string|array|null Aliases for the types provided. |
1244
|
|
|
*/ |
1245
|
|
|
public function mapType($ctype) |
1246
|
|
|
{ |
1247
|
|
|
if (is_array($ctype)) { |
1248
|
|
|
return array_map([$this, 'mapType'], $ctype); |
1249
|
|
|
} |
1250
|
|
|
|
1251
|
|
|
foreach ($this->_mimeTypes as $alias => $types) { |
1252
|
|
|
if (in_array($ctype, (array)$types)) { |
1253
|
|
|
return $alias; |
1254
|
|
|
} |
1255
|
|
|
} |
1256
|
|
|
|
1257
|
|
|
return null; |
1258
|
|
|
} |
1259
|
|
|
|
1260
|
|
|
/** |
1261
|
|
|
* Sets the response charset |
1262
|
|
|
* if $charset is null the current charset is returned |
1263
|
|
|
* |
1264
|
|
|
* @param string|null $charset Character set string. |
1265
|
|
|
* @return string Current charset |
1266
|
|
|
* @deprecated 3.5.0 Use getCharset()/withCharset() instead. |
1267
|
|
|
*/ |
1268
|
|
|
public function charset($charset = null) |
1269
|
|
|
{ |
1270
|
|
|
deprecationWarning( |
1271
|
|
|
'Response::charset() is deprecated. ' . |
1272
|
|
|
'Use getCharset()/withCharset() instead.' |
1273
|
|
|
); |
1274
|
|
|
|
1275
|
|
|
if ($charset === null) { |
1276
|
|
|
return $this->_charset; |
1277
|
|
|
} |
1278
|
|
|
$this->_charset = $charset; |
1279
|
|
|
$this->_setContentType(); |
1280
|
|
|
|
1281
|
|
|
return $this->_charset; |
1282
|
|
|
} |
1283
|
|
|
|
1284
|
|
|
/** |
1285
|
|
|
* Returns the current charset. |
1286
|
|
|
* |
1287
|
|
|
* @return string |
1288
|
|
|
*/ |
1289
|
|
|
public function getCharset() |
1290
|
|
|
{ |
1291
|
|
|
return $this->_charset; |
1292
|
|
|
} |
1293
|
|
|
|
1294
|
|
|
/** |
1295
|
|
|
* Get a new instance with an updated charset. |
1296
|
|
|
* |
1297
|
|
|
* @param string $charset Character set string. |
1298
|
|
|
* @return static |
1299
|
|
|
*/ |
1300
|
|
|
public function withCharset($charset) |
1301
|
|
|
{ |
1302
|
|
|
$new = clone $this; |
1303
|
|
|
$new->_charset = $charset; |
1304
|
|
|
$new->_setContentType(); |
1305
|
|
|
|
1306
|
|
|
return $new; |
1307
|
|
|
} |
1308
|
|
|
|
1309
|
|
|
/** |
1310
|
|
|
* Sets the correct headers to instruct the client to not cache the response |
1311
|
|
|
* |
1312
|
|
|
* @return void |
1313
|
|
|
* @deprecated 3.4.0 Use withDisabledCache() instead. |
1314
|
|
|
*/ |
1315
|
|
|
public function disableCache() |
1316
|
|
|
{ |
1317
|
|
|
deprecationWarning( |
1318
|
|
|
'Response::disableCache() is deprecated. ' . |
1319
|
|
|
'Use withDisabledCache() instead.' |
1320
|
|
|
); |
1321
|
|
|
|
1322
|
|
|
$this->_setHeader('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT'); |
1323
|
|
|
$this->_setHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT'); |
1324
|
|
|
$this->_setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'); |
1325
|
|
|
} |
1326
|
|
|
|
1327
|
|
|
/** |
1328
|
|
|
* Create a new instance with headers to instruct the client to not cache the response |
1329
|
|
|
* |
1330
|
|
|
* @return static |
1331
|
|
|
*/ |
1332
|
|
|
public function withDisabledCache() |
1333
|
|
|
{ |
1334
|
|
|
return $this->withHeader('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT') |
1335
|
|
|
->withHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT') |
1336
|
|
|
->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'); |
1337
|
|
|
} |
1338
|
|
|
|
1339
|
|
|
/** |
1340
|
|
|
* Sets the correct headers to instruct the client to cache the response. |
1341
|
|
|
* |
1342
|
|
|
* @param string|int|\DateTimeInterface|null $since a valid time since the response text has not been modified |
1343
|
|
|
* @param string|int $time a valid time for cache expiry |
1344
|
|
|
* @return void |
1345
|
|
|
* @throws \InvalidArgumentException |
1346
|
|
|
* @deprecated 3.4.0 Use withCache() instead. |
1347
|
|
|
*/ |
1348
|
|
|
public function cache($since, $time = '+1 day') |
1349
|
|
|
{ |
1350
|
|
|
deprecationWarning( |
1351
|
|
|
'Response::cache() is deprecated. ' . |
1352
|
|
|
'Use withCache() instead.' |
1353
|
|
|
); |
1354
|
|
|
|
1355
|
|
View Code Duplication |
if (!is_int($time)) { |
1356
|
|
|
$time = strtotime($time); |
1357
|
|
|
if ($time === false) { |
1358
|
|
|
throw new InvalidArgumentException('Invalid time parameter. Ensure your time value can be parsed by strtotime'); |
1359
|
|
|
} |
1360
|
|
|
} |
1361
|
|
|
|
1362
|
|
|
$this->_setHeader('Date', gmdate('D, j M Y G:i:s ', time()) . 'GMT'); |
1363
|
|
|
|
1364
|
|
|
$this->modified($since); |
|
|
|
|
1365
|
|
|
$this->expires($time); |
|
|
|
|
1366
|
|
|
$this->sharable(true); |
1367
|
|
|
$this->maxAge($time - time()); |
|
|
|
|
1368
|
|
|
} |
1369
|
|
|
|
1370
|
|
|
/** |
1371
|
|
|
* Create a new instance with the headers to enable client caching. |
1372
|
|
|
* |
1373
|
|
|
* @param string|int|\DateTimeInterface|null $since A valid time since the response text has not been modified |
1374
|
|
|
* @param string|int $time A valid time for cache expiry |
1375
|
|
|
* @return static |
1376
|
|
|
* @throws \InvalidArgumentException |
1377
|
|
|
*/ |
1378
|
|
|
public function withCache($since, $time = '+1 day') |
1379
|
|
|
{ |
1380
|
|
View Code Duplication |
if (!is_int($time)) { |
1381
|
|
|
$time = strtotime($time); |
1382
|
|
|
if ($time === false) { |
1383
|
|
|
throw new InvalidArgumentException('Invalid time parameter. Ensure your time value can be parsed by strtotime'); |
1384
|
|
|
} |
1385
|
|
|
} |
1386
|
|
|
|
1387
|
|
|
return $this->withHeader('Date', gmdate('D, j M Y G:i:s ', time()) . 'GMT') |
1388
|
|
|
->withModified($since) |
1389
|
|
|
->withExpires($time) |
1390
|
|
|
->withSharable(true) |
1391
|
|
|
->withMaxAge($time - time()); |
1392
|
|
|
} |
1393
|
|
|
|
1394
|
|
|
/** |
1395
|
|
|
* Sets whether a response is eligible to be cached by intermediate proxies |
1396
|
|
|
* This method controls the `public` or `private` directive in the Cache-Control |
1397
|
|
|
* header |
1398
|
|
|
* |
1399
|
|
|
* @param bool|null $public If set to true, the Cache-Control header will be set as public |
1400
|
|
|
* if set to false, the response will be set to private |
1401
|
|
|
* if no value is provided, it will return whether the response is sharable or not |
1402
|
|
|
* @param int|null $time time in seconds after which the response should no longer be considered fresh |
1403
|
|
|
* @return bool|null |
1404
|
|
|
*/ |
1405
|
|
|
public function sharable($public = null, $time = null) |
1406
|
|
|
{ |
1407
|
|
|
deprecationWarning( |
1408
|
|
|
'Response::sharable() is deprecated. ' . |
1409
|
|
|
'Use withSharable() instead.' |
1410
|
|
|
); |
1411
|
|
|
if ($public === null) { |
1412
|
|
|
$public = array_key_exists('public', $this->_cacheDirectives); |
1413
|
|
|
$private = array_key_exists('private', $this->_cacheDirectives); |
1414
|
|
|
$noCache = array_key_exists('no-cache', $this->_cacheDirectives); |
1415
|
|
|
if (!$public && !$private && !$noCache) { |
1416
|
|
|
return null; |
1417
|
|
|
} |
1418
|
|
|
|
1419
|
|
|
return $public || !($private || $noCache); |
1420
|
|
|
} |
1421
|
|
|
if ($public) { |
1422
|
|
|
$this->_cacheDirectives['public'] = true; |
1423
|
|
|
unset($this->_cacheDirectives['private']); |
1424
|
|
|
} else { |
1425
|
|
|
$this->_cacheDirectives['private'] = true; |
1426
|
|
|
unset($this->_cacheDirectives['public']); |
1427
|
|
|
} |
1428
|
|
|
|
1429
|
|
|
$this->maxAge($time); |
|
|
|
|
1430
|
|
|
if (!$time) { |
|
|
|
|
1431
|
|
|
$this->_setCacheControl(); |
1432
|
|
|
} |
1433
|
|
|
|
1434
|
|
|
return (bool)$public; |
1435
|
|
|
} |
1436
|
|
|
|
1437
|
|
|
/** |
1438
|
|
|
* Create a new instace with the public/private Cache-Control directive set. |
1439
|
|
|
* |
1440
|
|
|
* @param bool $public If set to true, the Cache-Control header will be set as public |
1441
|
|
|
* if set to false, the response will be set to private. |
1442
|
|
|
* @param int|null $time time in seconds after which the response should no longer be considered fresh. |
1443
|
|
|
* @return static |
1444
|
|
|
*/ |
1445
|
|
|
public function withSharable($public, $time = null) |
1446
|
|
|
{ |
1447
|
|
|
$new = clone $this; |
1448
|
|
|
unset($new->_cacheDirectives['private'], $new->_cacheDirectives['public']); |
1449
|
|
|
|
1450
|
|
|
$key = $public ? 'public' : 'private'; |
1451
|
|
|
$new->_cacheDirectives[$key] = true; |
1452
|
|
|
|
1453
|
|
|
if ($time !== null) { |
1454
|
|
|
$new->_cacheDirectives['max-age'] = $time; |
1455
|
|
|
} |
1456
|
|
|
$new->_setCacheControl(); |
1457
|
|
|
|
1458
|
|
|
return $new; |
1459
|
|
|
} |
1460
|
|
|
|
1461
|
|
|
/** |
1462
|
|
|
* Sets the Cache-Control s-maxage directive. |
1463
|
|
|
* |
1464
|
|
|
* The max-age is the number of seconds after which the response should no longer be considered |
1465
|
|
|
* a good candidate to be fetched from a shared cache (like in a proxy server). |
1466
|
|
|
* If called with no parameters, this function will return the current max-age value if any |
1467
|
|
|
* |
1468
|
|
|
* @deprecated 3.6.5 Use withSharedMaxAge() instead. |
1469
|
|
|
* @param int|null $seconds if null, the method will return the current s-maxage value |
1470
|
|
|
* @return int|null |
1471
|
|
|
*/ |
1472
|
|
View Code Duplication |
public function sharedMaxAge($seconds = null) |
1473
|
|
|
{ |
1474
|
|
|
deprecationWarning( |
1475
|
|
|
'Response::sharedMaxAge() is deprecated. ' . |
1476
|
|
|
'Use withSharedMaxAge() instead.' |
1477
|
|
|
); |
1478
|
|
|
if ($seconds !== null) { |
1479
|
|
|
$this->_cacheDirectives['s-maxage'] = $seconds; |
1480
|
|
|
$this->_setCacheControl(); |
1481
|
|
|
} |
1482
|
|
|
if (isset($this->_cacheDirectives['s-maxage'])) { |
1483
|
|
|
return $this->_cacheDirectives['s-maxage']; |
1484
|
|
|
} |
1485
|
|
|
|
1486
|
|
|
return null; |
1487
|
|
|
} |
1488
|
|
|
|
1489
|
|
|
/** |
1490
|
|
|
* Create a new instance with the Cache-Control s-maxage directive. |
1491
|
|
|
* |
1492
|
|
|
* The max-age is the number of seconds after which the response should no longer be considered |
1493
|
|
|
* a good candidate to be fetched from a shared cache (like in a proxy server). |
1494
|
|
|
* |
1495
|
|
|
* @param int $seconds The number of seconds for shared max-age |
1496
|
|
|
* @return static |
1497
|
|
|
*/ |
1498
|
|
|
public function withSharedMaxAge($seconds) |
1499
|
|
|
{ |
1500
|
|
|
$new = clone $this; |
1501
|
|
|
$new->_cacheDirectives['s-maxage'] = $seconds; |
1502
|
|
|
$new->_setCacheControl(); |
1503
|
|
|
|
1504
|
|
|
return $new; |
1505
|
|
|
} |
1506
|
|
|
|
1507
|
|
|
/** |
1508
|
|
|
* Sets the Cache-Control max-age directive. |
1509
|
|
|
* The max-age is the number of seconds after which the response should no longer be considered |
1510
|
|
|
* a good candidate to be fetched from the local (client) cache. |
1511
|
|
|
* If called with no parameters, this function will return the current max-age value if any |
1512
|
|
|
* |
1513
|
|
|
* @deprecated 3.6.5 Use withMaxAge() instead. |
1514
|
|
|
* @param int|null $seconds if null, the method will return the current max-age value |
1515
|
|
|
* @return int|null |
1516
|
|
|
*/ |
1517
|
|
View Code Duplication |
public function maxAge($seconds = null) |
1518
|
|
|
{ |
1519
|
|
|
deprecationWarning( |
1520
|
|
|
'Response::maxAge() is deprecated. ' . |
1521
|
|
|
'Use withMaxAge() instead.' |
1522
|
|
|
); |
1523
|
|
|
if ($seconds !== null) { |
1524
|
|
|
$this->_cacheDirectives['max-age'] = $seconds; |
1525
|
|
|
$this->_setCacheControl(); |
1526
|
|
|
} |
1527
|
|
|
if (isset($this->_cacheDirectives['max-age'])) { |
1528
|
|
|
return $this->_cacheDirectives['max-age']; |
1529
|
|
|
} |
1530
|
|
|
|
1531
|
|
|
return null; |
1532
|
|
|
} |
1533
|
|
|
|
1534
|
|
|
/** |
1535
|
|
|
* Create an instance with Cache-Control max-age directive set. |
1536
|
|
|
* |
1537
|
|
|
* The max-age is the number of seconds after which the response should no longer be considered |
1538
|
|
|
* a good candidate to be fetched from the local (client) cache. |
1539
|
|
|
* |
1540
|
|
|
* @param int $seconds The seconds a cached response can be considered valid |
1541
|
|
|
* @return static |
1542
|
|
|
*/ |
1543
|
|
|
public function withMaxAge($seconds) |
1544
|
|
|
{ |
1545
|
|
|
$new = clone $this; |
1546
|
|
|
$new->_cacheDirectives['max-age'] = $seconds; |
1547
|
|
|
$new->_setCacheControl(); |
1548
|
|
|
|
1549
|
|
|
return $new; |
1550
|
|
|
} |
1551
|
|
|
|
1552
|
|
|
/** |
1553
|
|
|
* Sets the Cache-Control must-revalidate directive. |
1554
|
|
|
* must-revalidate indicates that the response should not be served |
1555
|
|
|
* stale by a cache under any circumstance without first revalidating |
1556
|
|
|
* with the origin. |
1557
|
|
|
* If called with no parameters, this function will return whether must-revalidate is present. |
1558
|
|
|
* |
1559
|
|
|
* @param bool|null $enable if null, the method will return the current |
1560
|
|
|
* must-revalidate value. If boolean sets or unsets the directive. |
1561
|
|
|
* @return bool |
1562
|
|
|
* @deprecated 3.4.0 Use withMustRevalidate() instead. |
1563
|
|
|
*/ |
1564
|
|
View Code Duplication |
public function mustRevalidate($enable = null) |
1565
|
|
|
{ |
1566
|
|
|
deprecationWarning( |
1567
|
|
|
'Response::mustRevalidate() is deprecated. ' . |
1568
|
|
|
'Use withMustRevalidate() instead.' |
1569
|
|
|
); |
1570
|
|
|
|
1571
|
|
|
if ($enable !== null) { |
1572
|
|
|
if ($enable) { |
1573
|
|
|
$this->_cacheDirectives['must-revalidate'] = true; |
1574
|
|
|
} else { |
1575
|
|
|
unset($this->_cacheDirectives['must-revalidate']); |
1576
|
|
|
} |
1577
|
|
|
$this->_setCacheControl(); |
1578
|
|
|
} |
1579
|
|
|
|
1580
|
|
|
return array_key_exists('must-revalidate', $this->_cacheDirectives); |
1581
|
|
|
} |
1582
|
|
|
|
1583
|
|
|
/** |
1584
|
|
|
* Create an instance with Cache-Control must-revalidate directive set. |
1585
|
|
|
* |
1586
|
|
|
* Sets the Cache-Control must-revalidate directive. |
1587
|
|
|
* must-revalidate indicates that the response should not be served |
1588
|
|
|
* stale by a cache under any circumstance without first revalidating |
1589
|
|
|
* with the origin. |
1590
|
|
|
* |
1591
|
|
|
* @param bool $enable If boolean sets or unsets the directive. |
1592
|
|
|
* @return static |
1593
|
|
|
*/ |
1594
|
|
|
public function withMustRevalidate($enable) |
1595
|
|
|
{ |
1596
|
|
|
$new = clone $this; |
1597
|
|
|
if ($enable) { |
1598
|
|
|
$new->_cacheDirectives['must-revalidate'] = true; |
1599
|
|
|
} else { |
1600
|
|
|
unset($new->_cacheDirectives['must-revalidate']); |
1601
|
|
|
} |
1602
|
|
|
$new->_setCacheControl(); |
1603
|
|
|
|
1604
|
|
|
return $new; |
1605
|
|
|
} |
1606
|
|
|
|
1607
|
|
|
/** |
1608
|
|
|
* Helper method to generate a valid Cache-Control header from the options set |
1609
|
|
|
* in other methods |
1610
|
|
|
* |
1611
|
|
|
* @return void |
1612
|
|
|
*/ |
1613
|
|
|
protected function _setCacheControl() |
1614
|
|
|
{ |
1615
|
|
|
$control = ''; |
1616
|
|
|
foreach ($this->_cacheDirectives as $key => $val) { |
1617
|
|
|
$control .= $val === true ? $key : sprintf('%s=%s', $key, $val); |
1618
|
|
|
$control .= ', '; |
1619
|
|
|
} |
1620
|
|
|
$control = rtrim($control, ', '); |
1621
|
|
|
$this->_setHeader('Cache-Control', $control); |
1622
|
|
|
} |
1623
|
|
|
|
1624
|
|
|
/** |
1625
|
|
|
* Sets the Expires header for the response by taking an expiration time |
1626
|
|
|
* If called with no parameters it will return the current Expires value |
1627
|
|
|
* |
1628
|
|
|
* ### Examples: |
1629
|
|
|
* |
1630
|
|
|
* `$response->expires('now')` Will Expire the response cache now |
1631
|
|
|
* `$response->expires(new DateTime('+1 day'))` Will set the expiration in next 24 hours |
1632
|
|
|
* `$response->expires()` Will return the current expiration header value |
1633
|
|
|
* |
1634
|
|
|
* @param string|int|\DateTimeInterface|null $time Valid time string or \DateTime instance. |
1635
|
|
|
* @return string|null |
1636
|
|
|
* @deprecated 3.4.0 Use withExpires() instead. |
1637
|
|
|
*/ |
1638
|
|
View Code Duplication |
public function expires($time = null) |
1639
|
|
|
{ |
1640
|
|
|
deprecationWarning( |
1641
|
|
|
'Response::expires() is deprecated. ' . |
1642
|
|
|
'Use withExpires() instead.' |
1643
|
|
|
); |
1644
|
|
|
|
1645
|
|
|
if ($time !== null) { |
1646
|
|
|
$date = $this->_getUTCDate($time); |
1647
|
|
|
$this->_setHeader('Expires', $date->format('D, j M Y H:i:s') . ' GMT'); |
1648
|
|
|
} |
1649
|
|
|
|
1650
|
|
|
if ($this->hasHeader('Expires')) { |
1651
|
|
|
return $this->getHeaderLine('Expires'); |
1652
|
|
|
} |
1653
|
|
|
|
1654
|
|
|
return null; |
1655
|
|
|
} |
1656
|
|
|
|
1657
|
|
|
/** |
1658
|
|
|
* Create a new instance with the Expires header set. |
1659
|
|
|
* |
1660
|
|
|
* ### Examples: |
1661
|
|
|
* |
1662
|
|
|
* ``` |
1663
|
|
|
* // Will Expire the response cache now |
1664
|
|
|
* $response->withExpires('now') |
1665
|
|
|
* |
1666
|
|
|
* // Will set the expiration in next 24 hours |
1667
|
|
|
* $response->withExpires(new DateTime('+1 day')) |
1668
|
|
|
* ``` |
1669
|
|
|
* |
1670
|
|
|
* @param string|int|\DateTimeInterface|null $time Valid time string or \DateTime instance. |
1671
|
|
|
* @return static |
1672
|
|
|
*/ |
1673
|
|
|
public function withExpires($time) |
1674
|
|
|
{ |
1675
|
|
|
$date = $this->_getUTCDate($time); |
1676
|
|
|
|
1677
|
|
|
return $this->withHeader('Expires', $date->format('D, j M Y H:i:s') . ' GMT'); |
1678
|
|
|
} |
1679
|
|
|
|
1680
|
|
|
/** |
1681
|
|
|
* Sets the Last-Modified header for the response by taking a modification time |
1682
|
|
|
* If called with no parameters it will return the current Last-Modified value |
1683
|
|
|
* |
1684
|
|
|
* ### Examples: |
1685
|
|
|
* |
1686
|
|
|
* `$response->modified('now')` Will set the Last-Modified to the current time |
1687
|
|
|
* `$response->modified(new DateTime('+1 day'))` Will set the modification date in the past 24 hours |
1688
|
|
|
* `$response->modified()` Will return the current Last-Modified header value |
1689
|
|
|
* |
1690
|
|
|
* @param string|int|\DateTimeInterface|null $time Valid time string or \DateTime instance. |
1691
|
|
|
* @return string|null |
1692
|
|
|
* @deprecated 3.4.0 Use withModified() instead. |
1693
|
|
|
*/ |
1694
|
|
View Code Duplication |
public function modified($time = null) |
1695
|
|
|
{ |
1696
|
|
|
deprecationWarning( |
1697
|
|
|
'Response::modified() is deprecated. ' . |
1698
|
|
|
'Use withModified() or getHeaderLine("Last-Modified") instead.' |
1699
|
|
|
); |
1700
|
|
|
|
1701
|
|
|
if ($time !== null) { |
1702
|
|
|
$date = $this->_getUTCDate($time); |
1703
|
|
|
$this->_setHeader('Last-Modified', $date->format('D, j M Y H:i:s') . ' GMT'); |
1704
|
|
|
} |
1705
|
|
|
|
1706
|
|
|
if ($this->hasHeader('Last-Modified')) { |
1707
|
|
|
return $this->getHeaderLine('Last-Modified'); |
1708
|
|
|
} |
1709
|
|
|
|
1710
|
|
|
return null; |
1711
|
|
|
} |
1712
|
|
|
|
1713
|
|
|
/** |
1714
|
|
|
* Create a new instance with the Last-Modified header set. |
1715
|
|
|
* |
1716
|
|
|
* ### Examples: |
1717
|
|
|
* |
1718
|
|
|
* ``` |
1719
|
|
|
* // Will Expire the response cache now |
1720
|
|
|
* $response->withModified('now') |
1721
|
|
|
* |
1722
|
|
|
* // Will set the expiration in next 24 hours |
1723
|
|
|
* $response->withModified(new DateTime('+1 day')) |
1724
|
|
|
* ``` |
1725
|
|
|
* |
1726
|
|
|
* @param string|int|\DateTimeInterface|null $time Valid time string or \DateTimeInterface instance. |
1727
|
|
|
* @return static |
1728
|
|
|
*/ |
1729
|
|
|
public function withModified($time) |
1730
|
|
|
{ |
1731
|
|
|
$date = $this->_getUTCDate($time); |
1732
|
|
|
|
1733
|
|
|
return $this->withHeader('Last-Modified', $date->format('D, j M Y H:i:s') . ' GMT'); |
1734
|
|
|
} |
1735
|
|
|
|
1736
|
|
|
/** |
1737
|
|
|
* Sets the response as Not Modified by removing any body contents |
1738
|
|
|
* setting the status code to "304 Not Modified" and removing all |
1739
|
|
|
* conflicting headers |
1740
|
|
|
* |
1741
|
|
|
* *Warning* This method mutates the response in-place and should be avoided. |
1742
|
|
|
* |
1743
|
|
|
* @return void |
1744
|
|
|
*/ |
1745
|
|
|
public function notModified() |
1746
|
|
|
{ |
1747
|
|
|
$this->_createStream(); |
1748
|
|
|
$this->_setStatus(304); |
1749
|
|
|
|
1750
|
|
|
$remove = [ |
1751
|
|
|
'Allow', |
1752
|
|
|
'Content-Encoding', |
1753
|
|
|
'Content-Language', |
1754
|
|
|
'Content-Length', |
1755
|
|
|
'Content-MD5', |
1756
|
|
|
'Content-Type', |
1757
|
|
|
'Last-Modified', |
1758
|
|
|
]; |
1759
|
|
|
foreach ($remove as $header) { |
1760
|
|
|
$this->_clearHeader($header); |
1761
|
|
|
} |
1762
|
|
|
} |
1763
|
|
|
|
1764
|
|
|
/** |
1765
|
|
|
* Create a new instance as 'not modified' |
1766
|
|
|
* |
1767
|
|
|
* This will remove any body contents set the status code |
1768
|
|
|
* to "304" and removing headers that describe |
1769
|
|
|
* a response body. |
1770
|
|
|
* |
1771
|
|
|
* @return static |
1772
|
|
|
*/ |
1773
|
|
|
public function withNotModified() |
1774
|
|
|
{ |
1775
|
|
|
$new = $this->withStatus(304); |
1776
|
|
|
$new->_createStream(); |
1777
|
|
|
$remove = [ |
1778
|
|
|
'Allow', |
1779
|
|
|
'Content-Encoding', |
1780
|
|
|
'Content-Language', |
1781
|
|
|
'Content-Length', |
1782
|
|
|
'Content-MD5', |
1783
|
|
|
'Content-Type', |
1784
|
|
|
'Last-Modified', |
1785
|
|
|
]; |
1786
|
|
|
foreach ($remove as $header) { |
1787
|
|
|
$new = $new->withoutHeader($header); |
1788
|
|
|
} |
1789
|
|
|
|
1790
|
|
|
return $new; |
1791
|
|
|
} |
1792
|
|
|
|
1793
|
|
|
/** |
1794
|
|
|
* Sets the Vary header for the response, if an array is passed, |
1795
|
|
|
* values will be imploded into a comma separated string. If no |
1796
|
|
|
* parameters are passed, then an array with the current Vary header |
1797
|
|
|
* value is returned |
1798
|
|
|
* |
1799
|
|
|
* @param string|array|null $cacheVariances A single Vary string or an array |
1800
|
|
|
* containing the list for variances. |
1801
|
|
|
* @return array|null |
1802
|
|
|
* @deprecated 3.4.0 Use withVary() instead. |
1803
|
|
|
*/ |
1804
|
|
|
public function vary($cacheVariances = null) |
1805
|
|
|
{ |
1806
|
|
|
deprecationWarning( |
1807
|
|
|
'Response::vary() is deprecated. ' . |
1808
|
|
|
'Use withVary() instead.' |
1809
|
|
|
); |
1810
|
|
|
|
1811
|
|
|
if ($cacheVariances !== null) { |
1812
|
|
|
$cacheVariances = (array)$cacheVariances; |
1813
|
|
|
$this->_setHeader('Vary', implode(', ', $cacheVariances)); |
1814
|
|
|
} |
1815
|
|
|
|
1816
|
|
|
if ($this->hasHeader('Vary')) { |
1817
|
|
|
return explode(', ', $this->getHeaderLine('Vary')); |
1818
|
|
|
} |
1819
|
|
|
|
1820
|
|
|
return null; |
1821
|
|
|
} |
1822
|
|
|
|
1823
|
|
|
/** |
1824
|
|
|
* Create a new instance with the Vary header set. |
1825
|
|
|
* |
1826
|
|
|
* If an array is passed values will be imploded into a comma |
1827
|
|
|
* separated string. If no parameters are passed, then an |
1828
|
|
|
* array with the current Vary header value is returned |
1829
|
|
|
* |
1830
|
|
|
* @param string|array $cacheVariances A single Vary string or an array |
1831
|
|
|
* containing the list for variances. |
1832
|
|
|
* @return static |
1833
|
|
|
*/ |
1834
|
|
|
public function withVary($cacheVariances) |
1835
|
|
|
{ |
1836
|
|
|
return $this->withHeader('Vary', (array)$cacheVariances); |
1837
|
|
|
} |
1838
|
|
|
|
1839
|
|
|
/** |
1840
|
|
|
* Sets the response Etag, Etags are a strong indicative that a response |
1841
|
|
|
* can be cached by a HTTP client. A bad way of generating Etags is |
1842
|
|
|
* creating a hash of the response output, instead generate a unique |
1843
|
|
|
* hash of the unique components that identifies a request, such as a |
1844
|
|
|
* modification time, a resource Id, and anything else you consider it |
1845
|
|
|
* makes it unique. |
1846
|
|
|
* |
1847
|
|
|
* Second parameter is used to instruct clients that the content has |
1848
|
|
|
* changed, but semantically, it can be used as the same thing. Think |
1849
|
|
|
* for instance of a page with a hit counter, two different page views |
1850
|
|
|
* are equivalent, but they differ by a few bytes. This leaves off to |
1851
|
|
|
* the Client the decision of using or not the cached page. |
1852
|
|
|
* |
1853
|
|
|
* If no parameters are passed, current Etag header is returned. |
1854
|
|
|
* |
1855
|
|
|
* @param string|null $hash The unique hash that identifies this response |
1856
|
|
|
* @param bool $weak Whether the response is semantically the same as |
1857
|
|
|
* other with the same hash or not |
1858
|
|
|
* @return string|null |
1859
|
|
|
* @deprecated 3.4.0 Use withEtag() instead. |
1860
|
|
|
*/ |
1861
|
|
|
public function etag($hash = null, $weak = false) |
1862
|
|
|
{ |
1863
|
|
|
deprecationWarning( |
1864
|
|
|
'Response::etag() is deprecated. ' . |
1865
|
|
|
'Use withEtag() or getHeaderLine("Etag") instead.' |
1866
|
|
|
); |
1867
|
|
|
|
1868
|
|
|
if ($hash !== null) { |
1869
|
|
|
$this->_setHeader('Etag', sprintf('%s"%s"', $weak ? 'W/' : null, $hash)); |
1870
|
|
|
} |
1871
|
|
|
|
1872
|
|
|
if ($this->hasHeader('Etag')) { |
1873
|
|
|
return $this->getHeaderLine('Etag'); |
1874
|
|
|
} |
1875
|
|
|
|
1876
|
|
|
return null; |
1877
|
|
|
} |
1878
|
|
|
|
1879
|
|
|
/** |
1880
|
|
|
* Create a new instance with the Etag header set. |
1881
|
|
|
* |
1882
|
|
|
* Etags are a strong indicative that a response can be cached by a |
1883
|
|
|
* HTTP client. A bad way of generating Etags is creating a hash of |
1884
|
|
|
* the response output, instead generate a unique hash of the |
1885
|
|
|
* unique components that identifies a request, such as a |
1886
|
|
|
* modification time, a resource Id, and anything else you consider it |
1887
|
|
|
* that makes the response unique. |
1888
|
|
|
* |
1889
|
|
|
* The second parameter is used to inform clients that the content has |
1890
|
|
|
* changed, but semantically it is equivalent to existing cached values. Consider |
1891
|
|
|
* a page with a hit counter, two different page views are equivalent, but |
1892
|
|
|
* they differ by a few bytes. This permits the Client to decide whether they should |
1893
|
|
|
* use the cached data. |
1894
|
|
|
* |
1895
|
|
|
* @param string $hash The unique hash that identifies this response |
1896
|
|
|
* @param bool $weak Whether the response is semantically the same as |
1897
|
|
|
* other with the same hash or not. Defaults to false |
1898
|
|
|
* @return static |
1899
|
|
|
*/ |
1900
|
|
|
public function withEtag($hash, $weak = false) |
1901
|
|
|
{ |
1902
|
|
|
$hash = sprintf('%s"%s"', $weak ? 'W/' : null, $hash); |
1903
|
|
|
|
1904
|
|
|
return $this->withHeader('Etag', $hash); |
1905
|
|
|
} |
1906
|
|
|
|
1907
|
|
|
/** |
1908
|
|
|
* Returns a DateTime object initialized at the $time param and using UTC |
1909
|
|
|
* as timezone |
1910
|
|
|
* |
1911
|
|
|
* @param string|int|\DateTimeInterface|null $time Valid time string or \DateTimeInterface instance. |
1912
|
|
|
* @return \DateTimeInterface |
1913
|
|
|
*/ |
1914
|
|
|
protected function _getUTCDate($time = null) |
1915
|
|
|
{ |
1916
|
|
|
if ($time instanceof DateTimeInterface) { |
1917
|
|
|
$result = clone $time; |
1918
|
|
|
} elseif (is_int($time)) { |
1919
|
|
|
$result = new DateTime(date('Y-m-d H:i:s', $time)); |
1920
|
|
|
} else { |
1921
|
|
|
$result = new DateTime($time); |
1922
|
|
|
} |
1923
|
|
|
|
1924
|
|
|
return $result->setTimezone(new DateTimeZone('UTC')); |
1925
|
|
|
} |
1926
|
|
|
|
1927
|
|
|
/** |
1928
|
|
|
* Sets the correct output buffering handler to send a compressed response. Responses will |
1929
|
|
|
* be compressed with zlib, if the extension is available. |
1930
|
|
|
* |
1931
|
|
|
* @return bool false if client does not accept compressed responses or no handler is available, true otherwise |
1932
|
|
|
*/ |
1933
|
|
|
public function compress() |
1934
|
|
|
{ |
1935
|
|
|
$compressionEnabled = ini_get('zlib.output_compression') !== '1' && |
1936
|
|
|
extension_loaded('zlib') && |
1937
|
|
|
(strpos(env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false); |
1938
|
|
|
|
1939
|
|
|
return $compressionEnabled && ob_start('ob_gzhandler'); |
1940
|
|
|
} |
1941
|
|
|
|
1942
|
|
|
/** |
1943
|
|
|
* Returns whether the resulting output will be compressed by PHP |
1944
|
|
|
* |
1945
|
|
|
* @return bool |
1946
|
|
|
*/ |
1947
|
|
|
public function outputCompressed() |
1948
|
|
|
{ |
1949
|
|
|
return strpos(env('HTTP_ACCEPT_ENCODING'), 'gzip') !== false |
1950
|
|
|
&& (ini_get('zlib.output_compression') === '1' || in_array('ob_gzhandler', ob_list_handlers(), true)); |
1951
|
|
|
} |
1952
|
|
|
|
1953
|
|
|
/** |
1954
|
|
|
* Sets the correct headers to instruct the browser to download the response as a file. |
1955
|
|
|
* |
1956
|
|
|
* @param string $filename The name of the file as the browser will download the response |
1957
|
|
|
* @return void |
1958
|
|
|
* @deprecated 3.4.0 Use withDownload() instead. |
1959
|
|
|
*/ |
1960
|
|
|
public function download($filename) |
1961
|
|
|
{ |
1962
|
|
|
deprecationWarning( |
1963
|
|
|
'Response::download() is deprecated. ' . |
1964
|
|
|
'Use withDownload() instead.' |
1965
|
|
|
); |
1966
|
|
|
|
1967
|
|
|
$this->header('Content-Disposition', 'attachment; filename="' . $filename . '"'); |
|
|
|
|
1968
|
|
|
} |
1969
|
|
|
|
1970
|
|
|
/** |
1971
|
|
|
* Create a new instance with the Content-Disposition header set. |
1972
|
|
|
* |
1973
|
|
|
* @param string $filename The name of the file as the browser will download the response |
1974
|
|
|
* @return static |
1975
|
|
|
*/ |
1976
|
|
|
public function withDownload($filename) |
1977
|
|
|
{ |
1978
|
|
|
return $this->withHeader('Content-Disposition', 'attachment; filename="' . $filename . '"'); |
1979
|
|
|
} |
1980
|
|
|
|
1981
|
|
|
/** |
1982
|
|
|
* Sets the protocol to be used when sending the response. Defaults to HTTP/1.1 |
1983
|
|
|
* If called with no arguments, it will return the current configured protocol |
1984
|
|
|
* |
1985
|
|
|
* @param string|null $protocol Protocol to be used for sending response. |
1986
|
|
|
* @return string Protocol currently set |
1987
|
|
|
* @deprecated 3.4.0 Use getProtocolVersion() instead. |
1988
|
|
|
*/ |
1989
|
|
|
public function protocol($protocol = null) |
1990
|
|
|
{ |
1991
|
|
|
deprecationWarning( |
1992
|
|
|
'Response::protocol() is deprecated. ' . |
1993
|
|
|
'Use getProtocolVersion() instead.' |
1994
|
|
|
); |
1995
|
|
|
|
1996
|
|
|
if ($protocol !== null) { |
1997
|
|
|
$this->_protocol = $protocol; |
1998
|
|
|
} |
1999
|
|
|
|
2000
|
|
|
return $this->_protocol; |
2001
|
|
|
} |
2002
|
|
|
|
2003
|
|
|
/** |
2004
|
|
|
* Sets the Content-Length header for the response |
2005
|
|
|
* If called with no arguments returns the last Content-Length set |
2006
|
|
|
* |
2007
|
|
|
* @param int|null $bytes Number of bytes |
2008
|
|
|
* @return string|null |
2009
|
|
|
* @deprecated 3.4.0 Use withLength() to set length instead. |
2010
|
|
|
*/ |
2011
|
|
|
public function length($bytes = null) |
2012
|
|
|
{ |
2013
|
|
|
deprecationWarning( |
2014
|
|
|
'Response::length() is deprecated. ' . |
2015
|
|
|
'Use withLength() instead.' |
2016
|
|
|
); |
2017
|
|
|
|
2018
|
|
|
if ($bytes !== null) { |
2019
|
|
|
$this->_setHeader('Content-Length', $bytes); |
2020
|
|
|
} |
2021
|
|
|
|
2022
|
|
|
if ($this->hasHeader('Content-Length')) { |
2023
|
|
|
return $this->getHeaderLine('Content-Length'); |
2024
|
|
|
} |
2025
|
|
|
|
2026
|
|
|
return null; |
2027
|
|
|
} |
2028
|
|
|
|
2029
|
|
|
/** |
2030
|
|
|
* Create a new response with the Content-Length header set. |
2031
|
|
|
* |
2032
|
|
|
* @param int|string $bytes Number of bytes |
2033
|
|
|
* @return static |
2034
|
|
|
*/ |
2035
|
|
|
public function withLength($bytes) |
2036
|
|
|
{ |
2037
|
|
|
return $this->withHeader('Content-Length', (string)$bytes); |
2038
|
|
|
} |
2039
|
|
|
|
2040
|
|
|
/** |
2041
|
|
|
* Create a new response with the Link header set. |
2042
|
|
|
* |
2043
|
|
|
* ### Examples |
2044
|
|
|
* |
2045
|
|
|
* ``` |
2046
|
|
|
* $response = $response->withAddedLink('http://example.com?page=1', ['rel' => 'prev']) |
2047
|
|
|
* ->withAddedLink('http://example.com?page=3', ['rel' => 'next']); |
2048
|
|
|
* ``` |
2049
|
|
|
* |
2050
|
|
|
* Will generate: |
2051
|
|
|
* |
2052
|
|
|
* ``` |
2053
|
|
|
* Link: <http://example.com?page=1>; rel="prev" |
2054
|
|
|
* Link: <http://example.com?page=3>; rel="next" |
2055
|
|
|
* ``` |
2056
|
|
|
* |
2057
|
|
|
* @param string $url The LinkHeader url. |
2058
|
|
|
* @param array $options The LinkHeader params. |
2059
|
|
|
* @return static |
2060
|
|
|
* @since 3.6.0 |
2061
|
|
|
*/ |
2062
|
|
|
public function withAddedLink($url, $options = []) |
2063
|
|
|
{ |
2064
|
|
|
$params = []; |
2065
|
|
|
foreach ($options as $key => $option) { |
2066
|
|
|
$params[] = $key . '="' . $option . '"'; |
2067
|
|
|
} |
2068
|
|
|
|
2069
|
|
|
$param = ''; |
2070
|
|
|
if ($params) { |
|
|
|
|
2071
|
|
|
$param = '; ' . implode('; ', $params); |
2072
|
|
|
} |
2073
|
|
|
|
2074
|
|
|
return $this->withAddedHeader('Link', '<' . $url . '>' . $param); |
2075
|
|
|
} |
2076
|
|
|
|
2077
|
|
|
/** |
2078
|
|
|
* Checks whether a response has not been modified according to the 'If-None-Match' |
2079
|
|
|
* (Etags) and 'If-Modified-Since' (last modification date) request |
2080
|
|
|
* headers. If the response is detected to be not modified, it |
2081
|
|
|
* is marked as so accordingly so the client can be informed of that. |
2082
|
|
|
* |
2083
|
|
|
* In order to mark a response as not modified, you need to set at least |
2084
|
|
|
* the Last-Modified etag response header before calling this method. Otherwise |
2085
|
|
|
* a comparison will not be possible. |
2086
|
|
|
* |
2087
|
|
|
* *Warning* This method mutates the response in-place and should be avoided. |
2088
|
|
|
* |
2089
|
|
|
* @param \Cake\Http\ServerRequest $request Request object |
2090
|
|
|
* @return bool Whether the response was marked as not modified or not. |
2091
|
|
|
*/ |
2092
|
|
|
public function checkNotModified(ServerRequest $request) |
2093
|
|
|
{ |
2094
|
|
|
$etags = preg_split('/\s*,\s*/', (string)$request->getHeaderLine('If-None-Match'), 0, PREG_SPLIT_NO_EMPTY); |
2095
|
|
|
$responseTag = $this->getHeaderLine('Etag'); |
2096
|
|
|
$etagMatches = null; |
2097
|
|
|
if ($responseTag) { |
2098
|
|
|
$etagMatches = in_array('*', $etags, true) || in_array($responseTag, $etags, true); |
2099
|
|
|
} |
2100
|
|
|
|
2101
|
|
|
$modifiedSince = $request->getHeaderLine('If-Modified-Since'); |
2102
|
|
|
$timeMatches = null; |
2103
|
|
|
if ($modifiedSince && $this->hasHeader('Last-Modified')) { |
2104
|
|
|
$timeMatches = strtotime($this->getHeaderLine('Last-Modified')) === strtotime($modifiedSince); |
2105
|
|
|
} |
2106
|
|
|
if ($etagMatches === null && $timeMatches === null) { |
2107
|
|
|
return false; |
2108
|
|
|
} |
2109
|
|
|
$notModified = $etagMatches !== false && $timeMatches !== false; |
2110
|
|
|
if ($notModified) { |
2111
|
|
|
$this->notModified(); |
2112
|
|
|
} |
2113
|
|
|
|
2114
|
|
|
return $notModified; |
2115
|
|
|
} |
2116
|
|
|
|
2117
|
|
|
/** |
2118
|
|
|
* String conversion. Fetches the response body as a string. |
2119
|
|
|
* Does *not* send headers. |
2120
|
|
|
* If body is a callable, a blank string is returned. |
2121
|
|
|
* |
2122
|
|
|
* @return string |
2123
|
|
|
*/ |
2124
|
|
|
public function __toString() |
2125
|
|
|
{ |
2126
|
|
|
$this->stream->rewind(); |
2127
|
|
|
|
2128
|
|
|
return (string)$this->stream->getContents(); |
2129
|
|
|
} |
2130
|
|
|
|
2131
|
|
|
/** |
2132
|
|
|
* Getter/Setter for cookie configs |
2133
|
|
|
* |
2134
|
|
|
* This method acts as a setter/getter depending on the type of the argument. |
2135
|
|
|
* If the method is called with no arguments, it returns all configurations. |
2136
|
|
|
* |
2137
|
|
|
* If the method is called with a string as argument, it returns either the |
2138
|
|
|
* given configuration if it is set, or null, if it's not set. |
2139
|
|
|
* |
2140
|
|
|
* If the method is called with an array as argument, it will set the cookie |
2141
|
|
|
* configuration to the cookie container. |
2142
|
|
|
* |
2143
|
|
|
* ### Options (when setting a configuration) |
2144
|
|
|
* - name: The Cookie name |
2145
|
|
|
* - value: Value of the cookie |
2146
|
|
|
* - expire: Time the cookie expires in |
2147
|
|
|
* - path: Path the cookie applies to |
2148
|
|
|
* - domain: Domain the cookie is for. |
2149
|
|
|
* - secure: Is the cookie https? |
2150
|
|
|
* - httpOnly: Is the cookie available in the client? |
2151
|
|
|
* |
2152
|
|
|
* ### Examples |
2153
|
|
|
* |
2154
|
|
|
* ### Getting all cookies |
2155
|
|
|
* |
2156
|
|
|
* `$this->cookie()` |
2157
|
|
|
* |
2158
|
|
|
* ### Getting a certain cookie configuration |
2159
|
|
|
* |
2160
|
|
|
* `$this->cookie('MyCookie')` |
2161
|
|
|
* |
2162
|
|
|
* ### Setting a cookie configuration |
2163
|
|
|
* |
2164
|
|
|
* `$this->cookie((array) $options)` |
2165
|
|
|
* |
2166
|
|
|
* @param array|null $options Either null to get all cookies, string for a specific cookie |
2167
|
|
|
* or array to set cookie. |
2168
|
|
|
* @return mixed |
2169
|
|
|
* @deprecated 3.4.0 Use getCookie(), getCookies() and withCookie() instead. |
2170
|
|
|
*/ |
2171
|
|
|
public function cookie($options = null) |
2172
|
|
|
{ |
2173
|
|
|
deprecationWarning( |
2174
|
|
|
'Response::cookie() is deprecated. ' . |
2175
|
|
|
'Use getCookie(), getCookies() and withCookie() instead.' |
2176
|
|
|
); |
2177
|
|
|
|
2178
|
|
|
if ($options === null) { |
2179
|
|
|
return $this->getCookies(); |
2180
|
|
|
} |
2181
|
|
|
|
2182
|
|
|
if (is_string($options)) { |
2183
|
|
|
if (!$this->_cookies->has($options)) { |
2184
|
|
|
return null; |
2185
|
|
|
} |
2186
|
|
|
|
2187
|
|
|
$cookie = $this->_cookies->get($options); |
2188
|
|
|
|
2189
|
|
|
return $this->convertCookieToArray($cookie); |
|
|
|
|
2190
|
|
|
} |
2191
|
|
|
|
2192
|
|
|
$options += [ |
2193
|
|
|
'name' => 'CakeCookie[default]', |
2194
|
|
|
'value' => '', |
2195
|
|
|
'expire' => 0, |
2196
|
|
|
'path' => '/', |
2197
|
|
|
'domain' => '', |
2198
|
|
|
'secure' => false, |
2199
|
|
|
'httpOnly' => false, |
2200
|
|
|
]; |
2201
|
|
|
$expires = $options['expire'] ? new DateTime('@' . $options['expire']) : null; |
2202
|
|
|
$cookie = new Cookie( |
2203
|
|
|
$options['name'], |
2204
|
|
|
$options['value'], |
2205
|
|
|
$expires, |
2206
|
|
|
$options['path'], |
2207
|
|
|
$options['domain'], |
2208
|
|
|
$options['secure'], |
2209
|
|
|
$options['httpOnly'] |
2210
|
|
|
); |
2211
|
|
|
$this->_cookies = $this->_cookies->add($cookie); |
2212
|
|
|
} |
2213
|
|
|
|
2214
|
|
|
/** |
2215
|
|
|
* Create a new response with a cookie set. |
2216
|
|
|
* |
2217
|
|
|
* ### Data |
2218
|
|
|
* |
2219
|
|
|
* - `value`: Value of the cookie |
2220
|
|
|
* - `expire`: Time the cookie expires in |
2221
|
|
|
* - `path`: Path the cookie applies to |
2222
|
|
|
* - `domain`: Domain the cookie is for. |
2223
|
|
|
* - `secure`: Is the cookie https? |
2224
|
|
|
* - `httpOnly`: Is the cookie available in the client? |
2225
|
|
|
* |
2226
|
|
|
* ### Examples |
2227
|
|
|
* |
2228
|
|
|
* ``` |
2229
|
|
|
* // set scalar value with defaults |
2230
|
|
|
* $response = $response->withCookie('remember_me', 1); |
2231
|
|
|
* |
2232
|
|
|
* // customize cookie attributes |
2233
|
|
|
* $response = $response->withCookie('remember_me', ['path' => '/login']); |
2234
|
|
|
* |
2235
|
|
|
* // add a cookie object |
2236
|
|
|
* $response = $response->withCookie(new Cookie('remember_me', 1)); |
2237
|
|
|
* ``` |
2238
|
|
|
* |
2239
|
|
|
* @param string|\Cake\Http\Cookie\Cookie $name The name of the cookie to set, or a cookie object |
2240
|
|
|
* @param array|string $data Either a string value, or an array of cookie options. |
2241
|
|
|
* @return static |
2242
|
|
|
*/ |
2243
|
|
|
public function withCookie($name, $data = '') |
2244
|
|
|
{ |
2245
|
|
|
if ($name instanceof Cookie) { |
2246
|
|
|
$cookie = $name; |
2247
|
|
|
} else { |
2248
|
|
|
deprecationWarning( |
2249
|
|
|
get_called_class() . '::withCookie(string $name, array $data) is deprecated. ' . |
2250
|
|
|
'Pass an instance of \Cake\Http\Cookie\Cookie instead.' |
2251
|
|
|
); |
2252
|
|
|
|
2253
|
|
|
if (!is_array($data)) { |
2254
|
|
|
$data = ['value' => $data]; |
2255
|
|
|
} |
2256
|
|
|
$data += [ |
2257
|
|
|
'value' => '', |
2258
|
|
|
'expire' => 0, |
2259
|
|
|
'path' => '/', |
2260
|
|
|
'domain' => '', |
2261
|
|
|
'secure' => false, |
2262
|
|
|
'httpOnly' => false, |
2263
|
|
|
]; |
2264
|
|
|
$expires = $data['expire'] ? new DateTime('@' . $data['expire']) : null; |
2265
|
|
|
$cookie = new Cookie( |
2266
|
|
|
$name, |
2267
|
|
|
$data['value'], |
2268
|
|
|
$expires, |
2269
|
|
|
$data['path'], |
2270
|
|
|
$data['domain'], |
2271
|
|
|
$data['secure'], |
2272
|
|
|
$data['httpOnly'] |
2273
|
|
|
); |
2274
|
|
|
} |
2275
|
|
|
|
2276
|
|
|
$new = clone $this; |
2277
|
|
|
$new->_cookies = $new->_cookies->add($cookie); |
2278
|
|
|
|
2279
|
|
|
return $new; |
2280
|
|
|
} |
2281
|
|
|
|
2282
|
|
|
/** |
2283
|
|
|
* Create a new response with an expired cookie set. |
2284
|
|
|
* |
2285
|
|
|
* ### Options |
2286
|
|
|
* |
2287
|
|
|
* - `path`: Path the cookie applies to |
2288
|
|
|
* - `domain`: Domain the cookie is for. |
2289
|
|
|
* - `secure`: Is the cookie https? |
2290
|
|
|
* - `httpOnly`: Is the cookie available in the client? |
2291
|
|
|
* |
2292
|
|
|
* ### Examples |
2293
|
|
|
* |
2294
|
|
|
* ``` |
2295
|
|
|
* // set scalar value with defaults |
2296
|
|
|
* $response = $response->withExpiredCookie('remember_me'); |
2297
|
|
|
* |
2298
|
|
|
* // customize cookie attributes |
2299
|
|
|
* $response = $response->withExpiredCookie('remember_me', ['path' => '/login']); |
2300
|
|
|
* |
2301
|
|
|
* // add a cookie object |
2302
|
|
|
* $response = $response->withExpiredCookie(new Cookie('remember_me')); |
2303
|
|
|
* ``` |
2304
|
|
|
* |
2305
|
|
|
* @param string|\Cake\Http\Cookie\CookieInterface $name The name of the cookie to expire, or a cookie object |
2306
|
|
|
* @param array $options An array of cookie options. |
2307
|
|
|
* @return static |
2308
|
|
|
*/ |
2309
|
|
|
public function withExpiredCookie($name, $options = []) |
2310
|
|
|
{ |
2311
|
|
|
if ($name instanceof CookieInterface) { |
2312
|
|
|
$cookie = $name->withExpired(); |
2313
|
|
|
} else { |
2314
|
|
|
deprecationWarning( |
2315
|
|
|
get_called_class() . '::withExpiredCookie(string $name, array $data) is deprecated. ' . |
2316
|
|
|
'Pass an instance of \Cake\Http\Cookie\Cookie instead.' |
2317
|
|
|
); |
2318
|
|
|
|
2319
|
|
|
$options += [ |
2320
|
|
|
'path' => '/', |
2321
|
|
|
'domain' => '', |
2322
|
|
|
'secure' => false, |
2323
|
|
|
'httpOnly' => false, |
2324
|
|
|
]; |
2325
|
|
|
|
2326
|
|
|
$cookie = new Cookie( |
2327
|
|
|
$name, |
2328
|
|
|
'', |
2329
|
|
|
DateTime::createFromFormat('U', 1), |
|
|
|
|
2330
|
|
|
$options['path'], |
2331
|
|
|
$options['domain'], |
2332
|
|
|
$options['secure'], |
2333
|
|
|
$options['httpOnly'] |
2334
|
|
|
); |
2335
|
|
|
} |
2336
|
|
|
|
2337
|
|
|
$new = clone $this; |
2338
|
|
|
$new->_cookies = $new->_cookies->add($cookie); |
2339
|
|
|
|
2340
|
|
|
return $new; |
2341
|
|
|
} |
2342
|
|
|
|
2343
|
|
|
/** |
2344
|
|
|
* Read a single cookie from the response. |
2345
|
|
|
* |
2346
|
|
|
* This method provides read access to pending cookies. It will |
2347
|
|
|
* not read the `Set-Cookie` header if set. |
2348
|
|
|
* |
2349
|
|
|
* @param string $name The cookie name you want to read. |
2350
|
|
|
* @return array|null Either the cookie data or null |
2351
|
|
|
*/ |
2352
|
|
|
public function getCookie($name) |
2353
|
|
|
{ |
2354
|
|
|
if (!$this->_cookies->has($name)) { |
2355
|
|
|
return null; |
2356
|
|
|
} |
2357
|
|
|
|
2358
|
|
|
$cookie = $this->_cookies->get($name); |
2359
|
|
|
|
2360
|
|
|
return $this->convertCookieToArray($cookie); |
|
|
|
|
2361
|
|
|
} |
2362
|
|
|
|
2363
|
|
|
/** |
2364
|
|
|
* Get all cookies in the response. |
2365
|
|
|
* |
2366
|
|
|
* Returns an associative array of cookie name => cookie data. |
2367
|
|
|
* |
2368
|
|
|
* @return array |
2369
|
|
|
*/ |
2370
|
|
|
public function getCookies() |
2371
|
|
|
{ |
2372
|
|
|
$out = []; |
2373
|
|
|
foreach ($this->_cookies as $cookie) { |
2374
|
|
|
$out[$cookie->getName()] = $this->convertCookieToArray($cookie); |
2375
|
|
|
} |
2376
|
|
|
|
2377
|
|
|
return $out; |
2378
|
|
|
} |
2379
|
|
|
|
2380
|
|
|
/** |
2381
|
|
|
* Convert the cookie into an array of its properties. |
2382
|
|
|
* |
2383
|
|
|
* This method is compatible with the historical behavior of Cake\Http\Response, |
2384
|
|
|
* where `httponly` is `httpOnly` and `expires` is `expire` |
2385
|
|
|
* |
2386
|
|
|
* @param \Cake\Http\Cookie\CookieInterface $cookie Cookie object. |
2387
|
|
|
* @return array |
2388
|
|
|
*/ |
2389
|
|
View Code Duplication |
protected function convertCookieToArray(CookieInterface $cookie) |
2390
|
|
|
{ |
2391
|
|
|
return [ |
2392
|
|
|
'name' => $cookie->getName(), |
2393
|
|
|
'value' => $cookie->getStringValue(), |
2394
|
|
|
'path' => $cookie->getPath(), |
2395
|
|
|
'domain' => $cookie->getDomain(), |
2396
|
|
|
'secure' => $cookie->isSecure(), |
2397
|
|
|
'httpOnly' => $cookie->isHttpOnly(), |
2398
|
|
|
'expire' => $cookie->getExpiresTimestamp(), |
2399
|
|
|
]; |
2400
|
|
|
} |
2401
|
|
|
|
2402
|
|
|
/** |
2403
|
|
|
* Get the CookieCollection from the response |
2404
|
|
|
* |
2405
|
|
|
* @return \Cake\Http\Cookie\CookieCollection |
2406
|
|
|
*/ |
2407
|
|
|
public function getCookieCollection() |
2408
|
|
|
{ |
2409
|
|
|
return $this->_cookies; |
2410
|
|
|
} |
2411
|
|
|
|
2412
|
|
|
/** |
2413
|
|
|
* Get a new instance with provided cookie collection. |
2414
|
|
|
* |
2415
|
|
|
* @param \Cake\Http\Cookie\CookieCollection $cookieCollection Cookie collection to set. |
2416
|
|
|
* @return static |
2417
|
|
|
*/ |
2418
|
|
|
public function withCookieCollection(CookieCollection $cookieCollection) |
2419
|
|
|
{ |
2420
|
|
|
$new = clone $this; |
2421
|
|
|
$new->_cookies = $cookieCollection; |
2422
|
|
|
|
2423
|
|
|
return $new; |
2424
|
|
|
} |
2425
|
|
|
|
2426
|
|
|
/** |
2427
|
|
|
* Setup access for origin and methods on cross origin requests |
2428
|
|
|
* |
2429
|
|
|
* This method allow multiple ways to setup the domains, see the examples |
2430
|
|
|
* |
2431
|
|
|
* ### Full URI |
2432
|
|
|
* ``` |
2433
|
|
|
* cors($request, 'https://www.cakephp.org'); |
2434
|
|
|
* ``` |
2435
|
|
|
* |
2436
|
|
|
* ### URI with wildcard |
2437
|
|
|
* ``` |
2438
|
|
|
* cors($request, 'https://*.cakephp.org'); |
2439
|
|
|
* ``` |
2440
|
|
|
* |
2441
|
|
|
* ### Ignoring the requested protocol |
2442
|
|
|
* ``` |
2443
|
|
|
* cors($request, 'www.cakephp.org'); |
2444
|
|
|
* ``` |
2445
|
|
|
* |
2446
|
|
|
* ### Any URI |
2447
|
|
|
* ``` |
2448
|
|
|
* cors($request, '*'); |
2449
|
|
|
* ``` |
2450
|
|
|
* |
2451
|
|
|
* ### Whitelist of URIs |
2452
|
|
|
* ``` |
2453
|
|
|
* cors($request, ['http://www.cakephp.org', '*.google.com', 'https://myproject.github.io']); |
2454
|
|
|
* ``` |
2455
|
|
|
* |
2456
|
|
|
* *Note* The `$allowedDomains`, `$allowedMethods`, `$allowedHeaders` parameters are deprecated. |
2457
|
|
|
* Instead the builder object should be used. |
2458
|
|
|
* |
2459
|
|
|
* @param \Cake\Http\ServerRequest $request Request object |
2460
|
|
|
* @param string|string[] $allowedDomains List of allowed domains, see method description for more details |
2461
|
|
|
* @param string|string[] $allowedMethods List of HTTP verbs allowed |
2462
|
|
|
* @param string|string[] $allowedHeaders List of HTTP headers allowed |
2463
|
|
|
* @return \Cake\Http\CorsBuilder A builder object the provides a fluent interface for defining |
2464
|
|
|
* additional CORS headers. |
2465
|
|
|
*/ |
2466
|
|
|
public function cors(ServerRequest $request, $allowedDomains = [], $allowedMethods = [], $allowedHeaders = []) |
2467
|
|
|
{ |
2468
|
|
|
$origin = $request->getHeaderLine('Origin'); |
2469
|
|
|
$ssl = $request->is('ssl'); |
2470
|
|
|
$builder = new CorsBuilder($this, $origin, $ssl); |
2471
|
|
|
if (!$origin) { |
2472
|
|
|
return $builder; |
2473
|
|
|
} |
2474
|
|
|
if (empty($allowedDomains) && empty($allowedMethods) && empty($allowedHeaders)) { |
2475
|
|
|
return $builder; |
2476
|
|
|
} |
2477
|
|
|
deprecationWarning( |
2478
|
|
|
'The $allowedDomains, $allowedMethods, and $allowedHeaders parameters of Response::cors() ' . |
2479
|
|
|
'are deprecated. Instead you should use the builder methods on the return of cors().' |
2480
|
|
|
); |
2481
|
|
|
|
2482
|
|
|
$updated = $builder->allowOrigin($allowedDomains) |
2483
|
|
|
->allowMethods((array)$allowedMethods) |
2484
|
|
|
->allowHeaders((array)$allowedHeaders) |
2485
|
|
|
->build(); |
2486
|
|
|
|
2487
|
|
|
// If $updated is a new instance, mutate this object in-place |
2488
|
|
|
// to retain existing behavior. |
2489
|
|
|
if ($updated !== $this) { |
2490
|
|
|
foreach ($updated->getHeaders() as $name => $values) { |
2491
|
|
|
if (!$this->hasHeader($name)) { |
2492
|
|
|
$this->_setHeader($name, $values[0]); |
2493
|
|
|
} |
2494
|
|
|
} |
2495
|
|
|
} |
2496
|
|
|
|
2497
|
|
|
return $builder; |
2498
|
|
|
} |
2499
|
|
|
|
2500
|
|
|
/** |
2501
|
|
|
* Setup for display or download the given file. |
2502
|
|
|
* |
2503
|
|
|
* If $_SERVER['HTTP_RANGE'] is set a slice of the file will be |
2504
|
|
|
* returned instead of the entire file. |
2505
|
|
|
* |
2506
|
|
|
* ### Options keys |
2507
|
|
|
* |
2508
|
|
|
* - name: Alternate download name |
2509
|
|
|
* - download: If `true` sets download header and forces file to be downloaded rather than displayed in browser |
2510
|
|
|
* |
2511
|
|
|
* @param string $path Path to file. If the path is not an absolute path that resolves |
2512
|
|
|
* to a file, `APP` will be prepended to the path (this behavior is deprecated). |
2513
|
|
|
* @param array $options Options See above. |
2514
|
|
|
* @return void |
2515
|
|
|
* @throws \Cake\Http\Exception\NotFoundException |
2516
|
|
|
* @deprecated 3.4.0 Use withFile() instead. |
2517
|
|
|
*/ |
2518
|
|
|
public function file($path, array $options = []) |
2519
|
|
|
{ |
2520
|
|
|
deprecationWarning( |
2521
|
|
|
'Response::file() is deprecated. ' . |
2522
|
|
|
'Use withFile() instead.' |
2523
|
|
|
); |
2524
|
|
|
|
2525
|
|
|
$file = $this->validateFile($path); |
2526
|
|
|
$options += [ |
2527
|
|
|
'name' => null, |
2528
|
|
|
'download' => null, |
2529
|
|
|
]; |
2530
|
|
|
|
2531
|
|
|
$extension = strtolower($file->ext()); |
2532
|
|
|
$download = $options['download']; |
2533
|
|
|
if ((!$extension || $this->type($extension) === false) && $download === null) { |
|
|
|
|
2534
|
|
|
$download = true; |
2535
|
|
|
} |
2536
|
|
|
|
2537
|
|
|
$fileSize = $file->size(); |
2538
|
|
|
if ($download) { |
2539
|
|
|
$agent = env('HTTP_USER_AGENT'); |
2540
|
|
|
|
2541
|
|
View Code Duplication |
if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent)) { |
2542
|
|
|
$contentType = 'application/octet-stream'; |
2543
|
|
|
} elseif (preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) { |
2544
|
|
|
$contentType = 'application/force-download'; |
2545
|
|
|
} |
2546
|
|
|
|
2547
|
|
|
if (!empty($contentType)) { |
2548
|
|
|
$this->type($contentType); |
|
|
|
|
2549
|
|
|
} |
2550
|
|
|
if ($options['name'] === null) { |
2551
|
|
|
$name = $file->name; |
2552
|
|
|
} else { |
2553
|
|
|
$name = $options['name']; |
2554
|
|
|
} |
2555
|
|
|
$this->download($name); |
|
|
|
|
2556
|
|
|
$this->header('Content-Transfer-Encoding', 'binary'); |
|
|
|
|
2557
|
|
|
} |
2558
|
|
|
|
2559
|
|
|
$this->header('Accept-Ranges', 'bytes'); |
|
|
|
|
2560
|
|
|
$httpRange = env('HTTP_RANGE'); |
2561
|
|
|
if (isset($httpRange)) { |
2562
|
|
|
$this->_fileRange($file, $httpRange); |
|
|
|
|
2563
|
|
|
} else { |
2564
|
|
|
$this->header('Content-Length', $fileSize); |
|
|
|
|
2565
|
|
|
} |
2566
|
|
|
|
2567
|
|
|
$this->_file = $file; |
2568
|
|
|
$this->stream = new Stream($file->path, 'rb'); |
2569
|
|
|
} |
2570
|
|
|
|
2571
|
|
|
/** |
2572
|
|
|
* Create a new instance that is based on a file. |
2573
|
|
|
* |
2574
|
|
|
* This method will augment both the body and a number of related headers. |
2575
|
|
|
* |
2576
|
|
|
* If `$_SERVER['HTTP_RANGE']` is set, a slice of the file will be |
2577
|
|
|
* returned instead of the entire file. |
2578
|
|
|
* |
2579
|
|
|
* ### Options keys |
2580
|
|
|
* |
2581
|
|
|
* - name: Alternate download name |
2582
|
|
|
* - download: If `true` sets download header and forces file to |
2583
|
|
|
* be downloaded rather than displayed inline. |
2584
|
|
|
* |
2585
|
|
|
* @param string $path Path to file. If the path is not an absolute path that resolves |
2586
|
|
|
* to a file, `APP` will be prepended to the path (this behavior is deprecated). |
2587
|
|
|
* @param array $options Options See above. |
2588
|
|
|
* @return static |
2589
|
|
|
* @throws \Cake\Http\Exception\NotFoundException |
2590
|
|
|
*/ |
2591
|
|
|
public function withFile($path, array $options = []) |
2592
|
|
|
{ |
2593
|
|
|
$file = $this->validateFile($path); |
2594
|
|
|
$options += [ |
2595
|
|
|
'name' => null, |
2596
|
|
|
'download' => null, |
2597
|
|
|
]; |
2598
|
|
|
|
2599
|
|
|
$extension = strtolower($file->ext()); |
2600
|
|
|
$mapped = $this->getMimeType($extension); |
2601
|
|
|
if ((!$extension || !$mapped) && $options['download'] === null) { |
2602
|
|
|
$options['download'] = true; |
2603
|
|
|
} |
2604
|
|
|
|
2605
|
|
|
$new = clone $this; |
2606
|
|
|
if ($mapped) { |
2607
|
|
|
$new = $new->withType($extension); |
2608
|
|
|
} |
2609
|
|
|
|
2610
|
|
|
$fileSize = $file->size(); |
2611
|
|
|
if ($options['download']) { |
2612
|
|
|
$agent = env('HTTP_USER_AGENT'); |
2613
|
|
|
|
2614
|
|
View Code Duplication |
if (preg_match('%Opera(/| )([0-9].[0-9]{1,2})%', $agent)) { |
2615
|
|
|
$contentType = 'application/octet-stream'; |
2616
|
|
|
} elseif (preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) { |
2617
|
|
|
$contentType = 'application/force-download'; |
2618
|
|
|
} |
2619
|
|
|
|
2620
|
|
|
if (isset($contentType)) { |
2621
|
|
|
$new = $new->withType($contentType); |
2622
|
|
|
} |
2623
|
|
|
$name = $options['name'] ?: $file->name; |
2624
|
|
|
$new = $new->withDownload($name) |
2625
|
|
|
->withHeader('Content-Transfer-Encoding', 'binary'); |
2626
|
|
|
} |
2627
|
|
|
|
2628
|
|
|
$new = $new->withHeader('Accept-Ranges', 'bytes'); |
2629
|
|
|
$httpRange = env('HTTP_RANGE'); |
2630
|
|
|
if (isset($httpRange)) { |
2631
|
|
|
$new->_fileRange($file, $httpRange); |
|
|
|
|
2632
|
|
|
} else { |
2633
|
|
|
$new = $new->withHeader('Content-Length', (string)$fileSize); |
2634
|
|
|
} |
2635
|
|
|
$new->_file = $file; |
2636
|
|
|
$new->stream = new Stream($file->path, 'rb'); |
2637
|
|
|
|
2638
|
|
|
return $new; |
2639
|
|
|
} |
2640
|
|
|
|
2641
|
|
|
/** |
2642
|
|
|
* Convenience method to set a string into the response body |
2643
|
|
|
* |
2644
|
|
|
* @param string $string The string to be sent |
2645
|
|
|
* @return static |
2646
|
|
|
*/ |
2647
|
|
|
public function withStringBody($string) |
2648
|
|
|
{ |
2649
|
|
|
$new = clone $this; |
2650
|
|
|
$new->_createStream(); |
2651
|
|
|
$new->stream->write((string)$string); |
2652
|
|
|
|
2653
|
|
|
return $new; |
2654
|
|
|
} |
2655
|
|
|
|
2656
|
|
|
/** |
2657
|
|
|
* Validate a file path is a valid response body. |
2658
|
|
|
* |
2659
|
|
|
* @param string $path The path to the file. |
2660
|
|
|
* @throws \Cake\Http\Exception\NotFoundException |
2661
|
|
|
* @return \Cake\Filesystem\File |
2662
|
|
|
*/ |
2663
|
|
|
protected function validateFile($path) |
2664
|
|
|
{ |
2665
|
|
|
if (strpos($path, '../') !== false || strpos($path, '..\\') !== false) { |
2666
|
|
|
throw new NotFoundException(__d('cake', 'The requested file contains `..` and will not be read.')); |
2667
|
|
|
} |
2668
|
|
|
if (!is_file($path)) { |
2669
|
|
|
deprecationWarning( |
2670
|
|
|
'Automatic prefixing of paths with `APP` by `Response::file()` and `withFile()` is deprecated. ' . |
2671
|
|
|
'Use absolute paths instead.' |
2672
|
|
|
); |
2673
|
|
|
$path = APP . $path; |
2674
|
|
|
} |
2675
|
|
|
if (!Folder::isAbsolute($path)) { |
2676
|
|
|
deprecationWarning( |
2677
|
|
|
'Serving files via `file()` or `withFile()` using relative paths is deprecated.' . |
2678
|
|
|
'Use an absolute path instead.' |
2679
|
|
|
); |
2680
|
|
|
} |
2681
|
|
|
|
2682
|
|
|
$file = new File($path); |
2683
|
|
|
if (!$file->exists() || !$file->readable()) { |
2684
|
|
|
if (Configure::read('debug')) { |
2685
|
|
|
throw new NotFoundException(sprintf('The requested file %s was not found or not readable', $path)); |
2686
|
|
|
} |
2687
|
|
|
throw new NotFoundException(__d('cake', 'The requested file was not found')); |
2688
|
|
|
} |
2689
|
|
|
|
2690
|
|
|
return $file; |
2691
|
|
|
} |
2692
|
|
|
|
2693
|
|
|
/** |
2694
|
|
|
* Get the current file if one exists. |
2695
|
|
|
* |
2696
|
|
|
* @return \Cake\Filesystem\File|null The file to use in the response or null |
2697
|
|
|
*/ |
2698
|
|
|
public function getFile() |
2699
|
|
|
{ |
2700
|
|
|
return $this->_file; |
2701
|
|
|
} |
2702
|
|
|
|
2703
|
|
|
/** |
2704
|
|
|
* Apply a file range to a file and set the end offset. |
2705
|
|
|
* |
2706
|
|
|
* If an invalid range is requested a 416 Status code will be used |
2707
|
|
|
* in the response. |
2708
|
|
|
* |
2709
|
|
|
* @param \Cake\Filesystem\File $file The file to set a range on. |
2710
|
|
|
* @param string $httpRange The range to use. |
2711
|
|
|
* @return void |
2712
|
|
|
* @deprecated 3.4.0 Long term this needs to be refactored to follow immutable paradigms. |
2713
|
|
|
* However for now, it is simpler to leave this alone. |
2714
|
|
|
*/ |
2715
|
|
|
protected function _fileRange($file, $httpRange) |
2716
|
|
|
{ |
2717
|
|
|
$fileSize = $file->size(); |
2718
|
|
|
$lastByte = $fileSize - 1; |
2719
|
|
|
$start = 0; |
2720
|
|
|
$end = $lastByte; |
2721
|
|
|
|
2722
|
|
|
preg_match('/^bytes\s*=\s*(\d+)?\s*-\s*(\d+)?$/', $httpRange, $matches); |
2723
|
|
|
if ($matches) { |
|
|
|
|
2724
|
|
|
$start = $matches[1]; |
2725
|
|
|
$end = isset($matches[2]) ? $matches[2] : ''; |
2726
|
|
|
} |
2727
|
|
|
|
2728
|
|
|
if ($start === '') { |
2729
|
|
|
$start = $fileSize - $end; |
2730
|
|
|
$end = $lastByte; |
2731
|
|
|
} |
2732
|
|
|
if ($end === '') { |
2733
|
|
|
$end = $lastByte; |
2734
|
|
|
} |
2735
|
|
|
|
2736
|
|
|
if ($start > $end || $end > $lastByte || $start > $lastByte) { |
2737
|
|
|
$this->_setStatus(416); |
2738
|
|
|
$this->_setHeader('Content-Range', 'bytes 0-' . $lastByte . '/' . $fileSize); |
2739
|
|
|
|
2740
|
|
|
return; |
2741
|
|
|
} |
2742
|
|
|
|
2743
|
|
|
$this->_setHeader('Content-Length', $end - $start + 1); |
2744
|
|
|
$this->_setHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $fileSize); |
2745
|
|
|
$this->_setStatus(206); |
2746
|
|
|
$this->_fileRange = [$start, $end]; |
2747
|
|
|
} |
2748
|
|
|
|
2749
|
|
|
/** |
2750
|
|
|
* Reads out a file, and echos the content to the client. |
2751
|
|
|
* |
2752
|
|
|
* @param \Cake\Filesystem\File $file File object |
2753
|
|
|
* @param array $range The range to read out of the file. |
2754
|
|
|
* @return bool True is whole file is echoed successfully or false if client connection is lost in between |
2755
|
|
|
* @deprecated 3.4.0 Will be removed in 4.0.0 |
2756
|
|
|
*/ |
2757
|
|
|
protected function _sendFile($file, $range) |
2758
|
|
|
{ |
2759
|
|
|
deprecationWarning('Will be removed in 4.0.0'); |
2760
|
|
|
|
2761
|
|
|
ob_implicit_flush(true); |
2762
|
|
|
|
2763
|
|
|
$file->open('rb'); |
2764
|
|
|
|
2765
|
|
|
$end = $start = false; |
2766
|
|
|
if ($range) { |
|
|
|
|
2767
|
|
|
list($start, $end) = $range; |
2768
|
|
|
} |
2769
|
|
|
if ($start !== false) { |
2770
|
|
|
$file->offset($start); |
2771
|
|
|
} |
2772
|
|
|
|
2773
|
|
|
$bufferSize = 8192; |
2774
|
|
|
if (strpos(ini_get('disable_functions'), 'set_time_limit') === false) { |
2775
|
|
|
set_time_limit(0); |
2776
|
|
|
} |
2777
|
|
|
session_write_close(); |
2778
|
|
|
while (!feof($file->handle)) { |
2779
|
|
|
if (!$this->_isActive()) { |
|
|
|
|
2780
|
|
|
$file->close(); |
2781
|
|
|
|
2782
|
|
|
return false; |
2783
|
|
|
} |
2784
|
|
|
$offset = $file->offset(); |
2785
|
|
|
if ($end && $offset >= $end) { |
2786
|
|
|
break; |
2787
|
|
|
} |
2788
|
|
|
if ($end && $offset + $bufferSize >= $end) { |
2789
|
|
|
$bufferSize = $end - $offset + 1; |
2790
|
|
|
} |
2791
|
|
|
echo fread($file->handle, $bufferSize); |
2792
|
|
|
} |
2793
|
|
|
$file->close(); |
2794
|
|
|
|
2795
|
|
|
return true; |
2796
|
|
|
} |
2797
|
|
|
|
2798
|
|
|
/** |
2799
|
|
|
* Returns true if connection is still active |
2800
|
|
|
* |
2801
|
|
|
* @return bool |
2802
|
|
|
* @deprecated 3.4.0 Will be removed in 4.0.0 |
2803
|
|
|
*/ |
2804
|
|
|
protected function _isActive() |
2805
|
|
|
{ |
2806
|
|
|
deprecationWarning('Will be removed in 4.0.0'); |
2807
|
|
|
|
2808
|
|
|
return connection_status() === CONNECTION_NORMAL && !connection_aborted(); |
2809
|
|
|
} |
2810
|
|
|
|
2811
|
|
|
/** |
2812
|
|
|
* Clears the contents of the topmost output buffer and discards them |
2813
|
|
|
* |
2814
|
|
|
* @return bool |
2815
|
|
|
* @deprecated 3.2.4 This function is not needed anymore |
2816
|
|
|
*/ |
2817
|
|
|
protected function _clearBuffer() |
2818
|
|
|
{ |
2819
|
|
|
deprecationWarning( |
2820
|
|
|
'This function is not needed anymore and will be removed.' |
2821
|
|
|
); |
2822
|
|
|
|
2823
|
|
|
//@codingStandardsIgnoreStart |
2824
|
|
|
return @ob_end_clean(); |
2825
|
|
|
//@codingStandardsIgnoreEnd |
2826
|
|
|
} |
2827
|
|
|
|
2828
|
|
|
/** |
2829
|
|
|
* Flushes the contents of the output buffer |
2830
|
|
|
* |
2831
|
|
|
* @return void |
2832
|
|
|
* @deprecated 3.2.4 This function is not needed anymore |
2833
|
|
|
*/ |
2834
|
|
|
protected function _flushBuffer() |
2835
|
|
|
{ |
2836
|
|
|
deprecationWarning( |
2837
|
|
|
'This function is not needed anymore and will be removed.' |
2838
|
|
|
); |
2839
|
|
|
|
2840
|
|
|
//@codingStandardsIgnoreStart |
2841
|
|
|
@flush(); |
2842
|
|
|
if (ob_get_level()) { |
2843
|
|
|
@ob_flush(); |
2844
|
|
|
} |
2845
|
|
|
//@codingStandardsIgnoreEnd |
2846
|
|
|
} |
2847
|
|
|
|
2848
|
|
|
/** |
2849
|
|
|
* Stop execution of the current script. Wraps exit() making |
2850
|
|
|
* testing easier. |
2851
|
|
|
* |
2852
|
|
|
* @param int|string $status See https://secure.php.net/exit for values |
2853
|
|
|
* @return void |
2854
|
|
|
* @deprecated 3.4.0 Will be removed in 4.0.0 |
2855
|
|
|
*/ |
2856
|
|
|
public function stop($status = 0) |
2857
|
|
|
{ |
2858
|
|
|
deprecationWarning('Will be removed in 4.0.0'); |
2859
|
|
|
|
2860
|
|
|
exit($status); |
2861
|
|
|
} |
2862
|
|
|
|
2863
|
|
|
/** |
2864
|
|
|
* Returns an array that can be used to describe the internal state of this |
2865
|
|
|
* object. |
2866
|
|
|
* |
2867
|
|
|
* @return array |
2868
|
|
|
*/ |
2869
|
|
|
public function __debugInfo() |
2870
|
|
|
{ |
2871
|
|
|
return [ |
2872
|
|
|
'status' => $this->_status, |
2873
|
|
|
'contentType' => $this->_contentType, |
2874
|
|
|
'headers' => $this->headers, |
2875
|
|
|
'file' => $this->_file, |
2876
|
|
|
'fileRange' => $this->_fileRange, |
2877
|
|
|
'cookies' => $this->_cookies, |
2878
|
|
|
'cacheDirectives' => $this->_cacheDirectives, |
2879
|
|
|
'body' => (string)$this->getBody(), |
2880
|
|
|
]; |
2881
|
|
|
} |
2882
|
|
|
} |
2883
|
|
|
|
2884
|
|
|
// @deprecated 3.4.0 Add backwards compat alias. |
2885
|
|
|
class_alias('Cake\Http\Response', 'Cake\Network\Response'); |
2886
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.