Exception
1 year ago
File
1 year ago
RateLimiter
1 year ago
Session
1 year ago
Test
1 year ago
AcceptHeader.php
1 year ago
AcceptHeaderItem.php
1 year ago
BinaryFileResponse.php
1 year ago
Cookie.php
1 year ago
ExpressionRequestMatcher.php
1 year ago
FileBag.php
1 year ago
HeaderBag.php
1 year ago
HeaderUtils.php
1 year ago
InputBag.php
1 year ago
IpUtils.php
1 year ago
JsonResponse.php
1 year ago
LICENSE
1 year ago
ParameterBag.php
1 year ago
RedirectResponse.php
1 year ago
Request.php
9 months ago
RequestMatcher.php
1 year ago
RequestMatcherInterface.php
1 year ago
RequestStack.php
1 year ago
Response.php
1 year ago
ResponseHeaderBag.php
1 year ago
ServerBag.php
1 year ago
StreamedResponse.php
1 year ago
UrlHelper.php
1 year ago
HeaderUtils.php
299 lines
| 1 | <?php |
| 2 | |
| 3 | /* |
| 4 | * This file is part of the Symfony package. |
| 5 | * |
| 6 | * (c) Fabien Potencier <fabien@symfony.com> |
| 7 | * |
| 8 | * For the full copyright and license information, please view the LICENSE |
| 9 | * file that was distributed with this source code. |
| 10 | */ |
| 11 | |
| 12 | namespace Give\Vendors\Symfony\Component\HttpFoundation; |
| 13 | |
| 14 | /** |
| 15 | * HTTP header utility functions. |
| 16 | * |
| 17 | * @author Christian Schmidt <github@chsc.dk> |
| 18 | */ |
| 19 | class HeaderUtils |
| 20 | { |
| 21 | public const DISPOSITION_ATTACHMENT = 'attachment'; |
| 22 | public const DISPOSITION_INLINE = 'inline'; |
| 23 | |
| 24 | /** |
| 25 | * This class should not be instantiated. |
| 26 | */ |
| 27 | private function __construct() |
| 28 | { |
| 29 | } |
| 30 | |
| 31 | /** |
| 32 | * Splits an HTTP header by one or more separators. |
| 33 | * |
| 34 | * Example: |
| 35 | * |
| 36 | * HeaderUtils::split('da, en-gb;q=0.8', ',;') |
| 37 | * // => ['da'], ['en-gb', 'q=0.8']] |
| 38 | * |
| 39 | * @param string $separators List of characters to split on, ordered by |
| 40 | * precedence, e.g. ',', ';=', or ',;=' |
| 41 | * |
| 42 | * @return array Nested array with as many levels as there are characters in |
| 43 | * $separators |
| 44 | */ |
| 45 | public static function split(string $header, string $separators): array |
| 46 | { |
| 47 | if ('' === $separators) { |
| 48 | throw new \InvalidArgumentException('At least one separator must be specified.'); |
| 49 | } |
| 50 | |
| 51 | $quotedSeparators = preg_quote($separators, '/'); |
| 52 | |
| 53 | preg_match_all(' |
| 54 | / |
| 55 | (?!\s) |
| 56 | (?: |
| 57 | # quoted-string |
| 58 | "(?:[^"\\\\]|\\\\.)*(?:"|\\\\|$) |
| 59 | | |
| 60 | # token |
| 61 | [^"'.$quotedSeparators.']+ |
| 62 | )+ |
| 63 | (?<!\s) |
| 64 | | |
| 65 | # separator |
| 66 | \s* |
| 67 | (?<separator>['.$quotedSeparators.']) |
| 68 | \s* |
| 69 | /x', trim($header), $matches, \PREG_SET_ORDER); |
| 70 | |
| 71 | return self::groupParts($matches, $separators); |
| 72 | } |
| 73 | |
| 74 | /** |
| 75 | * Combines an array of arrays into one associative array. |
| 76 | * |
| 77 | * Each of the nested arrays should have one or two elements. The first |
| 78 | * value will be used as the keys in the associative array, and the second |
| 79 | * will be used as the values, or true if the nested array only contains one |
| 80 | * element. Array keys are lowercased. |
| 81 | * |
| 82 | * Example: |
| 83 | * |
| 84 | * HeaderUtils::combine([['foo', 'abc'], ['bar']]) |
| 85 | * // => ['foo' => 'abc', 'bar' => true] |
| 86 | */ |
| 87 | public static function combine(array $parts): array |
| 88 | { |
| 89 | $assoc = []; |
| 90 | foreach ($parts as $part) { |
| 91 | $name = strtolower($part[0]); |
| 92 | $value = $part[1] ?? true; |
| 93 | $assoc[$name] = $value; |
| 94 | } |
| 95 | |
| 96 | return $assoc; |
| 97 | } |
| 98 | |
| 99 | /** |
| 100 | * Joins an associative array into a string for use in an HTTP header. |
| 101 | * |
| 102 | * The key and value of each entry are joined with '=', and all entries |
| 103 | * are joined with the specified separator and an additional space (for |
| 104 | * readability). Values are quoted if necessary. |
| 105 | * |
| 106 | * Example: |
| 107 | * |
| 108 | * HeaderUtils::toString(['foo' => 'abc', 'bar' => true, 'baz' => 'a b c'], ',') |
| 109 | * // => 'foo=abc, bar, baz="a b c"' |
| 110 | */ |
| 111 | public static function toString(array $assoc, string $separator): string |
| 112 | { |
| 113 | $parts = []; |
| 114 | foreach ($assoc as $name => $value) { |
| 115 | if (true === $value) { |
| 116 | $parts[] = $name; |
| 117 | } else { |
| 118 | $parts[] = $name.'='.self::quote($value); |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | return implode($separator.' ', $parts); |
| 123 | } |
| 124 | |
| 125 | /** |
| 126 | * Encodes a string as a quoted string, if necessary. |
| 127 | * |
| 128 | * If a string contains characters not allowed by the "token" construct in |
| 129 | * the HTTP specification, it is backslash-escaped and enclosed in quotes |
| 130 | * to match the "quoted-string" construct. |
| 131 | */ |
| 132 | public static function quote(string $s): string |
| 133 | { |
| 134 | if (preg_match('/^[a-z0-9!#$%&\'*.^_`|~-]+$/i', $s)) { |
| 135 | return $s; |
| 136 | } |
| 137 | |
| 138 | return '"'.addcslashes($s, '"\\"').'"'; |
| 139 | } |
| 140 | |
| 141 | /** |
| 142 | * Decodes a quoted string. |
| 143 | * |
| 144 | * If passed an unquoted string that matches the "token" construct (as |
| 145 | * defined in the HTTP specification), it is passed through verbatim. |
| 146 | */ |
| 147 | public static function unquote(string $s): string |
| 148 | { |
| 149 | return preg_replace('/\\\\(.)|"/', '$1', $s); |
| 150 | } |
| 151 | |
| 152 | /** |
| 153 | * Generates an HTTP Content-Disposition field-value. |
| 154 | * |
| 155 | * @param string $disposition One of "inline" or "attachment" |
| 156 | * @param string $filename A unicode string |
| 157 | * @param string $filenameFallback A string containing only ASCII characters that |
| 158 | * is semantically equivalent to $filename. If the filename is already ASCII, |
| 159 | * it can be omitted, or just copied from $filename |
| 160 | * |
| 161 | * @throws \InvalidArgumentException |
| 162 | * |
| 163 | * @see RFC 6266 |
| 164 | */ |
| 165 | public static function makeDisposition(string $disposition, string $filename, string $filenameFallback = ''): string |
| 166 | { |
| 167 | if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) { |
| 168 | throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE)); |
| 169 | } |
| 170 | |
| 171 | if ('' === $filenameFallback) { |
| 172 | $filenameFallback = $filename; |
| 173 | } |
| 174 | |
| 175 | // filenameFallback is not ASCII. |
| 176 | if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) { |
| 177 | throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.'); |
| 178 | } |
| 179 | |
| 180 | // percent characters aren't safe in fallback. |
| 181 | if (str_contains($filenameFallback, '%')) { |
| 182 | throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.'); |
| 183 | } |
| 184 | |
| 185 | // path separators aren't allowed in either. |
| 186 | if (str_contains($filename, '/') || str_contains($filename, '\\') || str_contains($filenameFallback, '/') || str_contains($filenameFallback, '\\')) { |
| 187 | throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.'); |
| 188 | } |
| 189 | |
| 190 | $params = ['filename' => $filenameFallback]; |
| 191 | if ($filename !== $filenameFallback) { |
| 192 | $params['filename*'] = "utf-8''".rawurlencode($filename); |
| 193 | } |
| 194 | |
| 195 | return $disposition.'; '.self::toString($params, ';'); |
| 196 | } |
| 197 | |
| 198 | /** |
| 199 | * Like parse_str(), but preserves dots in variable names. |
| 200 | */ |
| 201 | public static function parseQuery(string $query, bool $ignoreBrackets = false, string $separator = '&'): array |
| 202 | { |
| 203 | $q = []; |
| 204 | |
| 205 | foreach (explode($separator, $query) as $v) { |
| 206 | if (false !== $i = strpos($v, "\0")) { |
| 207 | $v = substr($v, 0, $i); |
| 208 | } |
| 209 | |
| 210 | if (false === $i = strpos($v, '=')) { |
| 211 | $k = urldecode($v); |
| 212 | $v = ''; |
| 213 | } else { |
| 214 | $k = urldecode(substr($v, 0, $i)); |
| 215 | $v = substr($v, $i); |
| 216 | } |
| 217 | |
| 218 | if (false !== $i = strpos($k, "\0")) { |
| 219 | $k = substr($k, 0, $i); |
| 220 | } |
| 221 | |
| 222 | $k = ltrim($k, ' '); |
| 223 | |
| 224 | if ($ignoreBrackets) { |
| 225 | $q[$k][] = urldecode(substr($v, 1)); |
| 226 | |
| 227 | continue; |
| 228 | } |
| 229 | |
| 230 | if (false === $i = strpos($k, '[')) { |
| 231 | $q[] = bin2hex($k).$v; |
| 232 | } else { |
| 233 | $q[] = bin2hex(substr($k, 0, $i)).rawurlencode(substr($k, $i)).$v; |
| 234 | } |
| 235 | } |
| 236 | |
| 237 | if ($ignoreBrackets) { |
| 238 | return $q; |
| 239 | } |
| 240 | |
| 241 | parse_str(implode('&', $q), $q); |
| 242 | |
| 243 | $query = []; |
| 244 | |
| 245 | foreach ($q as $k => $v) { |
| 246 | if (false !== $i = strpos($k, '_')) { |
| 247 | $query[substr_replace($k, hex2bin(substr($k, 0, $i)).'[', 0, 1 + $i)] = $v; |
| 248 | } else { |
| 249 | $query[hex2bin($k)] = $v; |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | return $query; |
| 254 | } |
| 255 | |
| 256 | private static function groupParts(array $matches, string $separators, bool $first = true): array |
| 257 | { |
| 258 | $separator = $separators[0]; |
| 259 | $separators = substr($separators, 1) ?: ''; |
| 260 | $i = 0; |
| 261 | |
| 262 | if ('' === $separators && !$first) { |
| 263 | $parts = ['']; |
| 264 | |
| 265 | foreach ($matches as $match) { |
| 266 | if (!$i && isset($match['separator'])) { |
| 267 | $i = 1; |
| 268 | $parts[1] = ''; |
| 269 | } else { |
| 270 | $parts[$i] .= self::unquote($match[0]); |
| 271 | } |
| 272 | } |
| 273 | |
| 274 | return $parts; |
| 275 | } |
| 276 | |
| 277 | $parts = []; |
| 278 | $partMatches = []; |
| 279 | |
| 280 | foreach ($matches as $match) { |
| 281 | if (($match['separator'] ?? null) === $separator) { |
| 282 | ++$i; |
| 283 | } else { |
| 284 | $partMatches[$i][] = $match; |
| 285 | } |
| 286 | } |
| 287 | |
| 288 | foreach ($partMatches as $matches) { |
| 289 | if ('' === $separators && '' !== $unquoted = self::unquote($matches[0][0])) { |
| 290 | $parts[] = $unquoted; |
| 291 | } elseif ($groupedParts = self::groupParts($matches, $separators, false)) { |
| 292 | $parts[] = $groupedParts; |
| 293 | } |
| 294 | } |
| 295 | |
| 296 | return $parts; |
| 297 | } |
| 298 | } |
| 299 |