first commit

This commit is contained in:
Sampanna Rimal
2024-08-27 17:48:06 +05:45
commit 53c0140f58
10839 changed files with 1125847 additions and 0 deletions

View File

@@ -0,0 +1,288 @@
<?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\QpMimeHeaderEncoder;
/**
* An abstract base MIME Header.
*
* @author Chris Corbyn
*/
abstract class AbstractHeader implements HeaderInterface
{
public const PHRASE_PATTERN = '(?:(?:(?:(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?[a-zA-Z0-9!#\$%&\'\*\+\-\/=\?\^_`\{\}\|~]+(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?)|(?:(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?"((?:(?:[ \t]*(?:\r\n))?[ \t])?(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21\x23-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])))*(?:(?:[ \t]*(?:\r\n))?[ \t])?"(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))*(?:(?:(?:(?:[ \t]*(?:\r\n))?[ \t])?(\((?:(?:(?:[ \t]*(?:\r\n))?[ \t])|(?:(?:[\x01-\x08\x0B\x0C\x0E-\x19\x7F]|[\x21-\x27\x2A-\x5B\x5D-\x7E])|(?:\\[\x00-\x08\x0B\x0C\x0E-\x7F])|(?1)))*(?:(?:[ \t]*(?:\r\n))?[ \t])?\)))|(?:(?:[ \t]*(?:\r\n))?[ \t])))?))+?)';
private static QpMimeHeaderEncoder $encoder;
private string $name;
private int $lineLength = 76;
private ?string $lang = null;
private string $charset = 'utf-8';
public function __construct(string $name)
{
$this->name = $name;
}
/**
* @return void
*/
public function setCharset(string $charset)
{
$this->charset = $charset;
}
public function getCharset(): ?string
{
return $this->charset;
}
/**
* Set the language used in this Header.
*
* For example, for US English, 'en-us'.
*
* @return void
*/
public function setLanguage(string $lang)
{
$this->lang = $lang;
}
public function getLanguage(): ?string
{
return $this->lang;
}
public function getName(): string
{
return $this->name;
}
/**
* @return void
*/
public function setMaxLineLength(int $lineLength)
{
$this->lineLength = $lineLength;
}
public function getMaxLineLength(): int
{
return $this->lineLength;
}
public function toString(): string
{
return $this->tokensToString($this->toTokens());
}
/**
* Produces a compliant, formatted RFC 2822 'phrase' based on the string given.
*
* @param string $string as displayed
* @param bool $shorten the first line to make remove for header name
*/
protected function createPhrase(HeaderInterface $header, string $string, string $charset, bool $shorten = false): string
{
// Treat token as exactly what was given
$phraseStr = $string;
// If it's not valid
if (!preg_match('/^'.self::PHRASE_PATTERN.'$/D', $phraseStr)) {
// .. but it is just ascii text, try escaping some characters
// and make it a quoted-string
if (preg_match('/^[\x00-\x08\x0B\x0C\x0E-\x7F]*$/D', $phraseStr)) {
foreach (['\\', '"'] as $char) {
$phraseStr = str_replace($char, '\\'.$char, $phraseStr);
}
$phraseStr = '"'.$phraseStr.'"';
} else {
// ... otherwise it needs encoding
// Determine space remaining on line if first line
if ($shorten) {
$usedLength = \strlen($header->getName().': ');
} else {
$usedLength = 0;
}
$phraseStr = $this->encodeWords($header, $string, $usedLength);
}
} elseif (str_contains($phraseStr, '(')) {
foreach (['\\', '"'] as $char) {
$phraseStr = str_replace($char, '\\'.$char, $phraseStr);
}
$phraseStr = '"'.$phraseStr.'"';
}
return $phraseStr;
}
/**
* Encode needed word tokens within a string of input.
*/
protected function encodeWords(HeaderInterface $header, string $input, int $usedLength = -1): string
{
$value = '';
$tokens = $this->getEncodableWordTokens($input);
foreach ($tokens as $token) {
// See RFC 2822, Sect 2.2 (really 2.2 ??)
if ($this->tokenNeedsEncoding($token)) {
// Don't encode starting WSP
$firstChar = substr($token, 0, 1);
switch ($firstChar) {
case ' ':
case "\t":
$value .= $firstChar;
$token = substr($token, 1);
}
if (-1 == $usedLength) {
$usedLength = \strlen($header->getName().': ') + \strlen($value);
}
$value .= $this->getTokenAsEncodedWord($token, $usedLength);
} else {
$value .= $token;
}
}
return $value;
}
protected function tokenNeedsEncoding(string $token): bool
{
return (bool) preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token);
}
/**
* Splits a string into tokens in blocks of words which can be encoded quickly.
*
* @return string[]
*/
protected function getEncodableWordTokens(string $string): array
{
$tokens = [];
$encodedToken = '';
// Split at all whitespace boundaries
foreach (preg_split('~(?=[\t ])~', $string) as $token) {
if ($this->tokenNeedsEncoding($token)) {
$encodedToken .= $token;
} else {
if ('' !== $encodedToken) {
$tokens[] = $encodedToken;
$encodedToken = '';
}
$tokens[] = $token;
}
}
if ('' !== $encodedToken) {
$tokens[] = $encodedToken;
}
return $tokens;
}
/**
* Get a token as an encoded word for safe insertion into headers.
*/
protected function getTokenAsEncodedWord(string $token, int $firstLineOffset = 0): string
{
self::$encoder ??= new QpMimeHeaderEncoder();
// Adjust $firstLineOffset to account for space needed for syntax
$charsetDecl = $this->charset;
if (null !== $this->lang) {
$charsetDecl .= '*'.$this->lang;
}
$encodingWrapperLength = \strlen('=?'.$charsetDecl.'?'.self::$encoder->getName().'??=');
if ($firstLineOffset >= 75) {
// Does this logic need to be here?
$firstLineOffset = 0;
}
$encodedTextLines = explode("\r\n",
self::$encoder->encodeString($token, $this->charset, $firstLineOffset, 75 - $encodingWrapperLength)
);
if ('iso-2022-jp' !== strtolower($this->charset)) {
// special encoding for iso-2022-jp using mb_encode_mimeheader
foreach ($encodedTextLines as $lineNum => $line) {
$encodedTextLines[$lineNum] = '=?'.$charsetDecl.'?'.self::$encoder->getName().'?'.$line.'?=';
}
}
return implode("\r\n ", $encodedTextLines);
}
/**
* Generates tokens from the given string which include CRLF as individual tokens.
*
* @return string[]
*/
protected function generateTokenLines(string $token): array
{
return preg_split('~(\r\n)~', $token, -1, \PREG_SPLIT_DELIM_CAPTURE);
}
/**
* Generate a list of all tokens in the final header.
*/
protected function toTokens(?string $string = null): array
{
$string ??= $this->getBodyAsString();
$tokens = [];
// Generate atoms; split at all invisible boundaries followed by WSP
foreach (preg_split('~(?=[ \t])~', $string) as $token) {
$newTokens = $this->generateTokenLines($token);
foreach ($newTokens as $newToken) {
$tokens[] = $newToken;
}
}
return $tokens;
}
/**
* Takes an array of tokens which appear in the header and turns them into
* an RFC 2822 compliant string, adding FWSP where needed.
*
* @param string[] $tokens
*/
private function tokensToString(array $tokens): string
{
$lineCount = 0;
$headerLines = [];
$headerLines[] = $this->name.': ';
$currentLine = &$headerLines[$lineCount++];
// Build all tokens back into compliant header
foreach ($tokens as $i => $token) {
// Line longer than specified maximum or token was just a new line
if (("\r\n" === $token)
|| ($i > 0 && \strlen($currentLine.$token) > $this->lineLength)
&& '' !== $currentLine) {
$headerLines[] = '';
$currentLine = &$headerLines[$lineCount++];
}
// Append token to the line
if ("\r\n" !== $token) {
$currentLine .= $token;
}
}
// Implode with FWS (RFC 2822, 2.2.3)
return implode("\r\n", $headerLines);
}
}

