Completed
Push — json-pretty ( 698f4c )
by Carsten
06:07
created

Response::getIsForbidden()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2
Metric Value
dl 0
loc 4
rs 10
ccs 0
cts 2
cp 0
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\web;
9
10
use Yii;
11
use yii\base\InvalidConfigException;
12
use yii\base\InvalidParamException;
13
use yii\helpers\Url;
14
use yii\helpers\FileHelper;
15
use yii\helpers\StringHelper;
16
17
/**
18
 * The web Response class represents an HTTP response
19
 *
20
 * It holds the [[headers]], [[cookies]] and [[content]] that is to be sent to the client.
21
 * It also controls the HTTP [[statusCode|status code]].
22
 *
23
 * Response is configured as an application component in [[\yii\web\Application]] by default.
24
 * You can access that instance via `Yii::$app->response`.
25
 *
26
 * You can modify its configuration by adding an array to your application config under `components`
27
 * as it is shown in the following example:
28
 *
29
 * ```php
30
 * 'response' => [
31
 *     'format' => yii\web\Response::FORMAT_JSON,
32
 *     'charset' => 'UTF-8',
33
 *     // ...
34
 * ]
35
 * ```
36
 *
37
 * @property CookieCollection $cookies The cookie collection. This property is read-only.
38
 * @property string $downloadHeaders The attachment file name. This property is write-only.
39
 * @property HeaderCollection $headers The header collection. This property is read-only.
40
 * @property boolean $isClientError Whether this response indicates a client error. This property is
41
 * read-only.
42
 * @property boolean $isEmpty Whether this response is empty. This property is read-only.
43
 * @property boolean $isForbidden Whether this response indicates the current request is forbidden. This
44
 * property is read-only.
45
 * @property boolean $isInformational Whether this response is informational. This property is read-only.
46
 * @property boolean $isInvalid Whether this response has a valid [[statusCode]]. This property is read-only.
47
 * @property boolean $isNotFound Whether this response indicates the currently requested resource is not
48
 * found. This property is read-only.
49
 * @property boolean $isOk Whether this response is OK. This property is read-only.
50
 * @property boolean $isRedirection Whether this response is a redirection. This property is read-only.
51
 * @property boolean $isServerError Whether this response indicates a server error. This property is
52
 * read-only.
53
 * @property boolean $isSuccessful Whether this response is successful. This property is read-only.
54
 * @property integer $statusCode The HTTP status code to send with the response.
55
 *
56
 * @author Qiang Xue <[email protected]>
57
 * @author Carsten Brandt <[email protected]>
58
 * @since 2.0
59
 */
