Response::withFile()   B
last analyzed

Complexity

Conditions 11
Paths 104

Size

Total Lines 49

Duplication

Lines 5
Ratio 10.2 %

Importance

Changes 0
Metric Value
cc 11
nc 104
nop 2
dl 5
loc 49
rs 7.2833
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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']);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::httpCodes() has been deprecated with message: 3.4.0 Will be removed in 4.0.0

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.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::statusCode() has been deprecated with message: 3.4.0 Use `getStatusCode()` and `withStatus()` instead.

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.

Loading history...
501
        }
502
503
        $this->_setContent();
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::_setContent() has been deprecated with message: 3.4.0 Will be removed in 4.0.0

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.

Loading history...
504
        $this->sendHeaders();
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::sendHeaders() has been deprecated with message: 3.4.0 Will be removed in 4.0.0

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.

Loading history...
505
506
        if ($this->_file) {
507
            $this->_sendFile($this->_file, $this->_fileRange);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::_sendFile() has been deprecated with message: 3.4.0 Will be removed in 4.0.0

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.

Loading history...
508
            $this->_file = null;
509
            $this->_fileRange = [];
510
        } else {
511
            $this->_sendContent($this->body());
0 ignored issues
show
Bug introduced by
It seems like $this->body() targeting Cake\Http\Response::body() can also be of type null; however, Cake\Http\Response::_sendContent() does only seem to accept callable, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Deprecated Code introduced by
The method Cake\Http\Response::body() has been deprecated with message: 3.4.0 Mutable response methods are deprecated. Use `withBody()`/`withStringBody()` and `getBody()` instead.

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.

Loading history...
Deprecated Code introduced by
The method Cake\Http\Response::_sendContent() has been deprecated with message: 3.4.0 Will be removed in 4.0.0

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.

Loading history...
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();
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::_setCookies() has been deprecated with message: 3.4.0 Will be removed in 4.0.0

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.

Loading history...
540
        $this->_sendHeader("{$this->_protocol} {$this->_status} {$codeMessage}");
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::_sendHeader() has been deprecated with message: 3.4.0 Will be removed in 4.0.0

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.

Loading history...
541
        $this->_setContentType();
542
543
        foreach ($this->headers as $header => $values) {
544
            foreach ((array)$values as $value) {
545
                $this->_sendHeader($header, $value);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::_sendHeader() has been deprecated with message: 3.4.0 Will be removed in 4.0.0

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.

Loading history...
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('');
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::body() has been deprecated with message: 3.4.0 Mutable response methods are deprecated. Use `withBody()`/`withStringBody()` and `getBody()` instead.

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.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::modified() has been deprecated with message: 3.4.0 Use withModified() instead.

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.

Loading history...
1365
        $this->expires($time);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::expires() has been deprecated with message: 3.4.0 Use withExpires() instead.

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.

Loading history...
1366
        $this->sharable(true);
1367
        $this->maxAge($time - time());
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::maxAge() has been deprecated with message: 3.6.5 Use withMaxAge() instead.

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.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::maxAge() has been deprecated with message: 3.6.5 Use withMaxAge() instead.

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.

Loading history...
1430
        if (!$time) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $time of type integer|null is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
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 . '"');
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::header() has been deprecated with message: 3.4.0 Use `withHeader()`, `getHeaderLine()` and `getHeaders()` instead.

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.

Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $params of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $cookie defined by $this->_cookies->get($options) on line 2187 can be null; however, Cake\Http\Response::convertCookieToArray() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
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),
0 ignored issues
show
Security Bug introduced by
It seems like \DateTime::createFromFormat('U', 1) targeting DateTime::createFromFormat() can also be of type false; however, Cake\Http\Cookie\Cookie::__construct() does only seem to accept object<DateTime>|object<DateTimeImmutable>|null, did you maybe forget to handle an error condition?
Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $cookie defined by $this->_cookies->get($name) on line 2358 can be null; however, Cake\Http\Response::convertCookieToArray() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
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) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::type() has been deprecated with message: 3.5.5 Use getType() or withType() instead.

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.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::type() has been deprecated with message: 3.5.5 Use getType() or withType() instead.

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.

Loading history...
2549
            }
2550
            if ($options['name'] === null) {
2551
                $name = $file->name;
2552
            } else {
2553
                $name = $options['name'];
2554
            }
2555
            $this->download($name);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::download() has been deprecated with message: 3.4.0 Use withDownload() instead.

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.

Loading history...
2556
            $this->header('Content-Transfer-Encoding', 'binary');
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::header() has been deprecated with message: 3.4.0 Use `withHeader()`, `getHeaderLine()` and `getHeaders()` instead.

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.

Loading history...
2557
        }
2558
2559
        $this->header('Accept-Ranges', 'bytes');
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::header() has been deprecated with message: 3.4.0 Use `withHeader()`, `getHeaderLine()` and `getHeaders()` instead.

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.

Loading history...
2560
        $httpRange = env('HTTP_RANGE');
2561
        if (isset($httpRange)) {
2562
            $this->_fileRange($file, $httpRange);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::_fileRange() has been deprecated with message: 3.4.0 Long term this needs to be refactored to follow immutable paradigms. However for now, it is simpler to leave this alone.

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.

Loading history...
2563
        } else {
2564
            $this->header('Content-Length', $fileSize);
0 ignored issues
show
Documentation introduced by
$fileSize is of type integer|false, but the function expects a string|array|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Deprecated Code introduced by
The method Cake\Http\Response::header() has been deprecated with message: 3.4.0 Use `withHeader()`, `getHeaderLine()` and `getHeaders()` instead.

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.

Loading history...
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);
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::_fileRange() has been deprecated with message: 3.4.0 Long term this needs to be refactored to follow immutable paradigms. However for now, it is simpler to leave this alone.

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.

Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $matches of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $range of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
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()) {
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Http\Response::_isActive() has been deprecated with message: 3.4.0 Will be removed in 4.0.0

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.

Loading history...
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