299 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
		
		
			
		
	
	
			299 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| 
								 | 
							
								<?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
							 | 
						||
| 
								 | 
							
								    {
							 | 
						||
| 
								 | 
							
								        if ('' === $separators) {
							 | 
						||
| 
								 | 
							
								            throw new \InvalidArgumentException('At least one separator must be specified.');
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        $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];
							 | 
						||
| 
								 | 
							
								        $separators = substr($separators, 1) ?: '';
							 | 
						||
| 
								 | 
							
								        $i = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        if ('' === $separators && !$first) {
							 | 
						||
| 
								 | 
							
								            $parts = [''];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            foreach ($matches as $match) {
							 | 
						||
| 
								 | 
							
								                if (!$i && isset($match['separator'])) {
							 | 
						||
| 
								 | 
							
								                    $i = 1;
							 | 
						||
| 
								 | 
							
								                    $parts[1] = '';
							 | 
						||
| 
								 | 
							
								                } else {
							 | 
						||
| 
								 | 
							
								                    $parts[$i] .= self::unquote($match[0]);
							 | 
						||
| 
								 | 
							
								                }
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            return $parts;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        $parts = [];
							 | 
						||
| 
								 | 
							
								        $partMatches = [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        foreach ($matches as $match) {
							 | 
						||
| 
								 | 
							
								            if (($match['separator'] ?? null) === $separator) {
							 | 
						||
| 
								 | 
							
								                ++$i;
							 | 
						||
| 
								 | 
							
								            } else {
							 | 
						||
| 
								 | 
							
								                $partMatches[$i][] = $match;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        foreach ($partMatches as $matches) {
							 | 
						||
| 
								 | 
							
								            if ('' === $separators && '' !== $unquoted = self::unquote($matches[0][0])) {
							 | 
						||
| 
								 | 
							
								                $parts[] = $unquoted;
							 | 
						||
| 
								 | 
							
								            } elseif ($groupedParts = self::groupParts($matches, $separators, false)) {
							 | 
						||
| 
								 | 
							
								                $parts[] = $groupedParts;
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return $parts;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 |