60
class Response extends \yii\base\Response
61
{
62
    /**
63
     * @event ResponseEvent an event that is triggered at the beginning of [[send()]].
64
     */
65
    const EVENT_BEFORE_SEND = 'beforeSend';
66
    /**
67
     * @event ResponseEvent an event that is triggered at the end of [[send()]].
68
     */
69
    const EVENT_AFTER_SEND = 'afterSend';
70
    /**
71
     * @event ResponseEvent an event that is triggered right after [[prepare()]] is called in [[send()]].
72
     * You may respond to this event to filter the response content before it is sent to the client.
73
     */
74
    const EVENT_AFTER_PREPARE = 'afterPrepare';
75
    const FORMAT_RAW = 'raw';
76
    const FORMAT_HTML = 'html';
77
    const FORMAT_JSON = 'json';
78
    const FORMAT_JSONP = 'jsonp';
79
    const FORMAT_XML = 'xml';
80
81
    /**
82
     * @var string the response format. This determines how to convert [[data]] into [[content]]
83
     * when the latter is not set. The value of this property must be one of the keys declared in the [[formatters] array.
84
     * By default, the following formats are supported:
85
     *
86
     * - [[FORMAT_RAW]]: the data will be treated as the response content without any conversion.
87
     *   No extra HTTP header will be added.
88
     * - [[FORMAT_HTML]]: the data will be treated as the response content without any conversion.
89
     *   The "Content-Type" header will set as "text/html".
90
     * - [[FORMAT_JSON]]: the data will be converted into JSON format, and the "Content-Type"
91
     *   header will be set as "application/json".
92
     * - [[FORMAT_JSONP]]: the data will be converted into JSONP format, and the "Content-Type"
93
     *   header will be set as "text/javascript". Note that in this case `$data` must be an array
94
     *   with "data" and "callback" elements. The former refers to the actual data to be sent,
95
     *   while the latter refers to the name of the JavaScript callback.
96
     * - [[FORMAT_XML]]: the data will be converted into XML format. Please refer to [[XmlResponseFormatter]]
97
     *   for more details.
98
     *
99
     * You may customize the formatting process or support additional formats by configuring [[formatters]].
100
     * @see formatters
101
     */
102
    public $format = self::FORMAT_HTML;
103
    /**
104
     * @var string the MIME type (e.g. `application/json`) from the request ACCEPT header chosen for this response.
105
     * This property is mainly set by [[\yii\filters\ContentNegotiator]].
106
     */
107
    public $acceptMimeType;
108
    /**
109
     * @var array the parameters (e.g. `['q' => 1, 'version' => '1.0']`) associated with the [[acceptMimeType|chosen MIME type]].
110
     * This is a list of name-value pairs associated with [[acceptMimeType]] from the ACCEPT HTTP header.
111
     * This property is mainly set by [[\yii\filters\ContentNegotiator]].
112
     */
113
    public $acceptParams = [];
114
    /**
115
     * @var array the formatters for converting data into the response content of the specified [[format]].
116
     * The array keys are the format names, and the array values are the corresponding configurations
117
     * for creating the formatter objects.
118
     * @see format
119
     * @see defaultFormatters
120
     */
121
    public $formatters = [];
122
    /**
123
     * @var mixed the original response data. When this is not null, it will be converted into [[content]]
124
     * according to [[format]] when the response is being sent out.
125
     * @see content
126
     */
127
    public $data;
128
    /**
129
     * @var string the response content. When [[data]] is not null, it will be converted into [[content]]
130
     * according to [[format]] when the response is being sent out.
131
     * @see data
132
     */
133
    public $content;
134
    /**
135
     * @var resource|array the stream to be sent. This can be a stream handle or an array of stream handle,
136
     * the begin position and the end position. Note that when this property is set, the [[data]] and [[content]]
137
     * properties will be ignored by [[send()]].
138
     */
139
    public $stream;
140
    /**
141
     * @var string the charset of the text response. If not set, it will use
142
     * the value of [[Application::charset]].
143
     */
144
    public $charset;
145
    /**
146
     * @var string the HTTP status description that comes together with the status code.
147
     * @see httpStatuses
148
     */
149
    public $statusText = 'OK';
150
    /**
151
     * @var string the version of the HTTP protocol to use. If not set, it will be determined via `$_SERVER['SERVER_PROTOCOL']`,
152
     * or '1.1' if that is not available.
153
     */
154
    public $version;
155
    /**
156
     * @var boolean whether the response has been sent. If this is true, calling [[send()]] will do nothing.
157
     */
158
    public $isSent = false;
159
    /**
160
     * @var array list of HTTP status codes and the corresponding texts
161
     */
162
    public static $httpStatuses = [
163
        100 => 'Continue',
164
        101 => 'Switching Protocols',
165
        102 => 'Processing',
166
        118 => 'Connection timed out',
167
        200 => 'OK',
168
        201 => 'Created',
169
        202 => 'Accepted',
170
        203 => 'Non-Authoritative',
171
        204 => 'No Content',
172
        205 => 'Reset Content',
173
        206 => 'Partial Content',
174
        207 => 'Multi-Status',
175
        208 => 'Already Reported',
176
        210 => 'Content Different',
177
        226 => 'IM Used',
178
        300 => 'Multiple Choices',
179
        301 => 'Moved Permanently',
180
        302 => 'Found',
181
        303 => 'See Other',
182
        304 => 'Not Modified',
183
        305 => 'Use Proxy',
184
        306 => 'Reserved',
185
        307 => 'Temporary Redirect',
186
        308 => 'Permanent Redirect',
187
        310 => 'Too many Redirect',
188
        400 => 'Bad Request',
189
        401 => 'Unauthorized',
190
        402 => 'Payment Required',
191
        403 => 'Forbidden',
192
        404 => 'Not Found',
193
        405 => 'Method Not Allowed',
194
        406 => 'Not Acceptable',
195
        407 => 'Proxy Authentication Required',
196
        408 => 'Request Time-out',
197
        409 => 'Conflict',
198
        410 => 'Gone',
199
        411 => 'Length Required',
200
        412 => 'Precondition Failed',
201
        413 => 'Request Entity Too Large',
202
        414 => 'Request-URI Too Long',
203
        415 => 'Unsupported Media Type',
204
        416 => 'Requested range unsatisfiable',
205
        417 => 'Expectation failed',
206
        418 => 'I\'m a teapot',
207
        422 => 'Unprocessable entity',
208
        423 => 'Locked',
209
        424 => 'Method failure',
210
        425 => 'Unordered Collection',
211
        426 => 'Upgrade Required',
212
        428 => 'Precondition Required',
213
        429 => 'Too Many Requests',
214
        431 => 'Request Header Fields Too Large',
215
        449 => 'Retry With',
216
        450 => 'Blocked by Windows Parental Controls',
217
        500 => 'Internal Server Error',
218
        501 => 'Not Implemented',
219
        502 => 'Bad Gateway or Proxy Error',
220
        503 => 'Service Unavailable',
221
        504 => 'Gateway Time-out',
222
        505 => 'HTTP Version not supported',
223
        507 => 'Insufficient storage',
224
        508 => 'Loop Detected',
225
        509 => 'Bandwidth Limit Exceeded',
226
        510 => 'Not Extended',
227
        511 => 'Network Authentication Required',
228
    ];
229
230
    /**
231
     * @var integer the HTTP status code to send with the response.
232
     */
233
    private $_statusCode = 200;
234
    /**
235
     * @var HeaderCollection
236
     */
237
    private $_headers;
238
239
240
    /**
241
     * Initializes this component.
242
     */
243 82
    public function init()
244
    {
245 82
        if ($this->version === null) {
246 82
            if (isset($_SERVER['SERVER_PROTOCOL']) && $_SERVER['SERVER_PROTOCOL'] === 'HTTP/1.0') {
247
                $this->version = '1.0';
248
            } else {
249 82
                $this->version = '1.1';
250
            }
251 82
        }
252 82
        if ($this->charset === null) {
253 82
            $this->charset = Yii::$app->charset;
254 82
        }
255 82
        $this->formatters = array_merge($this->defaultFormatters(), $this->formatters);
256 82
    }
257
258
    /**
259
     * @return integer the HTTP status code to send with the response.
260
     */
261 4
    public function getStatusCode()
262
    {
263 4
        return $this->_statusCode;
264
    }
265
266
    /**
267
     * Sets the response status code.
268
     * This method will set the corresponding status text if `$text` is null.
269
     * @param integer $value the status code
270
     * @param string $text the status text. If not set, it will be set automatically based on the status code.
271
     * @throws InvalidParamException if the status code is invalid.
272
     */
273 4
    public function setStatusCode($value, $text = null)
274
    {
275 4
        if ($value === null) {
276
            $value = 200;
277
        }
278 4
        $this->_statusCode = (int) $value;
279 4
        if ($this->getIsInvalid()) {
280
            throw new InvalidParamException("The HTTP status code is invalid: $value");
281
        }
282 4
        if ($text === null) {
283 4
            $this->statusText = isset(static::$httpStatuses[$this->_statusCode]) ? static::$httpStatuses[$this->_statusCode] : '';
284 4
        } else {
285
            $this->statusText = $text;
286
        }
287 4
    }
288
289
    /**
290
     * Returns the header collection.
291
     * The header collection contains the currently registered HTTP headers.
292
     * @return HeaderCollection the header collection
293
     */
294 42
    public function getHeaders()
295
    {
296 42
        if ($this->_headers === null) {
297 42
            $this->_headers = new HeaderCollection;
298 42
        }
299 42
        return $this->_headers;
300
    }
301
302
    /**
303
     * Sends the response to the client.
304
     */
305 4
    public function send()
306
    {
307 4
        if ($this->isSent) {
308
            return;
309
        }
310 4
        $this->trigger(self::EVENT_BEFORE_SEND);
311 4
        $this->prepare();
312 4
        $this->trigger(self::EVENT_AFTER_PREPARE);
313 4
        $this->sendHeaders();
314 4
        $this->sendContent();
315 4
        $this->trigger(self::EVENT_AFTER_SEND);
316 4
        $this->isSent = true;
317 4
    }
318
319
    /**
320
     * Clears the headers, cookies, content, status code of the response.
321
     */
322
    public function clear()
323
    {
324
        $this->_headers = null;
325
        $this->_cookies = null;
326
        $this->_statusCode = 200;
327
        $this->statusText = 'OK';
328
        $this->data = null;
329
        $this->stream = null;
330
        $this->content = null;
331
        $this->isSent = false;
332
    }
333
334
    /**
335
     * Sends the response headers to the client
336
     */
337 4
    protected function sendHeaders()
338
    {
339 4
        if (headers_sent()) {
340 4
            return;
341
        }
342
        $statusCode = $this->getStatusCode();
343
        header("HTTP/{$this->version} $statusCode {$this->statusText}");
344
        if ($this->_headers) {
345
            $headers = $this->getHeaders();
346
            foreach ($headers as $name => $values) {
347
                $name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name)));
348
                // set replace for first occurrence of header but false afterwards to allow multiple
349
                $replace = true;
350
                foreach ($values as $value) {
351
                    header("$name: $value", $replace);
352
                    $replace = false;
353
                }
354
            }
