1<?php 2namespace TYPO3\CMS\Core\Utility; 3 4/* 5 * This file is part of the TYPO3 CMS project. 6 * 7 * It is free software; you can redistribute it and/or modify it under 8 * the terms of the GNU General Public License, either version 2 9 * of the License, or any later version. 10 * 11 * For the full copyright and license information, please read the 12 * LICENSE.txt file that was distributed with this source code. 13 * 14 * The TYPO3 project - inspiring people to share! 15 */ 16 17/** 18 * HTTP Utility class 19 */ 20class HttpUtility 21{ 22 // HTTP Headers, see https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml 23 // INFORMATIONAL CODES 24 const HTTP_STATUS_100 = 'HTTP/1.1 100 Continue'; 25 const HTTP_STATUS_101 = 'HTTP/1.1 101 Switching Protocols'; 26 const HTTP_STATUS_102 = 'HTTP/1.1 102 Processing'; 27 const HTTP_STATUS_103 = 'HTTP/1.1 103 Early Hints'; 28 // SUCCESS CODES 29 const HTTP_STATUS_200 = 'HTTP/1.1 200 OK'; 30 const HTTP_STATUS_201 = 'HTTP/1.1 201 Created'; 31 const HTTP_STATUS_202 = 'HTTP/1.1 202 Accepted'; 32 const HTTP_STATUS_203 = 'HTTP/1.1 203 Non-Authoritative Information'; 33 const HTTP_STATUS_204 = 'HTTP/1.1 204 No Content'; 34 const HTTP_STATUS_205 = 'HTTP/1.1 205 Reset Content'; 35 const HTTP_STATUS_206 = 'HTTP/1.1 206 Partial Content'; 36 const HTTP_STATUS_207 = 'HTTP/1.1 207 Multi-status'; 37 const HTTP_STATUS_208 = 'HTTP/1.1 208 Already Reported'; 38 const HTTP_STATUS_226 = 'HTTP/1.1 226 IM Used'; 39 // REDIRECTION CODES 40 const HTTP_STATUS_300 = 'HTTP/1.1 300 Multiple Choices'; 41 const HTTP_STATUS_301 = 'HTTP/1.1 301 Moved Permanently'; 42 const HTTP_STATUS_302 = 'HTTP/1.1 302 Found'; 43 const HTTP_STATUS_303 = 'HTTP/1.1 303 See Other'; 44 const HTTP_STATUS_304 = 'HTTP/1.1 304 Not Modified'; 45 const HTTP_STATUS_305 = 'HTTP/1.1 305 Use Proxy'; 46 const HTTP_STATUS_306 = 'HTTP/1.1 306 Switch Proxy'; // Deprecated 47 const HTTP_STATUS_307 = 'HTTP/1.1 307 Temporary Redirect'; 48 const HTTP_STATUS_308 = 'HTTP/1.1 308 Permanent Redirect'; 49 // CLIENT ERROR 50 const HTTP_STATUS_400 = 'HTTP/1.1 400 Bad Request'; 51 const HTTP_STATUS_401 = 'HTTP/1.1 401 Unauthorized'; 52 const HTTP_STATUS_402 = 'HTTP/1.1 402 Payment Required'; 53 const HTTP_STATUS_403 = 'HTTP/1.1 403 Forbidden'; 54 const HTTP_STATUS_404 = 'HTTP/1.1 404 Not Found'; 55 const HTTP_STATUS_405 = 'HTTP/1.1 405 Method Not Allowed'; 56 const HTTP_STATUS_406 = 'HTTP/1.1 406 Not Acceptable'; 57 const HTTP_STATUS_407 = 'HTTP/1.1 407 Proxy Authentication Required'; 58 const HTTP_STATUS_408 = 'HTTP/1.1 408 Request Timeout'; 59 const HTTP_STATUS_409 = 'HTTP/1.1 409 Conflict'; 60 const HTTP_STATUS_410 = 'HTTP/1.1 410 Gone'; 61 const HTTP_STATUS_411 = 'HTTP/1.1 411 Length Required'; 62 const HTTP_STATUS_412 = 'HTTP/1.1 412 Precondition Failed'; 63 const HTTP_STATUS_413 = 'HTTP/1.1 413 Request Entity Too Large'; 64 const HTTP_STATUS_414 = 'HTTP/1.1 414 URI Too Long'; 65 const HTTP_STATUS_415 = 'HTTP/1.1 415 Unsupported Media Type'; 66 const HTTP_STATUS_416 = 'HTTP/1.1 416 Requested range not satisfiable'; 67 const HTTP_STATUS_417 = 'HTTP/1.1 417 Expectation Failed'; 68 const HTTP_STATUS_418 = 'HTTP/1.1 418 I\'m a teapot'; 69 const HTTP_STATUS_422 = 'HTTP/1.1 422 Unprocessable Entity'; 70 const HTTP_STATUS_423 = 'HTTP/1.1 423 Locked'; 71 const HTTP_STATUS_424 = 'HTTP/1.1 424 Failed Dependency'; 72 const HTTP_STATUS_425 = 'HTTP/1.1 425 Unordered Collection'; 73 const HTTP_STATUS_426 = 'HTTP/1.1 426 Upgrade Required'; 74 const HTTP_STATUS_428 = 'HTTP/1.1 428 Precondition Required'; 75 const HTTP_STATUS_429 = 'HTTP/1.1 429 Too Many Requests'; 76 const HTTP_STATUS_431 = 'HTTP/1.1 431 Request Header Fields Too Large'; 77 const HTTP_STATUS_451 = 'HTTP/1.1 451 Unavailable For Legal Reasons'; 78 // SERVER ERROR 79 const HTTP_STATUS_500 = 'HTTP/1.1 500 Internal Server Error'; 80 const HTTP_STATUS_501 = 'HTTP/1.1 501 Not Implemented'; 81 const HTTP_STATUS_502 = 'HTTP/1.1 502 Bad Gateway'; 82 const HTTP_STATUS_503 = 'HTTP/1.1 503 Service Unavailable'; 83 const HTTP_STATUS_504 = 'HTTP/1.1 504 Gateway Time-out'; 84 const HTTP_STATUS_505 = 'HTTP/1.1 505 Version not Supported'; 85 const HTTP_STATUS_506 = 'HTTP/1.1 506 Variant Also Negotiates'; 86 const HTTP_STATUS_507 = 'HTTP/1.1 507 Insufficient Storage'; 87 const HTTP_STATUS_508 = 'HTTP/1.1 508 Loop Detected'; 88 const HTTP_STATUS_509 = 'HTTP/1.1 509 Bandwidth Limit Exceeded'; 89 const HTTP_STATUS_511 = 'HTTP/1.1 511 Network Authentication Required'; 90 // URL Schemes 91 const SCHEME_HTTP = 1; 92 const SCHEME_HTTPS = 2; 93 94 /** 95 * Sends a redirect header response and exits. Additionally the URL is 96 * checked and if needed corrected to match the format required for a 97 * Location redirect header. By default the HTTP status code sent is 98 * a 'HTTP/1.1 303 See Other'. 99 * 100 * @param string $url The target URL to redirect to 101 * @param string $httpStatus An optional HTTP status header. Default is 'HTTP/1.1 303 See Other' 102 */ 103 public static function redirect($url, $httpStatus = self::HTTP_STATUS_303) 104 { 105 self::setResponseCode($httpStatus); 106 header('Location: ' . GeneralUtility::locationHeaderUrl($url)); 107 die; 108 } 109 110 /** 111 * Set a specific response code like 404. 112 * 113 * @param string $httpStatus One of the HTTP_STATUS_* class class constants, default to self::HTTP_STATUS_303 114 */ 115 public static function setResponseCode($httpStatus = self::HTTP_STATUS_303) 116 { 117 header($httpStatus); 118 } 119 120 /** 121 * Set a specific response code and exit script execution. 122 * 123 * @param string $httpStatus One of the HTTP_STATUS_* class class constants, default to self::HTTP_STATUS_303 124 */ 125 public static function setResponseCodeAndExit($httpStatus = self::HTTP_STATUS_303) 126 { 127 self::setResponseCode($httpStatus); 128 die; 129 } 130 131 /** 132 * Builds a URL string from an array with the URL parts, as e.g. output by parse_url(). 133 * 134 * @param array $urlParts 135 * @return string 136 * @see http://www.php.net/parse_url 137 */ 138 public static function buildUrl(array $urlParts) 139 { 140 return (isset($urlParts['scheme']) ? $urlParts['scheme'] . '://' : '') . 141 (isset($urlParts['user']) ? $urlParts['user'] . 142 (isset($urlParts['pass']) ? ':' . $urlParts['pass'] : '') . '@' : '') . 143 ($urlParts['host'] ?? '') . 144 (isset($urlParts['port']) ? ':' . $urlParts['port'] : '') . 145 ($urlParts['path'] ?? '') . 146 (isset($urlParts['query']) ? '?' . $urlParts['query'] : '') . 147 (isset($urlParts['fragment']) ? '#' . $urlParts['fragment'] : ''); 148 } 149 150 /** 151 * Implodes a multidimensional array of query parameters to a string of GET parameters (eg. param[key][key2]=value2¶m[key][key3]=value3) 152 * and properly encodes parameter names as well as values. Spaces are encoded as %20 153 * 154 * @param array $parameters The (multidimensional) array of query parameters with values 155 * @param string $prependCharacter If the created query string is not empty, prepend this character "?" or "&" else no prepend 156 * @param bool $skipEmptyParameters If true, empty parameters (blank string, empty array, null) are removed. 157 * @return string Imploded result, for example param[key][key2]=value2¶m[key][key3]=value3 158 * @see explodeUrl2Array() 159 */ 160 public static function buildQueryString(array $parameters, string $prependCharacter = '', bool $skipEmptyParameters = false): string 161 { 162 if (empty($parameters)) { 163 return ''; 164 } 165 166 if ($skipEmptyParameters) { 167 // This callback filters empty strings, array and null but keeps zero integers 168 $parameters = ArrayUtility::filterRecursive( 169 $parameters, 170 function ($item) { 171 return $item !== '' && $item !== [] && $item !== null; 172 } 173 ); 174 } 175 176 $queryString = http_build_query($parameters, '', '&', PHP_QUERY_RFC3986); 177 $prependCharacter = $prependCharacter === '?' || $prependCharacter === '&' ? $prependCharacter : ''; 178 179 return $queryString && $prependCharacter ? $prependCharacter . $queryString : $queryString; 180 } 181 182 /** 183 * Compatibility layer for PHP versions running ICU 4.4, as the constant INTL_IDNA_VARIANT_UTS46 184 * is only available as of ICU 4.6. 185 * 186 * Please note: Once PHP 7.4 is the minimum requirement, this method will vanish without further notice 187 * as it is recommended to use the native method instead, when working against a clean environment. 188 * 189 * @internal 190 * @param string $domain the domain name to convert Punicode to ASCII. 191 * @return string|bool 192 */ 193 public static function idn_to_ascii(string $domain) 194 { 195 if (defined('INTL_IDNA_VARIANT_UTS46') && !defined('TYPO3_IDN_TO_ASCII_USE_COMPAT')) { 196 return idn_to_ascii($domain, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46); 197 } 198 // This is done due to some heavy old systems where native functionality is there, but does not support UTS46 yet. 199 if (!defined('TYPO3_IDN_TO_ASCII_USE_COMPAT')) { 200 define('TYPO3_IDN_TO_ASCII_USE_COMPAT', true); 201 } 202 if (!defined('INTL_IDNA_VARIANT_UTS46')) { 203 define('INTL_IDNA_VARIANT_UTS46', 1); 204 } 205 $result = []; 206 return \Symfony\Polyfill\Intl\Idn\Idn::idn_to_ascii($domain, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $result); 207 } 208} 209