Bump imapengine from 1.25.4 to 1.25.6 and dependencies

This commit is contained in:
johnnyq
2026-08-26 13:02:29 -04:00
parent 71cfdf5e39
commit 3d9a41bec4
54 changed files with 978 additions and 543 deletions

View File

@@ -54,7 +54,7 @@ final class Address
throw new InvalidArgumentException('Email address contains control characters.');
}
if (!self::$validator->isValid($this->address, class_exists(MessageIDValidation::class) ? new MessageIDValidation() : new RFCValidation())) {
if (!self::isValidAddrSpec($this->address)) {
throw new RfcComplianceException(\sprintf('Email "%s" does not comply with addr-spec of RFC 2822.', $address));
}
}
@@ -87,7 +87,7 @@ final class Address
return '';
}
return \sprintf('"%s"', preg_replace('/"/u', '\"', $this->getName()));
return \sprintf('"%s"', preg_replace('/["\\\\]/', '\\\\$0', $this->getName()));
}
public static function create(self|string $address): self
@@ -141,4 +141,19 @@ final class Address
{
return (bool) preg_match('/[\x80-\xFF].*@/', $this->address);
}
private static function isValidAddrSpec(string $address): bool
{
// the message id validation is needed as this class also holds the ids of the Message-ID,
// In-Reply-To and References headers, but it accepts an unquoted "@" in the local part
if (!self::$validator->isValid($address, class_exists(MessageIDValidation::class) ? new MessageIDValidation() : new RFCValidation())) {
return false;
}
if (substr_count($address, '@') < 2) {
return true;
}
return self::$validator->isValid(substr($address, 0, strrpos($address, '@')).'@example.com', new RFCValidation());
}
}

View File

@@ -482,6 +482,7 @@ class Email extends Message
}
$otherParts = $relatedParts = [];
$cidReplacements = [];
foreach ($this->attachments as $part) {
foreach ($names as $name) {
if ($name !== $part->getName() && (!$part->hasContentId() || $name !== $part->getContentId())) {
@@ -491,9 +492,7 @@ class Email extends Message
continue 2;
}
if ($name !== $part->getContentId()) {
$html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html);
}
$cidReplacements['cid:'.$name] = 'cid:'.$part->getContentId();
$relatedParts[$name] = $part;
$part->setName($part->getName() ?? $part->getContentId())->asInline();
@@ -502,6 +501,11 @@ class Email extends Message
$otherParts[] = $part;
}
if ($cidReplacements) {
// all references are replaced at once as strtr() matches the longest name first and
// never replaces inside already substituted text, unlike successive str_replace() calls
$html = strtr($html, $cidReplacements);
}
if (null !== $htmlPart) {
$htmlPart = new TextPart($html, $this->htmlCharset, 'html');
}

View File