355
        }
356
        $this->sendCookies();
357
    }
358
359
    /**
360
     * Sends the cookies to the client.
361
     */
362
    protected function sendCookies()
363
    {
364
        if ($this->_cookies === null) {
365
            return;
366
        }
367
        $request = Yii::$app->getRequest();
368
        if ($request->enableCookieValidation) {
369
            if ($request->cookieValidationKey == '') {
370
                throw new InvalidConfigException(get_class($request) . '::cookieValidationKey must be configured with a secret key.');
371
            }
372
            $validationKey = $request->cookieValidationKey;
373
        }
374
        foreach ($this->getCookies() as $cookie) {
0 ignored issues
show
Bug introduced by
The expression $this->getCookies() of type object<yii\web\CookieCollection>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
375
            $value = $cookie->value;
376
            if ($cookie->expire != 1  && isset($validationKey)) {
377
                $value = Yii::$app->getSecurity()->hashData(serialize([$cookie->name, $value]), $validationKey);
378
            }
379
            setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly);
380
        }
381
    }
382
383
    /**
384
     * Sends the response content to the client
385
     */
386 4
    protected function sendContent()
387
    {
388 4
        if ($this->stream === null) {
389 1
            echo $this->content;
390
391 1
            return;
392
        }
393
394 3
        set_time_limit(0); // Reset time limit for big files
395 3
        $chunkSize = 8 * 1024 * 1024; // 8MB per chunk
396
397 3
        if (is_array($this->stream)) {
398 3
            list ($handle, $begin, $end) = $this->stream;
399 3
            fseek($handle, $begin);
400 3
            while (!feof($handle) && ($pos = ftell($handle)) <= $end) {
401 3
                if ($pos + $chunkSize > $end) {
402 3
                    $chunkSize = $end - $pos + 1;
403 3
                }
404 3
                echo fread($handle, $chunkSize);
405 3
                flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit.
406 3
            }
407 3
            fclose($handle);
408 3
        } else {
409
            while (!feof($this->stream)) {
410
                echo fread($this->stream, $chunkSize);
411
                flush();
412
            }
413
            fclose($this->stream);
414
        }
415 3
    }
