package and depencies

This commit is contained in:
RafficMohammed
2023-01-08 02:57:24 +05:30
parent d5332eb421
commit 1d54b8bc7f
4309 changed files with 193331 additions and 172289 deletions

View File

@@ -33,11 +33,11 @@ final class Address
*/
private const FROM_STRING_PATTERN = '~(?<displayName>[^<]*)<(?<addrSpec>.*)>[^>]*~';
private static $validator;
private static $encoder;
private static EmailValidator $validator;
private static IdnAddressEncoder $encoder;
private $address;
private $name;
private string $address;
private string $name;
public function __construct(string $address, string $name = '')
{
@@ -45,9 +45,7 @@ final class Address
throw new LogicException(sprintf('The "%s" class cannot be used as it needs "%s"; try running "composer require egulias/email-validator".', __CLASS__, EmailValidator::class));
}
if (null === self::$validator) {
self::$validator = new EmailValidator();
}
self::$validator ??= new EmailValidator();
$this->address = trim($address);
$this->name = trim(str_replace(["\n", "\r"], '', $name));
@@ -69,9 +67,7 @@ final class Address
public function getEncodedAddress(): string
{
if (null === self::$encoder) {
self::$encoder = new IdnAddressEncoder();
}
self::$encoder ??= new IdnAddressEncoder();
return self::$encoder->encodeString($this->address);
}
@@ -90,20 +86,13 @@ final class Address
return sprintf('"%s"', preg_replace('/"/u', '\"', $this->getName()));
}
/**
* @param Address|string $address
*/
public static function create($address): self
public static function create(self|string $address): self
{
if ($address instanceof self) {
return $address;
}
if (!\is_string($address)) {
throw new InvalidArgumentException(sprintf('An address can be an instance of Address or a string ("%s" given).', get_debug_type($address)));
}
if (false === strpos($address, '<')) {
if (!str_contains($address, '<')) {
return new self($address);
}
@@ -128,22 +117,4 @@ final class Address
return $addrs;
}
/**
* @deprecated since Symfony 5.2, use "create()" instead.
*/
public static function fromString(string $string): self
{
trigger_deprecation('symfony/mime', '5.2', '"%s()" is deprecated, use "%s::create()" instead.', __METHOD__, __CLASS__);
if (!str_contains($string, '<')) {
return new self($string, '');
}
if (!preg_match(self::FROM_STRING_PATTERN, $string, $matches)) {
throw new InvalidArgumentException(sprintf('Could not parse "%s" to a "%s" instance.', $string, self::class));
}
return new self($matches['addrSpec'], trim($matches['displayName'], ' \'"'));
}
}

View File

@@ -1,6 +1,24 @@
CHANGELOG
=========
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
-----

View File

