| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 | <?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 verbatimly.     */    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     *     * @return string A string suitable for use as a Content-Disposition field-value     *     * @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 (false !== strpos($filenameFallback, '%')) {            throw new \InvalidArgumentException('The filename fallback cannot contain the "%" character.');        }        // path separators aren't allowed in either.        if (false !== strpos($filename, '/') || false !== strpos($filename, '\\') || false !== strpos($filenameFallback, '/') || false !== strpos($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, ';');    }    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;    }}
 |