416
417
    /**
418
     * Sends a file to the browser.
419
     *
420
     * Note that this method only prepares the response for file sending. The file is not sent
421
     * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action.
422
     *
423
     * @param string $filePath the path of the file to be sent.
424
     * @param string $attachmentName the file name shown to the user. If null, it will be determined from `$filePath`.
425
     * @param array $options additional options for sending the file. The following options are supported:
426
     *
427
     *  - `mimeType`: the MIME type of the content. If not set, it will be guessed based on `$filePath`
428
     *  - `inline`: boolean, whether the browser should open the file within the browser window. Defaults to false,
429
     *    meaning a download dialog will pop up.
430
     *
431
     * @return $this the response object itself
432
     */
433 7
    public function sendFile($filePath, $attachmentName = null, $options = [])
434
    {
435 7
        if (!isset($options['mimeType'])) {
436 7
            $options['mimeType'] = FileHelper::getMimeTypeByExtension($filePath);
437 7
        }
438 7
        if ($attachmentName === null) {
439 7
            $attachmentName = basename($filePath);
440 7
        }
441 7
        $handle = fopen($filePath, 'rb');
442 7
        $this->sendStreamAsFile($handle, $attachmentName, $options);
443
444 3
        return $this;
445
    }
446
447
    /**
448
     * Sends the specified content as a file to the browser.
449
     *
450
     * Note that this method only prepares the response for file sending. The file is not sent
451
     * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action.
452
     *
453
     * @param string $content the content to be sent. The existing [[content]] will be discarded.
454
     * @param string $attachmentName the file name shown to the user.
455
     * @param array $options additional options for sending the file. The following options are supported:
456
     *
457
     *  - `mimeType`: the MIME type of the content. Defaults to 'application/octet-stream'.
458
     *  - `inline`: boolean, whether the browser should open the file within the browser window. Defaults to false,
459
     *    meaning a download dialog will pop up.
460
     *
461
     * @return $this the response object itself
462
     * @throws HttpException if the requested range is not satisfiable
463
     */
464 1
    public function sendContentAsFile($content, $attachmentName, $options = [])
465
    {
466 1
        $headers = $this->getHeaders();
467
468 1
        $contentLength = StringHelper::byteLength($content);
469 1
        $range = $this->getHttpRange($contentLength);
470
471 1
        if ($range === false) {
472
            $headers->set('Content-Range', "bytes */$contentLength");
473
            throw new HttpException(416, 'Requested range not satisfiable');
474
        }
475
476 1
        list($begin, $end) = $range;
477 1
        if ($begin != 0 || $end != $contentLength - 1) {
478
            $this->setStatusCode(206);
479
            $headers->set('Content-Range', "bytes $begin-$end/$contentLength");
480
            $this->content = StringHelper::byteSubstr($content, $begin, $end - $begin + 1);
481
        } else {
482 1
            $this->setStatusCode(200);
483 1
            $this->content = $content;
484
        }
485
486 1
        $mimeType = isset($options['mimeType']) ? $options['mimeType'] : 'application/octet-stream';
487 1
        $this->setDownloadHeaders($attachmentName, $mimeType, !empty($options['inline']), $end - $begin + 1);
488
489 1
        $this->format = self::FORMAT_RAW;
490
491 1
        return $this;
492
    }