@@ -55,12 +55,12 @@ final class CharacterStream
"\xf8" => 5, "\xf9" => 5, "\xfa" => 5, "\xfb" => 5, "\xfc" => 6, "\xfd" => 6, "\xfe" => 0, "\xff" => 0,
];
private $data = '';
private $dataSize = 0;
private $map = [];
private $charCount = 0;
private $currentPos = 0;
private $fixedWidth = 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
@@ -72,29 +72,22 @@ final class CharacterStream
$this->fixedWidth = 0;
$this->map = ['p' => [], 'i' => []];
} else {
switch ($charset) {
$this->fixedWidth = match ($charset) {
// 16 bits
case 'ucs2':
case 'ucs-2':
case 'utf16':
case 'utf-16':
$this->fixedWidth = 2;
break;
// 32 bits
case 'ucs4':
case 'ucs-4':
case 'utf32':
case 'utf-32':
$this->fixedWidth = 4;
break;
// 7-8 bit charsets: (us-)?ascii, (iso|iec)-?8859-?[0-9]+, windows-?125[0-9], cp-?[0-9]+, ansi, macintosh,
'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:
$this->fixedWidth = 1;
}
// and fallback
default => 1,
};
}
if (\is_resource($input)) {
$blocks = 16372;

View File

@@ -18,7 +18,7 @@ namespace Symfony\Component\Mime\Crypto;
*/
final class DkimOptions
{
private $options = [];
private array $options = [];
public function toArray(): array
{
@@ -28,7 +28,7 @@ final class DkimOptions
/**
* @return $this
*/
public function algorithm(string $algo): self
public function algorithm(string $algo): static
{
$this->options['algorithm'] = $algo;
@@ -38,7 +38,7 @@ final class DkimOptions
/**
* @return $this
*/
public function signatureExpirationDelay(int $show): self
public function signatureExpirationDelay(int $show): static
{
$this->options['signature_expiration_delay'] = $show;
@@ -48,7 +48,7 @@ final class DkimOptions
/**
* @return $this
*/
public function bodyMaxLength(int $max): self
public function bodyMaxLength(int $max): static
{
$this->options['body_max_length'] = $max;
@@ -58,7 +58,7 @@ final class DkimOptions
/**
* @return $this
*/
public function bodyShowLength(bool $show): self
public function bodyShowLength(bool $show): static
{
$this->options['body_show_length'] = $show;
@@ -68,7 +68,7 @@ final class DkimOptions
/**
* @return $this
*/
public function headerCanon(string $canon): self
public function headerCanon(string $canon): static
{
$this->options['header_canon'] = $canon;
@@ -78,7 +78,7 @@ final class DkimOptions
/**
* @return $this
*/
public function bodyCanon(string $canon): self
public function bodyCanon(string $canon): static
{
$this->options['body_canon'] = $canon;
@@ -88,7 +88,7 @@ final class DkimOptions
/**
* @return $this
*/
public function headersToIgnore(array $headers): self
public function headersToIgnore(array $headers): static
{
$this->options['headers_to_ignore'] = $headers;

View File

@@ -30,10 +30,10 @@ final class DkimSigner
public const ALGO_SHA256 = 'rsa-sha256';
public const ALGO_ED25519 = 'ed25519-sha256'; // RFC 8463
private $key;
private $domainName;
private $selector;
private $defaultOptions;
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)
@@ -44,10 +44,7 @@ final class DkimSigner
if (!\extension_loaded('openssl')) {
throw new \LogicException('PHP extension "openssl" is required to use DKIM.');
}
if (!$this->key = openssl_pkey_get_private($pk, $passphrase)) {
throw new InvalidArgumentException('Unable to load DKIM private key: '.openssl_error_string());
}
$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 + [

View File

@@ -19,21 +19,21 @@ use Symfony\Component\Mime\Message;
*/
final class SMimeEncrypter extends SMime
{
private $certs;
private $cipher;
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($certificate, int $cipher = null)
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);
$this->certs = array_map($this->normalizeFilePath(...), $certificate);
} else {
$this->certs = $this->normalizeFilePath($certificate);
}

View File

@@ -19,10 +19,10 @@ use Symfony\Component\Mime\Message;
*/
final class SMimeSigner extends SMime
{
private $signCertificate;
private $signPrivateKey;
private $signOptions;
private $extraCerts;
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)

View File

@@ -22,27 +22,11 @@ use Symfony\Component\DependencyInjection\Reference;
*/
class AddMimeTypeGuesserPass implements CompilerPassInterface
{
private $mimeTypesService;
private $mimeTypeGuesserTag;
public function __construct(string $mimeTypesService = 'mime_types', string $mimeTypeGuesserTag = 'mime.mime_type_guesser')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/mime', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->mimeTypesService = $mimeTypesService;
$this->mimeTypeGuesserTag = $mimeTypeGuesserTag;
}
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if ($container->has($this->mimeTypesService)) {
$definition = $container->findDefinition($this->mimeTypesService);
foreach ($container->findTaggedServiceIds($this->mimeTypeGuesserTag, true) as $id => $attributes) {
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
View 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;
}
}

View File

@@ -14,6 +14,7 @@ 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;
@@ -38,20 +39,26 @@ class Email extends Message
self::PRIORITY_LOWEST => 'Lowest',
];
private $text;
private $textCharset;
private $html;
private $htmlCharset;
private $attachments = [];
/**
* @var AbstractPart|null
* @var resource|string|null
*/
private $cachedBody; // Used to avoid wrong body hash in DKIM signatures with multiple parts (e.g. HTML + TEXT) due to multiple boundaries.
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)
public function subject(string $subject): static
{
return $this->setHeaderBody('Text', 'Subject', $subject);
}
@@ -64,7 +71,7 @@ class Email extends Message
/**
* @return $this
*/
public function date(\DateTimeInterface $dateTime)
public function date(\DateTimeInterface $dateTime): static
{
return $this->setHeaderBody('Date', 'Date', $dateTime);
}
@@ -75,11 +82,9 @@ class Email extends Message
}
/**
* @param Address|string $address
*
* @return $this
*/
public function returnPath($address)
public function returnPath(Address|string $address): static
{
return $this->setHeaderBody('Path', 'Return-Path', Address::create($address));
}
@@ -90,11 +95,9 @@ class Email extends Message
}
/**
* @param Address|string $address
*
* @return $this
*/
public function sender($address)
public function sender(Address|string $address): static
{
return $this->setHeaderBody('Mailbox', 'Sender', Address::create($address));
}
@@ -105,21 +108,17 @@ class Email extends Message
}
/**
* @param Address|string ...$addresses
*
* @return $this
*/
public function addFrom(...$addresses)
public function addFrom(Address|string ...$addresses): static
{
return $this->addListAddressHeaderBody('From', $addresses);
}
/**
* @param Address|string ...$addresses
*
* @return $this
*/
public function from(...$addresses)
public function from(Address|string ...$addresses): static
{
return $this->setListAddressHeaderBody('From', $addresses);
}
@@ -133,21 +132,17 @@ class Email extends Message
}
/**
* @param Address|string ...$addresses
*
* @return $this
*/
public function addReplyTo(...$addresses)
public function addReplyTo(Address|string ...$addresses): static
{
return $this->addListAddressHeaderBody('Reply-To', $addresses);
}
/**
* @param Address|string ...$addresses
*
* @return $this
*/
public function replyTo(...$addresses)
public function replyTo(Address|string ...$addresses): static
{
return $this->setListAddressHeaderBody('Reply-To', $addresses);
}
@@ -161,21 +156,17 @@ class Email extends Message
}
/**
* @param Address|string ...$addresses
*
* @return $this
*/
public function addTo(...$addresses)
public function addTo(Address|string ...$addresses): static
{
return $this->addListAddressHeaderBody('To', $addresses);
}
/**
* @param Address|string ...$addresses
*
* @return $this
*/
public function to(...$addresses)
public function to(Address|string ...$addresses): static
{
return $this->setListAddressHeaderBody('To', $addresses);
}
@@ -189,21 +180,17 @@ class Email extends Message
}
/**
* @param Address|string ...$addresses
*
* @return $this
*/
public function addCc(...$addresses)
public function addCc(Address|string ...$addresses): static
{
return $this->addListAddressHeaderBody('Cc', $addresses);
}
/**
* @param Address|string ...$addresses
*
* @return $this
*/
public function cc(...$addresses)
public function cc(Address|string ...$addresses): static
{
return $this->setListAddressHeaderBody('Cc', $addresses);
}
@@ -217,21 +204,17 @@ class Email extends Message
}
/**
* @param Address|string ...$addresses
*
* @return $this
*/
public function addBcc(...$addresses)
public function addBcc(Address|string ...$addresses): static
{
return $this->addListAddressHeaderBody('Bcc', $addresses);
}
/**
* @param Address|string ...$addresses
*
* @return $this
*/
public function bcc(...$addresses)
public function bcc(Address|string ...$addresses): static
{
return $this->setListAddressHeaderBody('Bcc', $addresses);
}
@@ -251,7 +234,7 @@ class Email extends Message
*
* @return $this
*/
public function priority(int $priority)
public function priority(int $priority): static
{
if ($priority > 5) {
$priority = 5;
@@ -280,7 +263,7 @@ class Email extends Message
*
* @return $this
*/
public function text($body, string $charset = 'utf-8')
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)));
@@ -311,7 +294,7 @@ class Email extends Message
*
* @return $this
*/
public function html($body, string $charset = 'utf-8')
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)));
@@ -342,27 +325,17 @@ class Email extends Message
*
* @return $this
*/
public function attach($body, string $name = null, string $contentType = null)
public function attach($body, string $name = null, string $contentType = null): static
{
if (!\is_string($body) && !\is_resource($body)) {
throw new \TypeError(sprintf('The body must be a string or a resource (got "%s").', get_debug_type($body)));
}
$this->cachedBody = null;
$this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => false];
return $this;
return $this->addPart(new DataPart($body, $name, $contentType));
}
/**
* @return $this
*/
public function attachFromPath(string $path, string $name = null, string $contentType = null)
public function attachFromPath(string $path, string $name = null, string $contentType = null): static
{
$this->cachedBody = null;
$this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => false];
return $this;
return $this->addPart(new DataPart(new File($path), $name, $contentType));
}
/**
@@ -370,51 +343,48 @@ class Email extends Message
*
* @return $this
*/
public function embed($body, string $name = null, string $contentType = null)
public function embed($body, string $name = null, string $contentType = null): static
{
if (!\is_string($body) && !\is_resource($body)) {
throw new \TypeError(sprintf('The body must be a string or a resource (got "%s").', get_debug_type($body)));
}
$this->cachedBody = null;
$this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => true];
return $this;
return $this->addPart((new DataPart($body, $name, $contentType))->asInline());
}
/**
* @return $this
*/
public function embedFromPath(string $path, string $name = null, string $contentType = null)
public function embedFromPath(string $path, string $name = null, string $contentType = null): static
{
$this->cachedBody = null;
$this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => true];
return $this->addPart((new DataPart(new File($path), $name, $contentType))->asInline());
}
return $this;
/**
* @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 attachPart(DataPart $part)
public function addPart(DataPart $part): static
{
$this->cachedBody = null;
$this->attachments[] = ['part' => $part];
$this->attachments[] = $part;
return $this;
}
/**
* @return array|DataPart[]
* @return DataPart[]
*/
public function getAttachments(): array
{
$parts = [];
foreach ($this->attachments as $attachment) {
$parts[] = $this->createDataPart($attachment);
}
return $parts;
return $this->attachments;
}
public function getBody(): AbstractPart
@@ -428,13 +398,22 @@ class Email extends Message
public function ensureValidity()
{
if (null === $this->text && null === $this->html && !$this->attachments) {
throw new LogicException('A message must have a text or an HTML part or attachments.');
$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.
*
@@ -461,7 +440,7 @@ class Email extends Message
return $this->cachedBody;
}
$this->ensureValidity();
$this->ensureBodyValid();
[$htmlPart, $otherParts, $relatedParts] = $this->prepareParts();
@@ -497,43 +476,37 @@ class Email extends Message
if (null !== $html) {
$htmlPart = new TextPart($html, $this->htmlCharset, 'html');
$html = $htmlPart->getBody();
preg_match_all('(<img\s+[^>]*src\s*=\s*(?:([\'"])cid:(.+?)\\1|cid:([^>\s]+)))i', $html, $names);
$names = array_filter(array_unique(array_merge($names[2], $names[3])));
$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));
}
// usage of reflection is a temporary workaround for missing getters that will be added in 6.2
$nameRef = new \ReflectionProperty(TextPart::class, 'name');
$nameRef->setAccessible(true);
$otherParts = $relatedParts = [];
foreach ($this->attachments as $attachment) {
$part = $this->createDataPart($attachment);
if (isset($attachment['part'])) {
$attachment['name'] = $nameRef->getValue($part);
}
$related = false;
foreach ($this->attachments as $part) {
foreach ($names as $name) {
if ($name !== $attachment['name']) {
if ($name !== $part->getName()) {
continue;
}
if (isset($relatedParts[$name])) {
continue 2;
}
$part->setDisposition('inline');
$html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html, $count);
if ($count) {
$related = true;
}
$part->setName($part->getContentId());
$relatedParts[$name] = $part;
$part->setName($part->getContentId())->asInline();
break;
continue 2;
}
if ($related) {
$relatedParts[$attachment['name']] = $part;
} else {
$otherParts[] = $part;
}
$otherParts[] = $part;
}
if (null !== $htmlPart) {
$htmlPart = new TextPart($html, $this->htmlCharset, 'html');
@@ -542,28 +515,10 @@ class Email extends Message
return [$htmlPart, $otherParts, array_values($relatedParts)];
}
private function createDataPart(array $attachment): DataPart
{
if (isset($attachment['part'])) {
return $attachment['part'];
}
if (isset($attachment['body'])) {
$part = new DataPart($attachment['body'], $attachment['name'] ?? null, $attachment['content-type'] ?? null);
} else {
$part = DataPart::fromPath($attachment['path'] ?? '', $attachment['name'] ?? null, $attachment['content-type'] ?? null);
}
if ($attachment['inline']) {
$part->asInline();
}
return $part;
}
/**
* @return $this
*/
private function setHeaderBody(string $type, string $name, $body): object
private function setHeaderBody(string $type, string $name, $body): static
{
$this->getHeaders()->setHeaderBody($type, $name, $body);
@@ -583,7 +538,7 @@ class Email extends Message
/**
* @return $this
*/
private function setListAddressHeaderBody(string $name, array $addresses)
private function setListAddressHeaderBody(string $name, array $addresses): static
{
$addresses = Address::createArray($addresses);
$headers = $this->getHeaders();
@@ -609,12 +564,6 @@ class Email extends Message
$this->html = (new TextPart($this->html))->getBody();
}
foreach ($this->attachments as $i => $attachment) {
if (isset($attachment['body']) && \is_resource($attachment['body'])) {
$this->attachments[$i]['body'] = (new TextPart($attachment['body']))->getBody();
}
}
return [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, parent::__serialize()];
}

View File

@@ -46,15 +46,10 @@ final class QpContentEncoder implements ContentEncoderInterface
// transform =0D=0A to CRLF
$string = str_replace(["\t=0D=0A", ' =0D=0A', '=0D=0A'], ["=09\r\n", "=20\r\n", "\r\n"], $string);
switch (\ord(substr($string, -1))) {
case 0x09:
$string = substr_replace($string, '=09', -1);
break;
case 0x20:
$string = substr_replace($string, '=20', -1);
break;
}
return $string;
return match (\ord(substr($string, -1))) {
0x09 => substr_replace($string, '=09', -1),
0x20 => substr_replace($string, '=20', -1),
default => $string,
};
}
}

