| @@ -8,8 +8,61 @@ | ||
| 8 | 8 | use YoastSEO_Vendor\Psr\Http\Message\UriInterface; |
| 9 | 9 | final class Utils |
| 10 | 10 | { |
| 11 | 11 | /** |
| 12 | + * Converts ASCII uppercase letters in a string to lowercase. | |
| 13 | + * | |
| 14 | + * Unlike strtolower(), which honors LC_CTYPE before PHP 8.2, the | |
| 15 | + * conversion is locale-independent and leaves every non-ASCII byte | |
| 16 | + * unchanged, as HTTP protocol elements require. | |
| 17 | + */ | |
| 18 | + public static function asciiToLower(string $string) : string | |
| 19 | + { | |
| 20 | + return \strtr($string, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); | |
| 21 | + } | |
| 22 | + /** | |
| 23 | + * Converts ASCII lowercase letters in a string to uppercase. | |
| 24 | + * | |
| 25 | + * Unlike strtoupper(), which honors LC_CTYPE before PHP 8.2, the | |
| 26 | + * conversion is locale-independent and leaves every non-ASCII byte | |
| 27 | + * unchanged, as HTTP protocol elements require. | |
| 28 | + */ | |
| 29 | + public static function asciiToUpper(string $string) : string | |
| 30 | + { | |
| 31 | + return \strtr($string, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'); | |
| 32 | + } | |
| 33 | + /** | |
| 34 | + * Converts the first character of a string to uppercase when it is an | |
| 35 | + * ASCII lowercase letter. | |
| 36 | + * | |
| 37 | + * Unlike ucfirst(), which honors LC_CTYPE before PHP 8.2, the conversion | |
| 38 | + * is locale-independent and leaves every non-ASCII byte unchanged, as | |
| 39 | + * HTTP protocol elements require. | |
| 40 | + */ | |
| 41 | + public static function asciiUcFirst(string $string) : string | |
| 42 | + { | |
| 43 | + if ($string === '') { | |
| 44 | + return ''; | |
| 45 | + } | |
| 46 | + return self::asciiToUpper($string[0]) . \substr($string, 1); | |
| 47 | + } | |
| 48 | + /** | |
| 49 | + * Checks whether the haystack contains the needle, comparing ASCII | |
| 50 | + * letters case-insensitively and without locale sensitivity. | |
| 51 | + */ | |
| 52 | + public static function caselessContains(string $haystack, string $needle) : bool | |
| 53 | + { | |
| 54 | + return \str_contains(self::asciiToLower($haystack), self::asciiToLower($needle)); | |
| 55 | + } | |
| 56 | + /** | |
| 57 | + * Checks whether two strings are equal, comparing ASCII letters | |
| 58 | + * case-insensitively and without locale sensitivity. | |
| 59 | + */ | |
| 60 | + public static function caselessEquals(string $left, string $right) : bool | |
| 61 | + { | |
| 62 | + return self::asciiToLower($left) === self::asciiToLower($right); | |
| 63 | + } | |
| 64 | + /** | |
| 12 | 65 | * Remove the items given by the keys, case insensitively from the data. |
| 13 | 66 | * |
| 14 | 67 | * @param (string|int)[] $keys |
| 15 | 68 | */ |
| @@ -16,12 +69,12 @@ | ||
| 16 | 69 | public static function caselessRemove(array $keys, array $data) : array |
| 17 | 70 | { |
| 18 | 71 | $result = []; |
| 19 | 72 | foreach ($keys as &$key) { |
| 20 | - $key = \strtolower((string) $key); | |
| 73 | + $key = self::asciiToLower((string) $key); | |
| 21 | 74 | } |
| 22 | 75 | foreach ($data as $k => $v) { |
| 23 | - if (!\in_array(\strtolower((string) $k), $keys)) { | |
| 76 | + if (!\in_array(self::asciiToLower((string) $k), $keys)) { | |
| 24 | 77 | $result[$k] = $v; |
| 25 | 78 | } |
| 26 | 79 | } |
| 27 | 80 | return $result; |
| @@ -185,9 +238,9 @@ | ||
| 185 | 238 | $host = $changes['uri']->getHost(); |
| 186 | 239 | if ($host !== '') { |
| 187 | 240 | if (isset($changes['set_headers']) && \is_array($changes['set_headers'])) { |
| 188 | 241 | foreach (\array_keys($changes['set_headers']) as $header) { |
| 189 | - if (\strtolower((string) $header) === 'host') { | |
| 242 | + if (self::asciiToLower((string) $header) === 'host') { | |
| 190 | 243 | throw new \InvalidArgumentException('Cannot modify request with both a URI containing a host and an explicit Host header.'); |
| 191 | 244 | } |
| 192 | 245 | } |
| 193 | 246 | } |
| @@ -213,9 +266,9 @@ | ||
| 213 | 266 | $uri = $uri->withQuery($changes['query']); |
| 214 | 267 | } |
| 215 | 268 | $hasHost = \false; |
| 216 | 269 | foreach (\array_keys($headers) as $header) { |
| 217 | - if (\strtolower((string) $header) === 'host') { | |
| 270 | + if (self::asciiToLower((string) $header) === 'host') { | |
| 218 | 271 | $hasHost = \true; |
| 219 | 272 | break; |
| 220 | 273 | } |
| 221 | 274 | } |
| @@ -241,9 +294,9 @@ | ||
| 241 | 294 | } |
| 242 | 295 | $addedHeaders = []; |
| 243 | 296 | foreach ($headers as $header => $value) { |
| 244 | 297 | $header = (string) $header; |
| 245 | - $normalized = \strtolower($header); | |
| 298 | + $normalized = self::asciiToLower($header); | |
| 246 | 299 | if (isset($addedHeaders[$normalized])) { |
| 247 | 300 | /** @var RequestInterface */ |
| 248 | 301 | $new = $new->withAddedHeader($addedHeaders[$normalized], $value); |
| 249 | 302 | } else { |