@@ -169,7 +169,7 @@ final class Headers
$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) {
if (\in_array($name, self::UNIQUE_HEADERS, true) && isset($this->headers[$name]) && $this->headers[$name]) {
throw new LogicException(\sprintf('Impossible to set header "%s" as it\'s already defined and must be unique.', $header->getName()));
}

View File

@@ -67,7 +67,7 @@ class RawMessage
if (\is_resource($this->message)) {
rewind($this->message);
while ($line = fgets($this->message)) {
while (false !== $line = fgets($this->message)) {
yield $line;
}

View File

@@ -29,7 +29,7 @@
"symfony/process": "^6.4|^7.0|^8.0",
"symfony/property-access": "^6.4|^7.0|^8.0",
"symfony/property-info": "^6.4|^7.0|^8.0",
"symfony/serializer": "^6.4.3|^7.0.3|^8.0"
"symfony/serializer": "^6.4.44|^7.4.17|^8.1.5"
},
"conflict": {
"egulias/email-validator": "~3.0.0",

View File

@@ -61,6 +61,21 @@ final class Idn
public const DELIMITER = '-';
public const MAX_INT = 2147483647;
/**
* Punycode decoding does work quadratic in the payload length. Valid ACE
* labels are limited to 63 bytes, so payloads beyond this (generous) bound
* are rejected without being decoded to keep the work bounded.
*/
private const MAX_DECODE_PAYLOAD_SIZE = 1024;
/**
* Encoding is quadratic the same way: it is O(n*r) in the length of a label
* and in its number of distinct code points. ext-intl never runs it on a
* domain this large, because it fails before converting when the result
* would not fit its own output buffer.
*/
private const MAX_CODE_POINTS = 255;
/**
* Contains the numeric value of a basic code point (for use in representing integers) in the
* range 0 to BASE-1, or -1 if b is does not represent a value.
@@ -153,6 +168,14 @@ final class Idn
@trigger_error('idn_to_ascii(): INTL_IDNA_VARIANT_2003 is deprecated', \E_USER_DEPRECATED);
}
// The ASCII form is at least as long as the number of code points in the
// domain, so beyond this many code points no result can fit in the output
// buffer ext-intl uses: it returns false there without reporting details,
// and so do we, rather than Punycode-encoding a label that cannot be used.
if (self::MAX_CODE_POINTS < \strlen((string) $domainName) && self::MAX_CODE_POINTS < self::countCodePoints((string) $domainName)) {
return false;
}
$options = [
'CheckHyphens' => true,
'CheckBidi' => self::INTL_IDNA_VARIANT_2003 === $variant || 0 !== ($options & self::IDNA_CHECK_BIDI),
@@ -358,6 +381,16 @@ final class Idn
continue;
}
// The decoder does work quadratic in the payload length. Valid
// labels are at most 63 bytes long, so a payload beyond this
// bound is always invalid input: reject it without decoding,
// like ext-intl does, to avoid spending unbounded time on it.
if (\strlen($label) - 4 > self::MAX_DECODE_PAYLOAD_SIZE) {
$info->errors |= self::ERROR_PUNYCODE;
continue;
}
// Step 4.2. Attempt to convert the rest of the label to Unicode according to Punycode [RFC3492]. If
// that conversion fails, record that there was an error, and continue
// with the next label. Otherwise replace the original label in the string by the results of the
@@ -766,6 +799,24 @@ final class Idn
return $output;
}
/**
* Counts the code points of a UTF-8 string, malformed bytes included.
*
* This is the expression Mbstring::mb_strlen() falls back to in
* symfony/polyfill-mbstring, inlined because this package does not depend on it.
* Counting only the bytes that can start a sequence would be shorter, but then a
* run of continuation bytes would pad a label without being counted, which is the
* one thing this bound has to prevent.
*
* @param string $input
*
* @return int
*/
private static function countCodePoints($input)
{
return preg_match_all('/[\x00-\x7F]|[\xC0-\xDF][\x80-\xBF]?|[\xE0-\xEF][\x80-\xBF]{0,2}|[\xF0-\xF7][\x80-\xBF]{0,3}|[\xF8-\xFB][\x80-\xBF]{0,4}|[\xFC-\xFD][\x80-\xBF]{0,5}|[\x80-\xBF\xFE\xFF]/s', $input);
}
/**
* @see https://tools.ietf.org/html/rfc3492#section-6.1
*

View File

@@ -129,7 +129,8 @@ class Normalizer
return false;
}
throw new \ValueError('normalizer_normalize(): Argument #2 ($form) must be a a valid normalization form');
// the doubled article was fixed in PHP 8.6
throw new \ValueError('normalizer_normalize(): Argument #2 ($form) must be a '.(80600 > \PHP_VERSION_ID ? 'a ' : '').'valid normalization form');
}
if ('' === $s) {

View File

@@ -153,7 +153,7 @@ class XliffLintCommand extends Command
libxml_clear_errors();
libxml_use_internal_errors($internal);
return ['file' => $file, 'valid' => 0 === \count($errors), 'messages' => $errors];
return ['file' => $file, 'valid' => !$errors, 'messages' => $errors];
}
private function display(SymfonyStyle $io, array $files): int

View File

@@ -30,6 +30,8 @@ class PoFileDumper extends FileDumper
$output .= "\n";
$newLine = false;
$isIntlDomain = str_ends_with($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX);
foreach ($messages->all($domain) as $source => $target) {
if ($newLine) {
$output .= "\n";
@@ -48,9 +50,10 @@ class PoFileDumper extends FileDumper
$output .= $this->formatComments(implode(' ', (array) $metadata['sources']), ':');
}
$sourceRules = $this->getStandardRules($source);
$targetRules = $this->getStandardRules($target);
if (2 == \count($sourceRules) && [] !== $targetRules) {
// in an ICU domain the pipe is an ordinary character, pluralization is expressed by the message itself
$sourceRules = $isIntlDomain ? [] : $this->getStandardRules($source);
$targetRules = $isIntlDomain ? [] : $this->getStandardRules($target);
if (2 == \count($sourceRules) && $targetRules) {
$output .= \sprintf('msgid "%s"'."\n", $this->escape($sourceRules[0]));
$output .= \sprintf('msgid_plural "%s"'."\n", $this->escape($sourceRules[1]));
foreach ($targetRules as $i => $targetRule) {

View File

@@ -13,6 +13,7 @@ namespace Symfony\Component\Translation\Extractor\Visitor;
use PhpParser\Node;
use PhpParser\NodeVisitor;
use Symfony\Component\Translation\TranslatableMessage;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
@@ -39,7 +40,9 @@ final class TranslatableMessageVisitor extends AbstractVisitor implements NodeVi
return null;
}
if (!\in_array('TranslatableMessage', $className->getParts(), true)) {
// the name resolver gives a fully qualified name; templates without a "use"
// statement resolve to the global namespace, which is accepted as well
if (!\in_array($className->toString(), [TranslatableMessage::class, 'TranslatableMessage'], true)) {
return null;
}

View File

@@ -31,23 +31,19 @@ class CsvFileLoader extends FileLoader
{
$messages = [];
try {
$file = new \SplFileObject($resource, 'rb');
} catch (\RuntimeException $e) {
throw new NotFoundResourceException(\sprintf('Error opening file "%s".', $resource), 0, $e);
if (!$file = @fopen($resource, 'r')) {
throw new NotFoundResourceException(\sprintf('Error opening file "%s".', $resource));
}
$file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY | \SplFileObject::DROP_NEW_LINE);
$file->setCsvControl($this->delimiter, $this->enclosure, $this->escape);
foreach ($file as $data) {
if (false === $data) {
continue;
}
if (!str_starts_with($data[0], '#') && isset($data[1]) && 2 === \count($data)) {
$messages[$data[0]] = $data[1];
try {
while (false !== $data = fgetcsv($file, null, $this->delimiter, $this->enclosure, $this->escape)) {
// empty lines are read as [null]
if (isset($data[1]) && 2 === \count($data) && !str_starts_with($data[0], '#')) {
$messages[$data[0]] = $data[1];
}
}
} finally {
fclose($file);
}
return $messages;

View File

@@ -55,6 +55,7 @@ class PoFileLoader extends FileLoader
* - No support for comments spanning multiple lines.
* - Translator and extracted comments are treated as being the same type.
* - Message IDs are allowed to have other encodings as just US-ASCII.
* - Contexts (msgctxt) are parsed but discarded.
*
* Items with an empty id are ignored.
*/
@@ -65,6 +66,7 @@ class PoFileLoader extends FileLoader
$defaults = [
'ids' => [],
'translated' => null,
'context' => null,
];
$messages = [];
@@ -76,23 +78,28 @@ class PoFileLoader extends FileLoader
if ('' === $line) {
// Whitespace indicated current item is done
if (!\in_array('fuzzy', $flags, true)) {
$this->addMessage($messages, $item);
}
$item = $defaults;
$flags = [];
$this->saveItem($messages, $item, $flags, $defaults);
} elseif (str_starts_with($line, '#,')) {
// flags belong to the next entry, so the previous one ends here
if (null !== $item['translated']) {
$this->saveItem($messages, $item, $flags, $defaults);
}
$flags = array_map('trim', explode(',', substr($line, 2)));
} elseif (str_starts_with($line, 'msgctxt "')) {
if (null !== $item['translated']) {
$this->saveItem($messages, $item, $flags, $defaults);
}
$item['context'] = substr($line, 9, -1);
} elseif (str_starts_with($line, 'msgid "')) {
// We start a new msg so save previous
// TODO: this fails when comments or contexts are added
$this->addMessage($messages, $item);
$item = $defaults;
if ($item['ids']) {
$this->saveItem($messages, $item, $flags, $defaults);
}
$item['ids']['singular'] = substr($line, 7, -1);
} elseif (str_starts_with($line, 'msgstr "')) {
$item['translated'] = substr($line, 8, -1);
} elseif ('"' === $line[0]) {
$continues = isset($item['translated']) ? 'translated' : 'ids';
$continues = isset($item['translated']) ? 'translated' : ($item['ids'] ? 'ids' : 'context');
if (\is_array($item[$continues])) {
end($item[$continues]);
@@ -108,14 +115,21 @@ class PoFileLoader extends FileLoader
}
}
// save last item
if (!\in_array('fuzzy', $flags, true)) {
$this->addMessage($messages, $item);
}
$this->saveItem($messages, $item, $flags, $defaults);
fclose($stream);
return $messages;
}
private function saveItem(array &$messages, array &$item, array &$flags, array $defaults): void
{
if (!\in_array('fuzzy', $flags, true)) {
$this->addMessage($messages, $item);
}
$item = $defaults;
$flags = [];
}
/**
* Save a translation item to the messages.
*

View File

@@ -218,7 +218,7 @@ function printTable($translations, $verboseOutput, bool $includeCompletedLanguag
if ($translation['translated'] > $translation['total']) {
textColorRed();
} elseif (count($translation['mismatches']) > 0) {
} elseif ($translation['mismatches']) {
textColorRed();
} elseif ($translation['is_completed']) {
textColorGreen();
@@ -235,7 +235,7 @@ function printTable($translations, $verboseOutput, bool $includeCompletedLanguag
textColorNormal();
$shouldBeClosed = false;
if (true === $verboseOutput && count($translation['missingKeys']) > 0) {
if ($verboseOutput && $translation['missingKeys']) {
echo '| Missing Translations:'.\PHP_EOL;
foreach ($translation['missingKeys'] as $id => $content) {
@@ -243,7 +243,7 @@ function printTable($translations, $verboseOutput, bool $includeCompletedLanguag
}
$shouldBeClosed = true;
}
if (true === $verboseOutput && count($translation['mismatches']) > 0) {
if ($verboseOutput && $translation['mismatches']) {
echo '| Mismatches between trans-unit id and source:'.\PHP_EOL;
foreach ($translation['mismatches'] as $id => $content) {

View File

@@ -158,11 +158,18 @@ class XliffUtils
*/
private static function fixXmlLocation(string $schemaSource, string $xmlUri): string
{
$path = __DIR__.'/../Resources/schemas/xml.xsd';
static $newPath;
if (0 === stripos($path, 'phar://')) {
if ($tmpfile = tempnam(sys_get_temp_dir(), 'symfony')) {
if (null === $newPath) {
$path = __DIR__.'/../Resources/schemas/xml.xsd';
if (0 !== stripos($path, 'phar://')) {
$newPath = self::getFileUrl($path);
} elseif ($tmpfile = tempnam(sys_get_temp_dir(), 'symfony')) {
copy($path, $tmpfile);
register_shutdown_function(static function () use ($tmpfile) {
@unlink($tmpfile);
});
$newPath = self::getFileUrl($tmpfile);
} else {
$parts = explode('/', '\\' === \DIRECTORY_SEPARATOR ? str_replace('\\', '/', $path) : $path);
@@ -170,8 +177,6 @@ class XliffUtils
$drive = '\\' === \DIRECTORY_SEPARATOR ? array_shift($parts).'/' : '';
$newPath = 'phar:///'.$drive.implode('/', array_map('rawurlencode', $parts));
}
} else {
$newPath = self::getFileUrl($path);
}
return str_replace($xmlUri, $newPath, $schemaSource);