View File

@@ -76,7 +76,7 @@ class QpEncoder implements EncoderInterface
255 => '=FF',
];
private static $safeMapShare = [];
private static array $safeMapShare = [];
/**
* A map of non-encoded ascii characters.
@@ -85,7 +85,7 @@ class QpEncoder implements EncoderInterface
*
* @internal
*/
protected $safeMap = [];
protected array $safeMap = [];
public function __construct()
{
@@ -106,8 +106,6 @@ class QpEncoder implements EncoderInterface
}
/**
* {@inheritdoc}
*
* Takes an unencoded string and produces a QP encoded string from it.
*
* QP encoded strings have a maximum line length of 76 characters.
@@ -184,12 +182,11 @@ class QpEncoder implements EncoderInterface
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);
switch ($end = \ord(substr($string, -1))) {
case 0x09:
case 0x20:
$string = substr_replace($string, self::QP_MAP[$end], -1);
}
return $string;
return match ($end = \ord(substr($string, -1))) {
0x09,
0x20 => substr_replace($string, self::QP_MAP[$end], -1),
default => $string,
};
}
}

View File

@@ -21,7 +21,7 @@ use Symfony\Component\Mime\Exception\LogicException;
*/
class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
{
private $cmd;
private string $cmd;
/**
* The $cmd pattern must contain a "%s" string that will be replaced
@@ -36,9 +36,6 @@ class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
$this->cmd = $cmd;
}
/**
* {@inheritdoc}
*/
public function isGuesserSupported(): bool
{
static $supported = null;
@@ -58,9 +55,6 @@ class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
return $supported = 0 === $exitStatus && '' !== $binPath;
}
/**
* {@inheritdoc}
*/
public function guessMimeType(string $path): ?string
{
if (!is_file($path) || !is_readable($path)) {

View File

@@ -21,7 +21,7 @@ use Symfony\Component\Mime\Exception\LogicException;
*/
class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface
{
private $magicFile;
private ?string $magicFile;
/**
* @param string $magicFile A magic file to use with the finfo instance
@@ -33,17 +33,11 @@ class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface
$this->magicFile = $magicFile;
}
/**
* {@inheritdoc}
*/
public function isGuesserSupported(): bool
{
return \function_exists('finfo_open');
}
/**
* {@inheritdoc}
*/
public function guessMimeType(string $path): ?string
{
if (!is_file($path) || !is_readable($path)) {

View File

@@ -22,12 +22,12 @@ 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 $encoder;
private static QpMimeHeaderEncoder $encoder;
private $name;
private $lineLength = 76;
private $lang;
private $charset = 'utf-8';
private string $name;
private int $lineLength = 76;
private ?string $lang = null;
private string $charset = 'utf-8';
public function __construct(string $name)
{
@@ -188,9 +188,7 @@ abstract class AbstractHeader implements HeaderInterface
*/
protected function getTokenAsEncodedWord(string $token, int $firstLineOffset = 0): string
{
if (null === self::$encoder) {
self::$encoder = new QpMimeHeaderEncoder();
}
self::$encoder ??= new QpMimeHeaderEncoder();
// Adjust $firstLineOffset to account for space needed for syntax
$charsetDecl = $this->charset;
@@ -233,9 +231,7 @@ abstract class AbstractHeader implements HeaderInterface
*/
protected function toTokens(string $string = null): array
{
if (null === $string) {
$string = $this->getBodyAsString();
}
$string ??= $this->getBodyAsString();
$tokens = [];
// Generate atoms; split at all invisible boundaries followed by WSP

View File

@@ -18,7 +18,7 @@ namespace Symfony\Component\Mime\Header;
*/
final class DateHeader extends AbstractHeader
{
private $dateTime;
private \DateTimeImmutable $dateTime;
public function __construct(string $name, \DateTimeInterface $date)
{
@@ -30,7 +30,7 @@ final class DateHeader extends AbstractHeader
/**
* @param \DateTimeInterface $body
*/
public function setBody($body)
public function setBody(mixed $body)
{
$this->setDateTime($body);
}

View File

@@ -22,19 +22,15 @@ interface HeaderInterface
* Sets the body.
*
* The type depends on the Header concrete class.
*
* @param mixed $body
*/
public function setBody($body);
public function setBody(mixed $body);
/**
* Gets the body.
*
* The return type depends on the Header concrete class.
*
* @return mixed
*/
public function getBody();
public function getBody(): mixed;
public function setCharset(string $charset);

View File

@@ -34,16 +34,16 @@ final class Headers
'cc' => MailboxListHeader::class,
'bcc' => MailboxListHeader::class,
'message-id' => IdentificationHeader::class,
'in-reply-to' => UnstructuredHeader::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, // ... `Message-ID`, even if that is no valid `msg-id`
'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 $headers = [];
private $lineLength = 76;
private array $headers = [];
private int $lineLength = 76;
public function __construct(HeaderInterface ...$headers)
{
@@ -79,37 +79,31 @@ final class Headers
*
* @return $this
*/
public function addMailboxListHeader(string $name, array $addresses): self
public function addMailboxListHeader(string $name, array $addresses): static
{
return $this->add(new MailboxListHeader($name, Address::createArray($addresses)));
}
/**
* @param Address|string $address
*
* @return $this
*/
public function addMailboxHeader(string $name, $address): self
public function addMailboxHeader(string $name, Address|string $address): static
{
return $this->add(new MailboxHeader($name, Address::create($address)));
}
/**
* @param string|array $ids
*
* @return $this
*/
public function addIdHeader(string $name, $ids): self
public function addIdHeader(string $name, string|array $ids): static
{
return $this->add(new IdentificationHeader($name, $ids));
}
/**
* @param Address|string $path
*
* @return $this
*/
public function addPathHeader(string $name, $path): self
public function addPathHeader(string $name, Address|string $path): static
{
return $this->add(new PathHeader($name, $path instanceof Address ? $path : new Address($path)));
}
@@ -117,7 +111,7 @@ final class Headers
/**
* @return $this
*/
public function addDateHeader(string $name, \DateTimeInterface $dateTime): self
public function addDateHeader(string $name, \DateTimeInterface $dateTime): static
{
return $this->add(new DateHeader($name, $dateTime));
}
@@ -125,7 +119,7 @@ final class Headers
/**
* @return $this
*/
public function addTextHeader(string $name, string $value): self
public function addTextHeader(string $name, string $value): static
{
return $this->add(new UnstructuredHeader($name, $value));
}
@@ -133,7 +127,7 @@ final class Headers
/**
* @return $this
*/
public function addParameterizedHeader(string $name, string $value, array $params = []): self
public function addParameterizedHeader(string $name, string $value, array $params = []): static
{
return $this->add(new ParameterizedHeader($name, $value, $params));
}
@@ -141,9 +135,13 @@ final class Headers
/**
* @return $this
*/
public function addHeader(string $name, $argument, array $more = []): self
public function addHeader(string $name, mixed $argument, array $more = []): static
{
$parts = explode('\\', self::HEADER_CLASS_MAP[strtolower($name)] ?? UnstructuredHeader::class);
$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';
@@ -162,7 +160,7 @@ final class Headers
/**
* @return $this
*/
public function add(HeaderInterface $header): self
public function add(HeaderInterface $header): static
{
self::checkHeaderClass($header);
@@ -226,10 +224,22 @@ final class Headers
public static function checkHeaderClass(HeaderInterface $header): void
{
$name = strtolower($header->getName());
if (($c = self::HEADER_CLASS_MAP[$name] ?? null) && !$header instanceof $c) {
throw new LogicException(sprintf('The "%s" header must be an instance of "%s" (got "%s").', $header->getName(), $c, get_debug_type($header)));
$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
@@ -254,9 +264,6 @@ final class Headers
return $arr;
}
/**
* @internal
*/
public function getHeaderBody(string $name)
{
return $this->has($name) ? $this->get($name)->getBody() : null;
@@ -265,7 +272,7 @@ final class Headers
/**
* @internal
*/
public function setHeaderBody(string $type, string $name, $body): void
public function setHeaderBody(string $type, string $name, mixed $body): void
{
if ($this->has($name)) {
$this->get($name)->setBody($body);

View File

@@ -21,13 +21,10 @@ use Symfony\Component\Mime\Exception\RfcComplianceException;
*/
final class IdentificationHeader extends AbstractHeader
{
private $ids = [];
private $idsAsAddresses = [];
private array $ids = [];
private array $idsAsAddresses = [];
/**
* @param string|array $ids
*/
public function __construct(string $name, $ids)
public function __construct(string $name, string|array $ids)
{
parent::__construct($name);
@@ -35,11 +32,11 @@ final class IdentificationHeader extends AbstractHeader
}
/**
* @param string|array $body a string ID or an array of IDs
* @param string|string[] $body a string ID or an array of IDs
*
* @throws RfcComplianceException
*/
public function setBody($body)
public function setBody(mixed $body)
{
$this->setId($body);
}
@@ -52,11 +49,11 @@ final class IdentificationHeader extends AbstractHeader
/**
* Set the ID used in the value of this header.
*
* @param string|array $id
* @param string|string[] $id
*
* @throws RfcComplianceException
*/
public function setId($id)
public function setId(string|array $id)
{
$this->setIds(\is_array($id) ? $id : [$id]);
}

View File

@@ -21,7 +21,7 @@ use Symfony\Component\Mime\Exception\RfcComplianceException;
*/
final class MailboxHeader extends AbstractHeader
{
private $address;
private Address $address;
public function __construct(string $name, Address $address)
{
@@ -35,7 +35,7 @@ final class MailboxHeader extends AbstractHeader
*
* @throws RfcComplianceException
*/
public function setBody($body)
public function setBody(mixed $body)
{
$this->setAddress($body);
}

View File

@@ -21,7 +21,7 @@ use Symfony\Component\Mime\Exception\RfcComplianceException;
*/
final class MailboxListHeader extends AbstractHeader
{
private $addresses = [];
private array $addresses = [];
/**
* @param Address[] $addresses
@@ -38,15 +38,15 @@ final class MailboxListHeader extends AbstractHeader
*
* @throws RfcComplianceException
*/
public function setBody($body)
public function setBody(mixed $body)
{
$this->setAddresses($body);
}
/**
* @throws RfcComplianceException
*
* @return Address[]
*
* @throws RfcComplianceException
*/
public function getBody(): array
{
@@ -99,9 +99,9 @@ final class MailboxListHeader extends AbstractHeader
/**
* Gets the full mailbox list of this Header as an array of valid RFC 2822 strings.
*
* @throws RfcComplianceException
*
* @return string[]
*
* @throws RfcComplianceException
*/
public function getAddressStrings(): array
{

View File

@@ -25,8 +25,8 @@ final class ParameterizedHeader extends UnstructuredHeader
*/
public const TOKEN_REGEX = '(?:[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+)';
private $encoder;
private $parameters = [];
private ?Rfc2231Encoder $encoder = null;
private array $parameters = [];
public function __construct(string $name, string $value, array $parameters = [])
{

View File

@@ -21,7 +21,7 @@ use Symfony\Component\Mime\Exception\RfcComplianceException;
*/
final class PathHeader extends AbstractHeader
{
private $address;
private Address $address;
public function __construct(string $name, Address $address)
{
@@ -35,7 +35,7 @@ final class PathHeader extends AbstractHeader
*
* @throws RfcComplianceException
*/
public function setBody($body)
public function setBody(mixed $body)
{
$this->setAddress($body);
}

View File

@@ -18,7 +18,7 @@ namespace Symfony\Component\Mime\Header;
*/
class UnstructuredHeader extends AbstractHeader
{
private $value;
private string $value;
public function __construct(string $name, string $value)
{
@@ -30,15 +30,12 @@ class UnstructuredHeader extends AbstractHeader
/**
* @param string $body
*/
public function setBody($body)
public function setBody(mixed $body)
{
$this->setValue($body);
}
/**
* @return string
*/
public function getBody()
public function getBody(): string
{
return $this->getValue();
}

View 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));
}
}

View 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;
}

View 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);
}
}

View File

@@ -21,8 +21,8 @@ use Symfony\Component\Mime\Part\TextPart;
*/
class Message extends RawMessage
{
private $headers;
private $body;
private Headers $headers;
private ?AbstractPart $body;
public function __construct(Headers $headers = null, AbstractPart $body = null)
{
@@ -42,8 +42,11 @@ class Message extends RawMessage
/**
* @return $this
*/
public function setBody(AbstractPart $body = null)
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;
@@ -57,7 +60,7 @@ class Message extends RawMessage
/**
* @return $this
*/
public function setHeaders(Headers $headers)
public function setHeaders(Headers $headers): static
{
$this->headers = $headers;

View File

@@ -58,7 +58,7 @@ final class MessageConverter
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::attachParts($email, \array_slice($parts, 1));
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)));
@@ -104,20 +104,17 @@ final class MessageConverter
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::attachParts($email, \array_slice($parts, 1));
return self::addParts($email, \array_slice($parts, 1));
}
private static function attachParts(Email $email, array $parts): Email
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)));
}
$headers = $part->getPreparedHeaders();
$method = 'inline' === $headers->getHeaderBody('Content-Disposition') ? 'embed' : 'attach';
$name = $headers->getHeaderParameter('Content-Disposition', 'filename');
$email->$method($part->getBody(), $name, $part->getMediaType().'/'.$part->getMediaSubtype());
$email->addPart($part);
}
return $email;

View File

@@ -36,14 +36,14 @@ use Symfony\Component\Mime\Exception\LogicException;
*/
final class MimeTypes implements MimeTypesInterface
{
private $extensions = [];
private $mimeTypes = [];
private array $extensions = [];
private array $mimeTypes = [];
/**
* @var MimeTypeGuesserInterface[]
*/
private $guessers = [];
private static $default;
private array $guessers = [];
private static MimeTypes $default;
public function __construct(array $map = [])
{
@@ -65,7 +65,7 @@ final class MimeTypes implements MimeTypesInterface
public static function getDefault(): self
{
return self::$default ?? self::$default = new self();
return self::$default ??= new self();
}
/**
@@ -78,9 +78,6 @@ final class MimeTypes implements MimeTypesInterface
array_unshift($this->guessers, $guesser);
}
/**
* {@inheritdoc}
*/
public function getExtensions(string $mimeType): array
{
if ($this->extensions) {
@@ -90,9 +87,6 @@ final class MimeTypes implements MimeTypesInterface
return $extensions ?? self::MAP[$mimeType] ?? self::MAP[$lcMimeType ?? strtolower($mimeType)] ?? [];
}
/**
* {@inheritdoc}
*/
public function getMimeTypes(string $ext): array
{
if ($this->mimeTypes) {
@@ -102,9 +96,6 @@ final class MimeTypes implements MimeTypesInterface
return $mimeTypes ?? self::REVERSE_MAP[$ext] ?? self::REVERSE_MAP[$lcExt ?? strtolower($ext)] ?? [];
}
/**
* {@inheritdoc}
*/
public function isGuesserSupported(): bool
{
foreach ($this->guessers as $guesser) {
@@ -117,8 +108,6 @@ final class MimeTypes implements MimeTypesInterface
}
/**
* {@inheritdoc}
*
* The file is passed to each registered MIME type guesser in reverse order
* of their registration (last registered is queried first). Once a guesser
* returns a value that is not null, this method terminates and returns the

View File

@@ -90,10 +90,6 @@ abstract class AbstractMultipartPart extends AbstractPart
private function getBoundary(): string
{
if (null === $this->boundary) {
$this->boundary = strtr(base64_encode(random_bytes(6)), '+/', '-_');
}
return $this->boundary;
return $this->boundary ??= strtr(base64_encode(random_bytes(6)), '+/', '-_');
}
}

View File

@@ -13,7 +13,6 @@ namespace Symfony\Component\Mime\Part;
use Symfony\Component\Mime\Exception\InvalidArgumentException;
use Symfony\Component\Mime\Header\Headers;
use Symfony\Component\Mime\MimeTypes;
/**
* @author Fabien Potencier <fabien@symfony.com>
@@ -23,23 +22,22 @@ class DataPart extends TextPart
/** @internal */
protected $_parent;
private static $mimeTypes;
private $filename;
private $mediaType;
private $cid;
private $handle;
/**
* @param resource|string $body
* @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)
{
unset($this->_parent);
if (null === $contentType) {
$contentType = 'application/octet-stream';
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);
@@ -53,42 +51,31 @@ class DataPart extends TextPart
public static function fromPath(string $path, string $name = null, string $contentType = null): self
{
if (null === $contentType) {
$ext = strtolower(substr($path, strrpos($path, '.') + 1));
if (null === self::$mimeTypes) {
self::$mimeTypes = new MimeTypes();
}
$contentType = self::$mimeTypes->getMimeTypes($ext)[0] ?? 'application/octet-stream';
}
if ((is_file($path) && !is_readable($path)) || is_dir($path)) {
throw new InvalidArgumentException(sprintf('Path "%s" is not readable.', $path));
}
if (false === $handle = @fopen($path, 'r', false)) {
throw new InvalidArgumentException(sprintf('Unable to open path "%s".', $path));
}
if (!is_file($path)) {
$cache = fopen('php://temp', 'r+');
stream_copy_to_stream($handle, $cache);
$handle = $cache;
}
$p = new self($handle, $name ?: basename($path), $contentType);
$p->handle = $handle;
return $p;
return new self(new File($path), $name, $contentType);
}
/**
* @return $this
*/
public function asInline()
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();
@@ -129,22 +116,22 @@ class DataPart extends TextPart
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 __destruct()
{
if (null !== $this->handle && \is_resource($this->handle)) {
fclose($this->handle);
}
}
/**
* @return array
*/
public function __sleep()
public function __sleep(): array
{
// converts the body to a string
parent::__sleep();
@@ -152,7 +139,6 @@ class DataPart extends TextPart
$this->_parent = [];
foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) {
$r = new \ReflectionProperty(TextPart::class, $name);
$r->setAccessible(true);
$this->_parent[$name] = $r->getValue($this);
}
$this->_headers = $this->getHeaders();
@@ -163,7 +149,6 @@ class DataPart extends TextPart
public function __wakeup()
{
$r = new \ReflectionProperty(AbstractPart::class, 'headers');
$r->setAccessible(true);
$r->setValue($this, $this->_headers);
unset($this->_headers);
@@ -175,7 +160,6 @@ class DataPart extends TextPart
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
$r = new \ReflectionProperty(TextPart::class, $name);
$r->setAccessible(true);
$r->setValue($this, $this->_parent[$name]);
}
unset($this->_parent);

51
vendor/symfony/mime/Part/File.php vendored Normal file
View 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;
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());
}
}

View File

@@ -60,10 +60,7 @@ class MessagePart extends DataPart
return $this->message->toIterable();
}
/**
* @return array
*/
public function __sleep()
public function __sleep(): array
{
return ['message'];
}

View File

@@ -34,7 +34,7 @@ final class FormDataPart extends AbstractMultipartPart
foreach ($fields as $name => $value) {
if (!\is_string($value) && !\is_array($value) && !$value instanceof TextPart) {
throw new InvalidArgumentException(sprintf('A form field value can only be a string, an array, or an instance of TextPart ("%s" given).', get_debug_type($value)));
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).', $name, get_debug_type($value)));
}
$this->fields[$name] = $value;
@@ -83,7 +83,7 @@ final class FormDataPart extends AbstractMultipartPart
return $values;
}
private function preparePart(string $name, $value): TextPart
private function preparePart(string $name, string|TextPart $value): TextPart
{
if (\is_string($value)) {
return $this->configurePart($name, new TextPart($value, 'utf-8', 'plain', '8bit'));
@@ -96,10 +96,7 @@ final class FormDataPart extends AbstractMultipartPart
{
static $r;
if (null === $r) {
$r = new \ReflectionProperty(TextPart::class, 'encoding');
$r->setAccessible(true);
}
$r ??= new \ReflectionProperty(TextPart::class, 'encoding');
$part->setDisposition('form-data');
$part->setName($name);

View File

@@ -26,19 +26,12 @@ class SMimePart extends AbstractPart
private $subtype;
private $parameters;
/**
* @param iterable|string $body
*/
public function __construct($body, string $type, string $subtype, array $parameters)
public function __construct(iterable|string $body, string $type, string $subtype, array $parameters)
{
unset($this->_headers);
parent::__construct();
if (!\is_string($body) && !is_iterable($body)) {
throw new \TypeError(sprintf('The body of "%s" must be a string or a iterable (got "%s").', self::class, get_debug_type($body)));
}
$this->body = $body;
$this->type = $type;
$this->subtype = $subtype;
@@ -114,7 +107,6 @@ class SMimePart extends AbstractPart
public function __wakeup(): void
{
$r = new \ReflectionProperty(AbstractPart::class, 'headers');
$r->setAccessible(true);
$r->setValue($this, $this->_headers);
unset($this->_headers);
}

View File

@@ -40,7 +40,7 @@ class TextPart extends AbstractPart
private $seekable;
/**
* @param resource|string $body
* @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)
{
@@ -48,8 +48,15 @@ class TextPart extends AbstractPart
parent::__construct();
if (!\is_string($body) && !\is_resource($body)) {
throw new \TypeError(sprintf('The body of "%s" must be a string or a resource (got "%s").', self::class, get_debug_type($body)));
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;
@@ -82,7 +89,7 @@ class TextPart extends AbstractPart
*
* @return $this
*/
public function setDisposition(string $disposition)
public function setDisposition(string $disposition): static
{
$this->disposition = $disposition;
@@ -94,15 +101,27 @@ class TextPart extends AbstractPart
*
* @return $this
*/
public function setName(string $name)
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;
}
@@ -121,7 +140,14 @@ class TextPart extends AbstractPart
public function bodyToIterable(): iterable
{
if (null !== $this->seekable) {
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);
}
@@ -170,14 +196,14 @@ class TextPart extends AbstractPart
private function getEncoder(): ContentEncoderInterface
{
if ('8bit' === $this->encoding) {
return self::$encoders[$this->encoding] ?? (self::$encoders[$this->encoding] = new EightBitContentEncoder());
return self::$encoders[$this->encoding] ??= new EightBitContentEncoder();
}
if ('quoted-printable' === $this->encoding) {
return self::$encoders[$this->encoding] ?? (self::$encoders[$this->encoding] = new QpContentEncoder());
return self::$encoders[$this->encoding] ??= new QpContentEncoder();
}
return self::$encoders[$this->encoding] ?? (self::$encoders[$this->encoding] = new Base64ContentEncoder());
return self::$encoders[$this->encoding] ??= new Base64ContentEncoder();
}
private function chooseEncoding(): string
@@ -189,13 +215,10 @@ class TextPart extends AbstractPart
return 'quoted-printable';
}
/**
* @return array
*/
public function __sleep()
public function __sleep(): array
{
// convert resources to strings for serialization
if (null !== $this->seekable) {
if (null !== $this->seekable || $this->body instanceof File) {
$this->body = $this->getBody();
$this->seekable = null;
}
@@ -208,7 +231,6 @@ class TextPart extends AbstractPart
public function __wakeup()
{
$r = new \ReflectionProperty(AbstractPart::class, 'headers');
$r->setAccessible(true);
$r->setValue($this, $this->_headers);
unset($this->_headers);
}

View File

@@ -16,14 +16,11 @@ use Symfony\Component\Mime\Exception\LogicException;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class RawMessage implements \Serializable
class RawMessage
{
private $message;
/**
* @param iterable|string $message
*/
public function __construct($message)
public function __construct(iterable|string $message)
{
$this->message = $message;
}
@@ -63,22 +60,6 @@ class RawMessage implements \Serializable
{
}
/**
* @internal
*/
final public function serialize(): string
{
return serialize($this->__serialize());
}
/**
* @internal
*/
final public function unserialize($serialized)
{
$this->__unserialize(unserialize($serialized));
}
public function __serialize(): array
{
return [$this->toString()];

View File

@@ -18,8 +18,8 @@ use Symfony\Component\Mime\RawMessage;
final class EmailAddressContains extends Constraint
{
private $headerName;
private $expectedValue;
private string $headerName;
private string $expectedValue;
public function __construct(string $headerName, string $expectedValue)
{
@@ -27,9 +27,6 @@ final class EmailAddressContains extends Constraint
$this->expectedValue = $expectedValue;
}
/**
* {@inheritdoc}
*/
public function toString(): string
{
return sprintf('contains address "%s" with value "%s"', $this->headerName, $this->expectedValue);
@@ -37,12 +34,10 @@ final class EmailAddressContains extends Constraint
/**
* @param RawMessage $message
*
* {@inheritdoc}
*/
protected function matches($message): bool
{
if (RawMessage::class === \get_class($message)) {
if (RawMessage::class === $message::class) {
throw new \LogicException('Unable to test a message address on a RawMessage instance.');
}
@@ -64,8 +59,6 @@ final class EmailAddressContains extends Constraint
/**
* @param RawMessage $message
*
* {@inheritdoc}
*/
protected function failureDescription($message): string
{

View File

@@ -17,8 +17,8 @@ use Symfony\Component\Mime\RawMessage;
final class EmailAttachmentCount extends Constraint
{
private $expectedValue;
private $transport;
private int $expectedValue;
private ?string $transport;
public function __construct(int $expectedValue, string $transport = null)
{
@@ -26,9 +26,6 @@ final class EmailAttachmentCount extends Constraint
$this->transport = $transport;
}
/**
* {@inheritdoc}
*/
public function toString(): string
{
return sprintf('has sent "%d" attachment(s)', $this->expectedValue);
@@ -36,12 +33,10 @@ final class EmailAttachmentCount extends Constraint
/**
* @param RawMessage $message
*
* {@inheritdoc}
*/
protected function matches($message): bool
{
if (RawMessage::class === \get_class($message) || Message::class === \get_class($message)) {
if (RawMessage::class === $message::class || Message::class === $message::class) {
throw new \LogicException('Unable to test a message attachment on a RawMessage or Message instance.');
}
@@ -50,8 +45,6 @@ final class EmailAttachmentCount extends Constraint
/**
* @param RawMessage $message
*
* {@inheritdoc}
*/
protected function failureDescription($message): string
{

View File

@@ -16,16 +16,13 @@ use Symfony\Component\Mime\RawMessage;
final class EmailHasHeader extends Constraint
{
private $headerName;
private string $headerName;
public function __construct(string $headerName)
{
$this->headerName = $headerName;
}
/**
* {@inheritdoc}
*/
public function toString(): string
{
return sprintf('has header "%s"', $this->headerName);
@@ -33,12 +30,10 @@ final class EmailHasHeader extends Constraint
/**
* @param RawMessage $message
*
* {@inheritdoc}
*/
protected function matches($message): bool
{
if (RawMessage::class === \get_class($message)) {
if (RawMessage::class === $message::class) {
throw new \LogicException('Unable to test a message header on a RawMessage instance.');
}
@@ -47,8 +42,6 @@ final class EmailHasHeader extends Constraint
/**
* @param RawMessage $message
*
* {@inheritdoc}
*/
protected function failureDescription($message): string
{

View File

@@ -17,8 +17,8 @@ use Symfony\Component\Mime\RawMessage;
final class EmailHeaderSame extends Constraint
{
private $headerName;
private $expectedValue;
private string $headerName;
private string $expectedValue;
public function __construct(string $headerName, string $expectedValue)
{
@@ -26,9 +26,6 @@ final class EmailHeaderSame extends Constraint
$this->expectedValue = $expectedValue;
}
/**
* {@inheritdoc}
*/
public function toString(): string
{
return sprintf('has header "%s" with value "%s"', $this->headerName, $this->expectedValue);
@@ -36,12 +33,10 @@ final class EmailHeaderSame extends Constraint
/**
* @param RawMessage $message
*
* {@inheritdoc}
*/
protected function matches($message): bool
{
if (RawMessage::class === \get_class($message)) {
if (RawMessage::class === $message::class) {
throw new \LogicException('Unable to test a message header on a RawMessage instance.');
}
@@ -50,8 +45,6 @@ final class EmailHeaderSame extends Constraint
/**
* @param RawMessage $message
*
* {@inheritdoc}
*/
protected function failureDescription($message): string
{

View File

@@ -17,38 +17,31 @@ use Symfony\Component\Mime\RawMessage;
final class EmailHtmlBodyContains extends Constraint
{
private $expectedText;
private string $expectedText;
public function __construct(string $expectedText)
{
$this->expectedText = $expectedText;
}
/**
* {@inheritdoc}
*/
public function toString(): string
{
return sprintf('contains "%s"', $this->expectedText);
}
/**
* {@inheritdoc}
*
* @param RawMessage $message
*/
protected function matches($message): bool
{
if (RawMessage::class === \get_class($message) || Message::class === \get_class($message)) {
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 false !== mb_strpos($message->getHtmlBody(), $this->expectedText);
return str_contains($message->getHtmlBody(), $this->expectedText);
}
/**
* {@inheritdoc}
*
* @param RawMessage $message
*/
protected function failureDescription($message): string

View File

@@ -17,38 +17,31 @@ use Symfony\Component\Mime\RawMessage;
final class EmailTextBodyContains extends Constraint
{
private $expectedText;
private string $expectedText;
public function __construct(string $expectedText)
{
$this->expectedText = $expectedText;
}
/**
* {@inheritdoc}
*/
public function toString(): string
{
return sprintf('contains "%s"', $this->expectedText);
}
/**
* {@inheritdoc}
*
* @param RawMessage $message
*/
protected function matches($message): bool
{
if (RawMessage::class === \get_class($message) || Message::class === \get_class($message)) {
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 false !== mb_strpos($message->getTextBody(), $this->expectedText);
return str_contains($message->getTextBody(), $this->expectedText);
}
/**
* {@inheritdoc}
*
* @param RawMessage $message
*/
protected function failureDescription($message): string

View File

@@ -16,26 +16,25 @@
}
],
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1|^3",
"php": ">=8.1",
"symfony/polyfill-intl-idn": "^1.10",
"symfony/polyfill-mbstring": "^1.0",
"symfony/polyfill-php80": "^1.16"
"symfony/polyfill-mbstring": "^1.0"
},
"require-dev": {
"egulias/email-validator": "^2.1.10|^3.1",
"league/html-to-markdown": "^5.0",
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
"symfony/dependency-injection": "^4.4|^5.0|^6.0",
"symfony/property-access": "^4.4|^5.1|^6.0",
"symfony/property-info": "^4.4|^5.1|^6.0",
"symfony/serializer": "^5.4.14|~6.0.14|^6.1.6"
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/property-access": "^5.4|^6.0",
"symfony/property-info": "^5.4|^6.0",
"symfony/serializer": "^6.2"
},
"conflict": {
"egulias/email-validator": "~3.0.0",
"phpdocumentor/reflection-docblock": "<3.2.2",
"phpdocumentor/type-resolver": "<1.4.0",
"symfony/mailer": "<4.4",
"symfony/serializer": "<5.4.14|>=6.0,<6.0.14|>=6.1,<6.1.6"
"symfony/mailer": "<5.4",
"symfony/serializer": "<6.2"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Mime\\": "" },