[BUGFIX] Ensure no PHP warning is thrown if legacy code is used
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Utility / HttpUtility.php
1 <?php
2 namespace 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 */
20 class 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&param[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&param[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 * @param string $domain the domain name to convert Punicode to ASCII.
190 * @return string|bool
191 */
192 public static function idn_to_ascii(string $domain)
193 {
194 if (defined('INTL_IDNA_VARIANT_UTS46') && !defined('TYPO3_IDN_TO_ASCII_USE_COMPAT')) {
195 return idn_to_ascii($domain, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46);
196 }
197 // This is done due to some heavy old systems where native functionality is there, but does not support UTS46 yet.
198 if (!defined('TYPO3_IDN_TO_ASCII_USE_COMPAT')) {
199 define('TYPO3_IDN_TO_ASCII_USE_COMPAT', true);
200 }
201 if (!defined('INTL_IDNA_VARIANT_UTS46')) {
202 define('INTL_IDNA_VARIANT_UTS46', 1);
203 }
204 $result = [];
205 return \Symfony\Polyfill\Intl\Idn\Idn::idn_to_ascii($domain, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $result);
206 }
207 }