Mail Parser: Completely remove Webklex IMAP and all dependcies

This commit is contained in:
johnnyq
2026-06-24 13:39:07 -04:00
parent 63ad3256ee
commit 171a0d38f8
779 changed files with 6408 additions and 82971 deletions

View File

@@ -79,6 +79,14 @@ abstract class ErrorBag implements IErrorBag
return $this;
}
/**
* Copies the source bag's own errors into this one without re-logging them.
*/
protected function copyErrorsFrom(ErrorBag $source) : void
{
$this->errors = \array_merge($this->errors, $source->getErrors(false, LogLevel::DEBUG));
}
public function getErrors(bool $validate = false, string $minPsrLevel = LogLevel::ERROR) : array
{
if ($validate && !$this->validated) {

View File

@@ -43,7 +43,8 @@ class MimeToken extends Token
public function __construct(LoggerInterface $logger, MbWrapper $charsetConverter, string $value)
{
parent::__construct($logger, $charsetConverter, $value);
$this->value = $this->decodeMime(\preg_replace('/\r|\n/', '', $this->value));
$decoded = $this->decodeMime(\preg_replace('/\r|\n/', '', $this->value));
$this->value = \preg_replace('/[\r\n]+/', '', $decoded) ?? '';
$pattern = self::MIME_PART_PATTERN;
$this->canIgnoreSpacesBefore = (bool) \preg_match("/^\s*{$pattern}|\s+/", $this->rawValue);
$this->canIgnoreSpacesAfter = (bool) \preg_match("/{$pattern}\s*|\s+\$/", $this->rawValue);

View File

@@ -64,10 +64,10 @@ class ParameterPart extends NameValuePart
protected function decodePartValue(string $value, ?string $charset = null) : string
{
if ($charset !== null) {
return $this->convertEncoding(\rawurldecode($value), $charset, true);
}
return $this->convertEncoding(\rawurldecode($value));
$decoded = ($charset !== null)
? $this->convertEncoding(\rawurldecode($value), $charset, true)
: $this->convertEncoding(\rawurldecode($value));
return \preg_replace('/[\r\n]+/', '', $decoded) ?? $decoded;
}
protected function getValueFromParts(array $parts) : string

View File

@@ -48,7 +48,7 @@ class MultipartHelper extends AbstractHelper
public function getUniqueBoundary(string $mimeType) : string
{
$type = \ltrim(\strtoupper(\preg_replace('/^(multipart\/(.{3}).*|.*)$/i', '$2-', $mimeType)), '-');
return \uniqid('----=MMP-' . $type . '-', true);
return '----=MMP-' . $type . '-' . \bin2hex(\random_bytes(16));
}
/**
@@ -311,7 +311,8 @@ class MultipartHelper extends AbstractHelper
$filename = 'file' . \uniqid();
}
$safe = \iconv('UTF-8', 'US-ASCII//translit//ignore', $filename);
$converted = \iconv('UTF-8', 'US-ASCII//translit//ignore', $filename);
$safe = \preg_replace('/[\x00-\x1F\x7F]+/', ' ', ($converted !== false) ? $converted : '') ?? '';
if ($message->isMime()) {
$part = $this->mimePartFactory->newInstance();
$part->setRawHeader(HeaderConsts::CONTENT_TRANSFER_ENCODING, $encoding);

View File

@@ -100,13 +100,16 @@ class PartChildrenContainer implements ArrayAccess, RecursiveIterator
*/
public function add(IMessagePart $part, $position = null) : static
{
$index = $position ?? \count($this->children);
\array_splice(
$this->children,
$index,
0,
[$part]
);
if ($position === null || $position >= \count($this->children)) {
$this->children[] = $part;
} else {
\array_splice(
$this->children,
$position,
0,
[$part]
);
}
return $this;
}

View File

@@ -77,6 +77,7 @@ class PartHeaderContainer extends ErrorBag implements IteratorAggregate
$this->headerObjects = $cloneSource->headerObjects;
$this->headerMap = $cloneSource->headerMap;
$this->nextIndex = $cloneSource->nextIndex;
$this->copyErrorsFrom($cloneSource);
}
}

View File

@@ -17,6 +17,16 @@ use ZBateson\MailMimeParser\Message\PartHeaderContainer;
*/
class HeaderParserService
{
private int $maxHeaderCount;
private int $maxHeaderSizeBytes;
public function __construct(int $maxHeaderCount = 1000, int $maxHeaderSizeBytes = 1048576)
{
$this->maxHeaderCount = $maxHeaderCount;
$this->maxHeaderSizeBytes = $maxHeaderSizeBytes;
}
/**
* Ensures the header isn't empty and contains a colon separator character,
* then splits it and adds it to the passed PartHeaderContainer.
@@ -51,16 +61,28 @@ class HeaderParserService
public function parse($handle, PartHeaderContainer $container) : static
{
$header = '';
$count = 0;
$start = \ftell($handle);
do {
$offset = \ftell($handle);
$line = MessageParserService::readLine($handle);
if ($line === false || $line === '' || $line[0] !== "\t" && $line[0] !== ' ') {
if ($header !== '') {
++$count;
}
$this->addRawHeaderToPart($offset, $header, $container);
$header = '';
} else {
$line = "\r\n" . $line;
}
$header .= \rtrim($line, "\r\n");
if ($count >= $this->maxHeaderCount || \ftell($handle) - $start >= $this->maxHeaderSizeBytes) {
$container->addError(
'Header count or total size limit reached while parsing headers',
LogLevel::ERROR
);
break;
}
} while ($header !== '');
return $this;
}

View File

@@ -7,6 +7,7 @@
namespace ZBateson\MailMimeParser\Parser;
use Psr\Log\LogLevel;
use ZBateson\MailMimeParser\Message\Factory\PartHeaderContainerFactory;
use ZBateson\MailMimeParser\Message\PartHeaderContainer;
use ZBateson\MailMimeParser\Parser\Proxy\ParserMessageProxyFactory;
@@ -32,16 +33,23 @@ class MimeParserService extends AbstractParserService
*/
protected HeaderParserService $headerParser;
/**
* @var int Maximum multipart nesting depth.
*/
protected int $maxMimePartDepth;
public function __construct(
ParserMessageProxyFactory $parserMessageProxyFactory,
ParserMimePartProxyFactory $parserMimePartProxyFactory,
PartBuilderFactory $partBuilderFactory,
PartHeaderContainerFactory $partHeaderContainerFactory,
HeaderParserService $headerParser
HeaderParserService $headerParser,
int $maxMimePartDepth = 256
) {
parent::__construct($parserMessageProxyFactory, $parserMimePartProxyFactory, $partBuilderFactory);
$this->partHeaderContainerFactory = $partHeaderContainerFactory;
$this->headerParser = $headerParser;
$this->maxMimePartDepth = $maxMimePartDepth;
}
/**
@@ -160,8 +168,29 @@ class MimeParserService extends AbstractParserService
if ($proxy->isParentBoundaryFound()) {
return null;
}
if ($this->exceedsMaxDepth($proxy)) {
$proxy->addError(
'Maximum MIME part nesting depth of ' . $this->maxMimePartDepth . ' reached',
LogLevel::ERROR
);
return null;
}
$headerContainer = $this->partHeaderContainerFactory->newInstance();
$child = $this->partBuilderFactory->newChildPartBuilder($headerContainer, $proxy);
return $this->createPart($proxy, $headerContainer, $child);
}
/**
* Returns true if adding a child to $proxy would exceed $maxMimePartDepth.
*/
private function exceedsMaxDepth(ParserMimePartProxy $proxy) : bool
{
$depth = 1;
for ($p = $proxy->getParent(); $p !== null; $p = $p->getParent()) {
if (++$depth >= $this->maxMimePartDepth) {
return true;
}
}
return false;
}
}

View File

@@ -14,6 +14,8 @@ use ZBateson\MailMimeParser\Header\Consumer\Received\GenericReceivedConsumerServ
use ZBateson\MailMimeParser\Header\Consumer\ReceivedConsumerService;
use ZBateson\MailMimeParser\Message\Factory\PartStreamContainerFactory;
use ZBateson\MailMimeParser\Message\PartStreamContainer;
use ZBateson\MailMimeParser\Parser\HeaderParserService;
use ZBateson\MailMimeParser\Parser\MimeParserService;
use ZBateson\MailMimeParser\Parser\Part\ParserPartStreamContainerFactory;
use ZBateson\MailMimeParser\Stream\StreamFactory;
@@ -24,6 +26,13 @@ return [
// header parts
'throwExceptionReadingPartContentFromUnsupportedCharsets' => false,
// Maximum multipart nesting depth before parsing stops with a recorded error.
'maxMimePartDepth' => 256,
// Maximum header count and total header bytes before parsing stops.
'maxHeaderCount' => 1000,
'maxHeaderSizeBytes' => 1048576,
'fromDomainConsumerService' => (new AutowireDefinitionHelper(DomainConsumerService::class))
->constructorParameter('partName', 'from'),
'byDomainConsumerService' => (new AutowireDefinitionHelper(DomainConsumerService::class))
@@ -61,4 +70,13 @@ return [
->constructor(
throwExceptionReadingPartContentFromUnsupportedCharsets: new Reference('throwExceptionReadingPartContentFromUnsupportedCharsets')
),
HeaderParserService::class => (new AutowireDefinitionHelper())
->constructor(
maxHeaderCount: new Reference('maxHeaderCount'),
maxHeaderSizeBytes: new Reference('maxHeaderSizeBytes')
),
MimeParserService::class => (new AutowireDefinitionHelper())
->constructor(
maxMimePartDepth: new Reference('maxMimePartDepth')
),
];

View File

@@ -1 +1 @@
3.0.4
3.0.6