Revision 148 | Blame | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed
<?php/** This file is part of the Symfony package.** (c) Fabien Potencier <fabien@symfony.com>** For the full copyright and license information, please view the LICENSE* file that was distributed with this source code.*/namespace Symfony\Component\HttpFoundation;/*** HTTP header utility functions.** @author Christian Schmidt <github@chsc.dk>*/class HeaderUtils{public const DISPOSITION_ATTACHMENT = 'attachment';public const DISPOSITION_INLINE = 'inline';/*** This class should not be instantiated.*/private function __construct(){}/*** Splits an HTTP header by one or more separators.** Example:** HeaderUtils::split("da, en-gb;q=0.8", ",;")* // => ['da'], ['en-gb', 'q=0.8']]** @param string $separators List of characters to split on, ordered by* precedence, e.g. ",", ";=", or ",;="** @return array Nested array with as many levels as there are characters in* $separators*/public static function split(string $header, string $separators): array{$quotedSeparators = preg_quote($separators, '/');preg_match_all('/(?!\s)(?:# quoted-string"(?:[^"\\\\]|\\\\.)*(?:"|\\\\|$)|# token[^"'.$quotedSeparators.']+)+(?<!\s)|# separator\s*(?<separator>['.$quotedSeparators.'])\s*/x', trim($header), $matches, \PREG_SET_ORDER);return self::groupParts($matches, $separators);}/*** Combines an array of arrays into one associative array.** Each of the nested arrays should have one or two elements. The first* value will be used as the keys in the associative array, and the second* will be used as the values, or true if the nested array only contains one* element. Array keys are lowercased.** Example:** HeaderUtils::combine([["foo", "abc"], ["bar"]])* // => ["foo" => "abc", "bar" => true]*/public static function combine(array $parts): array{$assoc = [];foreach ($parts as $part) {$name = strtolower($part[0]);$value = $part[1] ?? true;$assoc[$name] = $value;}return $assoc;}/*** Joins an associative array into a string for use in an HTTP header.** The key and value of each entry are joined with "=", and all entries* are joined with the specified separator and an additional space (for* readability). Values are quoted if necessary.** Example:** HeaderUtils::toString(["foo" => "abc", "bar" => true, "baz" => "a b c"], ",")* // => 'foo=abc, bar, baz="a b c"'*/public static function toString(array $assoc, string $separator): string{$parts = [];foreach ($assoc as $name => $value) {if (true === $value) {$parts[] = $name;} else {$parts[] = $name.'='.self::quote($value);}}return implode($separator.' ', $parts);}/*** Encodes a string as a quoted string, if necessary.** If a string contains characters not allowed by the "token" construct in* the HTTP specification, it is backslash-escaped and enclosed in quotes* to match the "quoted-string" construct.*/public static function quote(string $s): string{if (preg_match('/^[a-z0-9!#$%&\'*.^_`|~-]+$/i', $s)) {return $s;}return '"'.addcslashes($s, '"\\"').'"';}/*** Decodes a quoted string.** If passed an unquoted string that matches the "token" construct (as* defined in the HTTP specification), it is passed through verbatim.*/public static function unquote(string $s): string{return preg_replace('/\\\\(.)|"/', '$1', $s);}/*** Generates an HTTP Content-Disposition field-value.** @param string $disposition One of "inline" or "attachment"* @param string $filename A unicode string* @param string $filenameFallback A string containing only ASCII characters that* is semantically equivalent to $filename. If the filename is already ASCII,* it can be omitted, or just copied from $filename** @throws \InvalidArgumentException** @see RFC 6266*/public static function makeDisposition(string $disposition, string $filename, string $filenameFallback = ''): string{if (!\in_array($disposition, [self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE])) {throw new \InvalidArgumentException(sprintf('The disposition must be either "%s" or "%s".', self::DISPOSITION_ATTACHMENT, self::DISPOSITION_INLINE));}if ('' === $filenameFallback) {$filenameFallback = $filename;}// filenameFallback is not ASCII.if (!preg_match('/^[\x20-\x7e]*$/', $filenameFallback)) {throw new \InvalidArgumentException('The filename fallback must only contain ASCII characters.');}// percent characters aren't safe in fallback.if (str_contains($filenameFallback, '%')) {throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.');}// path separators aren't allowed in either.if (str_contains($filename, '/') || str_contains($filename, '\\') || str_contains($filenameFallback, '/') || str_contains($filenameFallback, '\\')) {throw new \InvalidArgumentException('The filename and the fallback cannot contain the "/" and "\\" characters.');}$params = ['filename' => $filenameFallback];if ($filename !== $filenameFallback) {$params['filename*'] = "utf-8''".rawurlencode($filename);}return $disposition.'; '.self::toString($params, ';');}/*** Like parse_str(), but preserves dots in variable names.*/public static function parseQuery(string $query, bool $ignoreBrackets = false, string $separator = '&'): array{$q = [];foreach (explode($separator, $query) as $v) {if (false !== $i = strpos($v, "\0")) {$v = substr($v, 0, $i);}if (false === $i = strpos($v, '=')) {$k = urldecode($v);$v = '';} else {$k = urldecode(substr($v, 0, $i));$v = substr($v, $i);}if (false !== $i = strpos($k, "\0")) {$k = substr($k, 0, $i);}$k = ltrim($k, ' ');if ($ignoreBrackets) {$q[$k][] = urldecode(substr($v, 1));continue;}if (false === $i = strpos($k, '[')) {$q[] = bin2hex($k).$v;} else {$q[] = bin2hex(substr($k, 0, $i)).rawurlencode(substr($k, $i)).$v;}}if ($ignoreBrackets) {return $q;}parse_str(implode('&', $q), $q);$query = [];foreach ($q as $k => $v) {if (false !== $i = strpos($k, '_')) {$query[substr_replace($k, hex2bin(substr($k, 0, $i)).'[', 0, 1 + $i)] = $v;} else {$query[hex2bin($k)] = $v;}}return $query;}private static function groupParts(array $matches, string $separators, bool $first = true): array{$separator = $separators[0];$partSeparators = substr($separators, 1);$i = 0;$partMatches = [];$previousMatchWasSeparator = false;foreach ($matches as $match) {if (!$first && $previousMatchWasSeparator && isset($match['separator']) && $match['separator'] === $separator) {$previousMatchWasSeparator = true;$partMatches[$i][] = $match;} elseif (isset($match['separator']) && $match['separator'] === $separator) {$previousMatchWasSeparator = true;++$i;} else {$previousMatchWasSeparator = false;$partMatches[$i][] = $match;}}$parts = [];if ($partSeparators) {foreach ($partMatches as $matches) {$parts[] = self::groupParts($matches, $partSeparators, false);}} else {foreach ($partMatches as $matches) {$parts[] = self::unquote($matches[0][0]);}if (!$first && 2 < \count($parts)) {$parts = [$parts[0],implode($separator, \array_slice($parts, 1)),];}}return $parts;}}