493
494
    /**
495
     * Sends the specified stream as a file to the browser.
496
     *
497
     * Note that this method only prepares the response for file sending. The file is not sent
498
     * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action.
499
     *
500
     * @param resource $handle the handle of the stream to be sent.
501
     * @param string $attachmentName the file name shown to the user.
502
     * @param array $options additional options for sending the file. The following options are supported:
503
     *
504
     *  - `mimeType`: the MIME type of the content. Defaults to 'application/octet-stream'.
505
     *  - `inline`: boolean, whether the browser should open the file within the browser window. Defaults to false,
506
     *    meaning a download dialog will pop up.
507
     *  - `fileSize`: the size of the content to stream this is useful when size of the content is known
508
     *    and the content is not seekable. Defaults to content size using `ftell()`.
509
     *    This option is available since version 2.0.4.
510
     *
511
     * @return $this the response object itself
512
     * @throws HttpException if the requested range cannot be satisfied.
513
     */
514 7
    public function sendStreamAsFile($handle, $attachmentName, $options = [])
515
    {
516 7
        $headers = $this->getHeaders();
517 7
        if (isset($options['fileSize'])) {
518
            $fileSize = $options['fileSize'];
519
        } else {
520 7
            fseek($handle, 0, SEEK_END);
521 7
            $fileSize = ftell($handle);
522
        }
523
524 7
        $range = $this->getHttpRange($fileSize);
525 7
        if ($range === false) {
526 4
            $headers->set('Content-Range', "bytes */$fileSize");
527 4
            throw new HttpException(416, 'Requested range not satisfiable');
528
        }
529
530 3
        list($begin, $end) = $range;
531 3
        if ($begin != 0 || $end != $fileSize - 1) {
532 3
            $this->setStatusCode(206);
533 3
            $headers->set('Content-Range', "bytes $begin-$end/$fileSize");
534 3
        } else {
535
            $this->setStatusCode(200);
536
        }
537
538 3
        $mimeType = isset($options['mimeType']) ? $options['mimeType'] : 'application/octet-stream';
539 3
        $this->setDownloadHeaders($attachmentName, $mimeType, !empty($options['inline']), $end - $begin + 1);
540
541 3
        $this->format = self::FORMAT_RAW;
542 3
        $this->stream = [$handle, $begin, $end];
543
544 3
        return $this;
545
    }
546
547
    /**
548
     * Sets a default set of HTTP headers for file downloading purpose.
549
     * @param string $attachmentName the attachment file name
550
     * @param string $mimeType the MIME type for the response. If null, `Content-Type` header will NOT be set.
551
     * @param boolean $inline whether the browser should open the file within the browser window. Defaults to false,
552
     * meaning a download dialog will pop up.
553
     * @param integer $contentLength the byte length of the file being downloaded. If null, `Content-Length` header will NOT be set.
554
     * @return $this the response object itself
555
     */
556 4
    public function setDownloadHeaders($attachmentName, $mimeType = null, $inline = false, $contentLength = null)
