238 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			PHP
		
	
	
		
		
			
		
	
	
			238 lines
		
	
	
		
			7.7 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 | ||
|  |     { | ||
|  |         $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; | ||
|  |     } | ||
|  | } |