View File

@@ -0,0 +1,62 @@
<?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;
/**
* A Date MIME Header.
*
* @author Chris Corbyn
*/
final class DateHeader extends AbstractHeader
{
private \DateTimeImmutable $dateTime;
public function __construct(string $name, \DateTimeInterface $date)
{
parent::__construct($name);
$this->setDateTime($date);
}
/**
* @param \DateTimeInterface $body
*/
public function setBody(mixed $body): void
{
$this->setDateTime($body);
}
public function getBody(): \DateTimeImmutable
{
return $this->getDateTime();
}
public function getDateTime(): \DateTimeImmutable
{
return $this->dateTime;
}
/**
* Set the date-time of the Date in this Header.
*
* If a DateTime instance is provided, it is converted to DateTimeImmutable.
*/
public function setDateTime(\DateTimeInterface $dateTime): void
{
$this->dateTime = \DateTimeImmutable::createFromInterface($dateTime);
}
public function getBodyAsString(): string
{
return $this->dateTime->format(\DateTimeInterface::RFC2822);
}
}

View File

@@ -0,0 +1,72 @@
<?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;
/**
* A MIME Header.
*
* @author Chris Corbyn
*/
interface HeaderInterface
{
/**
* Sets the body.
*
* The type depends on the Header concrete class.
*
* @return void
*/
public function setBody(mixed $body);
/**
* Gets the body.
*
* The return type depends on the Header concrete class.
*/
public function getBody(): mixed;
/**
* @return void
*/
public function setCharset(string $charset);
public function getCharset(): ?string;
/**
* @return void
*/
public function setLanguage(string $lang);
public function getLanguage(): ?string;
public function getName(): string;
/**
* @return void
*/
public function setMaxLineLength(int $lineLength);
public function getMaxLineLength(): int;
/**
* Gets this Header rendered as a compliant string.
*/
public function toString(): string;
/**
* Gets the header's body, prepared for folding into a final header value.
*
* This is not necessarily RFC 2822 compliant since folding white space is
* not added at this stage (see {@link toString()} for that).
*/
public function getBodyAsString(): string;
}

