You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
191 lines
6.2 KiB
191 lines
6.2 KiB
<?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; |
|
} |
|
}
|
|
|