git subrepo clone https://github.com/mailcow/mailcow-dockerized.git mailcow/src/mailcow-dockerized

subrepo: subdir:   "mailcow/src/mailcow-dockerized"
  merged:   "a832becb"
upstream: origin:   "https://github.com/mailcow/mailcow-dockerized.git"
  branch:   "master"
  commit:   "a832becb"
git-subrepo: version:  "0.4.3"
  origin:   "???"
  commit:   "???"
Change-Id: If5be2d621a211e164c9b6577adaa7884449f16b5
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php
new file mode 100644
index 0000000..9187c38
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractMessage.php
@@ -0,0 +1,266 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Ddeboer\Imap\Message;
+
+use Ddeboer\Imap\Exception\InvalidDateHeaderException;
+
+abstract class AbstractMessage extends AbstractPart
+{
+    /**
+     * @var null|array
+     */
+    private $attachments;
+
+    /**
+     * Get message headers.
+     */
+    abstract public function getHeaders(): Headers;
+
+    /**
+     * Get message id.
+     *
+     * A unique message id in the form <...>
+     */
+    final public function getId(): ?string
+    {
+        return $this->getHeaders()->get('message_id');
+    }
+
+    /**
+     * Get message sender (from headers).
+     */
+    final public function getFrom(): ?EmailAddress
+    {
+        $from = $this->getHeaders()->get('from');
+
+        return null !== $from ? $this->decodeEmailAddress($from[0]) : null;
+    }
+
+    /**
+     * Get To recipients.
+     *
+     * @return EmailAddress[] Empty array in case message has no To: recipients
+     */
+    final public function getTo(): array
+    {
+        return $this->decodeEmailAddresses($this->getHeaders()->get('to') ?: []);
+    }
+
+    /**
+     * Get Cc recipients.
+     *
+     * @return EmailAddress[] Empty array in case message has no CC: recipients
+     */
+    final public function getCc(): array
+    {
+        return $this->decodeEmailAddresses($this->getHeaders()->get('cc') ?: []);
+    }
+
+    /**
+     * Get Bcc recipients.
+     *
+     * @return EmailAddress[] Empty array in case message has no BCC: recipients
+     */
+    final public function getBcc(): array
+    {
+        return $this->decodeEmailAddresses($this->getHeaders()->get('bcc') ?: []);
+    }
+
+    /**
+     * Get Reply-To recipients.
+     *
+     * @return EmailAddress[] Empty array in case message has no Reply-To: recipients
+     */
+    final public function getReplyTo(): array
+    {
+        return $this->decodeEmailAddresses($this->getHeaders()->get('reply_to') ?: []);
+    }
+
+    /**
+     * Get Sender.
+     *
+     * @return EmailAddress[] Empty array in case message has no Sender: recipients
+     */
+    final public function getSender(): array
+    {
+        return $this->decodeEmailAddresses($this->getHeaders()->get('sender') ?: []);
+    }
+
+    /**
+     * Get Return-Path.
+     *
+     * @return EmailAddress[] Empty array in case message has no Return-Path: recipients
+     */
+    final public function getReturnPath(): array
+    {
+        return $this->decodeEmailAddresses($this->getHeaders()->get('return_path') ?: []);
+    }
+
+    /**
+     * Get date (from headers).
+     */
+    final public function getDate(): ?\DateTimeImmutable
+    {
+        /** @var null|string $dateHeader */
+        $dateHeader = $this->getHeaders()->get('date');
+        if (null === $dateHeader) {
+            return null;
+        }
+
+        $alteredValue = $dateHeader;
+        $alteredValue = \str_replace(',', '', $alteredValue);
+        $alteredValue = (string) \preg_replace('/^[a-zA-Z]+ ?/', '', $alteredValue);
+        $alteredValue = (string) \preg_replace('/\(.*\)/', '', $alteredValue);
+        $alteredValue = (string) \preg_replace('/\bUT\b/', 'UTC', $alteredValue);
+        if (0 === \preg_match('/\d\d:\d\d:\d\d.* [\+\-]\d\d:?\d\d/', $alteredValue)) {
+            $alteredValue .= ' +0000';
+        }
+        // Handle numeric months
+        $alteredValue = (string) \preg_replace('/^(\d\d) (\d\d) (\d\d(?:\d\d)?) /', '$3-$2-$1 ', $alteredValue);
+
+        try {
+            $date = new \DateTimeImmutable($alteredValue);
+        } catch (\Throwable $ex) {
+            throw new InvalidDateHeaderException(\sprintf('Invalid Date header found: "%s"', $dateHeader), 0, $ex);
+        }
+
+        return $date;
+    }
+
+    /**
+     * Get message size (from headers).
+     *
+     * @return null|int|string
+     */
+    final public function getSize()
+    {
+        return $this->getHeaders()->get('size');
+    }
+
+    /**
+     * Get message subject (from headers).
+     */
+    final public function getSubject(): ?string
+    {
+        return $this->getHeaders()->get('subject');
+    }
+
+    /**
+     * Get message In-Reply-To (from headers).
+     */
+    final public function getInReplyTo(): array
+    {
+        $inReplyTo = $this->getHeaders()->get('in_reply_to');
+
+        return null !== $inReplyTo ? \explode(' ', $inReplyTo) : [];
+    }
+
+    /**
+     * Get message References (from headers).
+     */
+    final public function getReferences(): array
+    {
+        $references = $this->getHeaders()->get('references');
+
+        return null !== $references ? \explode(' ', $references) : [];
+    }
+
+    /**
+     * Get body HTML.
+     */
+    final public function getBodyHtml(): ?string
+    {
+        $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST);
+        foreach ($iterator as $part) {
+            if (self::SUBTYPE_HTML === $part->getSubtype()) {
+                return $part->getDecodedContent();
+            }
+        }
+
+        // If message has no parts and is HTML, return content of message itself.
+        if (self::SUBTYPE_HTML === $this->getSubtype()) {
+            return $this->getDecodedContent();
+        }
+
+        return null;
+    }
+
+    /**
+     * Get body text.
+     */
+    final public function getBodyText(): ?string
+    {
+        $iterator = new \RecursiveIteratorIterator($this, \RecursiveIteratorIterator::SELF_FIRST);
+        foreach ($iterator as $part) {
+            if (self::SUBTYPE_PLAIN === $part->getSubtype()) {
+                return $part->getDecodedContent();
+            }
+        }
+
+        // If message has no parts, return content of message itself.
+        if (self::SUBTYPE_PLAIN === $this->getSubtype()) {
+            return $this->getDecodedContent();
+        }
+
+        return null;
+    }
+
+    /**
+     * Get attachments (if any) linked to this e-mail.
+     *
+     * @return AttachmentInterface[]
+     */
+    final public function getAttachments(): array
+    {
+        if (null === $this->attachments) {
+            $this->attachments = self::gatherAttachments($this);
+        }
+
+        return $this->attachments;
+    }
+
+    private static function gatherAttachments(PartInterface $part): array
+    {
+        $attachments = [];
+        foreach ($part->getParts() as $childPart) {
+            if ($childPart instanceof Attachment) {
+                $attachments[] = $childPart;
+            }
+            if ($childPart->hasChildren()) {
+                $attachments = \array_merge($attachments, self::gatherAttachments($childPart));
+            }
+        }
+
+        return $attachments;
+    }
+
+    /**
+     * Does this message have attachments?
+     */
+    final public function hasAttachments(): bool
+    {
+        return \count($this->getAttachments()) > 0;
+    }
+
+    /**
+     * @param \stdClass[] $addresses
+     */
+    private function decodeEmailAddresses(array $addresses): array
+    {
+        $return = [];
+        foreach ($addresses as $address) {
+            if (isset($address->mailbox)) {
+                $return[] = $this->decodeEmailAddress($address);
+            }
+        }
+
+        return $return;
+    }
+
+    private function decodeEmailAddress(\stdClass $value): EmailAddress
+    {
+        return new EmailAddress($value->mailbox, $value->host, $value->personal);
+    }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php
new file mode 100644
index 0000000..923f300
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AbstractPart.php
@@ -0,0 +1,572 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Ddeboer\Imap\Message;
+
+use Ddeboer\Imap\Exception\ImapFetchbodyException;
+use Ddeboer\Imap\Exception\UnexpectedEncodingException;
+use Ddeboer\Imap\ImapResourceInterface;
+use Ddeboer\Imap\Message;
+
+/**
+ * A message part.
+ */
+abstract class AbstractPart implements PartInterface
+{
+    /**
+     * @var ImapResourceInterface
+     */
+    protected $resource;
+
+    /**
+     * @var bool
+     */
+    private $structureParsed = false;
+
+    /**
+     * @var array
+     */
+    private $parts = [];
+
+    /**
+     * @var string
+     */
+    private $partNumber;
+
+    /**
+     * @var int
+     */
+    private $messageNumber;
+
+    /**
+     * @var \stdClass
+     */
+    private $structure;
+
+    /**
+     * @var Parameters
+     */
+    private $parameters;
+
+    /**
+     * @var null|string
+     */
+    private $type;
+
+    /**
+     * @var null|string
+     */
+    private $subtype;
+
+    /**
+     * @var null|string
+     */
+    private $encoding;
+
+    /**
+     * @var null|string
+     */
+    private $disposition;
+
+    /**
+     * @var null|string
+     */
+    private $description;
+
+    /**
+     * @var null|string
+     */
+    private $bytes;
+
+    /**
+     * @var null|string
+     */
+    private $lines;
+
+    /**
+     * @var null|string
+     */
+    private $content;
+
+    /**
+     * @var null|string
+     */
+    private $decodedContent;
+
+    /**
+     * @var int
+     */
+    private $key = 0;
+
+    /**
+     * @var array
+     */
+    private static $typesMap = [
+        \TYPETEXT        => self::TYPE_TEXT,
+        \TYPEMULTIPART   => self::TYPE_MULTIPART,
+        \TYPEMESSAGE     => self::TYPE_MESSAGE,
+        \TYPEAPPLICATION => self::TYPE_APPLICATION,
+        \TYPEAUDIO       => self::TYPE_AUDIO,
+        \TYPEIMAGE       => self::TYPE_IMAGE,
+        \TYPEVIDEO       => self::TYPE_VIDEO,
+        \TYPEMODEL       => self::TYPE_MODEL,
+        \TYPEOTHER       => self::TYPE_OTHER,
+    ];
+
+    /**
+     * @var array
+     */
+    private static $encodingsMap = [
+        \ENC7BIT            => self::ENCODING_7BIT,
+        \ENC8BIT            => self::ENCODING_8BIT,
+        \ENCBINARY          => self::ENCODING_BINARY,
+        \ENCBASE64          => self::ENCODING_BASE64,
+        \ENCQUOTEDPRINTABLE => self::ENCODING_QUOTED_PRINTABLE,
+    ];
+
+    /**
+     * @var array
+     */
+    private static $attachmentKeys = [
+        'name'      => true,
+        'filename'  => true,
+        'name*'     => true,
+        'filename*' => true,
+    ];
+
+    /**
+     * Constructor.
+     *
+     * @param ImapResourceInterface $resource      IMAP resource
+     * @param int                   $messageNumber Message number
+     * @param string                $partNumber    Part number
+     * @param \stdClass             $structure     Part structure
+     */
+    public function __construct(
+        ImapResourceInterface $resource,
+        int $messageNumber,
+        string $partNumber,
+        \stdClass $structure
+    ) {
+        $this->resource      = $resource;
+        $this->messageNumber = $messageNumber;
+        $this->partNumber    = $partNumber;
+        $this->setStructure($structure);
+    }
+
+    /**
+     * Get message number (from headers).
+     */
+    final public function getNumber(): int
+    {
+        $this->assertMessageExists($this->messageNumber);
+
+        return $this->messageNumber;
+    }
+
+    /**
+     * Ensure message exists.
+     */
+    protected function assertMessageExists(int $messageNumber): void
+    {
+    }
+
+    /**
+     * @param \stdClass $structure Part structure
+     */
+    final protected function setStructure(\stdClass $structure): void
+    {
+        $this->structure = $structure;
+    }
+
+    /**
+     * Part structure.
+     */
+    final public function getStructure(): \stdClass
+    {
+        $this->lazyLoadStructure();
+
+        return $this->structure;
+    }
+
+    /**
+     * Lazy load structure.
+     */
+    protected function lazyLoadStructure(): void
+    {
+    }
+
+    /**
+     * Part parameters.
+     */
+    final public function getParameters(): Parameters
+    {
+        $this->lazyParseStructure();
+
+        return $this->parameters;
+    }
+
+    /**
+     * Part charset.
+     */
+    final public function getCharset(): ?string
+    {
+        $this->lazyParseStructure();
+
+        return $this->parameters->get('charset') ?: null;
+    }
+
+    /**
+     * Part type.
+     */
+    final public function getType(): ?string
+    {
+        $this->lazyParseStructure();
+
+        return $this->type;
+    }
+
+    /**
+     * Part subtype.
+     */
+    final public function getSubtype(): ?string
+    {
+        $this->lazyParseStructure();
+
+        return $this->subtype;
+    }
+
+    /**
+     * Part encoding.
+     */
+    final public function getEncoding(): ?string
+    {
+        $this->lazyParseStructure();
+
+        return $this->encoding;
+    }
+
+    /**
+     * Part disposition.
+     */
+    final public function getDisposition(): ?string
+    {
+        $this->lazyParseStructure();
+
+        return $this->disposition;
+    }
+
+    /**
+     * Part description.
+     */
+    final public function getDescription(): ?string
+    {
+        $this->lazyParseStructure();
+
+        return $this->description;
+    }
+
+    /**
+     * Part bytes.
+     *
+     * @return null|int|string
+     */
+    final public function getBytes()
+    {
+        $this->lazyParseStructure();
+
+        return $this->bytes;
+    }
+
+    /**
+     * Part lines.
+     */
+    final public function getLines(): ?string
+    {
+        $this->lazyParseStructure();
+
+        return $this->lines;
+    }
+
+    /**
+     * Get raw part content.
+     */
+    final public function getContent(): string
+    {
+        if (null === $this->content) {
+            $this->content = $this->doGetContent($this->getContentPartNumber());
+        }
+
+        return $this->content;
+    }
+
+    /**
+     * Get content part number.
+     */
+    protected function getContentPartNumber(): string
+    {
+        return $this->partNumber;
+    }
+
+    /**
+     * Get part number.
+     */
+    final public function getPartNumber(): string
+    {
+        return $this->partNumber;
+    }
+
+    /**
+     * Get decoded part content.
+     */
+    final public function getDecodedContent(): string
+    {
+        if (null === $this->decodedContent) {
+            if (self::ENCODING_UNKNOWN === $this->getEncoding()) {
+                throw new UnexpectedEncodingException('Cannot decode a content with an uknown encoding');
+            }
+
+            $content = $this->getContent();
+            if (self::ENCODING_BASE64 === $this->getEncoding()) {
+                $content = \base64_decode($content, false);
+            } elseif (self::ENCODING_QUOTED_PRINTABLE === $this->getEncoding()) {
+                $content = \quoted_printable_decode($content);
+            }
+
+            if (false === $content) {
+                throw new UnexpectedEncodingException('Cannot decode content');
+            }
+
+            // If this part is a text part, convert its charset to UTF-8.
+            // We don't want to decode an attachment's charset.
+            if (!$this instanceof Attachment && null !== $this->getCharset() && self::TYPE_TEXT === $this->getType()) {
+                $content = Transcoder::decode($content, $this->getCharset());
+            }
+
+            $this->decodedContent = $content;
+        }
+
+        return $this->decodedContent;
+    }
+
+    /**
+     * Get raw message content.
+     */
+    final protected function doGetContent(string $partNumber): string
+    {
+        $return = \imap_fetchbody(
+            $this->resource->getStream(),
+            $this->getNumber(),
+            $partNumber,
+            \FT_UID | \FT_PEEK
+        );
+
+        if (false === $return) {
+            throw new ImapFetchbodyException('imap_fetchbody failed');
+        }
+
+        return $return;
+    }
+
+    /**
+     * Get an array of all parts for this message.
+     *
+     * @return PartInterface[]
+     */
+    final public function getParts(): array
+    {
+        $this->lazyParseStructure();
+
+        return $this->parts;
+    }
+
+    /**
+     * Get current child part.
+     *
+     * @return mixed
+     */
+    final public function current()
+    {
+        $this->lazyParseStructure();
+
+        return $this->parts[$this->key];
+    }
+
+    /**
+     * Get current child part.
+     *
+     * @return \RecursiveIterator
+     */
+    final public function getChildren()
+    {
+        return $this->current();
+    }
+
+    /**
+     * Get current child part.
+     *
+     * @return bool
+     */
+    final public function hasChildren()
+    {
+        $this->lazyParseStructure();
+
+        return \count($this->parts) > 0;
+    }
+
+    /**
+     * Get current part key.
+     *
+     * @return int
+     */
+    final public function key()
+    {
+        return $this->key;
+    }
+
+    /**
+     * Move to next part.
+     *
+     * @return void
+     */
+    final public function next()
+    {
+        ++$this->key;
+    }
+
+    /**
+     * Reset part key.
+     *
+     * @return void
+     */
+    final public function rewind()
+    {
+        $this->key = 0;
+    }
+
+    /**
+     * Check if current part is a valid one.
+     *
+     * @return bool
+     */
+    final public function valid()
+    {
+        $this->lazyParseStructure();
+
+        return isset($this->parts[$this->key]);
+    }
+
+    /**
+     * Parse part structure.
+     */
+    private function lazyParseStructure(): void
+    {
+        if (true === $this->structureParsed) {
+            return;
+        }
+        $this->structureParsed = true;
+
+        $this->lazyLoadStructure();
+
+        $this->type = self::$typesMap[$this->structure->type] ?? self::TYPE_UNKNOWN;
+
+        // In our context, \ENCOTHER is as useful as an uknown encoding
+        $this->encoding = self::$encodingsMap[$this->structure->encoding] ?? self::ENCODING_UNKNOWN;
+        $this->subtype  = $this->structure->subtype;
+
+        if (isset($this->structure->bytes)) {
+            $this->bytes = $this->structure->bytes;
+        }
+        if ($this->structure->ifdisposition) {
+            $this->disposition = $this->structure->disposition;
+        }
+        if ($this->structure->ifdescription) {
+            $this->description = $this->structure->description;
+        }
+
+        $this->parameters = new Parameters();
+        if ($this->structure->ifparameters) {
+            $this->parameters->add($this->structure->parameters);
+        }
+
+        if ($this->structure->ifdparameters) {
+            $this->parameters->add($this->structure->dparameters);
+        }
+
+        // When the message is not multipart and the body is the attachment content
+        // Prevents infinite recursion
+        if (self::isAttachment($this->structure) && !$this instanceof Attachment) {
+            $this->parts[] = new Attachment($this->resource, $this->getNumber(), '1', $this->structure);
+        }
+
+        if (isset($this->structure->parts)) {
+            $parts = $this->structure->parts;
+            // https://secure.php.net/manual/en/function.imap-fetchbody.php#89002
+            if ($this instanceof Attachment && $this->isEmbeddedMessage() && 1 === \count($parts) && \TYPEMULTIPART === $parts[0]->type) {
+                $parts = $parts[0]->parts;
+            }
+            foreach ($parts as $key => $partStructure) {
+                $partNumber = (!$this instanceof Message) ? $this->partNumber . '.' : '';
+                $partNumber .= (string) ($key + 1);
+
+                $newPartClass = self::isAttachment($partStructure)
+                    ? Attachment::class
+                    : SimplePart::class
+                ;
+
+                $this->parts[] = new $newPartClass($this->resource, $this->getNumber(), $partNumber, $partStructure);
+            }
+        }
+    }
+
+    /**
+     * Check if the given part is an attachment.
+     */
+    private static function isAttachment(\stdClass $part): bool
+    {
+        if (isset(self::$typesMap[$part->type]) && self::TYPE_MULTIPART === self::$typesMap[$part->type]) {
+            return false;
+        }
+
+        // Attachment with correct Content-Disposition header
+        if ($part->ifdisposition) {
+            if ('attachment' === \strtolower($part->disposition)) {
+                return true;
+            }
+
+            if (
+                    'inline' === \strtolower($part->disposition)
+                && self::SUBTYPE_PLAIN !== \strtoupper($part->subtype)
+                && self::SUBTYPE_HTML !== \strtoupper($part->subtype)
+            ) {
+                return true;
+            }
+        }
+
+        // Attachment without Content-Disposition header
+        if ($part->ifparameters) {
+            foreach ($part->parameters as $parameter) {
+                if (isset(self::$attachmentKeys[\strtolower($parameter->attribute)])) {
+                    return true;
+                }
+            }
+        }
+
+        /*
+        if ($part->ifdparameters) {
+            foreach ($part->dparameters as $parameter) {
+                if (isset(self::$attachmentKeys[\strtolower($parameter->attribute)])) {
+                    return true;
+                }
+            }
+        }
+         */
+
+        if (self::SUBTYPE_RFC822 === \strtoupper($part->subtype)) {
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Attachment.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Attachment.php
new file mode 100644
index 0000000..bd76769
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Attachment.php
@@ -0,0 +1,63 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Ddeboer\Imap\Message;
+
+use Ddeboer\Imap\Exception\NotEmbeddedMessageException;
+
+/**
+ * An e-mail attachment.
+ */
+final class Attachment extends AbstractPart implements AttachmentInterface
+{
+    /**
+     * Get attachment filename.
+     */
+    public function getFilename(): ?string
+    {
+        return $this->getParameters()->get('filename')
+            ?: $this->getParameters()->get('name');
+    }
+
+    /**
+     * Get attachment file size.
+     *
+     * @return null|int Number of bytes
+     */
+    public function getSize()
+    {
+        $size = $this->getParameters()->get('size');
+        if (\is_numeric($size)) {
+            $size = (int) $size;
+        }
+
+        return $size;
+    }
+
+    /**
+     * Is this attachment also an Embedded Message?
+     */
+    public function isEmbeddedMessage(): bool
+    {
+        return self::TYPE_MESSAGE === $this->getType();
+    }
+
+    /**
+     * Return embedded message.
+     *
+     * @throws NotEmbeddedMessageException
+     */
+    public function getEmbeddedMessage(): EmbeddedMessageInterface
+    {
+        if (!$this->isEmbeddedMessage()) {
+            throw new NotEmbeddedMessageException(\sprintf(
+                'Attachment "%s" in message "%s" is not embedded message',
+                $this->getPartNumber(),
+                $this->getNumber()
+            ));
+        }
+
+        return new EmbeddedMessage($this->resource, $this->getNumber(), $this->getPartNumber(), $this->getStructure()->parts[0]);
+    }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AttachmentInterface.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AttachmentInterface.php
new file mode 100644
index 0000000..0d20f44
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/AttachmentInterface.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Ddeboer\Imap\Message;
+
+/**
+ * An e-mail attachment.
+ */
+interface AttachmentInterface extends PartInterface
+{
+    /**
+     * Get attachment filename.
+     */
+    public function getFilename(): ?string;
+
+    /**
+     * Get attachment file size.
+     *
+     * @return null|int Number of bytes
+     */
+    public function getSize();
+
+    /**
+     * Is this attachment also an Embedded Message?
+     */
+    public function isEmbeddedMessage(): bool;
+
+    /**
+     * Return embedded message.
+     */
+    public function getEmbeddedMessage(): EmbeddedMessageInterface;
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/BasicMessageInterface.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/BasicMessageInterface.php
new file mode 100644
index 0000000..20e6b1a
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/BasicMessageInterface.php
@@ -0,0 +1,130 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Ddeboer\Imap\Message;
+
+interface BasicMessageInterface extends PartInterface
+{
+    /**
+     * Get raw message headers.
+     */
+    public function getRawHeaders(): string;
+
+    /**
+     * Get the raw message, including all headers, parts, etc. unencoded and unparsed.
+     *
+     * @return string the raw message
+     */
+    public function getRawMessage(): string;
+
+    /**
+     * Get message headers.
+     */
+    public function getHeaders(): Headers;
+
+    /**
+     * Get message id.
+     *
+     * A unique message id in the form <...>
+     */
+    public function getId(): ?string;
+
+    /**
+     * Get message sender (from headers).
+     */
+    public function getFrom(): ?EmailAddress;
+
+    /**
+     * Get To recipients.
+     *
+     * @return EmailAddress[] Empty array in case message has no To: recipients
+     */
+    public function getTo(): array;
+
+    /**
+     * Get Cc recipients.
+     *
+     * @return EmailAddress[] Empty array in case message has no CC: recipients
+     */
+    public function getCc(): array;
+
+    /**
+     * Get Bcc recipients.
+     *
+     * @return EmailAddress[] Empty array in case message has no BCC: recipients
+     */
+    public function getBcc(): array;
+
+    /**
+     * Get Reply-To recipients.
+     *
+     * @return EmailAddress[] Empty array in case message has no Reply-To: recipients
+     */
+    public function getReplyTo(): array;
+
+    /**
+     * Get Sender.
+     *
+     * @return EmailAddress[] Empty array in case message has no Sender: recipients
+     */
+    public function getSender(): array;
+
+    /**
+     * Get Return-Path.
+     *
+     * @return EmailAddress[] Empty array in case message has no Return-Path: recipients
+     */
+    public function getReturnPath(): array;
+
+    /**
+     * Get date (from headers).
+     */
+    public function getDate(): ?\DateTimeImmutable;
+
+    /**
+     * Get message size (from headers).
+     *
+     * @return null|int|string
+     */
+    public function getSize();
+
+    /**
+     * Get message subject (from headers).
+     */
+    public function getSubject(): ?string;
+
+    /**
+     * Get message In-Reply-To (from headers).
+     */
+    public function getInReplyTo(): array;
+
+    /**
+     * Get message References (from headers).
+     */
+    public function getReferences(): array;
+
+    /**
+     * Get body HTML.
+     *
+     * @return null|string Null if message has no HTML message part
+     */
+    public function getBodyHtml(): ?string;
+
+    /**
+     * Get body text.
+     */
+    public function getBodyText(): ?string;
+
+    /**
+     * Get attachments (if any) linked to this e-mail.
+     *
+     * @return AttachmentInterface[]
+     */
+    public function getAttachments(): array;
+
+    /**
+     * Does this message have attachments?
+     */
+    public function hasAttachments(): bool;
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmailAddress.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmailAddress.php
new file mode 100644
index 0000000..b88e0f9
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmailAddress.php
@@ -0,0 +1,84 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Ddeboer\Imap\Message;
+
+/**
+ * An e-mail address.
+ */
+final class EmailAddress
+{
+    /**
+     * @var string
+     */
+    private $mailbox;
+
+    /**
+     * @var null|string
+     */
+    private $hostname;
+
+    /**
+     * @var null|string
+     */
+    private $name;
+
+    /**
+     * @var null|string
+     */
+    private $address;
+
+    public function __construct(string $mailbox, string $hostname = null, string $name = null)
+    {
+        $this->mailbox  = $mailbox;
+        $this->hostname = $hostname;
+        $this->name     = $name;
+
+        if (null !== $hostname) {
+            $this->address = $mailbox . '@' . $hostname;
+        }
+    }
+
+    /**
+     * @return null|string
+     */
+    public function getAddress()
+    {
+        return $this->address;
+    }
+
+    /**
+     * Returns address with person name.
+     */
+    public function getFullAddress(): string
+    {
+        $address = \sprintf('%s@%s', $this->mailbox, $this->hostname);
+        if (null !== $this->name) {
+            $address = \sprintf('"%s" <%s>', \addcslashes($this->name, '"'), $address);
+        }
+
+        return $address;
+    }
+
+    public function getMailbox(): string
+    {
+        return $this->mailbox;
+    }
+
+    /**
+     * @return null|string
+     */
+    public function getHostname()
+    {
+        return $this->hostname;
+    }
+
+    /**
+     * @return null|string
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php
new file mode 100644
index 0000000..243cff6
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessage.php
@@ -0,0 +1,75 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Ddeboer\Imap\Message;
+
+final class EmbeddedMessage extends AbstractMessage implements EmbeddedMessageInterface
+{
+    /**
+     * @var null|Headers
+     */
+    private $headers;
+
+    /**
+     * @var null|string
+     */
+    private $rawHeaders;
+
+    /**
+     * @var null|string
+     */
+    private $rawMessage;
+
+    /**
+     * Get message headers.
+     */
+    public function getHeaders(): Headers
+    {
+        if (null === $this->headers) {
+            $this->headers = new Headers(\imap_rfc822_parse_headers($this->getRawHeaders()));
+        }
+
+        return $this->headers;
+    }
+
+    /**
+     * Get raw message headers.
+     */
+    public function getRawHeaders(): string
+    {
+        if (null === $this->rawHeaders) {
+            $rawHeaders       = \explode("\r\n\r\n", $this->getRawMessage(), 2);
+            $this->rawHeaders = \current($rawHeaders);
+        }
+
+        return $this->rawHeaders;
+    }
+
+    /**
+     * Get the raw message, including all headers, parts, etc. unencoded and unparsed.
+     *
+     * @return string the raw message
+     */
+    public function getRawMessage(): string
+    {
+        if (null === $this->rawMessage) {
+            $this->rawMessage = $this->doGetContent($this->getPartNumber());
+        }
+
+        return $this->rawMessage;
+    }
+
+    /**
+     * Get content part number.
+     */
+    protected function getContentPartNumber(): string
+    {
+        $partNumber = $this->getPartNumber();
+        if (0 === \count($this->getParts())) {
+            $partNumber .= '.1';
+        }
+
+        return $partNumber;
+    }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessageInterface.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessageInterface.php
new file mode 100644
index 0000000..c685edf
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/EmbeddedMessageInterface.php
@@ -0,0 +1,9 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Ddeboer\Imap\Message;
+
+interface EmbeddedMessageInterface extends BasicMessageInterface
+{
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Headers.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Headers.php
new file mode 100644
index 0000000..f76fec3
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Headers.php
@@ -0,0 +1,72 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Ddeboer\Imap\Message;
+
+/**
+ * Collection of message headers.
+ */
+final class Headers extends Parameters
+{
+    /**
+     * Constructor.
+     */
+    public function __construct(\stdClass $headers)
+    {
+        parent::__construct();
+
+        // Store all headers as lowercase
+        $headers = \array_change_key_case((array) $headers);
+
+        foreach ($headers as $key => $value) {
+            $this[$key] = $this->parseHeader($key, $value);
+        }
+    }
+
+    /**
+     * Get header.
+     *
+     * @return mixed
+     */
+    public function get(string $key)
+    {
+        return parent::get(\strtolower($key));
+    }
+
+    /**
+     * Parse header.
+     *
+     * @param mixed $value
+     *
+     * @return mixed
+     */
+    private function parseHeader(string $key, $value)
+    {
+        switch ($key) {
+            case 'msgno':
+                return (int) $value;
+            case 'from':
+            case 'to':
+            case 'cc':
+            case 'bcc':
+            case 'reply_to':
+            case 'sender':
+            case 'return_path':
+                /** @var \stdClass $address */
+                foreach ($value as $address) {
+                    if (isset($address->mailbox)) {
+                        $address->host     = $address->host ?? null;
+                        $address->personal = isset($address->personal) ? $this->decode($address->personal) : null;
+                    }
+                }
+
+                return $value;
+            case 'date':
+            case 'subject':
+                return $this->decode($value);
+        }
+
+        return $value;
+    }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Parameters.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Parameters.php
new file mode 100644
index 0000000..2f7d8a1
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Parameters.php
@@ -0,0 +1,74 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Ddeboer\Imap\Message;
+
+class Parameters extends \ArrayIterator
+{
+    /**
+     * @var array
+     */
+    private static $attachmentCustomKeys = [
+        'name*'     => 'name',
+        'filename*' => 'filename',
+    ];
+
+    public function __construct(array $parameters = [])
+    {
+        parent::__construct();
+
+        $this->add($parameters);
+    }
+
+    public function add(array $parameters = []): void
+    {
+        foreach ($parameters as $parameter) {
+            $key = \strtolower($parameter->attribute);
+            if (isset(self::$attachmentCustomKeys[$key])) {
+                $key = self::$attachmentCustomKeys[$key];
+            }
+            $value      = $this->decode($parameter->value);
+            $this[$key] = $value;
+        }
+    }
+
+    /**
+     * @return mixed
+     */
+    public function get(string $key)
+    {
+        return $this[$key] ?? null;
+    }
+
+    /**
+     * Decode value.
+     */
+    final protected function decode(string $value): string
+    {
+        $parts = \imap_mime_header_decode($value);
+        if (!\is_array($parts)) {
+            return $value;
+        }
+
+        $decoded = '';
+        foreach ($parts as $part) {
+            $text = $part->text;
+            if ('default' !== $part->charset) {
+                $text = Transcoder::decode($text, $part->charset);
+            }
+            // RFC2231
+            if (1 === \preg_match('/^(?<encoding>[^\']+)\'[^\']*?\'(?<urltext>.+)$/', $text, $matches)) {
+                $hasInvalidChars = 1 === \preg_match('#[^%a-zA-Z0-9\-_\.\+]#', $matches['urltext']);
+                $hasEscapedChars = 1 === \preg_match('#%[a-zA-Z0-9]{2}#', $matches['urltext']);
+                if (!$hasInvalidChars && $hasEscapedChars) {
+                    $text = Transcoder::decode(\urldecode($matches['urltext']), $matches['encoding']);
+                }
+            }
+
+            $decoded .= $text;
+        }
+
+        return $decoded;
+    }
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/PartInterface.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/PartInterface.php
new file mode 100644
index 0000000..70a83f2
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/PartInterface.php
@@ -0,0 +1,112 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Ddeboer\Imap\Message;
+
+/**
+ * A message part.
+ */
+interface PartInterface extends \RecursiveIterator
+{
+    public const TYPE_TEXT          = 'text';
+    public const TYPE_MULTIPART     = 'multipart';
+    public const TYPE_MESSAGE       = 'message';
+    public const TYPE_APPLICATION   = 'application';
+    public const TYPE_AUDIO         = 'audio';
+    public const TYPE_IMAGE         = 'image';
+    public const TYPE_VIDEO         = 'video';
+    public const TYPE_MODEL         = 'model';
+    public const TYPE_OTHER         = 'other';
+    public const TYPE_UNKNOWN       = 'unknown';
+
+    public const ENCODING_7BIT              = '7bit';
+    public const ENCODING_8BIT              = '8bit';
+    public const ENCODING_BINARY            = 'binary';
+    public const ENCODING_BASE64            = 'base64';
+    public const ENCODING_QUOTED_PRINTABLE  = 'quoted-printable';
+    public const ENCODING_UNKNOWN           = 'unknown';
+
+    public const SUBTYPE_PLAIN  = 'PLAIN';
+    public const SUBTYPE_HTML   = 'HTML';
+    public const SUBTYPE_RFC822 = 'RFC822';
+
+    /**
+     * Get message number (from headers).
+     */
+    public function getNumber(): int;
+
+    /**
+     * Part charset.
+     */
+    public function getCharset(): ?string;
+
+    /**
+     * Part type.
+     */
+    public function getType(): ?string;
+
+    /**
+     * Part subtype.
+     */
+    public function getSubtype(): ?string;
+
+    /**
+     * Part encoding.
+     */
+    public function getEncoding(): ?string;
+
+    /**
+     * Part disposition.
+     */
+    public function getDisposition(): ?string;
+
+    /**
+     * Part description.
+     */
+    public function getDescription(): ?string;
+
+    /**
+     * Part bytes.
+     *
+     * @return null|int|string
+     */
+    public function getBytes();
+
+    /**
+     * Part lines.
+     */
+    public function getLines(): ?string;
+
+    /**
+     * Part parameters.
+     */
+    public function getParameters(): Parameters;
+
+    /**
+     * Get raw part content.
+     */
+    public function getContent(): string;
+
+    /**
+     * Get decoded part content.
+     */
+    public function getDecodedContent(): string;
+
+    /**
+     * Part structure.
+     */
+    public function getStructure(): \stdClass;
+
+    /**
+     * Get part number.
+     */
+    public function getPartNumber(): string;
+
+    /**
+     * Get an array of all parts for this message.
+     *
+     * @return PartInterface[]
+     */
+    public function getParts(): array;
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/SimplePart.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/SimplePart.php
new file mode 100644
index 0000000..3c6188d
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/SimplePart.php
@@ -0,0 +1,12 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Ddeboer\Imap\Message;
+
+/**
+ * A message part.
+ */
+final class SimplePart extends AbstractPart
+{
+}
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Transcoder.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Transcoder.php
new file mode 100644
index 0000000..15dfb87
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/ddeboer/imap/src/Message/Transcoder.php
@@ -0,0 +1,328 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Ddeboer\Imap\Message;
+
+use Ddeboer\Imap\Exception\UnsupportedCharsetException;
+
+final class Transcoder
+{
+    /**
+     * @var array
+     *
+     * @see https://encoding.spec.whatwg.org/#encodings
+     * @see https://dxr.mozilla.org/mozilla-central/source/dom/encoding/labelsencodings.properties
+     * @see https://dxr.mozilla.org/mozilla1.9.1/source/intl/uconv/src/charsetalias.properties
+     * @see https://msdn.microsoft.com/en-us/library/cc194829.aspx
+     */
+    private static $charsetAliases = [
+        '128'                       => 'Shift_JIS',
+        '129'                       => 'EUC-KR',
+        '134'                       => 'GB2312',
+        '136'                       => 'Big5',
+        '161'                       => 'windows-1253',
+        '162'                       => 'windows-1254',
+        '177'                       => 'windows-1255',
+        '178'                       => 'windows-1256',
+        '186'                       => 'windows-1257',
+        '204'                       => 'windows-1251',
+        '222'                       => 'windows-874',
+        '238'                       => 'windows-1250',
+        '5601'                      => 'EUC-KR',
+        '646'                       => 'us-ascii',
+        '850'                       => 'IBM850',
+        '852'                       => 'IBM852',
+        '855'                       => 'IBM855',
+        '857'                       => 'IBM857',
+        '862'                       => 'IBM862',
+        '864'                       => 'IBM864',
+        '864i'                      => 'IBM864i',
+        '866'                       => 'IBM866',
+        'ansi-1251'                 => 'windows-1251',
+        'ansi_x3.4-1968'            => 'us-ascii',
+        'arabic'                    => 'ISO-8859-6',
+        'ascii'                     => 'us-ascii',
+        'asmo-708'                  => 'ISO-8859-6',
+        'big5-hkscs'                => 'Big5',
+        'chinese'                   => 'GB2312',
+        'cn-big5'                   => 'Big5',
+        'cns11643'                  => 'x-euc-tw',
+        'cp-866'                    => 'IBM866',
+        'cp1250'                    => 'windows-1250',
+        'cp1251'                    => 'windows-1251',
+        'cp1252'                    => 'windows-1252',
+        'cp1253'                    => 'windows-1253',
+        'cp1254'                    => 'windows-1254',
+        'cp1255'                    => 'windows-1255',
+        'cp1256'                    => 'windows-1256',
+        'cp1257'                    => 'windows-1257',
+        'cp1258'                    => 'windows-1258',
+        'cp819'                     => 'ISO-8859-1',
+        'cp850'                     => 'IBM850',
+        'cp852'                     => 'IBM852',
+        'cp855'                     => 'IBM855',
+        'cp857'                     => 'IBM857',
+        'cp862'                     => 'IBM862',
+        'cp864'                     => 'IBM864',
+        'cp864i'                    => 'IBM864i',
+        'cp866'                     => 'IBM866',
+        'cp932'                     => 'Shift_JIS',
+        'csbig5'                    => 'Big5',
+        'cseucjpkdfmtjapanese'      => 'EUC-JP',
+        'cseuckr'                   => 'EUC-KR',
+        'cseucpkdfmtjapanese'       => 'EUC-JP',
+        'csgb2312'                  => 'GB2312',
+        'csibm850'                  => 'IBM850',
+        'csibm852'                  => 'IBM852',
+        'csibm855'                  => 'IBM855',
+        'csibm857'                  => 'IBM857',
+        'csibm862'                  => 'IBM862',
+        'csibm864'                  => 'IBM864',
+        'csibm864i'                 => 'IBM864i',
+        'csibm866'                  => 'IBM866',
+        'csiso103t618bit'           => 'T.61-8bit',
+        'csiso111ecmacyrillic'      => 'ISO-IR-111',
+        'csiso2022jp'               => 'ISO-2022-JP',
+        'csiso2022jp2'              => 'ISO-2022-JP',
+        'csiso2022kr'               => 'ISO-2022-KR',
+        'csiso58gb231280'           => 'GB2312',
+        'csiso88596e'               => 'ISO-8859-6-E',
+        'csiso88596i'               => 'ISO-8859-6-I',
+        'csiso88598e'               => 'ISO-8859-8-E',
+        'csiso88598i'               => 'ISO-8859-8-I',
+        'csisolatin1'               => 'ISO-8859-1',
+        'csisolatin2'               => 'ISO-8859-2',
+        'csisolatin3'               => 'ISO-8859-3',
+        'csisolatin4'               => 'ISO-8859-4',
+        'csisolatin5'               => 'ISO-8859-9',
+        'csisolatin6'               => 'ISO-8859-10',
+        'csisolatin9'               => 'ISO-8859-15',
+        'csisolatinarabic'          => 'ISO-8859-6',
+        'csisolatincyrillic'        => 'ISO-8859-5',
+        'csisolatingreek'           => 'ISO-8859-7',
+        'csisolatinhebrew'          => 'ISO-8859-8',
+        'cskoi8r'                   => 'KOI8-R',
+        'csksc56011987'             => 'EUC-KR',
+        'csmacintosh'               => 'x-mac-roman',
+        'csshiftjis'                => 'Shift_JIS',
+        'csueckr'                   => 'EUC-KR',
+        'csunicode'                 => 'UTF-16BE',
+        'csunicode11'               => 'UTF-16BE',
+        'csunicode11utf7'           => 'UTF-7',
+        'csunicodeascii'            => 'UTF-16BE',
+        'csunicodelatin1'           => 'UTF-16BE',
+        'csviqr'                    => 'VIQR',
+        'csviscii'                  => 'VISCII',
+        'cyrillic'                  => 'ISO-8859-5',
+        'dos-874'                   => 'windows-874',
+        'ecma-114'                  => 'ISO-8859-6',
+        'ecma-118'                  => 'ISO-8859-7',
+        'ecma-cyrillic'             => 'ISO-IR-111',
+        'elot_928'                  => 'ISO-8859-7',
+        'gb_2312'                   => 'GB2312',
+        'gb_2312-80'                => 'GB2312',
+        'greek'                     => 'ISO-8859-7',
+        'greek8'                    => 'ISO-8859-7',
+        'hebrew'                    => 'ISO-8859-8',
+        'ibm-864'                   => 'IBM864',
+        'ibm-864i'                  => 'IBM864i',
+        'ibm819'                    => 'ISO-8859-1',
+        'ibm874'                    => 'windows-874',
+        'iso-10646'                 => 'UTF-16BE',
+        'iso-10646-j-1'             => 'UTF-16BE',
+        'iso-10646-ucs-2'           => 'UTF-16BE',
+        'iso-10646-ucs-4'           => 'UTF-32BE',
+        'iso-10646-ucs-basic'       => 'UTF-16BE',
+        'iso-10646-unicode-latin1'  => 'UTF-16BE',
+        'iso-2022-cn-ext'           => 'ISO-2022-CN',
+        'iso-2022-jp-2'             => 'ISO-2022-JP',
+        'iso-8859-8i'               => 'ISO-8859-8-I',
+        'iso-ir-100'                => 'ISO-8859-1',
+        'iso-ir-101'                => 'ISO-8859-2',
+        'iso-ir-103'                => 'T.61-8bit',
+        'iso-ir-109'                => 'ISO-8859-3',
+        'iso-ir-110'                => 'ISO-8859-4',
+        'iso-ir-126'                => 'ISO-8859-7',
+        'iso-ir-127'                => 'ISO-8859-6',
+        'iso-ir-138'                => 'ISO-8859-8',
+        'iso-ir-144'                => 'ISO-8859-5',
+        'iso-ir-148'                => 'ISO-8859-9',
+        'iso-ir-149'                => 'EUC-KR',
+        'iso-ir-157'                => 'ISO-8859-10',
+        'iso-ir-58'                 => 'GB2312',
+        'iso8859-1'                 => 'ISO-8859-1',
+        'iso8859-10'                => 'ISO-8859-10',
+        'iso8859-11'                => 'ISO-8859-11',
+        'iso8859-13'                => 'ISO-8859-13',
+        'iso8859-14'                => 'ISO-8859-14',
+        'iso8859-15'                => 'ISO-8859-15',
+        'iso8859-2'                 => 'ISO-8859-2',
+        'iso8859-3'                 => 'ISO-8859-3',
+        'iso8859-4'                 => 'ISO-8859-4',
+        'iso8859-5'                 => 'ISO-8859-5',
+        'iso8859-6'                 => 'ISO-8859-6',
+        'iso8859-7'                 => 'ISO-8859-7',
+        'iso8859-8'                 => 'ISO-8859-8',
+        'iso8859-9'                 => 'ISO-8859-9',
+        'iso88591'                  => 'ISO-8859-1',
+        'iso885910'                 => 'ISO-8859-10',
+        'iso885911'                 => 'ISO-8859-11',
+        'iso885912'                 => 'ISO-8859-12',
+        'iso885913'                 => 'ISO-8859-13',
+        'iso885914'                 => 'ISO-8859-14',
+        'iso885915'                 => 'ISO-8859-15',
+        'iso88592'                  => 'ISO-8859-2',
+        'iso88593'                  => 'ISO-8859-3',
+        'iso88594'                  => 'ISO-8859-4',
+        'iso88595'                  => 'ISO-8859-5',
+        'iso88596'                  => 'ISO-8859-6',
+        'iso88597'                  => 'ISO-8859-7',
+        'iso88598'                  => 'ISO-8859-8',
+        'iso88599'                  => 'ISO-8859-9',
+        'iso_8859-1'                => 'ISO-8859-1',
+        'iso_8859-15'               => 'ISO-8859-15',
+        'iso_8859-1:1987'           => 'ISO-8859-1',
+        'iso_8859-2'                => 'ISO-8859-2',
+        'iso_8859-2:1987'           => 'ISO-8859-2',
+        'iso_8859-3'                => 'ISO-8859-3',
+        'iso_8859-3:1988'           => 'ISO-8859-3',
+        'iso_8859-4'                => 'ISO-8859-4',
+        'iso_8859-4:1988'           => 'ISO-8859-4',
+        'iso_8859-5'                => 'ISO-8859-5',
+        'iso_8859-5:1988'           => 'ISO-8859-5',
+        'iso_8859-6'                => 'ISO-8859-6',
+        'iso_8859-6:1987'           => 'ISO-8859-6',
+        'iso_8859-7'                => 'ISO-8859-7',
+        'iso_8859-7:1987'           => 'ISO-8859-7',
+        'iso_8859-8'                => 'ISO-8859-8',
+        'iso_8859-8:1988'           => 'ISO-8859-8',
+        'iso_8859-9'                => 'ISO-8859-9',
+        'iso_8859-9:1989'           => 'ISO-8859-9',
+        'koi'                       => 'KOI8-R',
+        'koi8'                      => 'KOI8-R',
+        'koi8-ru'                   => 'KOI8-U',
+        'koi8_r'                    => 'KOI8-R',
+        'korean'                    => 'EUC-KR',
+        'ks_c_5601-1987'            => 'EUC-KR',
+        'ks_c_5601-1989'            => 'EUC-KR',
+        'ksc5601'                   => 'EUC-KR',
+        'ksc_5601'                  => 'EUC-KR',
+        'l1'                        => 'ISO-8859-1',
+        'l2'                        => 'ISO-8859-2',
+        'l3'                        => 'ISO-8859-3',
+        'l4'                        => 'ISO-8859-4',
+        'l5'                        => 'ISO-8859-9',
+        'l6'                        => 'ISO-8859-10',
+        'l9'                        => 'ISO-8859-15',
+        'latin1'                    => 'ISO-8859-1',
+        'latin2'                    => 'ISO-8859-2',
+        'latin3'                    => 'ISO-8859-3',
+        'latin4'                    => 'ISO-8859-4',
+        'latin5'                    => 'ISO-8859-9',
+        'latin6'                    => 'ISO-8859-10',
+        'logical'                   => 'ISO-8859-8-I',
+        'mac'                       => 'x-mac-roman',
+        'macintosh'                 => 'x-mac-roman',
+        'ms932'                     => 'Shift_JIS',
+        'ms_kanji'                  => 'Shift_JIS',
+        'shift-jis'                 => 'Shift_JIS',
+        'sjis'                      => 'Shift_JIS',
+        'sun_eu_greek'              => 'ISO-8859-7',
+        't.61'                      => 'T.61-8bit',
+        'tis620'                    => 'TIS-620',
+        'unicode-1-1-utf-7'         => 'UTF-7',
+        'unicode-1-1-utf-8'         => 'UTF-8',
+        'unicode-2-0-utf-7'         => 'UTF-7',
+        'visual'                    => 'ISO-8859-8',
+        'windows-31j'               => 'Shift_JIS',
+        'windows-949'               => 'EUC-KR',
+        'x-cp1250'                  => 'windows-1250',
+        'x-cp1251'                  => 'windows-1251',
+        'x-cp1252'                  => 'windows-1252',
+        'x-cp1253'                  => 'windows-1253',
+        'x-cp1254'                  => 'windows-1254',
+        'x-cp1255'                  => 'windows-1255',
+        'x-cp1256'                  => 'windows-1256',
+        'x-cp1257'                  => 'windows-1257',
+        'x-cp1258'                  => 'windows-1258',
+        'x-euc-jp'                  => 'EUC-JP',
+        'x-gbk'                     => 'gbk',
+        'x-iso-10646-ucs-2-be'      => 'UTF-16BE',
+        'x-iso-10646-ucs-2-le'      => 'UTF-16LE',
+        'x-iso-10646-ucs-4-be'      => 'UTF-32BE',
+        'x-iso-10646-ucs-4-le'      => 'UTF-32LE',
+        'x-sjis'                    => 'Shift_JIS',
+        'x-unicode-2-0-utf-7'       => 'UTF-7',
+        'x-x-big5'                  => 'Big5',
+        'zh_cn.euc'                 => 'GB2312',
+        'zh_tw-big5'                => 'Big5',
+        'zh_tw-euc'                 => 'x-euc-tw',
+    ];
+
+    /**
+     * Decode text to UTF-8.
+     *
+     * @param string $text        Text to decode
+     * @param string $fromCharset Original charset
+     */
+    public static function decode(string $text, string $fromCharset): string
+    {
+        static $utf8Aliases = [
+            'unicode-1-1-utf-8' => true,
+            'utf8'              => true,
+            'utf-8'             => true,
+            'UTF8'              => true,
+            'UTF-8'             => true,
+        ];
+
+        if (isset($utf8Aliases[$fromCharset])) {
+            return $text;
+        }
+
+        $originalFromCharset  = $fromCharset;
+        $lowercaseFromCharset = \strtolower($fromCharset);
+        if (isset(self::$charsetAliases[$lowercaseFromCharset])) {
+            $fromCharset = self::$charsetAliases[$lowercaseFromCharset];
+        }
+
+        \set_error_handler(static function (): bool {
+            return true;
+        });
+
+        $iconvDecodedText = \iconv($fromCharset, 'UTF-8', $text);
+        if (false === $iconvDecodedText) {
+            $iconvDecodedText = \iconv($originalFromCharset, 'UTF-8', $text);
+        }
+
+        \restore_error_handler();
+
+        if (false !== $iconvDecodedText) {
+            return $iconvDecodedText;
+        }
+
+        $errorMessage = null;
+        $errorNumber  = 0;
+        \set_error_handler(static function ($nr, $message) use (&$errorMessage, &$errorNumber): bool {
+            $errorMessage = $message;
+            $errorNumber = $nr;
+
+            return true;
+        });
+
+        $decodedText = \mb_convert_encoding($text, 'UTF-8', $fromCharset);
+
+        \restore_error_handler();
+
+        if (null !== $errorMessage) {
+            throw new UnsupportedCharsetException(\sprintf(
+                'Unsupported charset "%s"%s: %s',
+                $originalFromCharset,
+                ($fromCharset !== $originalFromCharset) ? \sprintf(' (alias found: "%s")', $fromCharset) : '',
+                $errorMessage
+            ), $errorNumber);
+        }
+
+        return $decodedText;
+    }
+}