316
vendor/symfony/mime/Header/Headers.php vendored Normal file
View File

@@ -0,0 +1,316 @@
<?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\Address;
use Symfony\Component\Mime\Exception\LogicException;
/**
* A collection of headers.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
final class Headers
{
private const UNIQUE_HEADERS = [
'date', 'from', 'sender', 'reply-to', 'to', 'cc', 'bcc',
'message-id', 'in-reply-to', 'references', 'subject',
];
private const HEADER_CLASS_MAP = [
'date' => DateHeader::class,
'from' => MailboxListHeader::class,
'sender' => MailboxHeader::class,
'reply-to' => MailboxListHeader::class,
'to' => MailboxListHeader::class,
'cc' => MailboxListHeader::class,
'bcc' => MailboxListHeader::class,
'message-id' => IdentificationHeader::class,
'in-reply-to' => [UnstructuredHeader::class, IdentificationHeader::class], // `In-Reply-To` and `References` are less strict than RFC 2822 (3.6.4) to allow users entering the original email's ...
'references' => [UnstructuredHeader::class, IdentificationHeader::class], // ... `Message-ID`, even if that is no valid `msg-id`
'return-path' => PathHeader::class,
];
/**
* @var HeaderInterface[][]
*/
private array $headers = [];
private int $lineLength = 76;
public function __construct(HeaderInterface ...$headers)
{
foreach ($headers as $header) {
$this->add($header);
}
}
public function __clone()
{
foreach ($this->headers as $name => $collection) {
foreach ($collection as $i => $header) {
$this->headers[$name][$i] = clone $header;
}
}
}
public function setMaxLineLength(int $lineLength): void
{
$this->lineLength = $lineLength;
foreach ($this->all() as $header) {
$header->setMaxLineLength($lineLength);
}
}
public function getMaxLineLength(): int
{
return $this->lineLength;
}
/**
* @param array<Address|string> $addresses
*
* @return $this
*/
public function addMailboxListHeader(string $name, array $addresses): static
{
return $this->add(new MailboxListHeader($name, Address::createArray($addresses)));
}
/**
* @return $this
*/
public function addMailboxHeader(string $name, Address|string $address): static
{
return $this->add(new MailboxHeader($name, Address::create($address)));
}
/**
* @return $this
*/
public function addIdHeader(string $name, string|array $ids): static
{
return $this->add(new IdentificationHeader($name, $ids));
}
/**
* @return $this
*/
public function addPathHeader(string $name, Address|string $path): static
{
return $this->add(new PathHeader($name, $path instanceof Address ? $path : new Address($path)));
}
/**
* @return $this
*/
public function addDateHeader(string $name, \DateTimeInterface $dateTime): static
{
return $this->add(new DateHeader($name, $dateTime));
}
/**
* @return $this
*/
public function addTextHeader(string $name, string $value): static
{
return $this->add(new UnstructuredHeader($name, $value));
}
/**
* @return $this
*/
public function addParameterizedHeader(string $name, string $value, array $params = []): static
{
return $this->add(new ParameterizedHeader($name, $value, $params));
}
/**
* @return $this
*/
public function addHeader(string $name, mixed $argument, array $more = []): static
{
$headerClass = self::HEADER_CLASS_MAP[strtolower($name)] ?? UnstructuredHeader::class;
if (\is_array($headerClass)) {
$headerClass = $headerClass[0];
}
$parts = explode('\\', $headerClass);
$method = 'add'.ucfirst(array_pop($parts));
if ('addUnstructuredHeader' === $method) {
$method = 'addTextHeader';
} elseif ('addIdentificationHeader' === $method) {
$method = 'addIdHeader';
} elseif ('addMailboxListHeader' === $method && !\is_array($argument)) {
$argument = [$argument];
}
return $this->$method($name, $argument, $more);
}
public function has(string $name): bool
{
return isset($this->headers[strtolower($name)]);
}
/**
* @return $this
*/
public function add(HeaderInterface $header): static
{
self::checkHeaderClass($header);
$header->setMaxLineLength($this->lineLength);
$name = strtolower($header->getName());
if (\in_array($name, self::UNIQUE_HEADERS, true) && isset($this->headers[$name]) && \count($this->headers[$name]) > 0) {
throw new LogicException(sprintf('Impossible to set header "%s" as it\'s already defined and must be unique.', $header->getName()));
}
$this->headers[$name][] = $header;
return $this;
}
public function get(string $name): ?HeaderInterface
{
$name = strtolower($name);
if (!isset($this->headers[$name])) {
return null;
}
$values = array_values($this->headers[$name]);
return array_shift($values);
}
public function all(?string $name = null): iterable
{
if (null === $name) {
foreach ($this->headers as $name => $collection) {
foreach ($collection as $header) {
yield $name => $header;
}
}
} elseif (isset($this->headers[strtolower($name)])) {
foreach ($this->headers[strtolower($name)] as $header) {
yield $header;
}
}
}
public function getNames(): array
{
return array_keys($this->headers);
}
public function remove(string $name): void
{
unset($this->headers[strtolower($name)]);
}
public static function isUniqueHeader(string $name): bool
{
return \in_array(strtolower($name), self::UNIQUE_HEADERS, true);
}
/**
* @throws LogicException if the header name and class are not compatible
*/
public static function checkHeaderClass(HeaderInterface $header): void
{
$name = strtolower($header->getName());
$headerClasses = self::HEADER_CLASS_MAP[$name] ?? [];
if (!\is_array($headerClasses)) {
$headerClasses = [$headerClasses];
}
if (!$headerClasses) {
return;
}
foreach ($headerClasses as $c) {
if ($header instanceof $c) {
return;
}
}
throw new LogicException(sprintf('The "%s" header must be an instance of "%s" (got "%s").', $header->getName(), implode('" or "', $headerClasses), get_debug_type($header)));
}
public function toString(): string
{
$string = '';
foreach ($this->toArray() as $str) {
$string .= $str."\r\n";
}
return $string;
}
public function toArray(): array
{
$arr = [];
foreach ($this->all() as $header) {
if ('' !== $header->getBodyAsString()) {
$arr[] = $header->toString();
}
}
return $arr;
}
public function getHeaderBody(string $name): mixed
{
return $this->has($name) ? $this->get($name)->getBody() : null;
}
/**
* @internal
*/
public function setHeaderBody(string $type, string $name, mixed $body): void
{
if ($this->has($name)) {
$this->get($name)->setBody($body);
} else {
$this->{'add'.$type.'Header'}($name, $body);
}
}
public function getHeaderParameter(string $name, string $parameter): ?string
{
if (!$this->has($name)) {
return null;
}
$header = $this->get($name);
if (!$header instanceof ParameterizedHeader) {
throw new LogicException(sprintf('Unable to get parameter "%s" on header "%s" as the header is not of class "%s".', $parameter, $name, ParameterizedHeader::class));
}
return $header->getParameter($parameter);
}
/**
* @internal
*/
public function setHeaderParameter(string $name, string $parameter, ?string $value): void
{
if (!$this->has($name)) {
throw new LogicException(sprintf('Unable to set parameter "%s" on header "%s" as the header is not defined.', $parameter, $name));
}
$header = $this->get($name);
if (!$header instanceof ParameterizedHeader) {
throw new LogicException(sprintf('Unable to set parameter "%s" on header "%s" as the header is not of class "%s".', $parameter, $name, ParameterizedHeader::class));
}
$header->setParameter($parameter, $value);
}
}

