176 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			PHP
		
	
	
		
		
			
		
	
	
			176 lines
		
	
	
		
			5.3 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\Mime\Header; | ||
|  | 
 | ||
|  | use Symfony\Component\Mime\Encoder\Rfc2231Encoder; | ||
|  | 
 | ||
|  | /** | ||
|  |  * @author Chris Corbyn | ||
|  |  */ | ||
|  | final class ParameterizedHeader extends UnstructuredHeader | ||
|  | { | ||
|  |     /** | ||
|  |      * RFC 2231's definition of a token. | ||
|  |      * | ||
|  |      * @var string | ||
|  |      */ | ||
|  |     public const TOKEN_REGEX = '(?:[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+)'; | ||
|  | 
 | ||
|  |     private $encoder; | ||
|  |     private $parameters = []; | ||
|  | 
 | ||
|  |     public function __construct(string $name, string $value, array $parameters = []) | ||
|  |     { | ||
|  |         parent::__construct($name, $value); | ||
|  | 
 | ||
|  |         foreach ($parameters as $k => $v) { | ||
|  |             $this->setParameter($k, $v); | ||
|  |         } | ||
|  | 
 | ||
|  |         if ('content-type' !== strtolower($name)) { | ||
|  |             $this->encoder = new Rfc2231Encoder(); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     public function setParameter(string $parameter, ?string $value) | ||
|  |     { | ||
|  |         $this->setParameters(array_merge($this->getParameters(), [$parameter => $value])); | ||
|  |     } | ||
|  | 
 | ||
|  |     public function getParameter(string $parameter): string | ||
|  |     { | ||
|  |         return $this->getParameters()[$parameter] ?? ''; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * @param string[] $parameters | ||
|  |      */ | ||
|  |     public function setParameters(array $parameters) | ||
|  |     { | ||
|  |         $this->parameters = $parameters; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * @return string[] | ||
|  |      */ | ||
|  |     public function getParameters(): array | ||
|  |     { | ||
|  |         return $this->parameters; | ||
|  |     } | ||
|  | 
 | ||
|  |     public function getBodyAsString(): string | ||
|  |     { | ||
|  |         $body = parent::getBodyAsString(); | ||
|  |         foreach ($this->parameters as $name => $value) { | ||
|  |             if (null !== $value) { | ||
|  |                 $body .= '; '.$this->createParameter($name, $value); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         return $body; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Generate a list of all tokens in the final header. | ||
|  |      * | ||
|  |      * This doesn't need to be overridden in theory, but it is for implementation | ||
|  |      * reasons to prevent potential breakage of attributes. | ||
|  |      */ | ||
|  |     protected function toTokens(string $string = null): array | ||
|  |     { | ||
|  |         $tokens = parent::toTokens(parent::getBodyAsString()); | ||
|  | 
 | ||
|  |         // Try creating any parameters
 | ||
|  |         foreach ($this->parameters as $name => $value) { | ||
|  |             if (null !== $value) { | ||
|  |                 // Add the semi-colon separator
 | ||
|  |                 $tokens[\count($tokens) - 1] .= ';'; | ||
|  |                 $tokens = array_merge($tokens, $this->generateTokenLines(' '.$this->createParameter($name, $value))); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         return $tokens; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Render an RFC 2047 compliant header parameter from the $name and $value. | ||
|  |      */ | ||
|  |     private function createParameter(string $name, string $value): string | ||
|  |     { | ||
|  |         $origValue = $value; | ||
|  | 
 | ||
|  |         $encoded = false; | ||
|  |         // Allow room for parameter name, indices, "=" and DQUOTEs
 | ||
|  |         $maxValueLength = $this->getMaxLineLength() - \strlen($name.'=*N"";') - 1; | ||
|  |         $firstLineOffset = 0; | ||
|  | 
 | ||
|  |         // If it's not already a valid parameter value...
 | ||
|  |         if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) { | ||
|  |             // TODO: text, or something else??
 | ||
|  |             // ... and it's not ascii
 | ||
|  |             if (!preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x7F]*$/D', $value)) { | ||
|  |                 $encoded = true; | ||
|  |                 // Allow space for the indices, charset and language
 | ||
|  |                 $maxValueLength = $this->getMaxLineLength() - \strlen($name.'*N*="";') - 1; | ||
|  |                 $firstLineOffset = \strlen($this->getCharset()."'".$this->getLanguage()."'"); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         // Encode if we need to
 | ||
|  |         if ($encoded || \strlen($value) > $maxValueLength) { | ||
|  |             if (null !== $this->encoder) { | ||
|  |                 $value = $this->encoder->encodeString($origValue, $this->getCharset(), $firstLineOffset, $maxValueLength); | ||
|  |             } else { | ||
|  |                 // We have to go against RFC 2183/2231 in some areas for interoperability
 | ||
|  |                 $value = $this->getTokenAsEncodedWord($origValue); | ||
|  |                 $encoded = false; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         $valueLines = $this->encoder ? explode("\r\n", $value) : [$value]; | ||
|  | 
 | ||
|  |         // Need to add indices
 | ||
|  |         if (\count($valueLines) > 1) { | ||
|  |             $paramLines = []; | ||
|  |             foreach ($valueLines as $i => $line) { | ||
|  |                 $paramLines[] = $name.'*'.$i.$this->getEndOfParameterValue($line, true, 0 === $i); | ||
|  |             } | ||
|  | 
 | ||
|  |             return implode(";\r\n ", $paramLines); | ||
|  |         } else { | ||
|  |             return $name.$this->getEndOfParameterValue($valueLines[0], $encoded, true); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Returns the parameter value from the "=" and beyond. | ||
|  |      * | ||
|  |      * @param string $value to append | ||
|  |      */ | ||
|  |     private function getEndOfParameterValue(string $value, bool $encoded = false, bool $firstLine = false): string | ||
|  |     { | ||
|  |         $forceHttpQuoting = 'content-disposition' === strtolower($this->getName()) && 'form-data' === $this->getValue(); | ||
|  |         if ($forceHttpQuoting || !preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) { | ||
|  |             $value = '"'.$value.'"'; | ||
|  |         } | ||
|  |         $prepend = '='; | ||
|  |         if ($encoded) { | ||
|  |             $prepend = '*='; | ||
|  |             if ($firstLine) { | ||
|  |                 $prepend = '*='.$this->getCharset()."'".$this->getLanguage()."'"; | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         return $prepend.$value; | ||
|  |     } | ||
|  | } |