1<?php 2 3/* 4 * This file is part of the TYPO3 CMS project. 5 * 6 * It is free software; you can redistribute it and/or modify it under 7 * the terms of the GNU General Public License, either version 2 8 * of the License, or any later version. 9 * 10 * For the full copyright and license information, please read the 11 * LICENSE.txt file that was distributed with this source code. 12 * 13 * The TYPO3 project - inspiring people to share! 14 */ 15 16namespace TYPO3\CMS\Extbase\Mvc; 17 18use TYPO3\CMS\Core\Page\PageRenderer; 19use TYPO3\CMS\Core\Utility\GeneralUtility; 20use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController; 21 22/** 23 * A generic and very basic response implementation 24 */ 25class Response implements ResponseInterface 26{ 27 /** 28 * @var string The response content 29 */ 30 protected $content; 31 32 /** 33 * The HTTP headers which will be sent in the response 34 * 35 * @var array 36 */ 37 protected $headers = []; 38 39 /** 40 * Additional header tags 41 * 42 * @var array 43 */ 44 protected $additionalHeaderData = []; 45 46 /** 47 * The HTTP status code 48 * 49 * @var int 50 */ 51 protected $statusCode; 52 53 /** 54 * The HTTP status message 55 * 56 * @var string 57 */ 58 protected $statusMessage = 'OK'; 59 60 /** 61 * The Request which generated the Response 62 * 63 * @var \TYPO3\CMS\Extbase\Mvc\Request 64 */ 65 protected $request; 66 67 /** 68 * The standardized and other important HTTP Status messages 69 * 70 * @var array 71 */ 72 protected $statusMessages = [ 73 // INFORMATIONAL CODES 74 100 => 'Continue', 75 101 => 'Switching Protocols', 76 102 => 'Processing', 77 103 => 'Early Hints', 78 // SUCCESS CODES 79 200 => 'OK', 80 201 => 'Created', 81 202 => 'Accepted', 82 203 => 'Non-Authoritative Information', 83 204 => 'No Content', 84 205 => 'Reset Content', 85 206 => 'Partial Content', 86 207 => 'Multi-status', 87 208 => 'Already Reported', 88 226 => 'IM Used', 89 // REDIRECTION CODES 90 300 => 'Multiple Choices', 91 301 => 'Moved Permanently', 92 302 => 'Found', 93 303 => 'See Other', 94 304 => 'Not Modified', 95 305 => 'Use Proxy', 96 306 => 'Switch Proxy', // Deprecated 97 307 => 'Temporary Redirect', 98 308 => 'Permanent Redirect', 99 // CLIENT ERROR 100 400 => 'Bad Request', 101 401 => 'Unauthorized', 102 402 => 'Payment Required', 103 403 => 'Forbidden', 104 404 => 'Not Found', 105 405 => 'Method Not Allowed', 106 406 => 'Not Acceptable', 107 407 => 'Proxy Authentication Required', 108 408 => 'Request Timeout', 109 409 => 'Conflict', 110 410 => 'Gone', 111 411 => 'Length Required', 112 412 => 'Precondition Failed', 113 413 => 'Request Entity Too Large', 114 414 => 'URI Too Long', 115 415 => 'Unsupported Media Type', 116 416 => 'Requested range not satisfiable', 117 417 => 'Expectation Failed', 118 418 => 'I\'m a teapot', 119 422 => 'Unprocessable Entity', 120 423 => 'Locked', 121 424 => 'Failed Dependency', 122 425 => 'Unordered Collection', 123 426 => 'Upgrade Required', 124 428 => 'Precondition Required', 125 429 => 'Too Many Requests', 126 431 => 'Request Header Fields Too Large', 127 451 => 'Unavailable For Legal Reasons', 128 // SERVER ERROR 129 500 => 'Internal Server Error', 130 501 => 'Not Implemented', 131 502 => 'Bad Gateway', 132 503 => 'Service Unavailable', 133 504 => 'Gateway Time-out', 134 505 => 'HTTP Version not supported', 135 506 => 'Variant Also Negotiates', 136 507 => 'Insufficient Storage', 137 508 => 'Loop Detected', 138 509 => 'Bandwidth Limit Exceeded', 139 511 => 'Network Authentication Required', 140 ]; 141 142 /** 143 * Overrides and sets the content of the response 144 * 145 * @param string $content The response content 146 */ 147 public function setContent($content) 148 { 149 $this->content = $content; 150 } 151 152 /** 153 * Appends content to the already existing content. 154 * 155 * @param string $content More response content 156 */ 157 public function appendContent($content) 158 { 159 $this->content .= $content; 160 } 161 162 /** 163 * Returns the response content without sending it. 164 * 165 * @return string The response content 166 */ 167 public function getContent() 168 { 169 return $this->content; 170 } 171 172 /** 173 * Fetches the content, returns and clears it. 174 * 175 * @return string 176 * @internal only to be used within Extbase, not part of TYPO3 Core API. 177 */ 178 public function shutdown() 179 { 180 $content = $this->getContent(); 181 $this->setContent(''); 182 return $content; 183 } 184 185 /** 186 * Returns the content of the response. 187 * 188 * @return string 189 */ 190 public function __toString() 191 { 192 return $this->getContent(); 193 } 194 195 /** 196 * Sets the HTTP status code and (optionally) a customized message. 197 * 198 * @param int $code The status code 199 * @param string $message If specified, this message is sent instead of the standard message 200 * @throws \InvalidArgumentException if the specified status code is not valid 201 */ 202 public function setStatus($code, $message = null) 203 { 204 if (!is_int($code)) { 205 throw new \InvalidArgumentException('The HTTP status code must be of type integer, ' . gettype($code) . ' given.', 1220526013); 206 } 207 if ($message === null && !isset($this->statusMessages[$code])) { 208 throw new \InvalidArgumentException('No message found for HTTP status code "' . $code . '".', 1220526014); 209 } 210 $this->statusCode = $code; 211 $this->statusMessage = $message ?? $this->statusMessages[$code]; 212 } 213 214 /** 215 * Returns status code and status message. 216 * 217 * @return string The status code and status message, eg. "404 Not Found 218 */ 219 public function getStatus() 220 { 221 return $this->statusCode . ' ' . $this->statusMessage; 222 } 223 224 /** 225 * Returns the status code, if not set, uses the OK status code 200 226 * 227 * @return int 228 * @internal only use for backend module handling 229 */ 230 public function getStatusCode() 231 { 232 return $this->statusCode ?: 200; 233 } 234 235 /** 236 * Sets the specified HTTP header 237 * 238 * @param string $name Name of the header, for example "Location", "Content-Description" etc. 239 * @param mixed $value The value of the given header 240 * @param bool $replaceExistingHeader If a header with the same name should be replaced. Default is TRUE. 241 * @throws \InvalidArgumentException 242 */ 243 public function setHeader($name, $value, $replaceExistingHeader = true) 244 { 245 if (stripos($name, 'HTTP') === 0) { 246 throw new \InvalidArgumentException('The HTTP status header must be set via setStatus().', 1220541963); 247 } 248 if ($replaceExistingHeader === true || !isset($this->headers[$name])) { 249 $this->headers[$name] = [$value]; 250 } else { 251 $this->headers[$name][] = $value; 252 } 253 } 254 255 /** 256 * Returns the HTTP headers - including the status header - of this web response 257 * 258 * @return string[] The HTTP headers 259 */ 260 public function getHeaders() 261 { 262 $preparedHeaders = []; 263 if ($this->statusCode !== null) { 264 $protocolVersion = $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0'; 265 $statusHeader = $protocolVersion . ' ' . $this->statusCode . ' ' . $this->statusMessage; 266 $preparedHeaders[] = $statusHeader; 267 } 268 foreach ($this->headers as $name => $values) { 269 foreach ($values as $value) { 270 $preparedHeaders[] = $name . ': ' . $value; 271 } 272 } 273 return $preparedHeaders; 274 } 275 276 /** 277 * Returns the HTTP headers grouped by name without the status header 278 * 279 * @return array all headers set for this request 280 * @internal only used within TYPO3 Core to convert to PSR-7 response headers 281 */ 282 public function getUnpreparedHeaders(): array 283 { 284 return $this->headers; 285 } 286 287 /** 288 * Sends the HTTP headers. 289 * 290 * If headers have already been sent, this method fails silently. 291 */ 292 public function sendHeaders() 293 { 294 if (headers_sent() === true) { 295 return; 296 } 297 foreach ($this->getHeaders() as $header) { 298 header($header); 299 } 300 } 301 302 /** 303 * Renders and sends the whole web response 304 */ 305 public function send() 306 { 307 $this->sendHeaders(); 308 if ($this->content !== null) { 309 echo $this->getContent(); 310 } 311 } 312 313 /** 314 * Adds an additional header data (something like 315 * '<script src="myext/Resources/JavaScript/my.js"></script>' 316 * ) 317 * 318 * @TODO The workaround and the $request member should be removed again, once the PageRender does support non-cached USER_INTs 319 * @param string $additionalHeaderData The value additional header 320 * @throws \InvalidArgumentException 321 */ 322 public function addAdditionalHeaderData($additionalHeaderData) 323 { 324 if (!is_string($additionalHeaderData)) { 325 throw new \InvalidArgumentException('The additional header data must be of type String, ' . gettype($additionalHeaderData) . ' given.', 1237370877); 326 } 327 if ($this->request->isCached()) { 328 /** @var PageRenderer $pageRenderer */ 329 $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class); 330 $pageRenderer->addHeaderData($additionalHeaderData); 331 } else { 332 $this->additionalHeaderData[] = $additionalHeaderData; 333 } 334 } 335 336 /** 337 * Returns the additional header data 338 * 339 * @return array The additional header data 340 */ 341 public function getAdditionalHeaderData() 342 { 343 return $this->additionalHeaderData; 344 } 345 346 /** 347 * @param \TYPO3\CMS\Extbase\Mvc\Request $request 348 * @internal only to be used within Extbase, not part of TYPO3 Core API. 349 */ 350 public function setRequest(Request $request) 351 { 352 $this->request = $request; 353 } 354 355 /** 356 * @return \TYPO3\CMS\Extbase\Mvc\Request 357 * @internal only to be used within Extbase, not part of TYPO3 Core API. 358 */ 359 public function getRequest() 360 { 361 return $this->request; 362 } 363 364 /** 365 * @return TypoScriptFrontendController 366 */ 367 protected function getTypoScriptFrontendController() 368 { 369 return $GLOBALS['TSFE']; 370 } 371} 372