| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 | <?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()."'");            }            if (\in_array($name, ['name', 'filename'], true) && 'form-data' === $this->getValue() && 'content-disposition' === strtolower($this->getName()) && preg_match('//u', $value)) {                // WHATWG HTML living standard 4.10.21.8 2 specifies:                // For field names and filenames for file fields, the result of the                // encoding in the previous bullet point must be escaped by replacing                // any 0x0A (LF) bytes with the byte sequence `%0A`, 0x0D (CR) with `%0D`                // and 0x22 (") with `%22`.                // The user agent must not perform any other escapes.                $value = str_replace(['"', "\r", "\n"], ['%22', '%0D', '%0A'], $value);                if (\strlen($value) <= $maxValueLength) {                    return $name.'="'.$value.'"';                }                $value = $origValue;            }        }        // 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 = 'form-data' === $this->getValue() && 'content-disposition' === strtolower($this->getName());        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;    }}
 |