View File

@@ -0,0 +1,107 @@
<?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\Address;
use Symfony\Component\Mime\Exception\RfcComplianceException;
/**
* An ID MIME Header for something like Message-ID or Content-ID (one or more addresses).
*
* @author Chris Corbyn
*/
final class IdentificationHeader extends AbstractHeader
{
private array $ids = [];
private array $idsAsAddresses = [];
public function __construct(string $name, string|array $ids)
{
parent::__construct($name);
$this->setId($ids);
}
/**
* @param string|string[] $body a string ID or an array of IDs
*
* @throws RfcComplianceException
*/
public function setBody(mixed $body): void
{
$this->setId($body);
}
public function getBody(): array
{
return $this->getIds();
}
/**
* Set the ID used in the value of this header.
*
* @param string|string[] $id
*
* @throws RfcComplianceException
*/
public function setId(string|array $id): void
{
$this->setIds(\is_array($id) ? $id : [$id]);
}
/**
* Get the ID used in the value of this Header.
*
* If multiple IDs are set only the first is returned.
*/
public function getId(): ?string
{
return $this->ids[0] ?? null;
}
/**
* Set a collection of IDs to use in the value of this Header.
*
* @param string[] $ids
*
* @throws RfcComplianceException
*/
public function setIds(array $ids): void
{
$this->ids = [];
$this->idsAsAddresses = [];
foreach ($ids as $id) {
$this->idsAsAddresses[] = new Address($id);
$this->ids[] = $id;
}
}
/**
* Get the list of IDs used in this Header.
*
* @return string[]
*/
public function getIds(): array
{
return $this->ids;
}
public function getBodyAsString(): string
{
$addrs = [];
foreach ($this->idsAsAddresses as $address) {
$addrs[] = '<'.$address->toString().'>';
}
return implode(' ', $addrs);
}
}

