7dfce3d6a518828ff2ba8bf482ce7fac27b94384
[GitHub/WoltLab/WCF.git] /
1 <?php
2
3 /**
4 * @see https://github.com/laminas/laminas-httphandlerrunner for the canonical source repository
5 * @copyright https://github.com/laminas/laminas-httphandlerrunner/blob/master/COPYRIGHT.md
6 * @license https://github.com/laminas/laminas-httphandlerrunner/blob/master/LICENSE.md New BSD License
7 */
8
9 declare(strict_types=1);
10
11 namespace Laminas\HttpHandlerRunner\Emitter;
12
13 use Psr\Http\Message\ResponseInterface;
14
15 use function preg_match;
16 use function strlen;
17 use function substr;
18
19 class SapiStreamEmitter implements EmitterInterface
20 {
21 use SapiEmitterTrait;
22
23 /**
24 * @var int Maximum output buffering size for each iteration.
25 */
26 private $maxBufferLength;
27
28 public function __construct(int $maxBufferLength = 8192)
29 {
30 $this->maxBufferLength = $maxBufferLength;
31 }
32
33 /**
34 * Emits a response for a PHP SAPI environment.
35 *
36 * Emits the status line and headers via the header() function, and the
37 * body content via the output buffer.
38 */
39 public function emit(ResponseInterface $response) : bool
40 {
41 $this->assertNoPreviousOutput();
42 $this->emitHeaders($response);
43 $this->emitStatusLine($response);
44
45 flush();
46
47 $range = $this->parseContentRange($response->getHeaderLine('Content-Range'));
48
49 if (null === $range || 'bytes' !== $range[0]) {
50 $this->emitBody($response);
51 return true;
52 }
53
54 $this->emitBodyRange($range, $response);
55 return true;
56 }
57
58 /**
59 * Emit the message body.
60 */
61 private function emitBody(ResponseInterface $response) : void
62 {
63 $body = $response->getBody();
64
65 if ($body->isSeekable()) {
66 $body->rewind();
67 }
68
69 if (! $body->isReadable()) {
70 echo $body;
71 return;
72 }
73
74 while (! $body->eof()) {
75 echo $body->read($this->maxBufferLength);
76 }
77 }
78
79 /**
80 * Emit a range of the message body.
81 */
82 private function emitBodyRange(array $range, ResponseInterface $response) : void
83 {
84 list($unit, $first, $last, $length) = $range;
85
86 $body = $response->getBody();
87
88 $length = $last - $first + 1;
89
90 if ($body->isSeekable()) {
91 $body->seek($first);
92
93 $first = 0;
94 }
95
96 if (! $body->isReadable()) {
97 echo substr($body->getContents(), $first, $length);
98 return;
99 }
100
101 $remaining = $length;
102
103 while ($remaining >= $this->maxBufferLength && ! $body->eof()) {
104 $contents = $body->read($this->maxBufferLength);
105 $remaining -= strlen($contents);
106
107 echo $contents;
108 }
109
110 if ($remaining > 0 && ! $body->eof()) {
111 echo $body->read($remaining);
112 }
113 }
114
115 /**
116 * Parse content-range header
117 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.16
118 *
119 * @return null|array [unit, first, last, length]; returns null if no
120 * content range or an invalid content range is provided
121 */
122 private function parseContentRange(string $header) : ?array
123 {
124 if (! preg_match('/(?P<unit>[\w]+)\s+(?P<first>\d+)-(?P<last>\d+)\/(?P<length>\d+|\*)/', $header, $matches)) {
125 return null;
126 }
127
128 return [
129 $matches['unit'],
130 (int) $matches['first'],
131 (int) $matches['last'],
132 $matches['length'] === '*' ? '*' : (int) $matches['length'],
133 ];
134 }
135 }