1<?php
2
3declare(strict_types=1);
4
5/*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18namespace TYPO3\CMS\Core\Http;
19
20use Psr\Http\Message\ResponseInterface;
21use Psr\Http\Message\ServerRequestInterface;
22use Psr\Http\Server\RequestHandlerInterface;
23use TYPO3\CMS\Core\Core\ApplicationInterface;
24
25/**
26 * @internal
27 */
28abstract class AbstractApplication implements ApplicationInterface
29{
30    private const MULTI_LINE_HEADERS = [
31        'set-cookie',
32    ];
33
34    /**
35     * @var RequestHandlerInterface|null
36     */
37    protected $requestHandler;
38
39    /**
40     * Outputs content
41     *
42     * @param ResponseInterface $response
43     */
44    protected function sendResponse(ResponseInterface $response)
45    {
46        if ($response instanceof NullResponse) {
47            return;
48        }
49
50        // @todo This requires some merge strategy or header callback handling
51        if (!headers_sent()) {
52            // If the response code was not changed by legacy code (still is 200)
53            // then allow the PSR-7 response object to explicitly set it.
54            // Otherwise let legacy code take precedence.
55            // This code path can be deprecated once we expose the response object to third party code
56            if (http_response_code() === 200) {
57                header('HTTP/' . $response->getProtocolVersion() . ' ' . $response->getStatusCode() . ' ' . $response->getReasonPhrase());
58            }
59
60            foreach ($response->getHeaders() as $name => $values) {
61                if (in_array(strtolower($name), self::MULTI_LINE_HEADERS, true)) {
62                    foreach ($values as $value) {
63                        header($name . ': ' . $value, false);
64                    }
65                } else {
66                    header($name . ': ' . implode(', ', $values));
67                }
68            }
69        }
70        $body = $response->getBody();
71        if ($body instanceof SelfEmittableStreamInterface) {
72            // Optimization for streams that use php functions like readfile() as fastpath for serving files.
73            $body->emit();
74        } else {
75            echo $body->__toString();
76        }
77    }
78
79    /**
80     * @param ServerRequestInterface $request
81     * @return ResponseInterface
82     */
83    protected function handle(ServerRequestInterface $request): ResponseInterface
84    {
85        return $this->requestHandler->handle($request);
86    }
87
88    /**
89     * Set up the application and shut it down afterwards
90     *
91     * @param callable $execute
92     */
93    final public function run(callable $execute = null)
94    {
95        try {
96            $response = $this->handle(
97                ServerRequestFactory::fromGlobals()
98            );
99            if ($execute !== null) {
100                call_user_func($execute);
101            }
102        } catch (ImmediateResponseException $exception) {
103            $response = $exception->getResponse();
104        }
105
106        $this->sendResponse($response);
107    }
108}
109