View File

@@ -0,0 +1,85 @@
<?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\Address;
use Symfony\Component\Mime\Exception\RfcComplianceException;
/**
* A Mailbox MIME Header for something like Sender (one named address).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
final class MailboxHeader extends AbstractHeader
{
private Address $address;
public function __construct(string $name, Address $address)
{
parent::__construct($name);
$this->setAddress($address);
}
/**
* @param Address $body
*
* @throws RfcComplianceException
*/
public function setBody(mixed $body): void
{
$this->setAddress($body);
}
/**
* @throws RfcComplianceException
*/
public function getBody(): Address
{
return $this->getAddress();
}
/**
* @throws RfcComplianceException
*/
public function setAddress(Address $address): void
{
$this->address = $address;
}
public function getAddress(): Address
{
return $this->address;
}
public function getBodyAsString(): string
{
$str = $this->address->getEncodedAddress();
if ($name = $this->address->getName()) {
$str = $this->createPhrase($this, $name, $this->getCharset(), true).' <'.$str.'>';
}
return $str;
}
/**
* Redefine the encoding requirements for an address.
*
* All "specials" must be encoded as the full header value will not be quoted
*
* @see RFC 2822 3.2.1
*/
protected function tokenNeedsEncoding(string $token): bool
{
return preg_match('/[()<>\[\]:;@\,."]/', $token) || parent::tokenNeedsEncoding($token);
}
}

View File