557
    {
558 4
        $headers = $this->getHeaders();
559
560 4
        $disposition = $inline ? 'inline' : 'attachment';
561 4
        $headers->setDefault('Pragma', 'public')
562 4
            ->setDefault('Accept-Ranges', 'bytes')
563 4
            ->setDefault('Expires', '0')
564 4
            ->setDefault('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
565 4
            ->setDefault('Content-Disposition', "$disposition; filename=\"$attachmentName\"");
566
567 4
        if ($mimeType !== null) {
568 4
            $headers->setDefault('Content-Type', $mimeType);
569 4
        }
570
571 4
        if ($contentLength !== null) {
572 4
            $headers->setDefault('Content-Length', $contentLength);
573 4
        }
574
575 4
        return $this;
576
    }
577
578
    /**
579
     * Determines the HTTP range given in the request.
580
     * @param integer $fileSize the size of the file that will be used to validate the requested HTTP range.
581
     * @return array|boolean the range (begin, end), or false if the range request is invalid.
582
     */
583 8
    protected function getHttpRange($fileSize)
584
    {
585 8
        if (!isset($_SERVER['HTTP_RANGE']) || $_SERVER['HTTP_RANGE'] === '-') {
586 1
            return [0, $fileSize - 1];
587
        }
588 7
        if (!preg_match('/^bytes=(\d*)-(\d*)$/', $_SERVER['HTTP_RANGE'], $matches)) {
589 1
            return false;
590
        }
591 6
        if ($matches[1] === '') {
592 2
            $start = $fileSize - $matches[2];
593 2
            $end = $fileSize - 1;
594 6
        } elseif ($matches[2] !== '') {
595 2
            $start = $matches[1];
596 2
            $end = $matches[2];
597 2
            if ($end >= $fileSize) {
598
                $end = $fileSize - 1;
599
            }
600 2
        } else {
601 2
            $start = $matches[1];
602 2
            $end = $fileSize - 1;
603
        }
604 6
        if ($start < 0 || $start > $end) {
605 3
            return false;
606
        } else {
607 3
            return [$start, $end];
608
        }
609
    }
610
611
    /**
612
     * Sends existing file to a browser as a download using x-sendfile.
613
     *
614
     * X-Sendfile is a feature allowing a web application to redirect the request for a file to the webserver
615
     * that in turn processes the request, this way eliminating the need to perform tasks like reading the file
616
     * and sending it to the user. When dealing with a lot of files (or very big files) this can lead to a great
617
     * increase in performance as the web application is allowed to terminate earlier while the webserver is
618
     * handling the request.
619
     *
620
     * The request is sent to the server through a special non-standard HTTP-header.
621
     * When the web server encounters the presence of such header it will discard all output and send the file
622
     * specified by that header using web server internals including all optimizations like caching-headers.
623
     *
624
     * As this header directive is non-standard different directives exists for different web servers applications:
625
     *
626
     * - Apache: [X-Sendfile](http://tn123.org/mod_xsendfile)
627
     * - Lighttpd v1.4: [X-LIGHTTPD-send-file](http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file)
628
     * - Lighttpd v1.5: [X-Sendfile](http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file)
629
     * - Nginx: [X-Accel-Redirect](http://wiki.nginx.org/XSendfile)
630
     * - Cherokee: [X-Sendfile and X-Accel-Redirect](http://www.cherokee-project.com/doc/other_goodies.html#x-sendfile)
631
     *
632
     * So for this method to work the X-SENDFILE option/module should be enabled by the web server and
633
     * a proper xHeader should be sent.
634
     *
635
     * **Note**
636
     *
637
     * This option allows to download files that are not under web folders, and even files that are otherwise protected
638
     * (deny from all) like `.htaccess`.
639
     *
640
     * **Side effects**
641
     *
642
     * If this option is disabled by the web server, when this method is called a download configuration dialog
643
     * will open but the downloaded file will have 0 bytes.
644
     *
645
     * **Known issues**
646
     *
647
     * There is a Bug with Internet Explorer 6, 7 and 8 when X-SENDFILE is used over an SSL connection, it will show
648
     * an error message like this: "Internet Explorer was not able to open this Internet site. The requested site
649
     * is either unavailable or cannot be found.". You can work around this problem by removing the `Pragma`-header.
650
     *
651
     * **Example**
652
     *
653
     * ```php
654
     * Yii::$app->response->xSendFile('/home/user/Pictures/picture1.jpg');
655
     * ```
656
     *
657
     * @param string $filePath file name with full path
658
     * @param string $attachmentName file name shown to the user. If null, it will be determined from `$filePath`.
659
     * @param array $options additional options for sending the file. The following options are supported:
660
     *
661
     *  - `mimeType`: the MIME type of the content. If not set, it will be guessed based on `$filePath`
662
     *  - `inline`: boolean, whether the browser should open the file within the browser window. Defaults to false,
663
     *    meaning a download dialog will pop up.
664
     *  - xHeader: string, the name of the x-sendfile header. Defaults to "X-Sendfile".
665
     *
666
     * @return $this the response object itself
667
     */
668
    public function xSendFile($filePath, $attachmentName = null, $options = [])
669
    {
670
        if ($attachmentName === null) {
671
            $attachmentName = basename($filePath);
672
        }
673
        if (isset($options['mimeType'])) {
674
            $mimeType = $options['mimeType'];
675
        } elseif (($mimeType = FileHelper::getMimeTypeByExtension($filePath)) === null) {
676
            $mimeType = 'application/octet-stream';
677
        }
678
        if (isset($options['xHeader'])) {
679
            $xHeader = $options['xHeader'];
680
        } else {
681
            $xHeader = 'X-Sendfile';
682
        }
683
684
        $disposition = empty($options['inline']) ? 'attachment' : 'inline';
685
        $this->getHeaders()
686
            ->setDefault($xHeader, $filePath)
687
            ->setDefault('Content-Type', $mimeType)
688
            ->setDefault('Content-Disposition', "{$disposition}; filename=\"{$attachmentName}\"");
689
690
        $this->format = self::FORMAT_RAW;
691
692
        return $this;
693
    }
694
695
    /**
696
     * Redirects the browser to the specified URL.
697
     *
698
     * This method adds a "Location" header to the current response. Note that it does not send out
699
     * the header until [[send()]] is called. In a controller action you may use this method as follows:
700
     *
701
     * ```php
702
     * return Yii::$app->getResponse()->redirect($url);
703
     * ```
704
     *
705
     * In other places, if you want to send out the "Location" header immediately, you should use
706
     * the following code:
707
     *
708
     * ```php
709
     * Yii::$app->getResponse()->redirect($url)->send();
710
     * return;
711
     * ```
712
     *
713
     * In AJAX mode, this normally will not work as expected unless there are some
714
     * client-side JavaScript code handling the redirection. To help achieve this goal,
715
     * this method will send out a "X-Redirect" header instead of "Location".
716
     *
717
     * If you use the "yii" JavaScript module, it will handle the AJAX redirection as
718
     * described above. Otherwise, you should write the following JavaScript code to
719
     * handle the redirection:
720
     *
721
     * ```javascript
722
     * $document.ajaxComplete(function (event, xhr, settings) {
723
     *     var url = xhr.getResponseHeader('X-Redirect');
724
     *     if (url) {
725
     *         window.location = url;
726
     *     }
727
     * });
728
     * ```
729
     *
730
     * @param string|array $url the URL to be redirected to. This can be in one of the following formats:
731
     *
732
     * - a string representing a URL (e.g. "http://example.com")
733
     * - a string representing a URL alias (e.g. "@example.com")
734
     * - an array in the format of `[$route, ...name-value pairs...]` (e.g. `['site/index', 'ref' => 1]`).
735
     *   Note that the route is with respect to the whole application, instead of relative to a controller or module.
736
     *   [[Url::to()]] will be used to convert the array into a URL.
737
     *
738
     * Any relative URL will be converted into an absolute one by prepending it with the host info
739
     * of the current request.
740
     *
741
     * @param integer $statusCode the HTTP status code. Defaults to 302.
742
     * See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html>
743
     * for details about HTTP status code
744
     * @param boolean $checkAjax whether to specially handle AJAX (and PJAX) requests. Defaults to true,
745
     * meaning if the current request is an AJAX or PJAX request, then calling this method will cause the browser
746
     * to redirect to the given URL. If this is false, a `Location` header will be sent, which when received as
747
     * an AJAX/PJAX response, may NOT cause browser redirection.
748
     * @return $this the response object itself
749
     */
750
    public function redirect($url, $statusCode = 302, $checkAjax = true)
751
    {
752
        if (is_array($url) && isset($url[0])) {
753
            // ensure the route is absolute
754
            $url[0] = '/' . ltrim($url[0], '/');
755
        }
756
        $url = Url::to($url);
757
        if (strpos($url, '/') === 0 && strpos($url, '//') !== 0) {
758
            $url = Yii::$app->getRequest()->getHostInfo() . $url;
759
        }
760
761
        if ($checkAjax) {
762
            if (Yii::$app->getRequest()->getIsPjax()) {
763
                $this->getHeaders()->set('X-Pjax-Url', $url);
764
            } elseif (Yii::$app->getRequest()->getIsAjax()) {
765
                $this->getHeaders()->set('X-Redirect', $url);
766
            } else {
767
                $this->getHeaders()->set('Location', $url);
768
            }
769
        } else {
770
            $this->getHeaders()->set('Location', $url);
771
        }
772
773
        $this->setStatusCode($statusCode);
774
775
        return $this;
776
    }
777
778
    /**
779
     * Refreshes the current page.
780
     * The effect of this method call is the same as the user pressing the refresh button of his browser
781
     * (without re-posting data).
782
     *
783
     * In a controller action you may use this method like this:
784
     *
785
     * ```php
786
     * return Yii::$app->getResponse()->refresh();
787
     * ```
788
     *
789
     * @param string $anchor the anchor that should be appended to the redirection URL.
790
     * Defaults to empty. Make sure the anchor starts with '#' if you want to specify it.
791
     * @return Response the response object itself
792
     */
793
    public function refresh($anchor = '')
794
    {
795
        return $this->redirect(Yii::$app->getRequest()->getUrl() . $anchor);
796
    }
797
798
    private $_cookies;
799
800
    /**
801
     * Returns the cookie collection.
802
     * Through the returned cookie collection, you add or remove cookies as follows,
803
     *
804
     * ```php
805
     * // add a cookie
806
     * $response->cookies->add(new Cookie([
807
     *     'name' => $name,
808
     *     'value' => $value,
809
     * ]);
810
     *
811
     * // remove a cookie
812
     * $response->cookies->remove('name');
813
     * // alternatively
814
     * unset($response->cookies['name']);
815
     * ```
816
     *
817
     * @return CookieCollection the cookie collection.
818
     */
819 18
    public function getCookies()
820
    {
821 18
        if ($this->_cookies === null) {
822 18
            $this->_cookies = new CookieCollection;
823 18
        }
824 18
        return $this->_cookies;
825
    }
826
827
    /**
828
     * @return boolean whether this response has a valid [[statusCode]].
829
     */
830 4
    public function getIsInvalid()
831
    {
832 4
        return $this->getStatusCode() < 100 || $this->getStatusCode() >= 600;
833
    }
834
835
    /**
836
     * @return boolean whether this response is informational
837
     */
838
    public function getIsInformational()
839
    {
840
        return $this->getStatusCode() >= 100 && $this->getStatusCode() < 200;
841
    }
842
843
    /**
844
     * @return boolean whether this response is successful
845
     */
846
    public function getIsSuccessful()
847
    {
848
        return $this->getStatusCode() >= 200 && $this->getStatusCode() < 300;
849
    }
850
851
    /**
852
     * @return boolean whether this response is a redirection
853
     */
854
    public function getIsRedirection()
855
    {
856
        return $this->getStatusCode() >= 300 && $this->getStatusCode() < 400;
857
    }
858
859
    /**
860
     * @return boolean whether this response indicates a client error
861
     */
862
    public function getIsClientError()
863
    {
864
        return $this->getStatusCode() >= 400 && $this->getStatusCode() < 500;
865
    }
866
867
    /**
868
     * @return boolean whether this response indicates a server error
869
     */
870
    public function getIsServerError()
871
    {
872
        return $this->getStatusCode() >= 500 && $this->getStatusCode() < 600;
873
    }
874
875
    /**
876
     * @return boolean whether this response is OK
877
     */
878
    public function getIsOk()
879
    {
880
        return $this->getStatusCode() == 200;
881
    }
882
883
    /**
884
     * @return boolean whether this response indicates the current request is forbidden
885
     */
886
    public function getIsForbidden()
887
    {
888
        return $this->getStatusCode() == 403;
889
    }
890
891
    /**
892
     * @return boolean whether this response indicates the currently requested resource is not found
893
     */
894
    public function getIsNotFound()
895
    {
896
        return $this->getStatusCode() == 404;
897
    }
898
899
    /**
900
     * @return boolean whether this response is empty
901
     */
902
    public function getIsEmpty()
903
    {
904
        return in_array($this->getStatusCode(), [201, 204, 304]);
905
    }
906
907
    /**
908
     * @return array the formatters that are supported by default
909
     */
910 82
    protected function defaultFormatters()
911
    {
912
        return [
913 82
            self::FORMAT_HTML => 'yii\web\HtmlResponseFormatter',
914 82
            self::FORMAT_XML => 'yii\web\XmlResponseFormatter',
915 82
            self::FORMAT_JSON => 'yii\web\JsonResponseFormatter',
916 82
            self::FORMAT_JSONP => [
917 82
                'class' => 'yii\web\JsonResponseFormatter',
918 82
                'useJsonp' => true,
919 82
            ],
920 82
        ];
921
    }
922
923
    /**
924
     * Prepares for sending the response.
925
     * The default implementation will convert [[data]] into [[content]] and set headers accordingly.
926
     * @throws InvalidConfigException if the formatter for the specified format is invalid or [[format]] is not supported
927
     */
928 4
    protected function prepare()
929
    {
930 4
        if ($this->stream !== null) {
931 3
            return;
932
        }
933
934 1
        if (isset($this->formatters[$this->format])) {
935
            $formatter = $this->formatters[$this->format];
936
            if (!is_object($formatter)) {
937
                $this->formatters[$this->format] = $formatter = Yii::createObject($formatter);
938
            }
939
            if ($formatter instanceof ResponseFormatterInterface) {
940
                $formatter->format($this);
941
            } else {
942
                throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatterInterface.");
943
            }
944 1
        } elseif ($this->format === self::FORMAT_RAW) {
945 1
            if ($this->data !== null) {
946
                $this->content = $this->data;
947
            }
948 1
        } else {
949
            throw new InvalidConfigException("Unsupported response format: {$this->format}");
950
        }
951
952 1
        if (is_array($this->content)) {
953
            throw new InvalidParamException('Response content must not be an array.');
954 1
        } elseif (is_object($this->content)) {
955
            if (method_exists($this->content, '__toString')) {
956
                $this->content = $this->content->__toString();
957
            } else {
958
                throw new InvalidParamException('Response content must be a string or an object implementing __toString().');
959
            }
960
        }
961 1
    }
962
}
963