first commit
This commit is contained in:
120
vendor/symfony/mime/Address.php
vendored
Normal file
120
vendor/symfony/mime/Address.php
vendored
Normal file
@ -0,0 +1,120 @@
|
||||
<?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;
|
||||
|
||||
use Egulias\EmailValidator\EmailValidator;
|
||||
use Egulias\EmailValidator\Validation\MessageIDValidation;
|
||||
use Egulias\EmailValidator\Validation\RFCValidation;
|
||||
use Symfony\Component\Mime\Encoder\IdnAddressEncoder;
|
||||
use Symfony\Component\Mime\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Mime\Exception\LogicException;
|
||||
use Symfony\Component\Mime\Exception\RfcComplianceException;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class Address
|
||||
{
|
||||
/**
|
||||
* A regex that matches a structure like 'Name <email@address.com>'.
|
||||
* It matches anything between the first < and last > as email address.
|
||||
* This allows to use a single string to construct an Address, which can be convenient to use in
|
||||
* config, and allows to have more readable config.
|
||||
* This does not try to cover all edge cases for address.
|
||||
*/
|
||||
private const FROM_STRING_PATTERN = '~(?<displayName>[^<]*)<(?<addrSpec>.*)>[^>]*~';
|
||||
|
||||
private static EmailValidator $validator;
|
||||
private static IdnAddressEncoder $encoder;
|
||||
|
||||
private string $address;
|
||||
private string $name;
|
||||
|
||||
public function __construct(string $address, string $name = '')
|
||||
{
|
||||
if (!class_exists(EmailValidator::class)) {
|
||||
throw new LogicException(sprintf('The "%s" class cannot be used as it needs "%s". Try running "composer require egulias/email-validator".', __CLASS__, EmailValidator::class));
|
||||
}
|
||||
|
||||
self::$validator ??= new EmailValidator();
|
||||
|
||||
$this->address = trim($address);
|
||||
$this->name = trim(str_replace(["\n", "\r"], '', $name));
|
||||
|
||||
if (!self::$validator->isValid($this->address, class_exists(MessageIDValidation::class) ? new MessageIDValidation() : new RFCValidation())) {
|
||||
throw new RfcComplianceException(sprintf('Email "%s" does not comply with addr-spec of RFC 2822.', $address));
|
||||
}
|
||||
}
|
||||
|
||||
public function getAddress(): string
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getEncodedAddress(): string
|
||||
{
|
||||
self::$encoder ??= new IdnAddressEncoder();
|
||||
|
||||
return self::$encoder->encodeString($this->address);
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return ($n = $this->getEncodedName()) ? $n.' <'.$this->getEncodedAddress().'>' : $this->getEncodedAddress();
|
||||
}
|
||||
|
||||
public function getEncodedName(): string
|
||||
{
|
||||
if ('' === $this->getName()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return sprintf('"%s"', preg_replace('/"/u', '\"', $this->getName()));
|
||||
}
|
||||
|
||||
public static function create(self|string $address): self
|
||||
{
|
||||
if ($address instanceof self) {
|
||||
return $address;
|
||||
}
|
||||
|
||||
if (!str_contains($address, '<')) {
|
||||
return new self($address);
|
||||
}
|
||||
|
||||
if (!preg_match(self::FROM_STRING_PATTERN, $address, $matches)) {
|
||||
throw new InvalidArgumentException(sprintf('Could not parse "%s" to a "%s" instance.', $address, self::class));
|
||||
}
|
||||
|
||||
return new self($matches['addrSpec'], trim($matches['displayName'], ' \'"'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Address|string> $addresses
|
||||
*
|
||||
* @return Address[]
|
||||
*/
|
||||
public static function createArray(array $addresses): array
|
||||
{
|
||||
$addrs = [];
|
||||
foreach ($addresses as $address) {
|
||||
$addrs[] = self::create($address);
|
||||
}
|
||||
|
||||
return $addrs;
|
||||
}
|
||||
}
|
20
vendor/symfony/mime/BodyRendererInterface.php
vendored
Normal file
20
vendor/symfony/mime/BodyRendererInterface.php
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface BodyRendererInterface
|
||||
{
|
||||
public function render(Message $message): void;
|
||||
}
|
50
vendor/symfony/mime/CHANGELOG.md
vendored
Normal file
50
vendor/symfony/mime/CHANGELOG.md
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
6.3
|
||||
---
|
||||
|
||||
* Support detection of related parts if `Content-Id` is used instead of the name
|
||||
* Add `TextPart::getDisposition()`
|
||||
|
||||
6.2
|
||||
---
|
||||
|
||||
* Add `File`
|
||||
* Deprecate `Email::attachPart()`, use `addPart()` instead
|
||||
* Deprecate calling `Message::setBody()` without arguments
|
||||
|
||||
6.1
|
||||
---
|
||||
|
||||
* Add `DataPart::getFilename()` and `DataPart::getContentType()`
|
||||
|
||||
6.0
|
||||
---
|
||||
|
||||
* Remove `Address::fromString()`, use `Address::create()` instead
|
||||
* Remove `Serializable` interface from `RawMessage`
|
||||
|
||||
5.2.0
|
||||
-----
|
||||
|
||||
* Add support for DKIM
|
||||
* Deprecated `Address::fromString()`, use `Address::create()` instead
|
||||
|
||||
4.4.0
|
||||
-----
|
||||
|
||||
* [BC BREAK] Removed `NamedAddress` (`Address` now supports a name)
|
||||
* Added PHPUnit constraints
|
||||
* Added `AbstractPart::asDebugString()`
|
||||
* Added `Address::fromString()`
|
||||
|
||||
4.3.3
|
||||
-----
|
||||
|
||||
* [BC BREAK] Renamed method `Headers::getAll()` to `Headers::all()`.
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
||||
* Introduced the component as experimental
|
211
vendor/symfony/mime/CharacterStream.php
vendored
Normal file
211
vendor/symfony/mime/CharacterStream.php
vendored
Normal file
@ -0,0 +1,211 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Xavier De Cock <xdecock@gmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class CharacterStream
|
||||
{
|
||||
/** Pre-computed for optimization */
|
||||
private const UTF8_LENGTH_MAP = [
|
||||
"\x00" => 1, "\x01" => 1, "\x02" => 1, "\x03" => 1, "\x04" => 1, "\x05" => 1, "\x06" => 1, "\x07" => 1,
|
||||
"\x08" => 1, "\x09" => 1, "\x0a" => 1, "\x0b" => 1, "\x0c" => 1, "\x0d" => 1, "\x0e" => 1, "\x0f" => 1,
|
||||
"\x10" => 1, "\x11" => 1, "\x12" => 1, "\x13" => 1, "\x14" => 1, "\x15" => 1, "\x16" => 1, "\x17" => 1,
|
||||
"\x18" => 1, "\x19" => 1, "\x1a" => 1, "\x1b" => 1, "\x1c" => 1, "\x1d" => 1, "\x1e" => 1, "\x1f" => 1,
|
||||
"\x20" => 1, "\x21" => 1, "\x22" => 1, "\x23" => 1, "\x24" => 1, "\x25" => 1, "\x26" => 1, "\x27" => 1,
|
||||
"\x28" => 1, "\x29" => 1, "\x2a" => 1, "\x2b" => 1, "\x2c" => 1, "\x2d" => 1, "\x2e" => 1, "\x2f" => 1,
|
||||
"\x30" => 1, "\x31" => 1, "\x32" => 1, "\x33" => 1, "\x34" => 1, "\x35" => 1, "\x36" => 1, "\x37" => 1,
|
||||
"\x38" => 1, "\x39" => 1, "\x3a" => 1, "\x3b" => 1, "\x3c" => 1, "\x3d" => 1, "\x3e" => 1, "\x3f" => 1,
|
||||
"\x40" => 1, "\x41" => 1, "\x42" => 1, "\x43" => 1, "\x44" => 1, "\x45" => 1, "\x46" => 1, "\x47" => 1,
|
||||
"\x48" => 1, "\x49" => 1, "\x4a" => 1, "\x4b" => 1, "\x4c" => 1, "\x4d" => 1, "\x4e" => 1, "\x4f" => 1,
|
||||
"\x50" => 1, "\x51" => 1, "\x52" => 1, "\x53" => 1, "\x54" => 1, "\x55" => 1, "\x56" => 1, "\x57" => 1,
|
||||
"\x58" => 1, "\x59" => 1, "\x5a" => 1, "\x5b" => 1, "\x5c" => 1, "\x5d" => 1, "\x5e" => 1, "\x5f" => 1,
|
||||
"\x60" => 1, "\x61" => 1, "\x62" => 1, "\x63" => 1, "\x64" => 1, "\x65" => 1, "\x66" => 1, "\x67" => 1,
|
||||
"\x68" => 1, "\x69" => 1, "\x6a" => 1, "\x6b" => 1, "\x6c" => 1, "\x6d" => 1, "\x6e" => 1, "\x6f" => 1,
|
||||
"\x70" => 1, "\x71" => 1, "\x72" => 1, "\x73" => 1, "\x74" => 1, "\x75" => 1, "\x76" => 1, "\x77" => 1,
|
||||
"\x78" => 1, "\x79" => 1, "\x7a" => 1, "\x7b" => 1, "\x7c" => 1, "\x7d" => 1, "\x7e" => 1, "\x7f" => 1,
|
||||
"\x80" => 0, "\x81" => 0, "\x82" => 0, "\x83" => 0, "\x84" => 0, "\x85" => 0, "\x86" => 0, "\x87" => 0,
|
||||
"\x88" => 0, "\x89" => 0, "\x8a" => 0, "\x8b" => 0, "\x8c" => 0, "\x8d" => 0, "\x8e" => 0, "\x8f" => 0,
|
||||
"\x90" => 0, "\x91" => 0, "\x92" => 0, "\x93" => 0, "\x94" => 0, "\x95" => 0, "\x96" => 0, "\x97" => 0,
|
||||
"\x98" => 0, "\x99" => 0, "\x9a" => 0, "\x9b" => 0, "\x9c" => 0, "\x9d" => 0, "\x9e" => 0, "\x9f" => 0,
|
||||
"\xa0" => 0, "\xa1" => 0, "\xa2" => 0, "\xa3" => 0, "\xa4" => 0, "\xa5" => 0, "\xa6" => 0, "\xa7" => 0,
|
||||
"\xa8" => 0, "\xa9" => 0, "\xaa" => 0, "\xab" => 0, "\xac" => 0, "\xad" => 0, "\xae" => 0, "\xaf" => 0,
|
||||
"\xb0" => 0, "\xb1" => 0, "\xb2" => 0, "\xb3" => 0, "\xb4" => 0, "\xb5" => 0, "\xb6" => 0, "\xb7" => 0,
|
||||
"\xb8" => 0, "\xb9" => 0, "\xba" => 0, "\xbb" => 0, "\xbc" => 0, "\xbd" => 0, "\xbe" => 0, "\xbf" => 0,
|
||||
"\xc0" => 2, "\xc1" => 2, "\xc2" => 2, "\xc3" => 2, "\xc4" => 2, "\xc5" => 2, "\xc6" => 2, "\xc7" => 2,
|
||||
"\xc8" => 2, "\xc9" => 2, "\xca" => 2, "\xcb" => 2, "\xcc" => 2, "\xcd" => 2, "\xce" => 2, "\xcf" => 2,
|
||||
"\xd0" => 2, "\xd1" => 2, "\xd2" => 2, "\xd3" => 2, "\xd4" => 2, "\xd5" => 2, "\xd6" => 2, "\xd7" => 2,
|
||||
"\xd8" => 2, "\xd9" => 2, "\xda" => 2, "\xdb" => 2, "\xdc" => 2, "\xdd" => 2, "\xde" => 2, "\xdf" => 2,
|
||||
"\xe0" => 3, "\xe1" => 3, "\xe2" => 3, "\xe3" => 3, "\xe4" => 3, "\xe5" => 3, "\xe6" => 3, "\xe7" => 3,
|
||||
"\xe8" => 3, "\xe9" => 3, "\xea" => 3, "\xeb" => 3, "\xec" => 3, "\xed" => 3, "\xee" => 3, "\xef" => 3,
|
||||
"\xf0" => 4, "\xf1" => 4, "\xf2" => 4, "\xf3" => 4, "\xf4" => 4, "\xf5" => 4, "\xf6" => 4, "\xf7" => 4,
|
||||
"\xf8" => 5, "\xf9" => 5, "\xfa" => 5, "\xfb" => 5, "\xfc" => 6, "\xfd" => 6, "\xfe" => 0, "\xff" => 0,
|
||||
];
|
||||
|
||||
private string $data = '';
|
||||
private int $dataSize = 0;
|
||||
private array $map = [];
|
||||
private int $charCount = 0;
|
||||
private int $currentPos = 0;
|
||||
private int $fixedWidth = 0;
|
||||
|
||||
/**
|
||||
* @param resource|string $input
|
||||
*/
|
||||
public function __construct($input, ?string $charset = 'utf-8')
|
||||
{
|
||||
$charset = strtolower(trim($charset)) ?: 'utf-8';
|
||||
if ('utf-8' === $charset || 'utf8' === $charset) {
|
||||
$this->fixedWidth = 0;
|
||||
$this->map = ['p' => [], 'i' => []];
|
||||
} else {
|
||||
$this->fixedWidth = match ($charset) {
|
||||
// 16 bits
|
||||
'ucs2',
|
||||
'ucs-2',
|
||||
'utf16',
|
||||
'utf-16' => 2,
|
||||
// 32 bits
|
||||
'ucs4',
|
||||
'ucs-4',
|
||||
'utf32',
|
||||
'utf-32' => 4,
|
||||
// 7-8 bit charsets: (us-)?ascii, (iso|iec)-?8859-?[0-9]+, windows-?125[0-9], cp-?[0-9]+, ansi, macintosh,
|
||||
// koi-?7, koi-?8-?.+, mik, (cork|t1), v?iscii
|
||||
// and fallback
|
||||
default => 1,
|
||||
};
|
||||
}
|
||||
if (\is_resource($input)) {
|
||||
$blocks = 16372;
|
||||
while (false !== $read = fread($input, $blocks)) {
|
||||
$this->write($read);
|
||||
}
|
||||
} else {
|
||||
$this->write($input);
|
||||
}
|
||||
}
|
||||
|
||||
public function read(int $length): ?string
|
||||
{
|
||||
if ($this->currentPos >= $this->charCount) {
|
||||
return null;
|
||||
}
|
||||
$length = ($this->currentPos + $length > $this->charCount) ? $this->charCount - $this->currentPos : $length;
|
||||
if ($this->fixedWidth > 0) {
|
||||
$len = $length * $this->fixedWidth;
|
||||
$ret = substr($this->data, $this->currentPos * $this->fixedWidth, $len);
|
||||
$this->currentPos += $length;
|
||||
} else {
|
||||
$end = $this->currentPos + $length;
|
||||
$end = $end > $this->charCount ? $this->charCount : $end;
|
||||
$ret = '';
|
||||
$start = 0;
|
||||
if ($this->currentPos > 0) {
|
||||
$start = $this->map['p'][$this->currentPos - 1];
|
||||
}
|
||||
$to = $start;
|
||||
for (; $this->currentPos < $end; ++$this->currentPos) {
|
||||
if (isset($this->map['i'][$this->currentPos])) {
|
||||
$ret .= substr($this->data, $start, $to - $start).'?';
|
||||
$start = $this->map['p'][$this->currentPos];
|
||||
} else {
|
||||
$to = $this->map['p'][$this->currentPos];
|
||||
}
|
||||
}
|
||||
$ret .= substr($this->data, $start, $to - $start);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public function readBytes(int $length): ?array
|
||||
{
|
||||
if (null !== $read = $this->read($length)) {
|
||||
return array_map('ord', str_split($read, 1));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setPointer(int $charOffset): void
|
||||
{
|
||||
if ($this->charCount < $charOffset) {
|
||||
$charOffset = $this->charCount;
|
||||
}
|
||||
$this->currentPos = $charOffset;
|
||||
}
|
||||
|
||||
public function write(string $chars): void
|
||||
{
|
||||
$ignored = '';
|
||||
$this->data .= $chars;
|
||||
if ($this->fixedWidth > 0) {
|
||||
$strlen = \strlen($chars);
|
||||
$ignoredL = $strlen % $this->fixedWidth;
|
||||
$ignored = $ignoredL ? substr($chars, -$ignoredL) : '';
|
||||
$this->charCount += ($strlen - $ignoredL) / $this->fixedWidth;
|
||||
} else {
|
||||
$this->charCount += $this->getUtf8CharPositions($chars, $this->dataSize, $ignored);
|
||||
}
|
||||
$this->dataSize = \strlen($this->data) - \strlen($ignored);
|
||||
}
|
||||
|
||||
private function getUtf8CharPositions(string $string, int $startOffset, string &$ignoredChars): int
|
||||
{
|
||||
$strlen = \strlen($string);
|
||||
$charPos = \count($this->map['p']);
|
||||
$foundChars = 0;
|
||||
$invalid = false;
|
||||
for ($i = 0; $i < $strlen; ++$i) {
|
||||
$char = $string[$i];
|
||||
$size = self::UTF8_LENGTH_MAP[$char];
|
||||
if (0 == $size) {
|
||||
/* char is invalid, we must wait for a resync */
|
||||
$invalid = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($invalid) {
|
||||
/* We mark the chars as invalid and start a new char */
|
||||
$this->map['p'][$charPos + $foundChars] = $startOffset + $i;
|
||||
$this->map['i'][$charPos + $foundChars] = true;
|
||||
++$foundChars;
|
||||
$invalid = false;
|
||||
}
|
||||
if (($i + $size) > $strlen) {
|
||||
$ignoredChars = substr($string, $i);
|
||||
break;
|
||||
}
|
||||
for ($j = 1; $j < $size; ++$j) {
|
||||
$char = $string[$i + $j];
|
||||
if ($char > "\x7F" && $char < "\xC0") {
|
||||
// Valid - continue parsing
|
||||
} else {
|
||||
/* char is invalid, we must wait for a resync */
|
||||
$invalid = true;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
/* Ok we got a complete char here */
|
||||
$this->map['p'][$charPos + $foundChars] = $startOffset + $i + $size;
|
||||
$i += $j - 1;
|
||||
++$foundChars;
|
||||
}
|
||||
|
||||
return $foundChars;
|
||||
}
|
||||
}
|
97
vendor/symfony/mime/Crypto/DkimOptions.php
vendored
Normal file
97
vendor/symfony/mime/Crypto/DkimOptions.php
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
<?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\Crypto;
|
||||
|
||||
/**
|
||||
* A helper providing autocompletion for available DkimSigner options.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class DkimOptions
|
||||
{
|
||||
private array $options = [];
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function algorithm(string $algo): static
|
||||
{
|
||||
$this->options['algorithm'] = $algo;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function signatureExpirationDelay(int $show): static
|
||||
{
|
||||
$this->options['signature_expiration_delay'] = $show;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function bodyMaxLength(int $max): static
|
||||
{
|
||||
$this->options['body_max_length'] = $max;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function bodyShowLength(bool $show): static
|
||||
{
|
||||
$this->options['body_show_length'] = $show;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function headerCanon(string $canon): static
|
||||
{
|
||||
$this->options['header_canon'] = $canon;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function bodyCanon(string $canon): static
|
||||
{
|
||||
$this->options['body_canon'] = $canon;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function headersToIgnore(array $headers): static
|
||||
{
|
||||
$this->options['headers_to_ignore'] = $headers;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
217
vendor/symfony/mime/Crypto/DkimSigner.php
vendored
Normal file
217
vendor/symfony/mime/Crypto/DkimSigner.php
vendored
Normal file
@ -0,0 +1,217 @@
|
||||
<?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\Crypto;
|
||||
|
||||
use Symfony\Component\Mime\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Mime\Exception\RuntimeException;
|
||||
use Symfony\Component\Mime\Header\UnstructuredHeader;
|
||||
use Symfony\Component\Mime\Message;
|
||||
use Symfony\Component\Mime\Part\AbstractPart;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* RFC 6376 and 8301
|
||||
*/
|
||||
final class DkimSigner
|
||||
{
|
||||
public const CANON_SIMPLE = 'simple';
|
||||
public const CANON_RELAXED = 'relaxed';
|
||||
|
||||
public const ALGO_SHA256 = 'rsa-sha256';
|
||||
public const ALGO_ED25519 = 'ed25519-sha256'; // RFC 8463
|
||||
|
||||
private \OpenSSLAsymmetricKey $key;
|
||||
private string $domainName;
|
||||
private string $selector;
|
||||
private array $defaultOptions;
|
||||
|
||||
/**
|
||||
* @param string $pk The private key as a string or the path to the file containing the private key, should be prefixed with file:// (in PEM format)
|
||||
* @param string $passphrase A passphrase of the private key (if any)
|
||||
*/
|
||||
public function __construct(string $pk, string $domainName, string $selector, array $defaultOptions = [], string $passphrase = '')
|
||||
{
|
||||
if (!\extension_loaded('openssl')) {
|
||||
throw new \LogicException('PHP extension "openssl" is required to use DKIM.');
|
||||
}
|
||||
$this->key = openssl_pkey_get_private($pk, $passphrase) ?: throw new InvalidArgumentException('Unable to load DKIM private key: '.openssl_error_string());
|
||||
$this->domainName = $domainName;
|
||||
$this->selector = $selector;
|
||||
$this->defaultOptions = $defaultOptions + [
|
||||
'algorithm' => self::ALGO_SHA256,
|
||||
'signature_expiration_delay' => 0,
|
||||
'body_max_length' => \PHP_INT_MAX,
|
||||
'body_show_length' => false,
|
||||
'header_canon' => self::CANON_RELAXED,
|
||||
'body_canon' => self::CANON_RELAXED,
|
||||
'headers_to_ignore' => [],
|
||||
];
|
||||
}
|
||||
|
||||
public function sign(Message $message, array $options = []): Message
|
||||
{
|
||||
$options += $this->defaultOptions;
|
||||
if (!\in_array($options['algorithm'], [self::ALGO_SHA256, self::ALGO_ED25519], true)) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid DKIM signing algorithm "%s".', $options['algorithm']));
|
||||
}
|
||||
$headersToIgnore['return-path'] = true;
|
||||
$headersToIgnore['x-transport'] = true;
|
||||
foreach ($options['headers_to_ignore'] as $name) {
|
||||
$headersToIgnore[strtolower($name)] = true;
|
||||
}
|
||||
unset($headersToIgnore['from']);
|
||||
$signedHeaderNames = [];
|
||||
$headerCanonData = '';
|
||||
$headers = $message->getPreparedHeaders();
|
||||
foreach ($headers->getNames() as $name) {
|
||||
foreach ($headers->all($name) as $header) {
|
||||
if (isset($headersToIgnore[strtolower($header->getName())])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('' !== $header->getBodyAsString()) {
|
||||
$headerCanonData .= $this->canonicalizeHeader($header->toString(), $options['header_canon']);
|
||||
$signedHeaderNames[] = $header->getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[$bodyHash, $bodyLength] = $this->hashBody($message->getBody(), $options['body_canon'], $options['body_max_length']);
|
||||
|
||||
$params = [
|
||||
'v' => '1',
|
||||
'q' => 'dns/txt',
|
||||
'a' => $options['algorithm'],
|
||||
'bh' => base64_encode($bodyHash),
|
||||
'd' => $this->domainName,
|
||||
'h' => implode(': ', $signedHeaderNames),
|
||||
'i' => '@'.$this->domainName,
|
||||
's' => $this->selector,
|
||||
't' => time(),
|
||||
'c' => $options['header_canon'].'/'.$options['body_canon'],
|
||||
];
|
||||
|
||||
if ($options['body_show_length']) {
|
||||
$params['l'] = $bodyLength;
|
||||
}
|
||||
if ($options['signature_expiration_delay']) {
|
||||
$params['x'] = $params['t'] + $options['signature_expiration_delay'];
|
||||
}
|
||||
$value = '';
|
||||
foreach ($params as $k => $v) {
|
||||
$value .= $k.'='.$v.'; ';
|
||||
}
|
||||
$value = trim($value);
|
||||
$header = new UnstructuredHeader('DKIM-Signature', $value);
|
||||
$headerCanonData .= rtrim($this->canonicalizeHeader($header->toString()."\r\n b=", $options['header_canon']));
|
||||
if (self::ALGO_SHA256 === $options['algorithm']) {
|
||||
if (!openssl_sign($headerCanonData, $signature, $this->key, \OPENSSL_ALGO_SHA256)) {
|
||||
throw new RuntimeException('Unable to sign DKIM hash: '.openssl_error_string());
|
||||
}
|
||||
} else {
|
||||
throw new \RuntimeException(sprintf('The "%s" DKIM signing algorithm is not supported yet.', self::ALGO_ED25519));
|
||||
}
|
||||
$header->setValue($value.' b='.trim(chunk_split(base64_encode($signature), 73, ' ')));
|
||||
$headers->add($header);
|
||||
|
||||
return new Message($headers, $message->getBody());
|
||||
}
|
||||
|
||||
private function canonicalizeHeader(string $header, string $headerCanon): string
|
||||
{
|
||||
if (self::CANON_RELAXED !== $headerCanon) {
|
||||
return $header."\r\n";
|
||||
}
|
||||
|
||||
$exploded = explode(':', $header, 2);
|
||||
$name = strtolower(trim($exploded[0]));
|
||||
$value = str_replace("\r\n", '', $exploded[1]);
|
||||
$value = trim(preg_replace("/[ \t][ \t]+/", ' ', $value));
|
||||
|
||||
return $name.':'.$value."\r\n";
|
||||
}
|
||||
|
||||
private function hashBody(AbstractPart $body, string $bodyCanon, int $maxLength): array
|
||||
{
|
||||
$hash = hash_init('sha256');
|
||||
$relaxed = self::CANON_RELAXED === $bodyCanon;
|
||||
$currentLine = '';
|
||||
$emptyCounter = 0;
|
||||
$isSpaceSequence = false;
|
||||
$length = 0;
|
||||
foreach ($body->bodyToIterable() as $chunk) {
|
||||
$canon = '';
|
||||
for ($i = 0, $len = \strlen($chunk); $i < $len; ++$i) {
|
||||
switch ($chunk[$i]) {
|
||||
case "\r":
|
||||
break;
|
||||
case "\n":
|
||||
// previous char is always \r
|
||||
if ($relaxed) {
|
||||
$isSpaceSequence = false;
|
||||
}
|
||||
if ('' === $currentLine) {
|
||||
++$emptyCounter;
|
||||
} else {
|
||||
$currentLine = '';
|
||||
$canon .= "\r\n";
|
||||
}
|
||||
break;
|
||||
case ' ':
|
||||
case "\t":
|
||||
if ($relaxed) {
|
||||
$isSpaceSequence = true;
|
||||
break;
|
||||
}
|
||||
// no break
|
||||
default:
|
||||
if ($emptyCounter > 0) {
|
||||
$canon .= str_repeat("\r\n", $emptyCounter);
|
||||
$emptyCounter = 0;
|
||||
}
|
||||
if ($isSpaceSequence) {
|
||||
$currentLine .= ' ';
|
||||
$canon .= ' ';
|
||||
$isSpaceSequence = false;
|
||||
}
|
||||
$currentLine .= $chunk[$i];
|
||||
$canon .= $chunk[$i];
|
||||
}
|
||||
}
|
||||
|
||||
if ($length + \strlen($canon) >= $maxLength) {
|
||||
$canon = substr($canon, 0, $maxLength - $length);
|
||||
$length += \strlen($canon);
|
||||
hash_update($hash, $canon);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
$length += \strlen($canon);
|
||||
hash_update($hash, $canon);
|
||||
}
|
||||
|
||||
// Add trailing Line return if last line is non empty
|
||||
if ('' !== $currentLine) {
|
||||
hash_update($hash, "\r\n");
|
||||
$length += \strlen("\r\n");
|
||||
}
|
||||
|
||||
if (!$relaxed && 0 === $length) {
|
||||
hash_update($hash, "\r\n");
|
||||
$length = 2;
|
||||
}
|
||||
|
||||
return [hash_final($hash, true), $length];
|
||||
}
|
||||
}
|
111
vendor/symfony/mime/Crypto/SMime.php
vendored
Normal file
111
vendor/symfony/mime/Crypto/SMime.php
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
<?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\Crypto;
|
||||
|
||||
use Symfony\Component\Mime\Exception\RuntimeException;
|
||||
use Symfony\Component\Mime\Part\SMimePart;
|
||||
|
||||
/**
|
||||
* @author Sebastiaan Stok <s.stok@rollerscapes.net>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class SMime
|
||||
{
|
||||
protected function normalizeFilePath(string $path): string
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
throw new RuntimeException(sprintf('File does not exist: "%s".', $path));
|
||||
}
|
||||
|
||||
return 'file://'.str_replace('\\', '/', realpath($path));
|
||||
}
|
||||
|
||||
protected function iteratorToFile(iterable $iterator, $stream): void
|
||||
{
|
||||
foreach ($iterator as $chunk) {
|
||||
fwrite($stream, $chunk);
|
||||
}
|
||||
}
|
||||
|
||||
protected function convertMessageToSMimePart($stream, string $type, string $subtype): SMimePart
|
||||
{
|
||||
rewind($stream);
|
||||
|
||||
$headers = '';
|
||||
|
||||
while (!feof($stream)) {
|
||||
$buffer = fread($stream, 78);
|
||||
$headers .= $buffer;
|
||||
|
||||
// Detect ending of header list
|
||||
if (preg_match('/(\r\n\r\n|\n\n)/', $headers, $match)) {
|
||||
$headersPosEnd = strpos($headers, $headerBodySeparator = $match[0]);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$headers = $this->getMessageHeaders(trim(substr($headers, 0, $headersPosEnd)));
|
||||
|
||||
fseek($stream, $headersPosEnd + \strlen($headerBodySeparator));
|
||||
|
||||
return new SMimePart($this->getStreamIterator($stream), $type, $subtype, $this->getParametersFromHeader($headers['content-type']));
|
||||
}
|
||||
|
||||
protected function getStreamIterator($stream): iterable
|
||||
{
|
||||
while (!feof($stream)) {
|
||||
yield str_replace("\n", "\r\n", str_replace("\r\n", "\n", fread($stream, 16372)));
|
||||
}
|
||||
}
|
||||
|
||||
private function getMessageHeaders(string $headerData): array
|
||||
{
|
||||
$headers = [];
|
||||
$headerLines = explode("\r\n", str_replace("\n", "\r\n", str_replace("\r\n", "\n", $headerData)));
|
||||
$currentHeaderName = '';
|
||||
|
||||
// Transform header lines into an associative array
|
||||
foreach ($headerLines as $headerLine) {
|
||||
// Empty lines between headers indicate a new mime-entity
|
||||
if ('' === $headerLine) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle headers that span multiple lines
|
||||
if (!str_contains($headerLine, ':')) {
|
||||
$headers[$currentHeaderName] .= ' '.trim($headerLine);
|
||||
continue;
|
||||
}
|
||||
|
||||
$header = explode(':', $headerLine, 2);
|
||||
$currentHeaderName = strtolower($header[0]);
|
||||
$headers[$currentHeaderName] = trim($header[1]);
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
private function getParametersFromHeader(string $header): array
|
||||
{
|
||||
$params = [];
|
||||
|
||||
preg_match_all('/(?P<name>[a-z-0-9]+)=(?P<value>"[^"]+"|(?:[^\s;]+|$))(?:\s+;)?/i', $header, $matches);
|
||||
|
||||
foreach ($matches['value'] as $pos => $paramValue) {
|
||||
$params[$matches['name'][$pos]] = trim($paramValue, '"');
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
63
vendor/symfony/mime/Crypto/SMimeEncrypter.php
vendored
Normal file
63
vendor/symfony/mime/Crypto/SMimeEncrypter.php
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
<?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\Crypto;
|
||||
|
||||
use Symfony\Component\Mime\Exception\RuntimeException;
|
||||
use Symfony\Component\Mime\Message;
|
||||
|
||||
/**
|
||||
* @author Sebastiaan Stok <s.stok@rollerscapes.net>
|
||||
*/
|
||||
final class SMimeEncrypter extends SMime
|
||||
{
|
||||
private string|array $certs;
|
||||
private int $cipher;
|
||||
|
||||
/**
|
||||
* @param string|string[] $certificate The path (or array of paths) of the file(s) containing the X.509 certificate(s)
|
||||
* @param int|null $cipher A set of algorithms used to encrypt the message. Must be one of these PHP constants: https://www.php.net/manual/en/openssl.ciphers.php
|
||||
*/
|
||||
public function __construct(string|array $certificate, ?int $cipher = null)
|
||||
{
|
||||
if (!\extension_loaded('openssl')) {
|
||||
throw new \LogicException('PHP extension "openssl" is required to use SMime.');
|
||||
}
|
||||
|
||||
if (\is_array($certificate)) {
|
||||
$this->certs = array_map($this->normalizeFilePath(...), $certificate);
|
||||
} else {
|
||||
$this->certs = $this->normalizeFilePath($certificate);
|
||||
}
|
||||
|
||||
$this->cipher = $cipher ?? \OPENSSL_CIPHER_AES_256_CBC;
|
||||
}
|
||||
|
||||
public function encrypt(Message $message): Message
|
||||
{
|
||||
$bufferFile = tmpfile();
|
||||
$outputFile = tmpfile();
|
||||
|
||||
$this->iteratorToFile($message->toIterable(), $bufferFile);
|
||||
|
||||
if (!@openssl_pkcs7_encrypt(stream_get_meta_data($bufferFile)['uri'], stream_get_meta_data($outputFile)['uri'], $this->certs, [], 0, $this->cipher)) {
|
||||
throw new RuntimeException(sprintf('Failed to encrypt S/Mime message. Error: "%s".', openssl_error_string()));
|
||||
}
|
||||
|
||||
$mimePart = $this->convertMessageToSMimePart($outputFile, 'application', 'pkcs7-mime');
|
||||
$mimePart->getHeaders()
|
||||
->addTextHeader('Content-Transfer-Encoding', 'base64')
|
||||
->addParameterizedHeader('Content-Disposition', 'attachment', ['name' => 'smime.p7m'])
|
||||
;
|
||||
|
||||
return new Message($message->getHeaders(), $mimePart);
|
||||
}
|
||||
}
|
65
vendor/symfony/mime/Crypto/SMimeSigner.php
vendored
Normal file
65
vendor/symfony/mime/Crypto/SMimeSigner.php
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
<?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\Crypto;
|
||||
|
||||
use Symfony\Component\Mime\Exception\RuntimeException;
|
||||
use Symfony\Component\Mime\Message;
|
||||
|
||||
/**
|
||||
* @author Sebastiaan Stok <s.stok@rollerscapes.net>
|
||||
*/
|
||||
final class SMimeSigner extends SMime
|
||||
{
|
||||
private string $signCertificate;
|
||||
private string|array $signPrivateKey;
|
||||
private int $signOptions;
|
||||
private ?string $extraCerts;
|
||||
|
||||
/**
|
||||
* @param string $certificate The path of the file containing the signing certificate (in PEM format)
|
||||
* @param string $privateKey The path of the file containing the private key (in PEM format)
|
||||
* @param string|null $privateKeyPassphrase A passphrase of the private key (if any)
|
||||
* @param string|null $extraCerts The path of the file containing intermediate certificates (in PEM format) needed by the signing certificate
|
||||
* @param int|null $signOptions Bitwise operator options for openssl_pkcs7_sign() (@see https://secure.php.net/manual/en/openssl.pkcs7.flags.php)
|
||||
*/
|
||||
public function __construct(string $certificate, string $privateKey, ?string $privateKeyPassphrase = null, ?string $extraCerts = null, ?int $signOptions = null)
|
||||
{
|
||||
if (!\extension_loaded('openssl')) {
|
||||
throw new \LogicException('PHP extension "openssl" is required to use SMime.');
|
||||
}
|
||||
|
||||
$this->signCertificate = $this->normalizeFilePath($certificate);
|
||||
|
||||
if (null !== $privateKeyPassphrase) {
|
||||
$this->signPrivateKey = [$this->normalizeFilePath($privateKey), $privateKeyPassphrase];
|
||||
} else {
|
||||
$this->signPrivateKey = $this->normalizeFilePath($privateKey);
|
||||
}
|
||||
|
||||
$this->signOptions = $signOptions ?? \PKCS7_DETACHED;
|
||||
$this->extraCerts = $extraCerts ? realpath($extraCerts) : null;
|
||||
}
|
||||
|
||||
public function sign(Message $message): Message
|
||||
{
|
||||
$bufferFile = tmpfile();
|
||||
$outputFile = tmpfile();
|
||||
|
||||
$this->iteratorToFile($message->getBody()->toIterable(), $bufferFile);
|
||||
|
||||
if (!@openssl_pkcs7_sign(stream_get_meta_data($bufferFile)['uri'], stream_get_meta_data($outputFile)['uri'], $this->signCertificate, $this->signPrivateKey, [], $this->signOptions, $this->extraCerts)) {
|
||||
throw new RuntimeException(sprintf('Failed to sign S/Mime message. Error: "%s".', openssl_error_string()));
|
||||
}
|
||||
|
||||
return new Message($message->getHeaders(), $this->convertMessageToSMimePart($outputFile, 'multipart', 'signed'));
|
||||
}
|
||||
}
|
37
vendor/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php
vendored
Normal file
37
vendor/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
<?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\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Registers custom mime types guessers.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class AddMimeTypeGuesserPass implements CompilerPassInterface
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if ($container->has('mime_types')) {
|
||||
$definition = $container->findDefinition('mime_types');
|
||||
foreach ($container->findTaggedServiceIds('mime.mime_type_guesser', true) as $id => $attributes) {
|
||||
$definition->addMethodCall('registerGuesser', [new Reference($id)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
45
vendor/symfony/mime/DraftEmail.php
vendored
Normal file
45
vendor/symfony/mime/DraftEmail.php
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
<?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;
|
||||
|
||||
use Symfony\Component\Mime\Header\Headers;
|
||||
use Symfony\Component\Mime\Part\AbstractPart;
|
||||
|
||||
/**
|
||||
* @author Kevin Bond <kevinbond@gmail.com>
|
||||
*/
|
||||
class DraftEmail extends Email
|
||||
{
|
||||
public function __construct(?Headers $headers = null, ?AbstractPart $body = null)
|
||||
{
|
||||
parent::__construct($headers, $body);
|
||||
|
||||
$this->getHeaders()->addTextHeader('X-Unsent', '1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Override default behavior as draft emails do not require From/Sender/Date/Message-ID headers.
|
||||
* These are added by the client that actually sends the email.
|
||||
*/
|
||||
public function getPreparedHeaders(): Headers
|
||||
{
|
||||
$headers = clone $this->getHeaders();
|
||||
|
||||
if (!$headers->has('MIME-Version')) {
|
||||
$headers->addTextHeader('MIME-Version', '1.0');
|
||||
}
|
||||
|
||||
$headers->remove('Bcc');
|
||||
|
||||
return $headers;
|
||||
}
|
||||
}
|
591
vendor/symfony/mime/Email.php
vendored
Normal file
591
vendor/symfony/mime/Email.php
vendored
Normal file
@ -0,0 +1,591 @@
|
||||
<?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;
|
||||
|
||||
use Symfony\Component\Mime\Exception\LogicException;
|
||||
use Symfony\Component\Mime\Part\AbstractPart;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
use Symfony\Component\Mime\Part\File;
|
||||
use Symfony\Component\Mime\Part\Multipart\AlternativePart;
|
||||
use Symfony\Component\Mime\Part\Multipart\MixedPart;
|
||||
use Symfony\Component\Mime\Part\Multipart\RelatedPart;
|
||||
use Symfony\Component\Mime\Part\TextPart;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Email extends Message
|
||||
{
|
||||
public const PRIORITY_HIGHEST = 1;
|
||||
public const PRIORITY_HIGH = 2;
|
||||
public const PRIORITY_NORMAL = 3;
|
||||
public const PRIORITY_LOW = 4;
|
||||
public const PRIORITY_LOWEST = 5;
|
||||
|
||||
private const PRIORITY_MAP = [
|
||||
self::PRIORITY_HIGHEST => 'Highest',
|
||||
self::PRIORITY_HIGH => 'High',
|
||||
self::PRIORITY_NORMAL => 'Normal',
|
||||
self::PRIORITY_LOW => 'Low',
|
||||
self::PRIORITY_LOWEST => 'Lowest',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var resource|string|null
|
||||
*/
|
||||
private $text;
|
||||
|
||||
private ?string $textCharset = null;
|
||||
|
||||
/**
|
||||
* @var resource|string|null
|
||||
*/
|
||||
private $html;
|
||||
|
||||
private ?string $htmlCharset = null;
|
||||
private array $attachments = [];
|
||||
private ?AbstractPart $cachedBody = null; // Used to avoid wrong body hash in DKIM signatures with multiple parts (e.g. HTML + TEXT) due to multiple boundaries.
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function subject(string $subject): static
|
||||
{
|
||||
return $this->setHeaderBody('Text', 'Subject', $subject);
|
||||
}
|
||||
|
||||
public function getSubject(): ?string
|
||||
{
|
||||
return $this->getHeaders()->getHeaderBody('Subject');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function date(\DateTimeInterface $dateTime): static
|
||||
{
|
||||
return $this->setHeaderBody('Date', 'Date', $dateTime);
|
||||
}
|
||||
|
||||
public function getDate(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->getHeaders()->getHeaderBody('Date');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function returnPath(Address|string $address): static
|
||||
{
|
||||
return $this->setHeaderBody('Path', 'Return-Path', Address::create($address));
|
||||
}
|
||||
|
||||
public function getReturnPath(): ?Address
|
||||
{
|
||||
return $this->getHeaders()->getHeaderBody('Return-Path');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function sender(Address|string $address): static
|
||||
{
|
||||
return $this->setHeaderBody('Mailbox', 'Sender', Address::create($address));
|
||||
}
|
||||
|
||||
public function getSender(): ?Address
|
||||
{
|
||||
return $this->getHeaders()->getHeaderBody('Sender');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function addFrom(Address|string ...$addresses): static
|
||||
{
|
||||
return $this->addListAddressHeaderBody('From', $addresses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function from(Address|string ...$addresses): static
|
||||
{
|
||||
if (!$addresses) {
|
||||
throw new LogicException('"from()" must be called with at least one address.');
|
||||
}
|
||||
|
||||
return $this->setListAddressHeaderBody('From', $addresses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Address[]
|
||||
*/
|
||||
public function getFrom(): array
|
||||
{
|
||||
return $this->getHeaders()->getHeaderBody('From') ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function addReplyTo(Address|string ...$addresses): static
|
||||
{
|
||||
return $this->addListAddressHeaderBody('Reply-To', $addresses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function replyTo(Address|string ...$addresses): static
|
||||
{
|
||||
return $this->setListAddressHeaderBody('Reply-To', $addresses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Address[]
|
||||
*/
|
||||
public function getReplyTo(): array
|
||||
{
|
||||
return $this->getHeaders()->getHeaderBody('Reply-To') ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function addTo(Address|string ...$addresses): static
|
||||
{
|
||||
return $this->addListAddressHeaderBody('To', $addresses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function to(Address|string ...$addresses): static
|
||||
{
|
||||
return $this->setListAddressHeaderBody('To', $addresses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Address[]
|
||||
*/
|
||||
public function getTo(): array
|
||||
{
|
||||
return $this->getHeaders()->getHeaderBody('To') ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function addCc(Address|string ...$addresses): static
|
||||
{
|
||||
return $this->addListAddressHeaderBody('Cc', $addresses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function cc(Address|string ...$addresses): static
|
||||
{
|
||||
return $this->setListAddressHeaderBody('Cc', $addresses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Address[]
|
||||
*/
|
||||
public function getCc(): array
|
||||
{
|
||||
return $this->getHeaders()->getHeaderBody('Cc') ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function addBcc(Address|string ...$addresses): static
|
||||
{
|
||||
return $this->addListAddressHeaderBody('Bcc', $addresses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function bcc(Address|string ...$addresses): static
|
||||
{
|
||||
return $this->setListAddressHeaderBody('Bcc', $addresses);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Address[]
|
||||
*/
|
||||
public function getBcc(): array
|
||||
{
|
||||
return $this->getHeaders()->getHeaderBody('Bcc') ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the priority of this message.
|
||||
*
|
||||
* The value is an integer where 1 is the highest priority and 5 is the lowest.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function priority(int $priority): static
|
||||
{
|
||||
if ($priority > 5) {
|
||||
$priority = 5;
|
||||
} elseif ($priority < 1) {
|
||||
$priority = 1;
|
||||
}
|
||||
|
||||
return $this->setHeaderBody('Text', 'X-Priority', sprintf('%d (%s)', $priority, self::PRIORITY_MAP[$priority]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the priority of this message.
|
||||
*
|
||||
* The returned value is an integer where 1 is the highest priority and 5
|
||||
* is the lowest.
|
||||
*/
|
||||
public function getPriority(): int
|
||||
{
|
||||
[$priority] = sscanf($this->getHeaders()->getHeaderBody('X-Priority') ?? '', '%[1-5]');
|
||||
|
||||
return $priority ?? 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource|string|null $body
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function text($body, string $charset = 'utf-8'): static
|
||||
{
|
||||
if (null !== $body && !\is_string($body) && !\is_resource($body)) {
|
||||
throw new \TypeError(sprintf('The body must be a string, a resource or null (got "%s").', get_debug_type($body)));
|
||||
}
|
||||
|
||||
$this->cachedBody = null;
|
||||
$this->text = $body;
|
||||
$this->textCharset = $charset;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource|string|null
|
||||
*/
|
||||
public function getTextBody()
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
public function getTextCharset(): ?string
|
||||
{
|
||||
return $this->textCharset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource|string|null $body
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function html($body, string $charset = 'utf-8'): static
|
||||
{
|
||||
if (null !== $body && !\is_string($body) && !\is_resource($body)) {
|
||||
throw new \TypeError(sprintf('The body must be a string, a resource or null (got "%s").', get_debug_type($body)));
|
||||
}
|
||||
|
||||
$this->cachedBody = null;
|
||||
$this->html = $body;
|
||||
$this->htmlCharset = $charset;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource|string|null
|
||||
*/
|
||||
public function getHtmlBody()
|
||||
{
|
||||
return $this->html;
|
||||
}
|
||||
|
||||
public function getHtmlCharset(): ?string
|
||||
{
|
||||
return $this->htmlCharset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource|string $body
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function attach($body, ?string $name = null, ?string $contentType = null): static
|
||||
{
|
||||
return $this->addPart(new DataPart($body, $name, $contentType));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function attachFromPath(string $path, ?string $name = null, ?string $contentType = null): static
|
||||
{
|
||||
return $this->addPart(new DataPart(new File($path), $name, $contentType));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource|string $body
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function embed($body, ?string $name = null, ?string $contentType = null): static
|
||||
{
|
||||
return $this->addPart((new DataPart($body, $name, $contentType))->asInline());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function embedFromPath(string $path, ?string $name = null, ?string $contentType = null): static
|
||||
{
|
||||
return $this->addPart((new DataPart(new File($path), $name, $contentType))->asInline());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*
|
||||
* @deprecated since Symfony 6.2, use addPart() instead
|
||||
*/
|
||||
public function attachPart(DataPart $part): static
|
||||
{
|
||||
@trigger_deprecation('symfony/mime', '6.2', 'The "%s()" method is deprecated, use "addPart()" instead.', __METHOD__);
|
||||
|
||||
return $this->addPart($part);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function addPart(DataPart $part): static
|
||||
{
|
||||
$this->cachedBody = null;
|
||||
$this->attachments[] = $part;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DataPart[]
|
||||
*/
|
||||
public function getAttachments(): array
|
||||
{
|
||||
return $this->attachments;
|
||||
}
|
||||
|
||||
public function getBody(): AbstractPart
|
||||
{
|
||||
if (null !== $body = parent::getBody()) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
return $this->generateBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function ensureValidity()
|
||||
{
|
||||
$this->ensureBodyValid();
|
||||
|
||||
if ('1' === $this->getHeaders()->getHeaderBody('X-Unsent')) {
|
||||
throw new LogicException('Cannot send messages marked as "draft".');
|
||||
}
|
||||
|
||||
parent::ensureValidity();
|
||||
}
|
||||
|
||||
private function ensureBodyValid(): void
|
||||
{
|
||||
if (null === $this->text && null === $this->html && !$this->attachments) {
|
||||
throw new LogicException('A message must have a text or an HTML part or attachments.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an AbstractPart based on the raw body of a message.
|
||||
*
|
||||
* The most "complex" part generated by this method is when there is text and HTML bodies
|
||||
* with related images for the HTML part and some attachments:
|
||||
*
|
||||
* multipart/mixed
|
||||
* |
|
||||
* |------------> multipart/related
|
||||
* | |
|
||||
* | |------------> multipart/alternative
|
||||
* | | |
|
||||
* | | ------------> text/plain (with content)
|
||||
* | | |
|
||||
* | | ------------> text/html (with content)
|
||||
* | |
|
||||
* | ------------> image/png (with content)
|
||||
* |
|
||||
* ------------> application/pdf (with content)
|
||||
*/
|
||||
private function generateBody(): AbstractPart
|
||||
{
|
||||
if (null !== $this->cachedBody) {
|
||||
return $this->cachedBody;
|
||||
}
|
||||
|
||||
$this->ensureBodyValid();
|
||||
|
||||
[$htmlPart, $otherParts, $relatedParts] = $this->prepareParts();
|
||||
|
||||
$part = null === $this->text ? null : new TextPart($this->text, $this->textCharset);
|
||||
if (null !== $htmlPart) {
|
||||
if (null !== $part) {
|
||||
$part = new AlternativePart($part, $htmlPart);
|
||||
} else {
|
||||
$part = $htmlPart;
|
||||
}
|
||||
}
|
||||
|
||||
if ($relatedParts) {
|
||||
$part = new RelatedPart($part, ...$relatedParts);
|
||||
}
|
||||
|
||||
if ($otherParts) {
|
||||
if ($part) {
|
||||
$part = new MixedPart($part, ...$otherParts);
|
||||
} else {
|
||||
$part = new MixedPart(...$otherParts);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->cachedBody = $part;
|
||||
}
|
||||
|
||||
private function prepareParts(): ?array
|
||||
{
|
||||
$names = [];
|
||||
$htmlPart = null;
|
||||
$html = $this->html;
|
||||
if (null !== $html) {
|
||||
$htmlPart = new TextPart($html, $this->htmlCharset, 'html');
|
||||
$html = $htmlPart->getBody();
|
||||
|
||||
$regexes = [
|
||||
'<img\s+[^>]*src\s*=\s*(?:([\'"])cid:(.+?)\\1|cid:([^>\s]+))',
|
||||
'<\w+\s+[^>]*background\s*=\s*(?:([\'"])cid:(.+?)\\1|cid:([^>\s]+))',
|
||||
];
|
||||
$tmpMatches = [];
|
||||
foreach ($regexes as $regex) {
|
||||
preg_match_all('/'.$regex.'/i', $html, $tmpMatches);
|
||||
$names = array_merge($names, $tmpMatches[2], $tmpMatches[3]);
|
||||
}
|
||||
$names = array_filter(array_unique($names));
|
||||
}
|
||||
|
||||
$otherParts = $relatedParts = [];
|
||||
foreach ($this->attachments as $part) {
|
||||
foreach ($names as $name) {
|
||||
if ($name !== $part->getName() && (!$part->hasContentId() || $name !== $part->getContentId())) {
|
||||
continue;
|
||||
}
|
||||
if (isset($relatedParts[$name])) {
|
||||
continue 2;
|
||||
}
|
||||
|
||||
if ($name !== $part->getContentId()) {
|
||||
$html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html, $count);
|
||||
}
|
||||
$relatedParts[$name] = $part;
|
||||
$part->setName($part->getContentId())->asInline();
|
||||
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$otherParts[] = $part;
|
||||
}
|
||||
if (null !== $htmlPart) {
|
||||
$htmlPart = new TextPart($html, $this->htmlCharset, 'html');
|
||||
}
|
||||
|
||||
return [$htmlPart, $otherParts, array_values($relatedParts)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
private function setHeaderBody(string $type, string $name, $body): static
|
||||
{
|
||||
$this->getHeaders()->setHeaderBody($type, $name, $body);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
private function addListAddressHeaderBody(string $name, array $addresses): static
|
||||
{
|
||||
if (!$header = $this->getHeaders()->get($name)) {
|
||||
return $this->setListAddressHeaderBody($name, $addresses);
|
||||
}
|
||||
$header->addAddresses(Address::createArray($addresses));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
private function setListAddressHeaderBody(string $name, array $addresses): static
|
||||
{
|
||||
$addresses = Address::createArray($addresses);
|
||||
$headers = $this->getHeaders();
|
||||
if ($header = $headers->get($name)) {
|
||||
$header->setAddresses($addresses);
|
||||
} else {
|
||||
$headers->addMailboxListHeader($name, $addresses);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function __serialize(): array
|
||||
{
|
||||
if (\is_resource($this->text)) {
|
||||
$this->text = (new TextPart($this->text))->getBody();
|
||||
}
|
||||
|
||||
if (\is_resource($this->html)) {
|
||||
$this->html = (new TextPart($this->html))->getBody();
|
||||
}
|
||||
|
||||
return [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, parent::__serialize()];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
[$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, $parentData] = $data;
|
||||
|
||||
parent::__unserialize($parentData);
|
||||
}
|
||||
}
|
28
vendor/symfony/mime/Encoder/AddressEncoderInterface.php
vendored
Normal file
28
vendor/symfony/mime/Encoder/AddressEncoderInterface.php
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
<?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\Encoder;
|
||||
|
||||
use Symfony\Component\Mime\Exception\AddressEncoderException;
|
||||
|
||||
/**
|
||||
* @author Christian Schmidt
|
||||
*/
|
||||
interface AddressEncoderInterface
|
||||
{
|
||||
/**
|
||||
* Encodes an email address.
|
||||
*
|
||||
* @throws AddressEncoderException if the email cannot be represented in
|
||||
* the encoding implemented by this class
|
||||
*/
|
||||
public function encodeString(string $address): string;
|
||||
}
|
45
vendor/symfony/mime/Encoder/Base64ContentEncoder.php
vendored
Normal file
45
vendor/symfony/mime/Encoder/Base64ContentEncoder.php
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
<?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\Encoder;
|
||||
|
||||
use Symfony\Component\Mime\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class Base64ContentEncoder extends Base64Encoder implements ContentEncoderInterface
|
||||
{
|
||||
public function encodeByteStream($stream, int $maxLineLength = 0): iterable
|
||||
{
|
||||
if (!\is_resource($stream)) {
|
||||
throw new \TypeError(sprintf('Method "%s" takes a stream as a first argument.', __METHOD__));
|
||||
}
|
||||
|
||||
$filter = stream_filter_append($stream, 'convert.base64-encode', \STREAM_FILTER_READ, [
|
||||
'line-length' => 0 >= $maxLineLength || 76 < $maxLineLength ? 76 : $maxLineLength,
|
||||
'line-break-chars' => "\r\n",
|
||||
]);
|
||||
if (!\is_resource($filter)) {
|
||||
throw new RuntimeException('Unable to set the base64 content encoder to the filter.');
|
||||
}
|
||||
|
||||
while (!feof($stream)) {
|
||||
yield fread($stream, 16372);
|
||||
}
|
||||
stream_filter_remove($filter);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'base64';
|
||||
}
|
||||
}
|
41
vendor/symfony/mime/Encoder/Base64Encoder.php
vendored
Normal file
41
vendor/symfony/mime/Encoder/Base64Encoder.php
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
<?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\Encoder;
|
||||
|
||||
/**
|
||||
* @author Chris Corbyn
|
||||
*/
|
||||
class Base64Encoder implements EncoderInterface
|
||||
{
|
||||
/**
|
||||
* Takes an unencoded string and produces a Base64 encoded string from it.
|
||||
*
|
||||
* Base64 encoded strings have a maximum line length of 76 characters.
|
||||
* If the first line needs to be shorter, indicate the difference with
|
||||
* $firstLineOffset.
|
||||
*/
|
||||
public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string
|
||||
{
|
||||
if (0 >= $maxLineLength || 76 < $maxLineLength) {
|
||||
$maxLineLength = 76;
|
||||
}
|
||||
|
||||
$encodedString = base64_encode($string);
|
||||
$firstLine = '';
|
||||
if (0 !== $firstLineOffset) {
|
||||
$firstLine = substr($encodedString, 0, $maxLineLength - $firstLineOffset)."\r\n";
|
||||
$encodedString = substr($encodedString, $maxLineLength - $firstLineOffset);
|
||||
}
|
||||
|
||||
return $firstLine.trim(chunk_split($encodedString, $maxLineLength, "\r\n"));
|
||||
}
|
||||
}
|
43
vendor/symfony/mime/Encoder/Base64MimeHeaderEncoder.php
vendored
Normal file
43
vendor/symfony/mime/Encoder/Base64MimeHeaderEncoder.php
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
<?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\Encoder;
|
||||
|
||||
/**
|
||||
* @author Chris Corbyn
|
||||
*/
|
||||
final class Base64MimeHeaderEncoder extends Base64Encoder implements MimeHeaderEncoderInterface
|
||||
{
|
||||
public function getName(): string
|
||||
{
|
||||
return 'B';
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an unencoded string and produces a Base64 encoded string from it.
|
||||
*
|
||||
* If the charset is iso-2022-jp, it uses mb_encode_mimeheader instead of
|
||||
* default encodeString, otherwise pass to the parent method.
|
||||
*/
|
||||
public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string
|
||||
{
|
||||
if ('iso-2022-jp' === strtolower($charset)) {
|
||||
$old = mb_internal_encoding();
|
||||
mb_internal_encoding('utf-8');
|
||||
$newstring = mb_encode_mimeheader($string, 'iso-2022-jp', $this->getName(), "\r\n");
|
||||
mb_internal_encoding($old);
|
||||
|
||||
return $newstring;
|
||||
}
|
||||
|
||||
return parent::encodeString($string, $charset, $firstLineOffset, $maxLineLength);
|
||||
}
|
||||
}
|
30
vendor/symfony/mime/Encoder/ContentEncoderInterface.php
vendored
Normal file
30
vendor/symfony/mime/Encoder/ContentEncoderInterface.php
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
<?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\Encoder;
|
||||
|
||||
/**
|
||||
* @author Chris Corbyn
|
||||
*/
|
||||
interface ContentEncoderInterface extends EncoderInterface
|
||||
{
|
||||
/**
|
||||
* Encodes the stream to a Generator.
|
||||
*
|
||||
* @param resource $stream
|
||||
*/
|
||||
public function encodeByteStream($stream, int $maxLineLength = 0): iterable;
|
||||
|
||||
/**
|
||||
* Gets the MIME name of this content encoding scheme.
|
||||
*/
|
||||
public function getName(): string;
|
||||
}
|
35
vendor/symfony/mime/Encoder/EightBitContentEncoder.php
vendored
Normal file
35
vendor/symfony/mime/Encoder/EightBitContentEncoder.php
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
<?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\Encoder;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class EightBitContentEncoder implements ContentEncoderInterface
|
||||
{
|
||||
public function encodeByteStream($stream, int $maxLineLength = 0): iterable
|
||||
{
|
||||
while (!feof($stream)) {
|
||||
yield fread($stream, 16372);
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return '8bit';
|
||||
}
|
||||
|
||||
public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string
|
||||
{
|
||||
return $string;
|
||||
}
|
||||
}
|
26
vendor/symfony/mime/Encoder/EncoderInterface.php
vendored
Normal file
26
vendor/symfony/mime/Encoder/EncoderInterface.php
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
<?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\Encoder;
|
||||
|
||||
/**
|
||||
* @author Chris Corbyn
|
||||
*/
|
||||
interface EncoderInterface
|
||||
{
|
||||
/**
|
||||
* Encode a given string to produce an encoded string.
|
||||
*
|
||||
* @param int $firstLineOffset if first line needs to be shorter
|
||||
* @param int $maxLineLength - 0 indicates the default length for this encoding
|
||||
*/
|
||||
public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string;
|
||||
}
|
44
vendor/symfony/mime/Encoder/IdnAddressEncoder.php
vendored
Normal file
44
vendor/symfony/mime/Encoder/IdnAddressEncoder.php
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
<?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\Encoder;
|
||||
|
||||
/**
|
||||
* An IDN email address encoder.
|
||||
*
|
||||
* Encodes the domain part of an address using IDN. This is compatible will all
|
||||
* SMTP servers.
|
||||
*
|
||||
* Note: It leaves the local part as is. In case there are non-ASCII characters
|
||||
* in the local part then it depends on the SMTP Server if this is supported.
|
||||
*
|
||||
* @author Christian Schmidt
|
||||
*/
|
||||
final class IdnAddressEncoder implements AddressEncoderInterface
|
||||
{
|
||||
/**
|
||||
* Encodes the domain part of an address using IDN.
|
||||
*/
|
||||
public function encodeString(string $address): string
|
||||
{
|
||||
$i = strrpos($address, '@');
|
||||
if (false !== $i) {
|
||||
$local = substr($address, 0, $i);
|
||||
$domain = substr($address, $i + 1);
|
||||
|
||||
if (preg_match('/[^\x00-\x7F]/', $domain)) {
|
||||
$address = sprintf('%s@%s', $local, idn_to_ascii($domain, \IDNA_DEFAULT | \IDNA_USE_STD3_RULES | \IDNA_CHECK_BIDI | \IDNA_CHECK_CONTEXTJ | \IDNA_NONTRANSITIONAL_TO_ASCII, \INTL_IDNA_VARIANT_UTS46));
|
||||
}
|
||||
}
|
||||
|
||||
return $address;
|
||||
}
|
||||
}
|
23
vendor/symfony/mime/Encoder/MimeHeaderEncoderInterface.php
vendored
Normal file
23
vendor/symfony/mime/Encoder/MimeHeaderEncoderInterface.php
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
<?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\Encoder;
|
||||
|
||||
/**
|
||||
* @author Chris Corbyn
|
||||
*/
|
||||
interface MimeHeaderEncoderInterface
|
||||
{
|
||||
/**
|
||||
* Get the MIME name of this content encoding scheme.
|
||||
*/
|
||||
public function getName(): string;
|
||||
}
|
55
vendor/symfony/mime/Encoder/QpContentEncoder.php
vendored
Normal file
55
vendor/symfony/mime/Encoder/QpContentEncoder.php
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
<?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\Encoder;
|
||||
|
||||
/**
|
||||
* @author Lars Strojny
|
||||
*/
|
||||
final class QpContentEncoder implements ContentEncoderInterface
|
||||
{
|
||||
public function encodeByteStream($stream, int $maxLineLength = 0): iterable
|
||||
{
|
||||
if (!\is_resource($stream)) {
|
||||
throw new \TypeError(sprintf('Method "%s" takes a stream as a first argument.', __METHOD__));
|
||||
}
|
||||
|
||||
// we don't use PHP stream filters here as the content should be small enough
|
||||
yield $this->encodeString(stream_get_contents($stream), 'utf-8', 0, $maxLineLength);
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'quoted-printable';
|
||||
}
|
||||
|
||||
public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string
|
||||
{
|
||||
return $this->standardize(quoted_printable_encode($string));
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure CRLF is correct and HT/SPACE are in valid places.
|
||||
*/
|
||||
private function standardize(string $string): string
|
||||
{
|
||||
// transform CR or LF to CRLF
|
||||
$string = preg_replace('~=0D(?!=0A)|(?<!=0D)=0A~', '=0D=0A', $string);
|
||||
// transform =0D=0A to CRLF
|
||||
$string = str_replace(["\t=0D=0A", ' =0D=0A', '=0D=0A'], ["=09\r\n", "=20\r\n", "\r\n"], $string);
|
||||
|
||||
return match (\ord(substr($string, -1))) {
|
||||
0x09 => substr_replace($string, '=09', -1),
|
||||
0x20 => substr_replace($string, '=20', -1),
|
||||
default => $string,
|
||||
};
|
||||
}
|
||||
}
|
192
vendor/symfony/mime/Encoder/QpEncoder.php
vendored
Normal file
192
vendor/symfony/mime/Encoder/QpEncoder.php
vendored
Normal file
@ -0,0 +1,192 @@
|
||||
<?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\Encoder;
|
||||
|
||||
use Symfony\Component\Mime\CharacterStream;
|
||||
|
||||
/**
|
||||
* @author Chris Corbyn
|
||||
*/
|
||||
class QpEncoder implements EncoderInterface
|
||||
{
|
||||
/**
|
||||
* Pre-computed QP for HUGE optimization.
|
||||
*/
|
||||
private const QP_MAP = [
|
||||
0 => '=00', 1 => '=01', 2 => '=02', 3 => '=03', 4 => '=04',
|
||||
5 => '=05', 6 => '=06', 7 => '=07', 8 => '=08', 9 => '=09',
|
||||
10 => '=0A', 11 => '=0B', 12 => '=0C', 13 => '=0D', 14 => '=0E',
|
||||
15 => '=0F', 16 => '=10', 17 => '=11', 18 => '=12', 19 => '=13',
|
||||
20 => '=14', 21 => '=15', 22 => '=16', 23 => '=17', 24 => '=18',
|
||||
25 => '=19', 26 => '=1A', 27 => '=1B', 28 => '=1C', 29 => '=1D',
|
||||
30 => '=1E', 31 => '=1F', 32 => '=20', 33 => '=21', 34 => '=22',
|
||||
35 => '=23', 36 => '=24', 37 => '=25', 38 => '=26', 39 => '=27',
|
||||
40 => '=28', 41 => '=29', 42 => '=2A', 43 => '=2B', 44 => '=2C',
|
||||
45 => '=2D', 46 => '=2E', 47 => '=2F', 48 => '=30', 49 => '=31',
|
||||
50 => '=32', 51 => '=33', 52 => '=34', 53 => '=35', 54 => '=36',
|
||||
55 => '=37', 56 => '=38', 57 => '=39', 58 => '=3A', 59 => '=3B',
|
||||
60 => '=3C', 61 => '=3D', 62 => '=3E', 63 => '=3F', 64 => '=40',
|
||||
65 => '=41', 66 => '=42', 67 => '=43', 68 => '=44', 69 => '=45',
|
||||
70 => '=46', 71 => '=47', 72 => '=48', 73 => '=49', 74 => '=4A',
|
||||
75 => '=4B', 76 => '=4C', 77 => '=4D', 78 => '=4E', 79 => '=4F',
|
||||
80 => '=50', 81 => '=51', 82 => '=52', 83 => '=53', 84 => '=54',
|
||||
85 => '=55', 86 => '=56', 87 => '=57', 88 => '=58', 89 => '=59',
|
||||
90 => '=5A', 91 => '=5B', 92 => '=5C', 93 => '=5D', 94 => '=5E',
|
||||
95 => '=5F', 96 => '=60', 97 => '=61', 98 => '=62', 99 => '=63',
|
||||
100 => '=64', 101 => '=65', 102 => '=66', 103 => '=67', 104 => '=68',
|
||||
105 => '=69', 106 => '=6A', 107 => '=6B', 108 => '=6C', 109 => '=6D',
|
||||
110 => '=6E', 111 => '=6F', 112 => '=70', 113 => '=71', 114 => '=72',
|
||||
115 => '=73', 116 => '=74', 117 => '=75', 118 => '=76', 119 => '=77',
|
||||
120 => '=78', 121 => '=79', 122 => '=7A', 123 => '=7B', 124 => '=7C',
|
||||
125 => '=7D', 126 => '=7E', 127 => '=7F', 128 => '=80', 129 => '=81',
|
||||
130 => '=82', 131 => '=83', 132 => '=84', 133 => '=85', 134 => '=86',
|
||||
135 => '=87', 136 => '=88', 137 => '=89', 138 => '=8A', 139 => '=8B',
|
||||
140 => '=8C', 141 => '=8D', 142 => '=8E', 143 => '=8F', 144 => '=90',
|
||||
145 => '=91', 146 => '=92', 147 => '=93', 148 => '=94', 149 => '=95',
|
||||
150 => '=96', 151 => '=97', 152 => '=98', 153 => '=99', 154 => '=9A',
|
||||
155 => '=9B', 156 => '=9C', 157 => '=9D', 158 => '=9E', 159 => '=9F',
|
||||
160 => '=A0', 161 => '=A1', 162 => '=A2', 163 => '=A3', 164 => '=A4',
|
||||
165 => '=A5', 166 => '=A6', 167 => '=A7', 168 => '=A8', 169 => '=A9',
|
||||
170 => '=AA', 171 => '=AB', 172 => '=AC', 173 => '=AD', 174 => '=AE',
|
||||
175 => '=AF', 176 => '=B0', 177 => '=B1', 178 => '=B2', 179 => '=B3',
|
||||
180 => '=B4', 181 => '=B5', 182 => '=B6', 183 => '=B7', 184 => '=B8',
|
||||
185 => '=B9', 186 => '=BA', 187 => '=BB', 188 => '=BC', 189 => '=BD',
|
||||
190 => '=BE', 191 => '=BF', 192 => '=C0', 193 => '=C1', 194 => '=C2',
|
||||
195 => '=C3', 196 => '=C4', 197 => '=C5', 198 => '=C6', 199 => '=C7',
|
||||
200 => '=C8', 201 => '=C9', 202 => '=CA', 203 => '=CB', 204 => '=CC',
|
||||
205 => '=CD', 206 => '=CE', 207 => '=CF', 208 => '=D0', 209 => '=D1',
|
||||
210 => '=D2', 211 => '=D3', 212 => '=D4', 213 => '=D5', 214 => '=D6',
|
||||
215 => '=D7', 216 => '=D8', 217 => '=D9', 218 => '=DA', 219 => '=DB',
|
||||
220 => '=DC', 221 => '=DD', 222 => '=DE', 223 => '=DF', 224 => '=E0',
|
||||
225 => '=E1', 226 => '=E2', 227 => '=E3', 228 => '=E4', 229 => '=E5',
|
||||
230 => '=E6', 231 => '=E7', 232 => '=E8', 233 => '=E9', 234 => '=EA',
|
||||
235 => '=EB', 236 => '=EC', 237 => '=ED', 238 => '=EE', 239 => '=EF',
|
||||
240 => '=F0', 241 => '=F1', 242 => '=F2', 243 => '=F3', 244 => '=F4',
|
||||
245 => '=F5', 246 => '=F6', 247 => '=F7', 248 => '=F8', 249 => '=F9',
|
||||
250 => '=FA', 251 => '=FB', 252 => '=FC', 253 => '=FD', 254 => '=FE',
|
||||
255 => '=FF',
|
||||
];
|
||||
|
||||
private static array $safeMapShare = [];
|
||||
|
||||
/**
|
||||
* A map of non-encoded ascii characters.
|
||||
*
|
||||
* @var string[]
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected array $safeMap = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$id = static::class;
|
||||
if (!isset(self::$safeMapShare[$id])) {
|
||||
$this->initSafeMap();
|
||||
self::$safeMapShare[$id] = $this->safeMap;
|
||||
} else {
|
||||
$this->safeMap = self::$safeMapShare[$id];
|
||||
}
|
||||
}
|
||||
|
||||
protected function initSafeMap(): void
|
||||
{
|
||||
foreach (array_merge([0x09, 0x20], range(0x21, 0x3C), range(0x3E, 0x7E)) as $byte) {
|
||||
$this->safeMap[$byte] = \chr($byte);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an unencoded string and produces a QP encoded string from it.
|
||||
*
|
||||
* QP encoded strings have a maximum line length of 76 characters.
|
||||
* If the first line needs to be shorter, indicate the difference with
|
||||
* $firstLineOffset.
|
||||
*/
|
||||
public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string
|
||||
{
|
||||
if ($maxLineLength > 76 || $maxLineLength <= 0) {
|
||||
$maxLineLength = 76;
|
||||
}
|
||||
|
||||
$thisLineLength = $maxLineLength - $firstLineOffset;
|
||||
|
||||
$lines = [];
|
||||
$lNo = 0;
|
||||
$lines[$lNo] = '';
|
||||
$currentLine = &$lines[$lNo++];
|
||||
$size = $lineLen = 0;
|
||||
$charStream = new CharacterStream($string, $charset);
|
||||
|
||||
// Fetching more than 4 chars at one is slower, as is fetching fewer bytes
|
||||
// Conveniently 4 chars is the UTF-8 safe number since UTF-8 has up to 6
|
||||
// bytes per char and (6 * 4 * 3 = 72 chars per line) * =NN is 3 bytes
|
||||
while (null !== $bytes = $charStream->readBytes(4)) {
|
||||
$enc = $this->encodeByteSequence($bytes, $size);
|
||||
|
||||
$i = strpos($enc, '=0D=0A');
|
||||
$newLineLength = $lineLen + (false === $i ? $size : $i);
|
||||
|
||||
if ($currentLine && $newLineLength >= $thisLineLength) {
|
||||
$lines[$lNo] = '';
|
||||
$currentLine = &$lines[$lNo++];
|
||||
$thisLineLength = $maxLineLength;
|
||||
$lineLen = 0;
|
||||
}
|
||||
|
||||
$currentLine .= $enc;
|
||||
|
||||
if (false === $i) {
|
||||
$lineLen += $size;
|
||||
} else {
|
||||
// 6 is the length of '=0D=0A'.
|
||||
$lineLen = $size - strrpos($enc, '=0D=0A') - 6;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->standardize(implode("=\r\n", $lines));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the given byte array into a verbatim QP form.
|
||||
*/
|
||||
private function encodeByteSequence(array $bytes, int &$size): string
|
||||
{
|
||||
$ret = '';
|
||||
$size = 0;
|
||||
foreach ($bytes as $b) {
|
||||
if (isset($this->safeMap[$b])) {
|
||||
$ret .= $this->safeMap[$b];
|
||||
++$size;
|
||||
} else {
|
||||
$ret .= self::QP_MAP[$b];
|
||||
$size += 3;
|
||||
}
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure CRLF is correct and HT/SPACE are in valid places.
|
||||
*/
|
||||
private function standardize(string $string): string
|
||||
{
|
||||
$string = str_replace(["\t=0D=0A", ' =0D=0A', '=0D=0A'], ["=09\r\n", "=20\r\n", "\r\n"], $string);
|
||||
|
||||
return match ($end = \ord(substr($string, -1))) {
|
||||
0x09,
|
||||
0x20 => substr_replace($string, self::QP_MAP[$end], -1),
|
||||
default => $string,
|
||||
};
|
||||
}
|
||||
}
|
40
vendor/symfony/mime/Encoder/QpMimeHeaderEncoder.php
vendored
Normal file
40
vendor/symfony/mime/Encoder/QpMimeHeaderEncoder.php
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
<?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\Encoder;
|
||||
|
||||
/**
|
||||
* @author Chris Corbyn
|
||||
*/
|
||||
final class QpMimeHeaderEncoder extends QpEncoder implements MimeHeaderEncoderInterface
|
||||
{
|
||||
protected function initSafeMap(): void
|
||||
{
|
||||
foreach (array_merge(
|
||||
range(0x61, 0x7A), range(0x41, 0x5A),
|
||||
range(0x30, 0x39), [0x20, 0x21, 0x2A, 0x2B, 0x2D, 0x2F]
|
||||
) as $byte) {
|
||||
$this->safeMap[$byte] = \chr($byte);
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'Q';
|
||||
}
|
||||
|
||||
public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string
|
||||
{
|
||||
return str_replace([' ', '=20', "=\r\n"], ['_', '_', "\r\n"],
|
||||
parent::encodeString($string, $charset, $firstLineOffset, $maxLineLength)
|
||||
);
|
||||
}
|
||||
}
|
50
vendor/symfony/mime/Encoder/Rfc2231Encoder.php
vendored
Normal file
50
vendor/symfony/mime/Encoder/Rfc2231Encoder.php
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
<?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\Encoder;
|
||||
|
||||
use Symfony\Component\Mime\CharacterStream;
|
||||
|
||||
/**
|
||||
* @author Chris Corbyn
|
||||
*/
|
||||
final class Rfc2231Encoder implements EncoderInterface
|
||||
{
|
||||
/**
|
||||
* Takes an unencoded string and produces a string encoded according to RFC 2231 from it.
|
||||
*/
|
||||
public function encodeString(string $string, ?string $charset = 'utf-8', int $firstLineOffset = 0, int $maxLineLength = 0): string
|
||||
{
|
||||
$lines = [];
|
||||
$lineCount = 0;
|
||||
$lines[] = '';
|
||||
$currentLine = &$lines[$lineCount++];
|
||||
|
||||
if (0 >= $maxLineLength) {
|
||||
$maxLineLength = 75;
|
||||
}
|
||||
|
||||
$charStream = new CharacterStream($string, $charset);
|
||||
$thisLineLength = $maxLineLength - $firstLineOffset;
|
||||
|
||||
while (null !== $char = $charStream->read(4)) {
|
||||
$encodedChar = rawurlencode($char);
|
||||
if ('' !== $currentLine && \strlen($currentLine.$encodedChar) > $thisLineLength) {
|
||||
$lines[] = '';
|
||||
$currentLine = &$lines[$lineCount++];
|
||||
$thisLineLength = $maxLineLength;
|
||||
}
|
||||
$currentLine .= $encodedChar;
|
||||
}
|
||||
|
||||
return implode("\r\n", $lines);
|
||||
}
|
||||
}
|
19
vendor/symfony/mime/Exception/AddressEncoderException.php
vendored
Normal file
19
vendor/symfony/mime/Exception/AddressEncoderException.php
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
<?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\Exception;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class AddressEncoderException extends RfcComplianceException
|
||||
{
|
||||
}
|
19
vendor/symfony/mime/Exception/ExceptionInterface.php
vendored
Normal file
19
vendor/symfony/mime/Exception/ExceptionInterface.php
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
<?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\Exception;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface ExceptionInterface extends \Throwable
|
||||
{
|
||||
}
|
19
vendor/symfony/mime/Exception/InvalidArgumentException.php
vendored
Normal file
19
vendor/symfony/mime/Exception/InvalidArgumentException.php
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
<?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\Exception;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
}
|
19
vendor/symfony/mime/Exception/LogicException.php
vendored
Normal file
19
vendor/symfony/mime/Exception/LogicException.php
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
<?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\Exception;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class LogicException extends \LogicException implements ExceptionInterface
|
||||
{
|
||||
}
|
19
vendor/symfony/mime/Exception/RfcComplianceException.php
vendored
Normal file
19
vendor/symfony/mime/Exception/RfcComplianceException.php
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
<?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\Exception;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class RfcComplianceException extends \InvalidArgumentException implements ExceptionInterface
|
||||
{
|
||||
}
|
19
vendor/symfony/mime/Exception/RuntimeException.php
vendored
Normal file
19
vendor/symfony/mime/Exception/RuntimeException.php
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
<?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\Exception;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class RuntimeException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
87
vendor/symfony/mime/FileBinaryMimeTypeGuesser.php
vendored
Normal file
87
vendor/symfony/mime/FileBinaryMimeTypeGuesser.php
vendored
Normal file
@ -0,0 +1,87 @@
|
||||
<?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;
|
||||
|
||||
use Symfony\Component\Mime\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Mime\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* Guesses the MIME type with the binary "file" (only available on *nix).
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
|
||||
{
|
||||
private string $cmd;
|
||||
|
||||
/**
|
||||
* The $cmd pattern must contain a "%s" string that will be replaced
|
||||
* with the file name to guess.
|
||||
*
|
||||
* The command output must start with the MIME type of the file.
|
||||
*
|
||||
* @param string $cmd The command to run to get the MIME type of a file
|
||||
*/
|
||||
public function __construct(string $cmd = 'file -b --mime -- %s 2>/dev/null')
|
||||
{
|
||||
$this->cmd = $cmd;
|
||||
}
|
||||
|
||||
public function isGuesserSupported(): bool
|
||||
{
|
||||
static $supported = null;
|
||||
|
||||
if (null !== $supported) {
|
||||
return $supported;
|
||||
}
|
||||
|
||||
if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('passthru') || !\function_exists('escapeshellarg')) {
|
||||
return $supported = false;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
passthru('command -v file', $exitStatus);
|
||||
$binPath = trim(ob_get_clean());
|
||||
|
||||
return $supported = 0 === $exitStatus && '' !== $binPath;
|
||||
}
|
||||
|
||||
public function guessMimeType(string $path): ?string
|
||||
{
|
||||
if (!is_file($path) || !is_readable($path)) {
|
||||
throw new InvalidArgumentException(sprintf('The "%s" file does not exist or is not readable.', $path));
|
||||
}
|
||||
|
||||
if (!$this->isGuesserSupported()) {
|
||||
throw new LogicException(sprintf('The "%s" guesser is not supported.', __CLASS__));
|
||||
}
|
||||
|
||||
ob_start();
|
||||
|
||||
// need to use --mime instead of -i. see #6641
|
||||
passthru(sprintf($this->cmd, escapeshellarg((str_starts_with($path, '-') ? './' : '').$path)), $return);
|
||||
if ($return > 0) {
|
||||
ob_end_clean();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = trim(ob_get_clean());
|
||||
|
||||
if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-\+\.]+)#i', $type, $match)) {
|
||||
// it's not a type, but an error message
|
||||
return null;
|
||||
}
|
||||
|
||||
return $match[1];
|
||||
}
|
||||
}
|
63
vendor/symfony/mime/FileinfoMimeTypeGuesser.php
vendored
Normal file
63
vendor/symfony/mime/FileinfoMimeTypeGuesser.php
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
<?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;
|
||||
|
||||
use Symfony\Component\Mime\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Mime\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* Guesses the MIME type using the PECL extension FileInfo.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface
|
||||
{
|
||||
private ?string $magicFile;
|
||||
|
||||
/**
|
||||
* @param string|null $magicFile A magic file to use with the finfo instance
|
||||
*
|
||||
* @see http://www.php.net/manual/en/function.finfo-open.php
|
||||
*/
|
||||
public function __construct(?string $magicFile = null)
|
||||
{
|
||||
$this->magicFile = $magicFile;
|
||||
}
|
||||
|
||||
public function isGuesserSupported(): bool
|
||||
{
|
||||
return \function_exists('finfo_open');
|
||||
}
|
||||
|
||||
public function guessMimeType(string $path): ?string
|
||||
{
|
||||
if (!is_file($path) || !is_readable($path)) {
|
||||
throw new InvalidArgumentException(sprintf('The "%s" file does not exist or is not readable.', $path));
|
||||
}
|
||||
|
||||
if (!$this->isGuesserSupported()) {
|
||||
throw new LogicException(sprintf('The "%s" guesser is not supported.', __CLASS__));
|
||||
}
|
||||
|
||||
if (false === $finfo = new \finfo(\FILEINFO_MIME_TYPE, $this->magicFile)) {
|
||||
return null;
|
||||
}
|
||||
$mimeType = $finfo->file($path);
|
||||
|
||||
if ($mimeType && 0 === (\strlen($mimeType) % 2)) {
|
||||
$mimeStart = substr($mimeType, 0, \strlen($mimeType) >> 1);
|
||||
$mimeType = $mimeStart.$mimeStart === $mimeType ? $mimeStart : $mimeType;
|
||||
}
|
||||
|
||||
return $mimeType;
|
||||
}
|
||||
}
|
288
vendor/symfony/mime/Header/AbstractHeader.php
vendored
Normal file
288
vendor/symfony/mime/Header/AbstractHeader.php
vendored
Normal 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);
|
||||
}
|
||||
}
|
62
vendor/symfony/mime/Header/DateHeader.php
vendored
Normal file
62
vendor/symfony/mime/Header/DateHeader.php
vendored
Normal 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);
|
||||
}
|
||||
}
|
72
vendor/symfony/mime/Header/HeaderInterface.php
vendored
Normal file
72
vendor/symfony/mime/Header/HeaderInterface.php
vendored
Normal 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
316
vendor/symfony/mime/Header/Headers.php
vendored
Normal 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);
|
||||
}
|
||||
}
|
107
vendor/symfony/mime/Header/IdentificationHeader.php
vendored
Normal file
107
vendor/symfony/mime/Header/IdentificationHeader.php
vendored
Normal 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);
|
||||
}
|
||||
}
|
85
vendor/symfony/mime/Header/MailboxHeader.php
vendored
Normal file
85
vendor/symfony/mime/Header/MailboxHeader.php
vendored
Normal 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);
|
||||
}
|
||||
}
|
136
vendor/symfony/mime/Header/MailboxListHeader.php
vendored
Normal file
136
vendor/symfony/mime/Header/MailboxListHeader.php
vendored
Normal 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);
|
||||
}
|
||||
}
|
191
vendor/symfony/mime/Header/ParameterizedHeader.php
vendored
Normal file
191
vendor/symfony/mime/Header/ParameterizedHeader.php
vendored
Normal 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;
|
||||
}
|
||||
}
|
62
vendor/symfony/mime/Header/PathHeader.php
vendored
Normal file
62
vendor/symfony/mime/Header/PathHeader.php
vendored
Normal 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().'>';
|
||||
}
|
||||
}
|
70
vendor/symfony/mime/Header/UnstructuredHeader.php
vendored
Normal file
70
vendor/symfony/mime/Header/UnstructuredHeader.php
vendored
Normal 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);
|
||||
}
|
||||
}
|
23
vendor/symfony/mime/HtmlToTextConverter/DefaultHtmlToTextConverter.php
vendored
Normal file
23
vendor/symfony/mime/HtmlToTextConverter/DefaultHtmlToTextConverter.php
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
<?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\HtmlToTextConverter;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class DefaultHtmlToTextConverter implements HtmlToTextConverterInterface
|
||||
{
|
||||
public function convert(string $html, string $charset): string
|
||||
{
|
||||
return strip_tags(preg_replace('{<(head|style)\b.*?</\1>}is', '', $html));
|
||||
}
|
||||
}
|
25
vendor/symfony/mime/HtmlToTextConverter/HtmlToTextConverterInterface.php
vendored
Normal file
25
vendor/symfony/mime/HtmlToTextConverter/HtmlToTextConverterInterface.php
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<?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\HtmlToTextConverter;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface HtmlToTextConverterInterface
|
||||
{
|
||||
/**
|
||||
* Converts an HTML representation of a Message to a text representation.
|
||||
*
|
||||
* The output must use the same charset as the HTML one.
|
||||
*/
|
||||
public function convert(string $html, string $charset): string;
|
||||
}
|
35
vendor/symfony/mime/HtmlToTextConverter/LeagueHtmlToMarkdownConverter.php
vendored
Normal file
35
vendor/symfony/mime/HtmlToTextConverter/LeagueHtmlToMarkdownConverter.php
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
<?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\HtmlToTextConverter;
|
||||
|
||||
use League\HTMLToMarkdown\HtmlConverter;
|
||||
use League\HTMLToMarkdown\HtmlConverterInterface;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class LeagueHtmlToMarkdownConverter implements HtmlToTextConverterInterface
|
||||
{
|
||||
public function __construct(
|
||||
private HtmlConverterInterface $converter = new HtmlConverter([
|
||||
'hard_break' => true,
|
||||
'strip_tags' => true,
|
||||
'remove_nodes' => 'head style',
|
||||
]),
|
||||
) {
|
||||
}
|
||||
|
||||
public function convert(string $html, string $charset): string
|
||||
{
|
||||
return $this->converter->convert($html);
|
||||
}
|
||||
}
|
19
vendor/symfony/mime/LICENSE
vendored
Normal file
19
vendor/symfony/mime/LICENSE
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2010-present Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
169
vendor/symfony/mime/Message.php
vendored
Normal file
169
vendor/symfony/mime/Message.php
vendored
Normal file
@ -0,0 +1,169 @@
|
||||
<?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;
|
||||
|
||||
use Symfony\Component\Mime\Exception\LogicException;
|
||||
use Symfony\Component\Mime\Header\Headers;
|
||||
use Symfony\Component\Mime\Part\AbstractPart;
|
||||
use Symfony\Component\Mime\Part\TextPart;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class Message extends RawMessage
|
||||
{
|
||||
private Headers $headers;
|
||||
private ?AbstractPart $body;
|
||||
|
||||
public function __construct(?Headers $headers = null, ?AbstractPart $body = null)
|
||||
{
|
||||
$this->headers = $headers ? clone $headers : new Headers();
|
||||
$this->body = $body;
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->headers = clone $this->headers;
|
||||
|
||||
if (null !== $this->body) {
|
||||
$this->body = clone $this->body;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setBody(?AbstractPart $body = null): static
|
||||
{
|
||||
if (1 > \func_num_args()) {
|
||||
trigger_deprecation('symfony/mime', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
|
||||
}
|
||||
$this->body = $body;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBody(): ?AbstractPart
|
||||
{
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setHeaders(Headers $headers): static
|
||||
{
|
||||
$this->headers = $headers;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getHeaders(): Headers
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
public function getPreparedHeaders(): Headers
|
||||
{
|
||||
$headers = clone $this->headers;
|
||||
|
||||
if (!$headers->has('From')) {
|
||||
if (!$headers->has('Sender')) {
|
||||
throw new LogicException('An email must have a "From" or a "Sender" header.');
|
||||
}
|
||||
$headers->addMailboxListHeader('From', [$headers->get('Sender')->getAddress()]);
|
||||
}
|
||||
|
||||
if (!$headers->has('MIME-Version')) {
|
||||
$headers->addTextHeader('MIME-Version', '1.0');
|
||||
}
|
||||
|
||||
if (!$headers->has('Date')) {
|
||||
$headers->addDateHeader('Date', new \DateTimeImmutable());
|
||||
}
|
||||
|
||||
// determine the "real" sender
|
||||
if (!$headers->has('Sender') && \count($froms = $headers->get('From')->getAddresses()) > 1) {
|
||||
$headers->addMailboxHeader('Sender', $froms[0]);
|
||||
}
|
||||
|
||||
if (!$headers->has('Message-ID')) {
|
||||
$headers->addIdHeader('Message-ID', $this->generateMessageId());
|
||||
}
|
||||
|
||||
// remove the Bcc field which should NOT be part of the sent message
|
||||
$headers->remove('Bcc');
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
if (null === $body = $this->getBody()) {
|
||||
$body = new TextPart('');
|
||||
}
|
||||
|
||||
return $this->getPreparedHeaders()->toString().$body->toString();
|
||||
}
|
||||
|
||||
public function toIterable(): iterable
|
||||
{
|
||||
if (null === $body = $this->getBody()) {
|
||||
$body = new TextPart('');
|
||||
}
|
||||
|
||||
yield $this->getPreparedHeaders()->toString();
|
||||
yield from $body->toIterable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function ensureValidity()
|
||||
{
|
||||
if (!$this->headers->has('To') && !$this->headers->has('Cc') && !$this->headers->has('Bcc')) {
|
||||
throw new LogicException('An email must have a "To", "Cc", or "Bcc" header.');
|
||||
}
|
||||
|
||||
if (!$this->headers->has('From') && !$this->headers->has('Sender')) {
|
||||
throw new LogicException('An email must have a "From" or a "Sender" header.');
|
||||
}
|
||||
|
||||
parent::ensureValidity();
|
||||
}
|
||||
|
||||
public function generateMessageId(): string
|
||||
{
|
||||
if ($this->headers->has('Sender')) {
|
||||
$sender = $this->headers->get('Sender')->getAddress();
|
||||
} elseif ($this->headers->has('From')) {
|
||||
if (!$froms = $this->headers->get('From')->getAddresses()) {
|
||||
throw new LogicException('A "From" header must have at least one email address.');
|
||||
}
|
||||
$sender = $froms[0];
|
||||
} else {
|
||||
throw new LogicException('An email must have a "From" or a "Sender" header.');
|
||||
}
|
||||
|
||||
return bin2hex(random_bytes(16)).strstr($sender->getAddress(), '@');
|
||||
}
|
||||
|
||||
public function __serialize(): array
|
||||
{
|
||||
return [$this->headers, $this->body];
|
||||
}
|
||||
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
[$this->headers, $this->body] = $data;
|
||||
}
|
||||
}
|
122
vendor/symfony/mime/MessageConverter.php
vendored
Normal file
122
vendor/symfony/mime/MessageConverter.php
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
<?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;
|
||||
|
||||
use Symfony\Component\Mime\Exception\RuntimeException;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
use Symfony\Component\Mime\Part\Multipart\AlternativePart;
|
||||
use Symfony\Component\Mime\Part\Multipart\MixedPart;
|
||||
use Symfony\Component\Mime\Part\Multipart\RelatedPart;
|
||||
use Symfony\Component\Mime\Part\TextPart;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class MessageConverter
|
||||
{
|
||||
/**
|
||||
* @throws RuntimeException when unable to convert the message to an email
|
||||
*/
|
||||
public static function toEmail(Message $message): Email
|
||||
{
|
||||
if ($message instanceof Email) {
|
||||
return $message;
|
||||
}
|
||||
|
||||
// try to convert to a "simple" Email instance
|
||||
$body = $message->getBody();
|
||||
if ($body instanceof TextPart) {
|
||||
return self::createEmailFromTextPart($message, $body);
|
||||
}
|
||||
|
||||
if ($body instanceof AlternativePart) {
|
||||
return self::createEmailFromAlternativePart($message, $body);
|
||||
}
|
||||
|
||||
if ($body instanceof RelatedPart) {
|
||||
return self::createEmailFromRelatedPart($message, $body);
|
||||
}
|
||||
|
||||
if ($body instanceof MixedPart) {
|
||||
$parts = $body->getParts();
|
||||
if ($parts[0] instanceof RelatedPart) {
|
||||
$email = self::createEmailFromRelatedPart($message, $parts[0]);
|
||||
} elseif ($parts[0] instanceof AlternativePart) {
|
||||
$email = self::createEmailFromAlternativePart($message, $parts[0]);
|
||||
} elseif ($parts[0] instanceof TextPart) {
|
||||
$email = self::createEmailFromTextPart($message, $parts[0]);
|
||||
} else {
|
||||
throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', get_debug_type($message)));
|
||||
}
|
||||
|
||||
return self::addParts($email, \array_slice($parts, 1));
|
||||
}
|
||||
|
||||
throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', get_debug_type($message)));
|
||||
}
|
||||
|
||||
private static function createEmailFromTextPart(Message $message, TextPart $part): Email
|
||||
{
|
||||
if ('text' === $part->getMediaType() && 'plain' === $part->getMediaSubtype()) {
|
||||
return (new Email(clone $message->getHeaders()))->text($part->getBody(), $part->getPreparedHeaders()->getHeaderParameter('Content-Type', 'charset') ?: 'utf-8');
|
||||
}
|
||||
if ('text' === $part->getMediaType() && 'html' === $part->getMediaSubtype()) {
|
||||
return (new Email(clone $message->getHeaders()))->html($part->getBody(), $part->getPreparedHeaders()->getHeaderParameter('Content-Type', 'charset') ?: 'utf-8');
|
||||
}
|
||||
|
||||
throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', get_debug_type($message)));
|
||||
}
|
||||
|
||||
private static function createEmailFromAlternativePart(Message $message, AlternativePart $part): Email
|
||||
{
|
||||
$parts = $part->getParts();
|
||||
if (
|
||||
2 === \count($parts)
|
||||
&& $parts[0] instanceof TextPart && 'text' === $parts[0]->getMediaType() && 'plain' === $parts[0]->getMediaSubtype()
|
||||
&& $parts[1] instanceof TextPart && 'text' === $parts[1]->getMediaType() && 'html' === $parts[1]->getMediaSubtype()
|
||||
) {
|
||||
return (new Email(clone $message->getHeaders()))
|
||||
->text($parts[0]->getBody(), $parts[0]->getPreparedHeaders()->getHeaderParameter('Content-Type', 'charset') ?: 'utf-8')
|
||||
->html($parts[1]->getBody(), $parts[1]->getPreparedHeaders()->getHeaderParameter('Content-Type', 'charset') ?: 'utf-8')
|
||||
;
|
||||
}
|
||||
|
||||
throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', get_debug_type($message)));
|
||||
}
|
||||
|
||||
private static function createEmailFromRelatedPart(Message $message, RelatedPart $part): Email
|
||||
{
|
||||
$parts = $part->getParts();
|
||||
if ($parts[0] instanceof AlternativePart) {
|
||||
$email = self::createEmailFromAlternativePart($message, $parts[0]);
|
||||
} elseif ($parts[0] instanceof TextPart) {
|
||||
$email = self::createEmailFromTextPart($message, $parts[0]);
|
||||
} else {
|
||||
throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', get_debug_type($message)));
|
||||
}
|
||||
|
||||
return self::addParts($email, \array_slice($parts, 1));
|
||||
}
|
||||
|
||||
private static function addParts(Email $email, array $parts): Email
|
||||
{
|
||||
foreach ($parts as $part) {
|
||||
if (!$part instanceof DataPart) {
|
||||
throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', get_debug_type($email)));
|
||||
}
|
||||
|
||||
$email->addPart($part);
|
||||
}
|
||||
|
||||
return $email;
|
||||
}
|
||||
}
|
33
vendor/symfony/mime/MimeTypeGuesserInterface.php
vendored
Normal file
33
vendor/symfony/mime/MimeTypeGuesserInterface.php
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Guesses the MIME type of a file.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface MimeTypeGuesserInterface
|
||||
{
|
||||
/**
|
||||
* Returns true if this guesser is supported.
|
||||
*/
|
||||
public function isGuesserSupported(): bool;
|
||||
|
||||
/**
|
||||
* Guesses the MIME type of the file with the given path.
|
||||
*
|
||||
* @throws \LogicException If the guesser is not supported
|
||||
* @throws \InvalidArgumentException If the file does not exist or is not readable
|
||||
*/
|
||||
public function guessMimeType(string $path): ?string;
|
||||
}
|
3655
vendor/symfony/mime/MimeTypes.php
vendored
Normal file
3655
vendor/symfony/mime/MimeTypes.php
vendored
Normal file
File diff suppressed because it is too large
Load Diff
32
vendor/symfony/mime/MimeTypesInterface.php
vendored
Normal file
32
vendor/symfony/mime/MimeTypesInterface.php
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface MimeTypesInterface extends MimeTypeGuesserInterface
|
||||
{
|
||||
/**
|
||||
* Gets the extensions for the given MIME type in decreasing order of preference.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getExtensions(string $mimeType): array;
|
||||
|
||||
/**
|
||||
* Gets the MIME types for the given extension in decreasing order of preference.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getMimeTypes(string $ext): array;
|
||||
}
|
95
vendor/symfony/mime/Part/AbstractMultipartPart.php
vendored
Normal file
95
vendor/symfony/mime/Part/AbstractMultipartPart.php
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
<?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\Part;
|
||||
|
||||
use Symfony\Component\Mime\Header\Headers;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
abstract class AbstractMultipartPart extends AbstractPart
|
||||
{
|
||||
private ?string $boundary = null;
|
||||
private array $parts = [];
|
||||
|
||||
public function __construct(AbstractPart ...$parts)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
foreach ($parts as $part) {
|
||||
$this->parts[] = $part;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return AbstractPart[]
|
||||
*/
|
||||
public function getParts(): array
|
||||
{
|
||||
return $this->parts;
|
||||
}
|
||||
|
||||
public function getMediaType(): string
|
||||
{
|
||||
return 'multipart';
|
||||
}
|
||||
|
||||
public function getPreparedHeaders(): Headers
|
||||
{
|
||||
$headers = parent::getPreparedHeaders();
|
||||
$headers->setHeaderParameter('Content-Type', 'boundary', $this->getBoundary());
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
public function bodyToString(): string
|
||||
{
|
||||
$parts = $this->getParts();
|
||||
$string = '';
|
||||
foreach ($parts as $part) {
|
||||
$string .= '--'.$this->getBoundary()."\r\n".$part->toString()."\r\n";
|
||||
}
|
||||
$string .= '--'.$this->getBoundary()."--\r\n";
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
public function bodyToIterable(): iterable
|
||||
{
|
||||
$parts = $this->getParts();
|
||||
foreach ($parts as $part) {
|
||||
yield '--'.$this->getBoundary()."\r\n";
|
||||
yield from $part->toIterable();
|
||||
yield "\r\n";
|
||||
}
|
||||
yield '--'.$this->getBoundary()."--\r\n";
|
||||
}
|
||||
|
||||
public function asDebugString(): string
|
||||
{
|
||||
$str = parent::asDebugString();
|
||||
foreach ($this->getParts() as $part) {
|
||||
$lines = explode("\n", $part->asDebugString());
|
||||
$str .= "\n └ ".array_shift($lines);
|
||||
foreach ($lines as $line) {
|
||||
$str .= "\n |".$line;
|
||||
}
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
private function getBoundary(): string
|
||||
{
|
||||
return $this->boundary ??= strtr(base64_encode(random_bytes(6)), '+/', '-_');
|
||||
}
|
||||
}
|
65
vendor/symfony/mime/Part/AbstractPart.php
vendored
Normal file
65
vendor/symfony/mime/Part/AbstractPart.php
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
<?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\Part;
|
||||
|
||||
use Symfony\Component\Mime\Header\Headers;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
abstract class AbstractPart
|
||||
{
|
||||
private Headers $headers;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->headers = new Headers();
|
||||
}
|
||||
|
||||
public function getHeaders(): Headers
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
public function getPreparedHeaders(): Headers
|
||||
{
|
||||
$headers = clone $this->headers;
|
||||
$headers->setHeaderBody('Parameterized', 'Content-Type', $this->getMediaType().'/'.$this->getMediaSubtype());
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return $this->getPreparedHeaders()->toString()."\r\n".$this->bodyToString();
|
||||
}
|
||||
|
||||
public function toIterable(): iterable
|
||||
{
|
||||
yield $this->getPreparedHeaders()->toString();
|
||||
yield "\r\n";
|
||||
yield from $this->bodyToIterable();
|
||||
}
|
||||
|
||||
public function asDebugString(): string
|
||||
{
|
||||
return $this->getMediaType().'/'.$this->getMediaSubtype();
|
||||
}
|
||||
|
||||
abstract public function bodyToString(): string;
|
||||
|
||||
abstract public function bodyToIterable(): iterable;
|
||||
|
||||
abstract public function getMediaType(): string;
|
||||
|
||||
abstract public function getMediaSubtype(): string;
|
||||
}
|
168
vendor/symfony/mime/Part/DataPart.php
vendored
Normal file
168
vendor/symfony/mime/Part/DataPart.php
vendored
Normal file
@ -0,0 +1,168 @@
|
||||
<?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\Part;
|
||||
|
||||
use Symfony\Component\Mime\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Mime\Header\Headers;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class DataPart extends TextPart
|
||||
{
|
||||
/** @internal */
|
||||
protected array $_parent;
|
||||
|
||||
private ?string $filename = null;
|
||||
private string $mediaType;
|
||||
private ?string $cid = null;
|
||||
|
||||
/**
|
||||
* @param resource|string|File $body Use a File instance to defer loading the file until rendering
|
||||
*/
|
||||
public function __construct($body, ?string $filename = null, ?string $contentType = null, ?string $encoding = null)
|
||||
{
|
||||
if ($body instanceof File && !$filename) {
|
||||
$filename = $body->getFilename();
|
||||
}
|
||||
|
||||
$contentType ??= $body instanceof File ? $body->getContentType() : 'application/octet-stream';
|
||||
[$this->mediaType, $subtype] = explode('/', $contentType);
|
||||
|
||||
parent::__construct($body, null, $subtype, $encoding);
|
||||
|
||||
if (null !== $filename) {
|
||||
$this->filename = $filename;
|
||||
$this->setName($filename);
|
||||
}
|
||||
$this->setDisposition('attachment');
|
||||
}
|
||||
|
||||
public static function fromPath(string $path, ?string $name = null, ?string $contentType = null): self
|
||||
{
|
||||
return new self(new File($path), $name, $contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function asInline(): static
|
||||
{
|
||||
return $this->setDisposition('inline');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setContentId(string $cid): static
|
||||
{
|
||||
if (!str_contains($cid, '@')) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid cid "%s".', $cid));
|
||||
}
|
||||
|
||||
$this->cid = $cid;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getContentId(): string
|
||||
{
|
||||
return $this->cid ?: $this->cid = $this->generateContentId();
|
||||
}
|
||||
|
||||
public function hasContentId(): bool
|
||||
{
|
||||
return null !== $this->cid;
|
||||
}
|
||||
|
||||
public function getMediaType(): string
|
||||
{
|
||||
return $this->mediaType;
|
||||
}
|
||||
|
||||
public function getPreparedHeaders(): Headers
|
||||
{
|
||||
$headers = parent::getPreparedHeaders();
|
||||
|
||||
if (null !== $this->cid) {
|
||||
$headers->setHeaderBody('Id', 'Content-ID', $this->cid);
|
||||
}
|
||||
|
||||
if (null !== $this->filename) {
|
||||
$headers->setHeaderParameter('Content-Disposition', 'filename', $this->filename);
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
public function asDebugString(): string
|
||||
{
|
||||
$str = parent::asDebugString();
|
||||
if (null !== $this->filename) {
|
||||
$str .= ' filename: '.$this->filename;
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
public function getFilename(): ?string
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
public function getContentType(): string
|
||||
{
|
||||
return implode('/', [$this->getMediaType(), $this->getMediaSubtype()]);
|
||||
}
|
||||
|
||||
private function generateContentId(): string
|
||||
{
|
||||
return bin2hex(random_bytes(16)).'@symfony';
|
||||
}
|
||||
|
||||
public function __sleep(): array
|
||||
{
|
||||
// converts the body to a string
|
||||
parent::__sleep();
|
||||
|
||||
$this->_parent = [];
|
||||
foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) {
|
||||
$r = new \ReflectionProperty(TextPart::class, $name);
|
||||
$this->_parent[$name] = $r->getValue($this);
|
||||
}
|
||||
$this->_headers = $this->getHeaders();
|
||||
|
||||
return ['_headers', '_parent', 'filename', 'mediaType'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
$r = new \ReflectionProperty(AbstractPart::class, 'headers');
|
||||
$r->setValue($this, $this->_headers);
|
||||
unset($this->_headers);
|
||||
|
||||
if (!\is_array($this->_parent)) {
|
||||
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
|
||||
}
|
||||
foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) {
|
||||
if (null !== $this->_parent[$name] && !\is_string($this->_parent[$name]) && !$this->_parent[$name] instanceof File) {
|
||||
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
|
||||
}
|
||||
$r = new \ReflectionProperty(TextPart::class, $name);
|
||||
$r->setValue($this, $this->_parent[$name]);
|
||||
}
|
||||
unset($this->_parent);
|
||||
}
|
||||
}
|
51
vendor/symfony/mime/Part/File.php
vendored
Normal file
51
vendor/symfony/mime/Part/File.php
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
<?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\Part;
|
||||
|
||||
use Symfony\Component\Mime\MimeTypes;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class File
|
||||
{
|
||||
private static MimeTypes $mimeTypes;
|
||||
|
||||
public function __construct(
|
||||
private string $path,
|
||||
private ?string $filename = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getPath(): string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
public function getContentType(): string
|
||||
{
|
||||
$ext = strtolower(pathinfo($this->path, \PATHINFO_EXTENSION));
|
||||
self::$mimeTypes ??= new MimeTypes();
|
||||
|
||||
return self::$mimeTypes->getMimeTypes($ext)[0] ?? 'application/octet-stream';
|
||||
}
|
||||
|
||||
public function getSize(): int
|
||||
{
|
||||
return filesize($this->path);
|
||||
}
|
||||
|
||||
public function getFilename(): string
|
||||
{
|
||||
return $this->filename ??= basename($this->getPath());
|
||||
}
|
||||
}
|
72
vendor/symfony/mime/Part/MessagePart.php
vendored
Normal file
72
vendor/symfony/mime/Part/MessagePart.php
vendored
Normal 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\Part;
|
||||
|
||||
use Symfony\Component\Mime\Message;
|
||||
use Symfony\Component\Mime\RawMessage;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class MessagePart extends DataPart
|
||||
{
|
||||
private RawMessage $message;
|
||||
|
||||
public function __construct(RawMessage $message)
|
||||
{
|
||||
if ($message instanceof Message) {
|
||||
$name = $message->getHeaders()->getHeaderBody('Subject').'.eml';
|
||||
} else {
|
||||
$name = 'email.eml';
|
||||
}
|
||||
parent::__construct('', $name);
|
||||
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
public function getMediaType(): string
|
||||
{
|
||||
return 'message';
|
||||
}
|
||||
|
||||
public function getMediaSubtype(): string
|
||||
{
|
||||
return 'rfc822';
|
||||
}
|
||||
|
||||
public function getBody(): string
|
||||
{
|
||||
return $this->message->toString();
|
||||
}
|
||||
|
||||
public function bodyToString(): string
|
||||
{
|
||||
return $this->getBody();
|
||||
}
|
||||
|
||||
public function bodyToIterable(): iterable
|
||||
{
|
||||
return $this->message->toIterable();
|
||||
}
|
||||
|
||||
public function __sleep(): array
|
||||
{
|
||||
return ['message'];
|
||||
}
|
||||
|
||||
public function __wakeup(): void
|
||||
{
|
||||
$this->__construct($this->message);
|
||||
}
|
||||
}
|
25
vendor/symfony/mime/Part/Multipart/AlternativePart.php
vendored
Normal file
25
vendor/symfony/mime/Part/Multipart/AlternativePart.php
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<?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\Part\Multipart;
|
||||
|
||||
use Symfony\Component\Mime\Part\AbstractMultipartPart;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class AlternativePart extends AbstractMultipartPart
|
||||
{
|
||||
public function getMediaSubtype(): string
|
||||
{
|
||||
return 'alternative';
|
||||
}
|
||||
}
|
31
vendor/symfony/mime/Part/Multipart/DigestPart.php
vendored
Normal file
31
vendor/symfony/mime/Part/Multipart/DigestPart.php
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
<?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\Part\Multipart;
|
||||
|
||||
use Symfony\Component\Mime\Part\AbstractMultipartPart;
|
||||
use Symfony\Component\Mime\Part\MessagePart;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class DigestPart extends AbstractMultipartPart
|
||||
{
|
||||
public function __construct(MessagePart ...$parts)
|
||||
{
|
||||
parent::__construct(...$parts);
|
||||
}
|
||||
|
||||
public function getMediaSubtype(): string
|
||||
{
|
||||
return 'digest';
|
||||
}
|
||||
}
|
108
vendor/symfony/mime/Part/Multipart/FormDataPart.php
vendored
Normal file
108
vendor/symfony/mime/Part/Multipart/FormDataPart.php
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
<?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\Part\Multipart;
|
||||
|
||||
use Symfony\Component\Mime\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Mime\Part\AbstractMultipartPart;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
use Symfony\Component\Mime\Part\TextPart;
|
||||
|
||||
/**
|
||||
* Implements RFC 7578.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class FormDataPart extends AbstractMultipartPart
|
||||
{
|
||||
private array $fields = [];
|
||||
|
||||
/**
|
||||
* @param array<string|array|DataPart> $fields
|
||||
*/
|
||||
public function __construct(array $fields = [])
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->fields = $fields;
|
||||
|
||||
// HTTP does not support \r\n in header values
|
||||
$this->getHeaders()->setMaxLineLength(\PHP_INT_MAX);
|
||||
}
|
||||
|
||||
public function getMediaSubtype(): string
|
||||
{
|
||||
return 'form-data';
|
||||
}
|
||||
|
||||
public function getParts(): array
|
||||
{
|
||||
return $this->prepareFields($this->fields);
|
||||
}
|
||||
|
||||
private function prepareFields(array $fields): array
|
||||
{
|
||||
$values = [];
|
||||
|
||||
$prepare = function ($item, $key, $root = null) use (&$values, &$prepare) {
|
||||
if (null === $root && \is_int($key) && \is_array($item)) {
|
||||
if (1 !== \count($item)) {
|
||||
throw new InvalidArgumentException(sprintf('Form field values with integer keys can only have one array element, the key being the field name and the value being the field value, %d provided.', \count($item)));
|
||||
}
|
||||
|
||||
$key = key($item);
|
||||
$item = $item[$key];
|
||||
}
|
||||
|
||||
$fieldName = null !== $root ? sprintf('%s[%s]', $root, $key) : $key;
|
||||
|
||||
if (\is_array($item)) {
|
||||
array_walk($item, $prepare, $fieldName);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!\is_string($item) && !$item instanceof TextPart) {
|
||||
throw new InvalidArgumentException(sprintf('The value of the form field "%s" can only be a string, an array, or an instance of TextPart, "%s" given.', $fieldName, get_debug_type($item)));
|
||||
}
|
||||
|
||||
$values[] = $this->preparePart($fieldName, $item);
|
||||
};
|
||||
|
||||
array_walk($fields, $prepare);
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
private function preparePart(string $name, string|TextPart $value): TextPart
|
||||
{
|
||||
if (\is_string($value)) {
|
||||
return $this->configurePart($name, new TextPart($value, 'utf-8', 'plain', '8bit'));
|
||||
}
|
||||
|
||||
return $this->configurePart($name, $value);
|
||||
}
|
||||
|
||||
private function configurePart(string $name, TextPart $part): TextPart
|
||||
{
|
||||
static $r;
|
||||
|
||||
$r ??= new \ReflectionProperty(TextPart::class, 'encoding');
|
||||
|
||||
$part->setDisposition('form-data');
|
||||
$part->setName($name);
|
||||
// HTTP does not support \r\n in header values
|
||||
$part->getHeaders()->setMaxLineLength(\PHP_INT_MAX);
|
||||
$r->setValue($part, '8bit');
|
||||
|
||||
return $part;
|
||||
}
|
||||
}
|
25
vendor/symfony/mime/Part/Multipart/MixedPart.php
vendored
Normal file
25
vendor/symfony/mime/Part/Multipart/MixedPart.php
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
<?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\Part\Multipart;
|
||||
|
||||
use Symfony\Component\Mime\Part\AbstractMultipartPart;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class MixedPart extends AbstractMultipartPart
|
||||
{
|
||||
public function getMediaSubtype(): string
|
||||
{
|
||||
return 'mixed';
|
||||
}
|
||||
}
|
55
vendor/symfony/mime/Part/Multipart/RelatedPart.php
vendored
Normal file
55
vendor/symfony/mime/Part/Multipart/RelatedPart.php
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
<?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\Part\Multipart;
|
||||
|
||||
use Symfony\Component\Mime\Part\AbstractMultipartPart;
|
||||
use Symfony\Component\Mime\Part\AbstractPart;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
final class RelatedPart extends AbstractMultipartPart
|
||||
{
|
||||
private AbstractPart $mainPart;
|
||||
|
||||
public function __construct(AbstractPart $mainPart, AbstractPart $part, AbstractPart ...$parts)
|
||||
{
|
||||
$this->mainPart = $mainPart;
|
||||
$this->prepareParts($part, ...$parts);
|
||||
|
||||
parent::__construct($part, ...$parts);
|
||||
}
|
||||
|
||||
public function getParts(): array
|
||||
{
|
||||
return array_merge([$this->mainPart], parent::getParts());
|
||||
}
|
||||
|
||||
public function getMediaSubtype(): string
|
||||
{
|
||||
return 'related';
|
||||
}
|
||||
|
||||
private function generateContentId(): string
|
||||
{
|
||||
return bin2hex(random_bytes(16)).'@symfony';
|
||||
}
|
||||
|
||||
private function prepareParts(AbstractPart ...$parts): void
|
||||
{
|
||||
foreach ($parts as $part) {
|
||||
if (!$part->getHeaders()->has('Content-ID')) {
|
||||
$part->getHeaders()->setHeaderBody('Id', 'Content-ID', $this->generateContentId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
111
vendor/symfony/mime/Part/SMimePart.php
vendored
Normal file
111
vendor/symfony/mime/Part/SMimePart.php
vendored
Normal file
@ -0,0 +1,111 @@
|
||||
<?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\Part;
|
||||
|
||||
use Symfony\Component\Mime\Header\Headers;
|
||||
|
||||
/**
|
||||
* @author Sebastiaan Stok <s.stok@rollerscapes.net>
|
||||
*/
|
||||
class SMimePart extends AbstractPart
|
||||
{
|
||||
/** @internal */
|
||||
protected Headers $_headers;
|
||||
|
||||
private iterable|string $body;
|
||||
private string $type;
|
||||
private string $subtype;
|
||||
private array $parameters;
|
||||
|
||||
public function __construct(iterable|string $body, string $type, string $subtype, array $parameters)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->body = $body;
|
||||
$this->type = $type;
|
||||
$this->subtype = $subtype;
|
||||
$this->parameters = $parameters;
|
||||
}
|
||||
|
||||
public function getMediaType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function getMediaSubtype(): string
|
||||
{
|
||||
return $this->subtype;
|
||||
}
|
||||
|
||||
public function bodyToString(): string
|
||||
{
|
||||
if (\is_string($this->body)) {
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
$body = '';
|
||||
foreach ($this->body as $chunk) {
|
||||
$body .= $chunk;
|
||||
}
|
||||
$this->body = $body;
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
public function bodyToIterable(): iterable
|
||||
{
|
||||
if (\is_string($this->body)) {
|
||||
yield $this->body;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$body = '';
|
||||
foreach ($this->body as $chunk) {
|
||||
$body .= $chunk;
|
||||
yield $chunk;
|
||||
}
|
||||
$this->body = $body;
|
||||
}
|
||||
|
||||
public function getPreparedHeaders(): Headers
|
||||
{
|
||||
$headers = clone parent::getHeaders();
|
||||
|
||||
$headers->setHeaderBody('Parameterized', 'Content-Type', $this->getMediaType().'/'.$this->getMediaSubtype());
|
||||
|
||||
foreach ($this->parameters as $name => $value) {
|
||||
$headers->setHeaderParameter('Content-Type', $name, $value);
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
public function __sleep(): array
|
||||
{
|
||||
// convert iterables to strings for serialization
|
||||
if (is_iterable($this->body)) {
|
||||
$this->body = $this->bodyToString();
|
||||
}
|
||||
|
||||
$this->_headers = $this->getHeaders();
|
||||
|
||||
return ['_headers', 'body', 'type', 'subtype', 'parameters'];
|
||||
}
|
||||
|
||||
public function __wakeup(): void
|
||||
{
|
||||
$r = new \ReflectionProperty(AbstractPart::class, 'headers');
|
||||
$r->setValue($this, $this->_headers);
|
||||
unset($this->_headers);
|
||||
}
|
||||
}
|
244
vendor/symfony/mime/Part/TextPart.php
vendored
Normal file
244
vendor/symfony/mime/Part/TextPart.php
vendored
Normal file
@ -0,0 +1,244 @@
|
||||
<?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\Part;
|
||||
|
||||
use Symfony\Component\Mime\Encoder\Base64ContentEncoder;
|
||||
use Symfony\Component\Mime\Encoder\ContentEncoderInterface;
|
||||
use Symfony\Component\Mime\Encoder\EightBitContentEncoder;
|
||||
use Symfony\Component\Mime\Encoder\QpContentEncoder;
|
||||
use Symfony\Component\Mime\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Mime\Header\Headers;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class TextPart extends AbstractPart
|
||||
{
|
||||
/** @internal */
|
||||
protected Headers $_headers;
|
||||
|
||||
private static array $encoders = [];
|
||||
|
||||
/** @var resource|string|File */
|
||||
private $body;
|
||||
private ?string $charset;
|
||||
private string $subtype;
|
||||
private ?string $disposition = null;
|
||||
private ?string $name = null;
|
||||
private string $encoding;
|
||||
private ?bool $seekable = null;
|
||||
|
||||
/**
|
||||
* @param resource|string|File $body Use a File instance to defer loading the file until rendering
|
||||
*/
|
||||
public function __construct($body, ?string $charset = 'utf-8', string $subtype = 'plain', ?string $encoding = null)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
if (!\is_string($body) && !\is_resource($body) && !$body instanceof File) {
|
||||
throw new \TypeError(sprintf('The body of "%s" must be a string, a resource, or an instance of "%s" (got "%s").', self::class, File::class, get_debug_type($body)));
|
||||
}
|
||||
|
||||
if ($body instanceof File) {
|
||||
$path = $body->getPath();
|
||||
if ((is_file($path) && !is_readable($path)) || is_dir($path)) {
|
||||
throw new InvalidArgumentException(sprintf('Path "%s" is not readable.', $path));
|
||||
}
|
||||
}
|
||||
|
||||
$this->body = $body;
|
||||
$this->charset = $charset;
|
||||
$this->subtype = $subtype;
|
||||
$this->seekable = \is_resource($body) ? stream_get_meta_data($body)['seekable'] && 0 === fseek($body, 0, \SEEK_CUR) : null;
|
||||
|
||||
if (null === $encoding) {
|
||||
$this->encoding = $this->chooseEncoding();
|
||||
} else {
|
||||
if ('quoted-printable' !== $encoding && 'base64' !== $encoding && '8bit' !== $encoding) {
|
||||
throw new InvalidArgumentException(sprintf('The encoding must be one of "quoted-printable", "base64", or "8bit" ("%s" given).', $encoding));
|
||||
}
|
||||
$this->encoding = $encoding;
|
||||
}
|
||||
}
|
||||
|
||||
public function getMediaType(): string
|
||||
{
|
||||
return 'text';
|
||||
}
|
||||
|
||||
public function getMediaSubtype(): string
|
||||
{
|
||||
return $this->subtype;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $disposition one of attachment, inline, or form-data
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setDisposition(string $disposition): static
|
||||
{
|
||||
$this->disposition = $disposition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?string null or one of attachment, inline, or form-data
|
||||
*/
|
||||
public function getDisposition(): ?string
|
||||
{
|
||||
return $this->disposition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the file (used by FormDataPart).
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setName(string $name): static
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the file.
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getBody(): string
|
||||
{
|
||||
if ($this->body instanceof File) {
|
||||
return file_get_contents($this->body->getPath());
|
||||
}
|
||||
|
||||
if (null === $this->seekable) {
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
if ($this->seekable) {
|
||||
rewind($this->body);
|
||||
}
|
||||
|
||||
return stream_get_contents($this->body) ?: '';
|
||||
}
|
||||
|
||||
public function bodyToString(): string
|
||||
{
|
||||
return $this->getEncoder()->encodeString($this->getBody(), $this->charset);
|
||||
}
|
||||
|
||||
public function bodyToIterable(): iterable
|
||||
{
|
||||
if ($this->body instanceof File) {
|
||||
$path = $this->body->getPath();
|
||||
if (false === $handle = @fopen($path, 'r', false)) {
|
||||
throw new InvalidArgumentException(sprintf('Unable to open path "%s".', $path));
|
||||
}
|
||||
|
||||
yield from $this->getEncoder()->encodeByteStream($handle);
|
||||
} elseif (null !== $this->seekable) {
|
||||
if ($this->seekable) {
|
||||
rewind($this->body);
|
||||
}
|
||||
yield from $this->getEncoder()->encodeByteStream($this->body);
|
||||
} else {
|
||||
yield $this->getEncoder()->encodeString($this->body);
|
||||
}
|
||||
}
|
||||
|
||||
public function getPreparedHeaders(): Headers
|
||||
{
|
||||
$headers = parent::getPreparedHeaders();
|
||||
|
||||
$headers->setHeaderBody('Parameterized', 'Content-Type', $this->getMediaType().'/'.$this->getMediaSubtype());
|
||||
if ($this->charset) {
|
||||
$headers->setHeaderParameter('Content-Type', 'charset', $this->charset);
|
||||
}
|
||||
if ($this->name && 'form-data' !== $this->disposition) {
|
||||
$headers->setHeaderParameter('Content-Type', 'name', $this->name);
|
||||
}
|
||||
$headers->setHeaderBody('Text', 'Content-Transfer-Encoding', $this->encoding);
|
||||
|
||||
if (!$headers->has('Content-Disposition') && null !== $this->disposition) {
|
||||
$headers->setHeaderBody('Parameterized', 'Content-Disposition', $this->disposition);
|
||||
if ($this->name) {
|
||||
$headers->setHeaderParameter('Content-Disposition', 'name', $this->name);
|
||||
}
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
public function asDebugString(): string
|
||||
{
|
||||
$str = parent::asDebugString();
|
||||
if (null !== $this->charset) {
|
||||
$str .= ' charset: '.$this->charset;
|
||||
}
|
||||
if (null !== $this->disposition) {
|
||||
$str .= ' disposition: '.$this->disposition;
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
private function getEncoder(): ContentEncoderInterface
|
||||
{
|
||||
if ('8bit' === $this->encoding) {
|
||||
return self::$encoders[$this->encoding] ??= new EightBitContentEncoder();
|
||||
}
|
||||
|
||||
if ('quoted-printable' === $this->encoding) {
|
||||
return self::$encoders[$this->encoding] ??= new QpContentEncoder();
|
||||
}
|
||||
|
||||
return self::$encoders[$this->encoding] ??= new Base64ContentEncoder();
|
||||
}
|
||||
|
||||
private function chooseEncoding(): string
|
||||
{
|
||||
if (null === $this->charset) {
|
||||
return 'base64';
|
||||
}
|
||||
|
||||
return 'quoted-printable';
|
||||
}
|
||||
|
||||
public function __sleep(): array
|
||||
{
|
||||
// convert resources to strings for serialization
|
||||
if (null !== $this->seekable) {
|
||||
$this->body = $this->getBody();
|
||||
$this->seekable = null;
|
||||
}
|
||||
|
||||
$this->_headers = $this->getHeaders();
|
||||
|
||||
return ['_headers', 'body', 'charset', 'subtype', 'disposition', 'name', 'encoding'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
$r = new \ReflectionProperty(AbstractPart::class, 'headers');
|
||||
$r->setValue($this, $this->_headers);
|
||||
unset($this->_headers);
|
||||
}
|
||||
}
|
13
vendor/symfony/mime/README.md
vendored
Normal file
13
vendor/symfony/mime/README.md
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
MIME Component
|
||||
==============
|
||||
|
||||
The MIME component allows manipulating MIME messages.
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Documentation](https://symfony.com/doc/current/components/mime.html)
|
||||
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
||||
* [Report issues](https://github.com/symfony/symfony/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/symfony/pulls)
|
||||
in the [main Symfony repository](https://github.com/symfony/symfony)
|
91
vendor/symfony/mime/RawMessage.php
vendored
Normal file
91
vendor/symfony/mime/RawMessage.php
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
<?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;
|
||||
|
||||
use Symfony\Component\Mime\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class RawMessage
|
||||
{
|
||||
private iterable|string $message;
|
||||
private bool $isGeneratorClosed;
|
||||
|
||||
public function __construct(iterable|string $message)
|
||||
{
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
if (\is_string($this->message)) {
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
$message = '';
|
||||
foreach ($this->message as $chunk) {
|
||||
$message .= $chunk;
|
||||
}
|
||||
|
||||
return $this->message = $message;
|
||||
}
|
||||
|
||||
public function toIterable(): iterable
|
||||
{
|
||||
if ($this->isGeneratorClosed ?? false) {
|
||||
trigger_deprecation('symfony/mime', '6.4', 'Sending an email with a closed generator is deprecated and will throw in 7.0.');
|
||||
// throw new LogicException('Unable to send the email as its generator is already closed.');
|
||||
}
|
||||
|
||||
if (\is_string($this->message)) {
|
||||
yield $this->message;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->message instanceof \Generator) {
|
||||
$message = '';
|
||||
foreach ($this->message as $chunk) {
|
||||
$message .= $chunk;
|
||||
yield $chunk;
|
||||
}
|
||||
$this->isGeneratorClosed = !$this->message->valid();
|
||||
$this->message = $message;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->message as $chunk) {
|
||||
yield $chunk;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*
|
||||
* @throws LogicException if the message is not valid
|
||||
*/
|
||||
public function ensureValidity()
|
||||
{
|
||||
}
|
||||
|
||||
public function __serialize(): array
|
||||
{
|
||||
return [$this->toString()];
|
||||
}
|
||||
|
||||
public function __unserialize(array $data): void
|
||||
{
|
||||
[$this->message] = $data;
|
||||
}
|
||||
}
|
168
vendor/symfony/mime/Resources/bin/update_mime_types.php
vendored
Normal file
168
vendor/symfony/mime/Resources/bin/update_mime_types.php
vendored
Normal file
@ -0,0 +1,168 @@
|
||||
<?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.
|
||||
*/
|
||||
|
||||
if ('cli' !== \PHP_SAPI) {
|
||||
throw new Exception('This script must be run from the command line.');
|
||||
}
|
||||
|
||||
// load new map
|
||||
$data = json_decode(file_get_contents('https://cdn.jsdelivr.net/gh/jshttp/mime-db/db.json'), true);
|
||||
$new = [];
|
||||
foreach ($data as $mimeType => $mimeTypeInformation) {
|
||||
if (!array_key_exists('extensions', $mimeTypeInformation)) {
|
||||
continue;
|
||||
}
|
||||
$new[$mimeType] = $mimeTypeInformation['extensions'];
|
||||
}
|
||||
|
||||
$xml = simplexml_load_string(file_get_contents('https://gitlab.freedesktop.org/xdg/shared-mime-info/-/raw/master/data/freedesktop.org.xml.in'));
|
||||
foreach ($xml as $node) {
|
||||
$exts = [];
|
||||
foreach ($node->glob as $glob) {
|
||||
$pattern = (string) $glob['pattern'];
|
||||
if ('*' != $pattern[0] || '.' != $pattern[1]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$exts[] = substr($pattern, 2);
|
||||
}
|
||||
|
||||
if (!$exts) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$mt = strtolower((string) $node['type']);
|
||||
$new[$mt] = array_merge($new[$mt] ?? [], $exts);
|
||||
foreach ($node->alias as $alias) {
|
||||
$mt = strtolower((string) $alias['type']);
|
||||
$new[$mt] = array_merge($new[$mt] ?? [], $exts);
|
||||
}
|
||||
}
|
||||
|
||||
// load current map
|
||||
$data = file_get_contents($output = __DIR__.'/../../MimeTypes.php');
|
||||
$current = [];
|
||||
$pre = '';
|
||||
$post = '';
|
||||
foreach (explode("\n", $data) as $line) {
|
||||
if (!preg_match("{^ '([^']+/[^']+)' => \['(.+)'\],$}", $line, $matches)) {
|
||||
if (!$current) {
|
||||
$pre .= $line."\n";
|
||||
} else {
|
||||
$post .= $line."\n";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$current[$matches[1]] = explode("', '", $matches[2]);
|
||||
}
|
||||
|
||||
$data = $pre;
|
||||
|
||||
// reverse map
|
||||
// we prefill the extensions with some preferences for content-types
|
||||
$exts = [
|
||||
'asice' => ['application/vnd.etsi.asic-e+zip'],
|
||||
'bz2' => ['application/x-bz2'],
|
||||
'csv' => ['text/csv'],
|
||||
'ecma' => ['application/ecmascript'],
|
||||
'flv' => ['video/x-flv'],
|
||||
'gif' => ['image/gif'],
|
||||
'gz' => ['application/x-gzip'],
|
||||
'htm' => ['text/html'],
|
||||
'html' => ['text/html'],
|
||||
'jar' => ['application/x-java-archive'],
|
||||
'jpg' => ['image/jpeg'],
|
||||
'js' => ['text/javascript'],
|
||||
'keynote' => ['application/vnd.apple.keynote'],
|
||||
'key' => ['application/vnd.apple.keynote'],
|
||||
'm3u' => ['audio/x-mpegurl'],
|
||||
'm4a' => ['audio/mp4'],
|
||||
'md' => ['text/markdown', 'text/x-markdown'],
|
||||
'mdb' => ['application/x-msaccess'],
|
||||
'mid' => ['audio/midi'],
|
||||
'mov' => ['video/quicktime'],
|
||||
'mp3' => ['audio/mpeg'],
|
||||
'ogg' => ['audio/ogg'],
|
||||
'pdf' => ['application/pdf'],
|
||||
'php' => ['application/x-php'],
|
||||
'png' => ['image/png'],
|
||||
'ppt' => ['application/vnd.ms-powerpoint'],
|
||||
'rar' => ['application/x-rar-compressed'],
|
||||
'hqx' => ['application/stuffit'],
|
||||
'sit' => ['application/x-stuffit', 'application/stuffit'],
|
||||
'svg' => ['image/svg+xml'],
|
||||
'tar' => ['application/x-tar'],
|
||||
'tif' => ['image/tiff'],
|
||||
'ttf' => ['application/x-font-truetype'],
|
||||
'vcf' => ['text/x-vcard'],
|
||||
'wav' => ['audio/wav'],
|
||||
'wma' => ['audio/x-ms-wma'],
|
||||
'wmv' => ['audio/x-ms-wmv'],
|
||||
'xls' => ['application/vnd.ms-excel'],
|
||||
'yaml' => ['application/yaml'],
|
||||
'yml' => ['application/yaml'],
|
||||
'zip' => ['application/zip'],
|
||||
];
|
||||
|
||||
// we merge the 2 maps (we never remove old mime types)
|
||||
$map = array_replace_recursive($current, $new);
|
||||
|
||||
foreach ($exts as $ext => $types) {
|
||||
foreach ($types as $mt) {
|
||||
if (!isset($map[$mt])) {
|
||||
$map += [$mt => [$ext]];
|
||||
}
|
||||
}
|
||||
}
|
||||
ksort($map);
|
||||
|
||||
foreach ($map as $mimeType => $extensions) {
|
||||
foreach ($exts as $ext => $types) {
|
||||
if (in_array($mimeType, $types, true)) {
|
||||
array_unshift($extensions, $ext);
|
||||
}
|
||||
}
|
||||
$data .= sprintf(" '%s' => ['%s'],\n", $mimeType, implode("', '", array_unique($extensions)));
|
||||
}
|
||||
$data .= $post;
|
||||
|
||||
foreach ($map as $mimeType => $extensions) {
|
||||
foreach ($extensions as $extension) {
|
||||
if ('application/octet-stream' === $mimeType && 'bin' !== $extension) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$exts[$extension][] = $mimeType;
|
||||
}
|
||||
}
|
||||
ksort($exts);
|
||||
|
||||
$updated = '';
|
||||
$state = 0;
|
||||
foreach (explode("\n", $data) as $line) {
|
||||
if (!preg_match("{^ '([^'/]+)' => \['(.+)'\],$}", $line, $matches)) {
|
||||
if (1 === $state) {
|
||||
$state = 2;
|
||||
foreach ($exts as $ext => $mimeTypes) {
|
||||
$updated .= sprintf(" '%s' => ['%s'],\n", $ext, implode("', '", array_unique($mimeTypes)));
|
||||
}
|
||||
}
|
||||
$updated .= $line."\n";
|
||||
continue;
|
||||
}
|
||||
$state = 1;
|
||||
}
|
||||
|
||||
$updated = preg_replace('{Updated from upstream on .+?\.}', sprintf('Updated from upstream on %s.', date('Y-m-d')), $updated, -1);
|
||||
|
||||
file_put_contents($output, rtrim($updated, "\n")."\n");
|
||||
|
||||
echo "Done.\n";
|
67
vendor/symfony/mime/Test/Constraint/EmailAddressContains.php
vendored
Normal file
67
vendor/symfony/mime/Test/Constraint/EmailAddressContains.php
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
<?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\Test\Constraint;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use Symfony\Component\Mime\Header\MailboxHeader;
|
||||
use Symfony\Component\Mime\Header\MailboxListHeader;
|
||||
use Symfony\Component\Mime\RawMessage;
|
||||
|
||||
final class EmailAddressContains extends Constraint
|
||||
{
|
||||
private string $headerName;
|
||||
private string $expectedValue;
|
||||
|
||||
public function __construct(string $headerName, string $expectedValue)
|
||||
{
|
||||
$this->headerName = $headerName;
|
||||
$this->expectedValue = $expectedValue;
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return sprintf('contains address "%s" with value "%s"', $this->headerName, $this->expectedValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RawMessage $message
|
||||
*/
|
||||
protected function matches($message): bool
|
||||
{
|
||||
if (RawMessage::class === $message::class) {
|
||||
throw new \LogicException('Unable to test a message address on a RawMessage instance.');
|
||||
}
|
||||
|
||||
$header = $message->getHeaders()->get($this->headerName);
|
||||
if ($header instanceof MailboxHeader) {
|
||||
return $this->expectedValue === $header->getAddress()->getAddress();
|
||||
} elseif ($header instanceof MailboxListHeader) {
|
||||
foreach ($header->getAddresses() as $address) {
|
||||
if ($this->expectedValue === $address->getAddress()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new \LogicException('Unable to test a message address on a non-address header.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RawMessage $message
|
||||
*/
|
||||
protected function failureDescription($message): string
|
||||
{
|
||||
return sprintf('the Email %s (value is %s)', $this->toString(), $message->getHeaders()->get($this->headerName)->getBodyAsString());
|
||||
}
|
||||
}
|
53
vendor/symfony/mime/Test/Constraint/EmailAttachmentCount.php
vendored
Normal file
53
vendor/symfony/mime/Test/Constraint/EmailAttachmentCount.php
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
<?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\Test\Constraint;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use Symfony\Component\Mime\Message;
|
||||
use Symfony\Component\Mime\RawMessage;
|
||||
|
||||
final class EmailAttachmentCount extends Constraint
|
||||
{
|
||||
private int $expectedValue;
|
||||
private ?string $transport;
|
||||
|
||||
public function __construct(int $expectedValue, ?string $transport = null)
|
||||
{
|
||||
$this->expectedValue = $expectedValue;
|
||||
$this->transport = $transport;
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return sprintf('has sent "%d" attachment(s)', $this->expectedValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RawMessage $message
|
||||
*/
|
||||
protected function matches($message): bool
|
||||
{
|
||||
if (RawMessage::class === $message::class || Message::class === $message::class) {
|
||||
throw new \LogicException('Unable to test a message attachment on a RawMessage or Message instance.');
|
||||
}
|
||||
|
||||
return $this->expectedValue === \count($message->getAttachments());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RawMessage $message
|
||||
*/
|
||||
protected function failureDescription($message): string
|
||||
{
|
||||
return 'the Email '.$this->toString();
|
||||
}
|
||||
}
|
50
vendor/symfony/mime/Test/Constraint/EmailHasHeader.php
vendored
Normal file
50
vendor/symfony/mime/Test/Constraint/EmailHasHeader.php
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
<?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\Test\Constraint;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use Symfony\Component\Mime\RawMessage;
|
||||
|
||||
final class EmailHasHeader extends Constraint
|
||||
{
|
||||
private string $headerName;
|
||||
|
||||
public function __construct(string $headerName)
|
||||
{
|
||||
$this->headerName = $headerName;
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return sprintf('has header "%s"', $this->headerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RawMessage $message
|
||||
*/
|
||||
protected function matches($message): bool
|
||||
{
|
||||
if (RawMessage::class === $message::class) {
|
||||
throw new \LogicException('Unable to test a message header on a RawMessage instance.');
|
||||
}
|
||||
|
||||
return $message->getHeaders()->has($this->headerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RawMessage $message
|
||||
*/
|
||||
protected function failureDescription($message): string
|
||||
{
|
||||
return 'the Email '.$this->toString();
|
||||
}
|
||||
}
|
62
vendor/symfony/mime/Test/Constraint/EmailHeaderSame.php
vendored
Normal file
62
vendor/symfony/mime/Test/Constraint/EmailHeaderSame.php
vendored
Normal 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\Test\Constraint;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use Symfony\Component\Mime\Header\UnstructuredHeader;
|
||||
use Symfony\Component\Mime\RawMessage;
|
||||
|
||||
final class EmailHeaderSame extends Constraint
|
||||
{
|
||||
private string $headerName;
|
||||
private string $expectedValue;
|
||||
|
||||
public function __construct(string $headerName, string $expectedValue)
|
||||
{
|
||||
$this->headerName = $headerName;
|
||||
$this->expectedValue = $expectedValue;
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return sprintf('has header "%s" with value "%s"', $this->headerName, $this->expectedValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RawMessage $message
|
||||
*/
|
||||
protected function matches($message): bool
|
||||
{
|
||||
if (RawMessage::class === $message::class) {
|
||||
throw new \LogicException('Unable to test a message header on a RawMessage instance.');
|
||||
}
|
||||
|
||||
return $this->expectedValue === $this->getHeaderValue($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RawMessage $message
|
||||
*/
|
||||
protected function failureDescription($message): string
|
||||
{
|
||||
return sprintf('the Email %s (value is %s)', $this->toString(), $this->getHeaderValue($message) ?? 'null');
|
||||
}
|
||||
|
||||
private function getHeaderValue($message): ?string
|
||||
{
|
||||
if (null === $header = $message->getHeaders()->get($this->headerName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $header instanceof UnstructuredHeader ? $header->getValue() : $header->getBodyAsString();
|
||||
}
|
||||
}
|
51
vendor/symfony/mime/Test/Constraint/EmailHtmlBodyContains.php
vendored
Normal file
51
vendor/symfony/mime/Test/Constraint/EmailHtmlBodyContains.php
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
<?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\Test\Constraint;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use Symfony\Component\Mime\Message;
|
||||
use Symfony\Component\Mime\RawMessage;
|
||||
|
||||
final class EmailHtmlBodyContains extends Constraint
|
||||
{
|
||||
private string $expectedText;
|
||||
|
||||
public function __construct(string $expectedText)
|
||||
{
|
||||
$this->expectedText = $expectedText;
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return sprintf('contains "%s"', $this->expectedText);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RawMessage $message
|
||||
*/
|
||||
protected function matches($message): bool
|
||||
{
|
||||
if (RawMessage::class === $message::class || Message::class === $message::class) {
|
||||
throw new \LogicException('Unable to test a message HTML body on a RawMessage or Message instance.');
|
||||
}
|
||||
|
||||
return str_contains($message->getHtmlBody(), $this->expectedText);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RawMessage $message
|
||||
*/
|
||||
protected function failureDescription($message): string
|
||||
{
|
||||
return 'the Email HTML body '.$this->toString();
|
||||
}
|
||||
}
|
47
vendor/symfony/mime/Test/Constraint/EmailSubjectContains.php
vendored
Normal file
47
vendor/symfony/mime/Test/Constraint/EmailSubjectContains.php
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
<?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\Test\Constraint;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use Symfony\Component\Mime\Email;
|
||||
|
||||
final class EmailSubjectContains extends Constraint
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $expectedSubjectValue,
|
||||
) {
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return sprintf('contains subject with value "%s"', $this->expectedSubjectValue);
|
||||
}
|
||||
|
||||
protected function matches($other): bool
|
||||
{
|
||||
if (!$other instanceof Email) {
|
||||
throw new \LogicException('Can only test a message subject on an Email instance.');
|
||||
}
|
||||
|
||||
return str_contains((string) $other->getSubject(), $this->expectedSubjectValue);
|
||||
}
|
||||
|
||||
protected function failureDescription($other): string
|
||||
{
|
||||
$message = 'The email subject '.$this->toString();
|
||||
if ($other instanceof Email) {
|
||||
$message .= sprintf('. The subject was: "%s"', $other->getSubject() ?? '<empty>');
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
51
vendor/symfony/mime/Test/Constraint/EmailTextBodyContains.php
vendored
Normal file
51
vendor/symfony/mime/Test/Constraint/EmailTextBodyContains.php
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
<?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\Test\Constraint;
|
||||
|
||||
use PHPUnit\Framework\Constraint\Constraint;
|
||||
use Symfony\Component\Mime\Message;
|
||||
use Symfony\Component\Mime\RawMessage;
|
||||
|
||||
final class EmailTextBodyContains extends Constraint
|
||||
{
|
||||
private string $expectedText;
|
||||
|
||||
public function __construct(string $expectedText)
|
||||
{
|
||||
$this->expectedText = $expectedText;
|
||||
}
|
||||
|
||||
public function toString(): string
|
||||
{
|
||||
return sprintf('contains "%s"', $this->expectedText);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RawMessage $message
|
||||
*/
|
||||
protected function matches($message): bool
|
||||
{
|
||||
if (RawMessage::class === $message::class || Message::class === $message::class) {
|
||||
throw new \LogicException('Unable to test a message text body on a RawMessage or Message instance.');
|
||||
}
|
||||
|
||||
return str_contains($message->getTextBody(), $this->expectedText);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RawMessage $message
|
||||
*/
|
||||
protected function failureDescription($message): string
|
||||
{
|
||||
return 'the Email text body '.$this->toString();
|
||||
}
|
||||
}
|
48
vendor/symfony/mime/composer.json
vendored
Normal file
48
vendor/symfony/mime/composer.json
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "symfony/mime",
|
||||
"type": "library",
|
||||
"description": "Allows manipulating MIME messages",
|
||||
"keywords": ["mime", "mime-type"],
|
||||
"homepage": "https://symfony.com",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"symfony/deprecation-contracts": "^2.5|^3",
|
||||
"symfony/polyfill-intl-idn": "^1.10",
|
||||
"symfony/polyfill-mbstring": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"egulias/email-validator": "^2.1.10|^3.1|^4",
|
||||
"league/html-to-markdown": "^5.0",
|
||||
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
|
||||
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
|
||||
"symfony/process": "^5.4|^6.4|^7.0",
|
||||
"symfony/property-access": "^5.4|^6.0|^7.0",
|
||||
"symfony/property-info": "^5.4|^6.0|^7.0",
|
||||
"symfony/serializer": "^6.3.2|^7.0"
|
||||
},
|
||||
"conflict": {
|
||||
"egulias/email-validator": "~3.0.0",
|
||||
"phpdocumentor/reflection-docblock": "<3.2.2",
|
||||
"phpdocumentor/type-resolver": "<1.4.0",
|
||||
"symfony/mailer": "<5.4",
|
||||
"symfony/serializer": "<6.3.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Component\\Mime\\": "" },
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev"
|
||||
}
|
Reference in New Issue
Block a user