@@ -0,0 +1,136 @@
<?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\Address;
use Symfony\Component\Mime\Exception\RfcComplianceException;
/**
* A Mailbox list MIME Header for something like From, To, Cc, and Bcc (one or more named addresses).
*
* @author Chris Corbyn
*/
final class MailboxListHeader extends AbstractHeader
{
private array $addresses = [];
/**
* @param Address[] $addresses
*/
public function __construct(string $name, array $addresses)
{
parent::__construct($name);
$this->setAddresses($addresses);
}
/**
* @param Address[] $body
*
* @throws RfcComplianceException
*/
public function setBody(mixed $body): void
{
$this->setAddresses($body);
}
/**
* @return Address[]
*
* @throws RfcComplianceException
*/
public function getBody(): array
{
return $this->getAddresses();
}
/**
* Sets a list of addresses to be shown in this Header.
*
* @param Address[] $addresses
*
* @throws RfcComplianceException
*/
public function setAddresses(array $addresses): void
{
$this->addresses = [];
$this->addAddresses($addresses);
}
/**
* Sets a list of addresses to be shown in this Header.
*
* @param Address[] $addresses
*
* @throws RfcComplianceException
*/
public function addAddresses(array $addresses): void
{
foreach ($addresses as $address) {
$this->addAddress($address);
}
}
/**
* @throws RfcComplianceException
*/
public function addAddress(Address $address): void
{
$this->addresses[] = $address;
}
/**
* @return Address[]
*/
public function getAddresses(): array
{
return $this->addresses;
}
/**
* Gets the full mailbox list of this Header as an array of valid RFC 2822 strings.
*
* @return string[]
*
* @throws RfcComplianceException
*/
public function getAddressStrings(): array
{
$strings = [];
foreach ($this->addresses as $address) {
$str = $address->getEncodedAddress();
if ($name = $address->getName()) {
$str = $this->createPhrase($this, $name, $this->getCharset(), !$strings).' <'.$str.'>';
}
$strings[] = $str;
}
return $strings;
}
public function getBodyAsString(): string
{
return implode(', ', $this->getAddressStrings());
}
/**
* Redefine the encoding requirements for addresses.
*
* All "specials" must be encoded as the full header value will not be quoted
*
* @see RFC 2822 3.2.1
*/
protected function tokenNeedsEncoding(string $token): bool
{
return preg_match('/[()<>\[\]:;@\,."]/', $token) || parent::tokenNeedsEncoding($token);
}
}

View File

@@ -0,0 +1,191 @@
<?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 ?Rfc2231Encoder $encoder = null;
private array $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): void
{
$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): void
{
$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;
}
}

View File

@@ -0,0 +1,62 @@
<?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\Address;
use Symfony\Component\Mime\Exception\RfcComplianceException;
/**
* A Path Header, such a Return-Path (one address).
*
* @author Chris Corbyn
*/
final class PathHeader extends AbstractHeader
{
private Address $address;
public function __construct(string $name, Address $address)
{
parent::__construct($name);
$this->setAddress($address);
}
/**
* @param Address $body
*
* @throws RfcComplianceException
*/
public function setBody(mixed $body): void
{
$this->setAddress($body);
}
public function getBody(): Address
{
return $this->getAddress();
}
public function setAddress(Address $address): void
{
$this->address = $address;
}
public function getAddress(): Address
{
return $this->address;
}
public function getBodyAsString(): string
{
return '<'.$this->address->toString().'>';
}
}

View File

@@ -0,0 +1,70 @@
<?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;
/**
* A Simple MIME Header.
*
* @author Chris Corbyn
*/
class UnstructuredHeader extends AbstractHeader
{
private string $value;
public function __construct(string $name, string $value)
{
parent::__construct($name);
$this->setValue($value);
}
/**
* @param string $body
*
* @return void
*/
public function setBody(mixed $body)
{
$this->setValue($body);
}
public function getBody(): string
{
return $this->getValue();
}
/**
* Get the (unencoded) value of this header.
*/
public function getValue(): string
{
return $this->value;
}
/**
* Set the (unencoded) value of this header.
*
* @return void
*/
public function setValue(string $value)
{
$this->value = $value;
}
/**
* Get the value of this header prepared for rendering.
*/
public function getBodyAsString(): string
{
return $this->encodeWords($this, $this->value);
}
}