Emitter::parseContentRange()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 0
cts 12
cp 0
rs 9.8666
c 0
b 0
f 0
cc 3
nc 3
nop 1
crap 12
1
<?php
2
3
namespace vakata\http;
4
5
use RuntimeException;
6
use Laminas\Diactoros\Response as PSRResponse;
7
8
use function ob_get_length;
9
use function ob_get_level;
10
use function sprintf;
11
use function str_replace;
12
use function ucwords;
13
14
class Emitter
15
{
16
    /**
17
     * Checks to see if content has previously been sent.
18
     *
19
     * If either headers have been sent or the output buffer contains content,
20
     * raises an exception.
21
     *
22
     * @throws RuntimeException if headers have already been sent.
23
     * @throws RuntimeException if output is present in the output buffer.
24
     */
25
    private function assertNoPreviousOutput()
26
    {
27
        if (headers_sent()) {
28
            throw new RuntimeException('Unable to emit response; headers already sent');
29
        }
30
31
        if (ob_get_level() > 0 && ob_get_length() > 0) {
32
            throw new RuntimeException('Output has been emitted previously; cannot emit response');
33
        }
34
    }
35
36
    /**
37
     * Emit the status line.
38
     *
39
     * Emits the status line using the protocol version and status code from
40
     * the response; if a reason phrase is available, it, too, is emitted.
41
     *
42
     * It is important to mention that this method should be called after
43
     * `emitHeaders()` in order to prevent PHP from changing the status code of
44
     * the emitted response.
45
     *
46
     * @param PSRResponse $response
47
     *
48
     * @see \Laminas\Diactoros\Response\SapiEmitterTrait::emitHeaders()
49
     */
50
    private function emitStatusLine(PSRResponse $response)
51
    {
52
        $reasonPhrase = $response->getReasonPhrase();
53
        $statusCode   = $response->getStatusCode();
54
55
        header(sprintf(
56
            'HTTP/%s %d%s',
57
            $response->getProtocolVersion(),
58
            $statusCode,
59
            ($reasonPhrase ? ' ' . $reasonPhrase : '')
60
        ), true, $statusCode);
61
    }
62
63
    /**
64
     * Emit response headers.
65
     *
66
     * Loops through each header, emitting each; if the header value
67
     * is an array with multiple values, ensures that each is sent
68
     * in such a way as to create aggregate headers (instead of replace
69
     * the previous).
70
     *
71
     * @param PSRResponse $response
72
     */
73
    private function emitHeaders(PSRResponse $response)
74
    {
75
        $statusCode = $response->getStatusCode();
76
77
        foreach ($response->getHeaders() as $header => $values) {
78
            $name  = $this->filterHeader($header);
79
            $first = $name === 'Set-Cookie' ? false : true;
80
            foreach ($values as $value) {
81
                header(sprintf(
82
                    '%s: %s',
83
                    $name,
84
                    $value
85
                ), $first, $statusCode);
86
                $first = false;
87
            }
88
        }
89
    }
90
91
    /**
92
     * Filter a header name to wordcase
93
     *
94
     * @param string $header
95
     * @return string
96
     */
97
    private function filterHeader($header)
98
    {
99
        $filtered = str_replace('-', ' ', $header);
100
        $filtered = ucwords($filtered);
101
        return str_replace(' ', '-', $filtered);
102
    }
103
104
    /**
105
     * Emits a response for a PHP SAPI environment.
106
     *
107
     * Emits the status line and headers via the header() function, and the
108
     * body content via the output buffer.
109
     *
110
     * @param PSRResponse $response
111
     */
112
    public function emit(PSRResponse $response, $maxBufferLength = 8192)
113
    {
114
        $this->assertNoPreviousOutput();
115
116
        $this->emitHeaders($response);
117
        $this->emitStatusLine($response);
118
        
119
        if (($response instanceof Response) && $response->hasCallback()) {
120
            call_user_func($response->getCallback());
121
        } elseif ($response->hasHeader('Accept-Ranges')) {
122
            $range = $this->parseContentRange($response->getHeaderLine('Content-Range'));
123
            if (is_array($range) && $range[0] === 'bytes') {
124
                $this->emitBodyRange($range, $response, $maxBufferLength);
125
            } else {
126
                $body = $response->getBody();
127
                if ($body->isSeekable()) {
128
                    $body->rewind();
129
                }
130
                if (!$body->isReadable()) {
131
                    echo $body;
132
                } else {
133
                    while (!$body->eof()) {
134
                        echo $body->read($maxBufferLength);
135
                    }
136
                }
137
            }
138
        } else {
139
            echo $response->getBody();
140
        }
141
    }
142
143
    /**
144
     * Parse content-range header
145
     * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.16
146
     *
147
     * @param string $header
148
     * @return false|array [unit, first, last, length]; returns false if no
149
     *     content range or an invalid content range is provided
150
     */
151
    private function parseContentRange($header)
152
    {
153
        if (preg_match('/(?P<unit>[\w]+)\s+(?P<first>\d+)-(?P<last>\d+)\/(?P<length>\d+|\*)/', $header, $matches)) {
154
            return [
155
                $matches['unit'],
156
                (int) $matches['first'],
157
                (int) $matches['last'],
158
                $matches['length'] === '*' ? '*' : (int) $matches['length'],
159
            ];
160
        }
161
        return false;
162
    }
163
    /**
164
     * Emit a range of the message body.
165
     *
166
     * @param array $range
167
     * @param PSRResponse $response
168
     * @param int $maxBufferLength
169
     */
170
    private function emitBodyRange(array $range, PSRResponse $response, $maxBufferLength)
171
    {
172
        list($unit, $first, $last, $length) = $range;
0 ignored issues
show
Unused Code introduced by
The assignment to $unit is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $length is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
173
174
        $body = $response->getBody();
175
176
        $length = $last - $first + 1;
177
178
        if ($body->isSeekable()) {
179
            $body->seek($first);
180
181
            $first = 0;
182
        }
183
184
        if (! $body->isReadable()) {
185
            echo substr($body->getContents(), $first, $length);
186
            return;
187
        }
188
189
        $remaining = $length;
190
191
        while ($remaining >= $maxBufferLength && ! $body->eof()) {
192
            $contents   = $body->read($maxBufferLength);
193
            $remaining -= strlen($contents);
194
195
            echo $contents;
196
        }
197
198
        if ($remaining > 0 && ! $body->eof()) {
199
            echo $body->read($remaining);
200
        }
201
    }
202
}
203