resolved conflicts

This commit is contained in:
2025-03-31 03:46:05 +03:00
6454 changed files with 1520539 additions and 9 deletions
@@ -0,0 +1,302 @@
<?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;
}
foreach ($tokens as $i => $token) {
// whitespace(s) between 2 encoded tokens
if (
0 < $i
&& isset($tokens[$i + 1])
&& preg_match('~^[\t ]+$~', $token)
&& $this->tokenNeedsEncoding($tokens[$i - 1])
&& $this->tokenNeedsEncoding($tokens[$i + 1])
) {
$tokens[$i - 1] .= $token.$tokens[$i + 1];
array_splice($tokens, $i, 2);
}
}
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);
}
}
+169
View 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->get('To')?->getBody() && !$this->headers->get('Cc')?->getBody() && !$this->headers->get('Bcc')?->getBody()) {
throw new LogicException('An email must have a "To", "Cc", or "Bcc" header.');
}
if (!$this->headers->get('From')?->getBody() && !$this->headers->get('Sender')?->getBody()) {
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;
}
}
@@ -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|TextPart> $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;
}
}
@@ -0,0 +1,248 @@
<?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) {
if (false === $ret = @file_get_contents($this->body->getPath())) {
throw new InvalidArgumentException(error_get_last()['message']);
}
return $ret;
}
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);
}
}
+115
View File
@@ -0,0 +1,115 @@
<?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
{
/** @var iterable<string>|string|resource */
private $message;
private bool $isGeneratorClosed;
/**
* @param iterable<string>|string|resource $message
*/
public function __construct(mixed $message)
{
$this->message = $message;
}
public function __destruct()
{
if (\is_resource($this->message)) {
fclose($this->message);
}
}
public function toString(): string
{
if (\is_string($this->message)) {
return $this->message;
}
if (\is_resource($this->message)) {
return stream_get_contents($this->message, -1, 0);
}
$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 (\is_resource($this->message)) {
rewind($this->message);
while ($line = fgets($this->message)) {
yield $line;
}
return;
}
if ($this->message instanceof \Generator) {
$message = fopen('php://temp', 'w+');
foreach ($this->message as $chunk) {
fwrite($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;
}
}
+48
View 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.4.3|^7.0.3"
},
"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.4.3|>7.0,<7.0.3"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Mime\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
"minimum-stability": "dev"
}