1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
20: namespace Cake\Http;
21:
22: use Cake\Core\Configure;
23: use Cake\Log\Log;
24: use Psr\Http\Message\ResponseInterface;
25: use Zend\Diactoros\RelativeStream;
26: use Zend\Diactoros\Response\EmitterInterface;
27:
28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39:
40: class ResponseEmitter implements EmitterInterface
41: {
42: 43: 44:
45: public function emit(ResponseInterface $response, $maxBufferLength = 8192)
46: {
47: $file = $line = null;
48: if (headers_sent($file, $line)) {
49: $message = "Unable to emit headers. Headers sent in file=$file line=$line";
50: if (Configure::read('debug')) {
51: trigger_error($message, E_USER_WARNING);
52: } else {
53: Log::warning($message);
54: }
55: }
56:
57: $this->emitStatusLine($response);
58: $this->emitHeaders($response);
59: $this->flush();
60:
61: $range = $this->parseContentRange($response->getHeaderLine('Content-Range'));
62: if (is_array($range)) {
63: $this->emitBodyRange($range, $response, $maxBufferLength);
64: } else {
65: $this->emitBody($response, $maxBufferLength);
66: }
67:
68: if (function_exists('fastcgi_finish_request')) {
69: session_write_close();
70: fastcgi_finish_request();
71: }
72: }
73:
74: 75: 76: 77: 78: 79: 80:
81: protected function emitBody(ResponseInterface $response, $maxBufferLength)
82: {
83: if (in_array($response->getStatusCode(), [204, 304])) {
84: return;
85: }
86: $body = $response->getBody();
87:
88: if (!$body->isSeekable()) {
89: echo $body;
90:
91: return;
92: }
93:
94: $body->rewind();
95: while (!$body->eof()) {
96: echo $body->read($maxBufferLength);
97: }
98: }
99:
100: 101: 102: 103: 104: 105: 106: 107:
108: protected function emitBodyRange(array $range, ResponseInterface $response, $maxBufferLength)
109: {
110: list($unit, $first, $last, $length) = $range;
111:
112: $body = $response->getBody();
113:
114: if (!$body->isSeekable()) {
115: $contents = $body->getContents();
116: echo substr($contents, $first, $last - $first + 1);
117:
118: return;
119: }
120:
121: $body = new RelativeStream($body, $first);
122: $body->rewind();
123: $pos = 0;
124: $length = $last - $first + 1;
125: while (!$body->eof() && $pos < $length) {
126: if (($pos + $maxBufferLength) > $length) {
127: echo $body->read($length - $pos);
128: break;
129: }
130:
131: echo $body->read($maxBufferLength);
132: $pos = $body->tell();
133: }
134: }
135:
136: 137: 138: 139: 140: 141: 142: 143: 144:
145: protected function emitStatusLine(ResponseInterface $response)
146: {
147: $reasonPhrase = $response->getReasonPhrase();
148: header(sprintf(
149: 'HTTP/%s %d%s',
150: $response->getProtocolVersion(),
151: $response->getStatusCode(),
152: ($reasonPhrase ? ' ' . $reasonPhrase : '')
153: ));
154: }
155:
156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166:
167: protected function emitHeaders(ResponseInterface $response)
168: {
169: $cookies = [];
170: if (method_exists($response, 'getCookies')) {
171: $cookies = $response->getCookies();
172: }
173:
174: foreach ($response->getHeaders() as $name => $values) {
175: if (strtolower($name) === 'set-cookie') {
176: $cookies = array_merge($cookies, $values);
177: continue;
178: }
179: $first = true;
180: foreach ($values as $value) {
181: header(sprintf(
182: '%s: %s',
183: $name,
184: $value
185: ), $first);
186: $first = false;
187: }
188: }
189:
190: $this->emitCookies($cookies);
191: }
192:
193: 194: 195: 196: 197: 198:
199: protected function emitCookies(array $cookies)
200: {
201: foreach ($cookies as $cookie) {
202: if (is_array($cookie)) {
203: setcookie(
204: $cookie['name'],
205: $cookie['value'],
206: $cookie['expire'],
207: $cookie['path'],
208: $cookie['domain'],
209: $cookie['secure'],
210: $cookie['httpOnly']
211: );
212: continue;
213: }
214:
215: if (strpos($cookie, '";"') !== false) {
216: $cookie = str_replace('";"', '{__cookie_replace__}', $cookie);
217: $parts = str_replace('{__cookie_replace__}', '";"', explode(';', $cookie));
218: } else {
219: $parts = preg_split('/\;[ \t]*/', $cookie);
220: }
221:
222: list($name, $value) = explode('=', array_shift($parts), 2);
223: $data = [
224: 'name' => urldecode($name),
225: 'value' => urldecode($value),
226: 'expires' => 0,
227: 'path' => '',
228: 'domain' => '',
229: 'secure' => false,
230: 'httponly' => false
231: ];
232:
233: foreach ($parts as $part) {
234: if (strpos($part, '=') !== false) {
235: list($key, $value) = explode('=', $part);
236: } else {
237: $key = $part;
238: $value = true;
239: }
240:
241: $key = strtolower($key);
242: $data[$key] = $value;
243: }
244: if (!empty($data['expires'])) {
245: $data['expires'] = strtotime($data['expires']);
246: }
247: setcookie(
248: $data['name'],
249: $data['value'],
250: $data['expires'],
251: $data['path'],
252: $data['domain'],
253: $data['secure'],
254: $data['httponly']
255: );
256: }
257: }
258:
259: 260: 261: 262: 263: 264: 265:
266: protected function flush($maxBufferLevel = null)
267: {
268: if (null === $maxBufferLevel) {
269: $maxBufferLevel = ob_get_level();
270: }
271:
272: while (ob_get_level() > $maxBufferLevel) {
273: ob_end_flush();
274: }
275: }
276:
277: 278: 279: 280: 281: 282: 283: 284:
285: protected function parseContentRange($header)
286: {
287: if (preg_match('/(?P<unit>[\w]+)\s+(?P<first>\d+)-(?P<last>\d+)\/(?P<length>\d+|\*)/', $header, $matches)) {
288: return [
289: $matches['unit'],
290: (int)$matches['first'],
291: (int)$matches['last'],
292: $matches['length'] === '*' ? '*' : (int)$matches['length'],
293: ];
294: }
295:
296: return false;
297: }
298: }
299: