glastree_on_gitea
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the ZBateson\MailMimeParser project.
|
||||
*
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
*/
|
||||
|
||||
namespace ZBateson\MailMimeParser\Header\Part;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
use ZBateson\MbWrapper\MbWrapper;
|
||||
|
||||
/**
|
||||
* Holds a group of addresses and a group name.
|
||||
*
|
||||
* @author Zaahid Bateson
|
||||
*/
|
||||
class AddressGroupPart extends NameValuePart
|
||||
{
|
||||
/**
|
||||
* @var AddressPart[] an array of AddressParts
|
||||
*/
|
||||
protected array $addresses;
|
||||
|
||||
/**
|
||||
* Creates an AddressGroupPart out of the passed array of AddressParts/
|
||||
* AddressGroupParts and name.
|
||||
*
|
||||
* @param HeaderPart[] $nameParts
|
||||
* @param AddressPart[]|AddressGroupPart[] $addressesAndGroupParts
|
||||
*/
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
MbWrapper $charsetConverter,
|
||||
array $nameParts,
|
||||
array $addressesAndGroupParts
|
||||
) {
|
||||
parent::__construct(
|
||||
$logger,
|
||||
$charsetConverter,
|
||||
$nameParts,
|
||||
$addressesAndGroupParts
|
||||
);
|
||||
$this->addresses = \array_merge(...\array_map(
|
||||
fn ($p) => ($p instanceof AddressGroupPart) ? $p->getAddresses() : [$p],
|
||||
$addressesAndGroupParts
|
||||
));
|
||||
// for backwards compatibility
|
||||
$this->value = $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the AddressGroupPart's array of addresses.
|
||||
*
|
||||
* @return AddressPart[] An array of address parts.
|
||||
*/
|
||||
public function getAddresses() : array
|
||||
{
|
||||
return $this->addresses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AddressPart at the passed index or null.
|
||||
*
|
||||
* @param int $index The 0-based index.
|
||||
* @return ?AddressPart The address.
|
||||
*/
|
||||
public function getAddress(int $index) : ?AddressPart
|
||||
{
|
||||
if (!isset($this->addresses[$index])) {
|
||||
return null;
|
||||
}
|
||||
return $this->addresses[$index];
|
||||
}
|
||||
|
||||
protected function validate() : void
|
||||
{
|
||||
if ($this->name === null || \mb_strlen($this->name) === 0) {
|
||||
$this->addError('Address group doesn\'t have a name', LogLevel::ERROR);
|
||||
}
|
||||
if (empty($this->addresses)) {
|
||||
$this->addError('Address group doesn\'t have any email addresses defined in it', LogLevel::NOTICE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the ZBateson\MailMimeParser project.
|
||||
*
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
*/
|
||||
|
||||
namespace ZBateson\MailMimeParser\Header\Part;
|
||||
|
||||
use Psr\Log\LogLevel;
|
||||
|
||||
/**
|
||||
* Holds a single address or name/address pair.
|
||||
*
|
||||
* The name part of the address may be mime-encoded, but the email address part
|
||||
* can't be mime-encoded. Any whitespace in the email address part is stripped
|
||||
* out.
|
||||
*
|
||||
* A convenience method, getEmail, is provided for clarity -- but getValue
|
||||
* returns the email address as well.
|
||||
*
|
||||
* @author Zaahid Bateson
|
||||
*/
|
||||
class AddressPart extends NameValuePart
|
||||
{
|
||||
protected function getValueFromParts(array $parts) : string
|
||||
{
|
||||
return \implode('', \array_map(
|
||||
function($p) {
|
||||
if ($p instanceof AddressPart) {
|
||||
return $p->getValue();
|
||||
} elseif ($p instanceof QuotedLiteralPart && $p->getValue() !== '') {
|
||||
return '"' . \preg_replace('/(["\\\])/', '\\\$1', $p->getValue()) . '"';
|
||||
}
|
||||
return \preg_replace('/\s+/', '', $p->getValue());
|
||||
},
|
||||
$parts
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the email address.
|
||||
*
|
||||
* @return string The email address.
|
||||
*/
|
||||
public function getEmail() : string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
protected function validate() : void
|
||||
{
|
||||
if (empty($this->value)) {
|
||||
$this->addError('Address doesn\'t contain an email address', LogLevel::ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the ZBateson\MailMimeParser project.
|
||||
*
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
*/
|
||||
|
||||
namespace ZBateson\MailMimeParser\Header\Part;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ZBateson\MbWrapper\MbWrapper;
|
||||
|
||||
/**
|
||||
* Represents a mime header comment -- text in a structured mime header
|
||||
* value existing within parentheses.
|
||||
*
|
||||
* @author Zaahid Bateson
|
||||
*/
|
||||
class CommentPart extends ContainerPart
|
||||
{
|
||||
/**
|
||||
* @var HeaderPartFactory used to create intermediate parts.
|
||||
*/
|
||||
protected HeaderPartFactory $partFactory;
|
||||
|
||||
/**
|
||||
* @var string the contents of the comment
|
||||
*/
|
||||
protected string $comment;
|
||||
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
MbWrapper $charsetConverter,
|
||||
HeaderPartFactory $partFactory,
|
||||
array $children
|
||||
) {
|
||||
$this->partFactory = $partFactory;
|
||||
parent::__construct($logger, $charsetConverter, $children);
|
||||
$this->comment = $this->value;
|
||||
$this->value = '';
|
||||
$this->isSpace = true;
|
||||
$this->canIgnoreSpacesBefore = true;
|
||||
$this->canIgnoreSpacesAfter = true;
|
||||
}
|
||||
|
||||
protected function getValueFromParts(array $parts) : string
|
||||
{
|
||||
$partFactory = $this->partFactory;
|
||||
return parent::getValueFromParts(\array_map(
|
||||
function($p) use ($partFactory) {
|
||||
if ($p instanceof CommentPart) {
|
||||
return $partFactory->newQuotedLiteralPart([$partFactory->newToken('(' . $p->getComment() . ')')]);
|
||||
} elseif ($p instanceof QuotedLiteralPart) {
|
||||
return $partFactory->newQuotedLiteralPart([$partFactory->newToken('"' . \str_replace('(["\\])', '\$1', $p->getValue()) . '"')]);
|
||||
}
|
||||
return $p;
|
||||
},
|
||||
$parts
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the comment's text.
|
||||
*/
|
||||
public function getComment() : string
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an empty string.
|
||||
*/
|
||||
public function getValue() : string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the ZBateson\MailMimeParser project.
|
||||
*
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
*/
|
||||
|
||||
namespace ZBateson\MailMimeParser\Header\Part;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ZBateson\MailMimeParser\ErrorBag;
|
||||
use ZBateson\MbWrapper\MbWrapper;
|
||||
|
||||
/**
|
||||
* Base HeaderPart for a part that consists of other parts.
|
||||
*
|
||||
* The base container part constructs a string value out of the passed parts by
|
||||
* concatenating their values, discarding whitespace between parts that can be
|
||||
* ignored (in general allows for a single space but removes extras.)
|
||||
*
|
||||
* A ContainerPart can also contain any number of child comment parts. The
|
||||
* CommentParts in this and all child parts can be returned by calling
|
||||
* getComments.
|
||||
*
|
||||
* @author Zaahid Bateson
|
||||
*/
|
||||
class ContainerPart extends HeaderPart
|
||||
{
|
||||
/**
|
||||
* @var HeaderPart[] parts that were used to create this part, collected for
|
||||
* proper error reporting and validation.
|
||||
*/
|
||||
protected $children = [];
|
||||
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
MbWrapper $charsetConverter,
|
||||
array $children
|
||||
) {
|
||||
ErrorBag::__construct($logger);
|
||||
$this->charsetConverter = $charsetConverter;
|
||||
$this->children = $children;
|
||||
$str = (!empty($children)) ? $this->getValueFromParts($children) : '';
|
||||
parent::__construct(
|
||||
$logger,
|
||||
$this->charsetConverter,
|
||||
$str
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters out ignorable space tokens.
|
||||
*
|
||||
* Spaces are removed if parts on either side of it have their
|
||||
* canIgnoreSpaceAfter/canIgnoreSpaceBefore properties set to true.
|
||||
*
|
||||
* @param HeaderPart[] $parts
|
||||
* @return HeaderPart[]
|
||||
*/
|
||||
protected function filterIgnoredSpaces(array $parts) : array
|
||||
{
|
||||
$ends = (object) ['isSpace' => true, 'canIgnoreSpacesAfter' => true, 'canIgnoreSpacesBefore' => true, 'value' => ''];
|
||||
|
||||
$spaced = \array_merge($parts, [$ends]);
|
||||
$filtered = \array_slice(\array_reduce(
|
||||
\array_slice(\array_keys($spaced), 0, -1),
|
||||
function($carry, $key) use ($spaced, $ends) {
|
||||
$p = $spaced[$key];
|
||||
$l = \end($carry);
|
||||
$a = $spaced[$key + 1];
|
||||
if ($p->isSpace && $a === $ends) {
|
||||
// trim
|
||||
if ($l->isSpace) {
|
||||
\array_pop($carry);
|
||||
}
|
||||
return $carry;
|
||||
} elseif ($p->isSpace && ($l->isSpace || ($l->canIgnoreSpacesAfter && $a->canIgnoreSpacesBefore))) {
|
||||
return $carry;
|
||||
}
|
||||
return \array_merge($carry, [$p]);
|
||||
},
|
||||
[$ends]
|
||||
), 1);
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the string value representation of this part constructed from the
|
||||
* child parts passed to it.
|
||||
*
|
||||
* The default implementation filters out ignorable whitespace between
|
||||
* parts, and concatenates parts calling 'getValue'.
|
||||
*
|
||||
* @param HeaderParts[] $parts
|
||||
*/
|
||||
protected function getValueFromParts(array $parts) : string
|
||||
{
|
||||
return \array_reduce($this->filterIgnoredSpaces($parts), fn ($c, $p) => $c . $p->getValue(), '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the child parts this container part consists of.
|
||||
*
|
||||
* @return IHeaderPart[]
|
||||
*/
|
||||
public function getChildParts() : array
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
|
||||
public function getComments() : array
|
||||
{
|
||||
return \array_merge(...\array_filter(\array_map(
|
||||
fn ($p) => ($p instanceof CommentPart) ? [$p] : $p->getComments(),
|
||||
$this->children
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this part's children, same as getChildParts().
|
||||
*
|
||||
* @return ErrorBag
|
||||
*/
|
||||
protected function getErrorBagChildren() : array
|
||||
{
|
||||
return $this->children;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the ZBateson\MailMimeParser project.
|
||||
*
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
*/
|
||||
|
||||
namespace ZBateson\MailMimeParser\Header\Part;
|
||||
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
use ZBateson\MbWrapper\MbWrapper;
|
||||
|
||||
/**
|
||||
* Represents the value of a date header, parsing the date into a \DateTime
|
||||
* object.
|
||||
*
|
||||
* @author Zaahid Bateson
|
||||
*/
|
||||
class DatePart extends ContainerPart
|
||||
{
|
||||
/**
|
||||
* @var DateTime the parsed date, or null if the date could not be parsed
|
||||
*/
|
||||
protected ?DateTime $date = null;
|
||||
|
||||
/**
|
||||
* Tries parsing the passed token as an RFC 2822 date, and failing that into
|
||||
* an RFC 822 date, and failing that, tries to parse it by calling
|
||||
* new DateTime($value).
|
||||
*
|
||||
* @param HeaderPart[] $children
|
||||
*/
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
MbWrapper $charsetConverter,
|
||||
array $children
|
||||
) {
|
||||
// parent::__construct converts character encoding -- may cause problems sometimes.
|
||||
parent::__construct($logger, $charsetConverter, $children);
|
||||
$this->value = $dateToken = \trim($this->value);
|
||||
|
||||
// Missing "+" in timezone definition. eg: Thu, 13 Mar 2014 15:02:47 0000 (not RFC compliant)
|
||||
// Won't result in an Exception, but in a valid DateTime in year `0000` - therefore we need to check this first:
|
||||
if (\preg_match('# [0-9]{4}$#', $dateToken)) {
|
||||
$dateToken = \preg_replace('# ([0-9]{4})$#', ' +$1', $dateToken);
|
||||
// @see https://bugs.php.net/bug.php?id=42486
|
||||
} elseif (\preg_match('#UT$#', $dateToken)) {
|
||||
$dateToken = $dateToken . 'C';
|
||||
}
|
||||
|
||||
try {
|
||||
$this->date = new DateTime($dateToken);
|
||||
} catch (Exception $e) {
|
||||
$this->addError(
|
||||
"Unable to parse date from header: \"{$dateToken}\"",
|
||||
LogLevel::ERROR,
|
||||
$e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DateTime object or null if it can't be parsed.
|
||||
*/
|
||||
public function getDateTime() : ?DateTime
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the ZBateson\MailMimeParser project.
|
||||
*
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
*/
|
||||
|
||||
namespace ZBateson\MailMimeParser\Header\Part;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
use ZBateson\MailMimeParser\ErrorBag;
|
||||
use ZBateson\MailMimeParser\Header\IHeaderPart;
|
||||
use ZBateson\MbWrapper\MbWrapper;
|
||||
use ZBateson\MbWrapper\UnsupportedCharsetException;
|
||||
|
||||
/**
|
||||
* Abstract base class representing a single part of a parsed header.
|
||||
*
|
||||
* @author Zaahid Bateson
|
||||
*/
|
||||
abstract class HeaderPart extends ErrorBag implements IHeaderPart
|
||||
{
|
||||
/**
|
||||
* @var string the representative value of the part after any conversion or
|
||||
* processing has been done on it (e.g. removing new lines, converting,
|
||||
* whatever else).
|
||||
*/
|
||||
protected string $value;
|
||||
|
||||
/**
|
||||
* @var MbWrapper $charsetConverter the charset converter used for
|
||||
* converting strings in HeaderPart::convertEncoding
|
||||
*/
|
||||
protected MbWrapper $charsetConverter;
|
||||
|
||||
/**
|
||||
* @var bool set to true to ignore spaces before this part
|
||||
*/
|
||||
protected bool $canIgnoreSpacesBefore = false;
|
||||
|
||||
/**
|
||||
* @var bool set to true to ignore spaces after this part
|
||||
*/
|
||||
protected bool $canIgnoreSpacesAfter = false;
|
||||
|
||||
/**
|
||||
* True if the part is a space token
|
||||
*/
|
||||
protected bool $isSpace = false;
|
||||
|
||||
public function __construct(LoggerInterface $logger, MbWrapper $charsetConverter, string $value)
|
||||
{
|
||||
parent::__construct($logger);
|
||||
$this->charsetConverter = $charsetConverter;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the part's representative value after any necessary processing
|
||||
* has been performed. For the raw value, call getRawValue().
|
||||
*/
|
||||
public function getValue() : string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the part (which is a string).
|
||||
*
|
||||
* @return string the value
|
||||
*/
|
||||
public function __toString() : string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the encoding of the passed string is set to UTF-8.
|
||||
*
|
||||
* The method does nothing if the passed $from charset is UTF-8 already, or
|
||||
* if $force is set to false and mb_check_encoding for $str returns true
|
||||
* for 'UTF-8'.
|
||||
*
|
||||
* @return string utf-8 string
|
||||
*/
|
||||
protected function convertEncoding(string $str, string $from = 'ISO-8859-1', bool $force = false) : string
|
||||
{
|
||||
if ($from !== 'UTF-8') {
|
||||
// mime header part decoding will force it. This is necessary for
|
||||
// UTF-7 because mb_check_encoding will return true
|
||||
if ($force || !($this->charsetConverter->checkEncoding($str, 'UTF-8'))) {
|
||||
try {
|
||||
return $this->charsetConverter->convert($str, $from, 'UTF-8');
|
||||
} catch (UnsupportedCharsetException $ce) {
|
||||
$this->addError('Unable to convert charset', LogLevel::ERROR, $ce);
|
||||
return $this->charsetConverter->convert($str, 'ISO-8859-1', 'UTF-8');
|
||||
}
|
||||
}
|
||||
}
|
||||
return $str;
|
||||
}
|
||||
|
||||
public function getComments() : array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation returns an empty array.
|
||||
*/
|
||||
protected function getErrorBagChildren() : array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the ZBateson\MailMimeParser project.
|
||||
*
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
*/
|
||||
|
||||
namespace ZBateson\MailMimeParser\Header\Part;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ZBateson\MailMimeParser\Header\IHeaderPart;
|
||||
use ZBateson\MbWrapper\MbWrapper;
|
||||
|
||||
/**
|
||||
* Constructs and returns IHeaderPart objects.
|
||||
*
|
||||
* @author Zaahid Bateson
|
||||
*/
|
||||
class HeaderPartFactory
|
||||
{
|
||||
/**
|
||||
* @var MbWrapper $charsetConverter passed to IHeaderPart constructors
|
||||
* for converting strings in IHeaderPart::convertEncoding
|
||||
*/
|
||||
protected MbWrapper $charsetConverter;
|
||||
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
public function __construct(LoggerInterface $logger, MbWrapper $charsetConverter)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$this->charsetConverter = $charsetConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a default IHeaderPart for this factory, allowing
|
||||
* subclass factories for specialized IHeaderParts.
|
||||
*
|
||||
* The default implementation returns a new Token
|
||||
*/
|
||||
public function newInstance(string $value) : IHeaderPart
|
||||
{
|
||||
return $this->newToken($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and returns a new Token.
|
||||
*/
|
||||
public function newToken(string $value, bool $isLiteral = false, bool $preserveSpaces = false) : Token
|
||||
{
|
||||
return new Token($this->logger, $this->charsetConverter, $value, $isLiteral, $preserveSpaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and returns a new SubjectToken.
|
||||
*/
|
||||
public function newSubjectToken(string $value) : SubjectToken
|
||||
{
|
||||
return new SubjectToken($this->logger, $this->charsetConverter, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and returns a new MimeToken.
|
||||
*/
|
||||
public function newMimeToken(string $value) : MimeToken
|
||||
{
|
||||
return new MimeToken($this->logger, $this->charsetConverter, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and returns a new ContainerPart.
|
||||
*
|
||||
* @param HeaderPart[] $children
|
||||
*/
|
||||
public function newContainerPart(array $children) : ContainerPart
|
||||
{
|
||||
return new ContainerPart($this->logger, $this->charsetConverter, $children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates and returns a SplitParameterPart.
|
||||
*
|
||||
* @param ParameterPart[] $children
|
||||
*/
|
||||
public function newSplitParameterPart(array $children) : SplitParameterPart
|
||||
{
|
||||
return new SplitParameterPart($this->logger, $this->charsetConverter, $this, $children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and returns a new QuotedLiteralPart.
|
||||
*
|
||||
* @param HeaderPart[] $parts
|
||||
*/
|
||||
public function newQuotedLiteralPart(array $parts) : QuotedLiteralPart
|
||||
{
|
||||
return new QuotedLiteralPart($this->logger, $this->charsetConverter, $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and returns a new CommentPart.
|
||||
*
|
||||
* @param HeaderPart[] $children
|
||||
*/
|
||||
public function newCommentPart(array $children) : CommentPart
|
||||
{
|
||||
return new CommentPart($this->logger, $this->charsetConverter, $this, $children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and returns a new AddressPart.
|
||||
*
|
||||
* @param HeaderPart[] $nameParts
|
||||
* @param HeaderPart[] $emailParts
|
||||
*/
|
||||
public function newAddress(array $nameParts, array $emailParts) : AddressPart
|
||||
{
|
||||
return new AddressPart($this->logger, $this->charsetConverter, $nameParts, $emailParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and returns a new AddressGroupPart
|
||||
*
|
||||
* @param HeaderPart[] $nameParts
|
||||
* @param AddressPart[]|AddressGroupPart[] $addressesAndGroups
|
||||
*/
|
||||
public function newAddressGroupPart(array $nameParts, array $addressesAndGroups) : AddressGroupPart
|
||||
{
|
||||
return new AddressGroupPart($this->logger, $this->charsetConverter, $nameParts, $addressesAndGroups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and returns a new DatePart
|
||||
*
|
||||
* @param HeaderPart[] $children
|
||||
*/
|
||||
public function newDatePart(array $children) : DatePart
|
||||
{
|
||||
return new DatePart($this->logger, $this->charsetConverter, $children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and returns a new ParameterPart.
|
||||
*
|
||||
* @param HeaderPart[] $nameParts
|
||||
*/
|
||||
public function newParameterPart(array $nameParts, ContainerPart $valuePart) : ParameterPart
|
||||
{
|
||||
return new ParameterPart($this->logger, $this->charsetConverter, $nameParts, $valuePart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and returns a new ReceivedPart.
|
||||
*
|
||||
* @param HeaderPart[] $children
|
||||
*/
|
||||
public function newReceivedPart(string $name, array $children) : ReceivedPart
|
||||
{
|
||||
return new ReceivedPart($this->logger, $this->charsetConverter, $name, $children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and returns a new ReceivedDomainPart.
|
||||
*
|
||||
* @param HeaderPart[] $children
|
||||
*/
|
||||
public function newReceivedDomainPart(string $name, array $children) : ReceivedDomainPart
|
||||
{
|
||||
return new ReceivedDomainPart(
|
||||
$this->logger,
|
||||
$this->charsetConverter,
|
||||
$name,
|
||||
$children
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the ZBateson\MailMimeParser project.
|
||||
*
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
*/
|
||||
|
||||
namespace ZBateson\MailMimeParser\Header\Part;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ZBateson\MbWrapper\MbWrapper;
|
||||
|
||||
/**
|
||||
* Represents a single mime header part token, with the possibility of it being
|
||||
* MIME-Encoded as per RFC-2047.
|
||||
*
|
||||
* MimeToken automatically decodes the value if it's encoded.
|
||||
*
|
||||
* @author Zaahid Bateson
|
||||
*/
|
||||
class MimeToken extends Token
|
||||
{
|
||||
/**
|
||||
* @var string regex pattern matching a mime-encoded part
|
||||
*/
|
||||
public const MIME_PART_PATTERN = '=\?[^?=]+\?[QBqb]\?[^\?]+\?=';
|
||||
|
||||
/**
|
||||
* @var string regex pattern used when parsing parameterized headers
|
||||
*/
|
||||
public const MIME_PART_PATTERN_NO_QUOTES = '=\?[^\?=]+\?[QBqb]\?[^\?"]+\?=';
|
||||
|
||||
/**
|
||||
* @var ?string the language code if any, or null otherwise
|
||||
*/
|
||||
protected ?string $language = null;
|
||||
|
||||
/**
|
||||
* @var ?string the charset if any, or null otherwise
|
||||
*/
|
||||
protected ?string $charset = null;
|
||||
|
||||
public function __construct(LoggerInterface $logger, MbWrapper $charsetConverter, string $value)
|
||||
{
|
||||
parent::__construct($logger, $charsetConverter, $value);
|
||||
$this->value = $this->decodeMime(\preg_replace('/\r|\n/', '', $this->value));
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and replaces mime parts with their values.
|
||||
*
|
||||
* The method splits the token value into an array on mime-part-patterns,
|
||||
* either replacing a mime part with its value by calling iconv_mime_decode
|
||||
* or converts the encoding on the text part by calling convertEncoding.
|
||||
*/
|
||||
protected function decodeMime(string $value) : string
|
||||
{
|
||||
if (\preg_match('/^=\?([A-Za-z\-_0-9]+)\*?([A-Za-z\-_0-9]+)?\?([QBqb])\?([^\?]*)\?=$/', $value, $matches)) {
|
||||
return $this->decodeMatchedEntity($matches);
|
||||
}
|
||||
return $this->convertEncoding($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a matched mime entity part into a string and returns it, after
|
||||
* adding the string into the languages array.
|
||||
*
|
||||
* @param string[] $matches
|
||||
*/
|
||||
private function decodeMatchedEntity(array $matches) : string
|
||||
{
|
||||
$body = $matches[4];
|
||||
if (\strtoupper($matches[3]) === 'Q') {
|
||||
$body = \quoted_printable_decode(\str_replace('_', '=20', $body));
|
||||
} else {
|
||||
$body = \base64_decode($body);
|
||||
}
|
||||
$this->charset = $matches[1];
|
||||
$this->language = (!empty($matches[2])) ? $matches[2] : null;
|
||||
if ($this->charset !== null) {
|
||||
return $this->convertEncoding($body, $this->charset, true);
|
||||
}
|
||||
return $this->convertEncoding($body, 'ISO-8859-1', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the language code for the mime part.
|
||||
*/
|
||||
public function getLanguage() : ?string
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the charset for the encoded part.
|
||||
*/
|
||||
public function getCharset() : ?string
|
||||
{
|
||||
return $this->charset;
|
||||
}
|
||||
|
||||
public function getRawValue() : string
|
||||
{
|
||||
return $this->rawValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the ZBateson\MailMimeParser project.
|
||||
*
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
*/
|
||||
|
||||
namespace ZBateson\MailMimeParser\Header\Part;
|
||||
|
||||
use ZBateson\MailMimeParser\Header\IHeaderPart;
|
||||
|
||||
/**
|
||||
* Extends HeaderPartFactory to instantiate MimeTokens for its
|
||||
* newInstance method.
|
||||
*
|
||||
* @author Zaahid Bateson
|
||||
*/
|
||||
class MimeTokenPartFactory extends HeaderPartFactory
|
||||
{
|
||||
/**
|
||||
* Creates and returns a MimeToken.
|
||||
*/
|
||||
public function newInstance(string $value) : IHeaderPart
|
||||
{
|
||||
return $this->newMimeToken($value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the ZBateson\MailMimeParser project.
|
||||
*
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
*/
|
||||
|
||||
namespace ZBateson\MailMimeParser\Header\Part;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
use ZBateson\MailMimeParser\ErrorBag;
|
||||
use ZBateson\MbWrapper\MbWrapper;
|
||||
|
||||
/**
|
||||
* Represents a name/value pair part of a header.
|
||||
*
|
||||
* @author Zaahid Bateson
|
||||
*/
|
||||
class NameValuePart extends ContainerPart
|
||||
{
|
||||
/**
|
||||
* @var string the name of the part
|
||||
*/
|
||||
protected string $name;
|
||||
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
MbWrapper $charsetConverter,
|
||||
array $nameParts,
|
||||
array $valueParts
|
||||
) {
|
||||
ErrorBag::__construct($logger);
|
||||
$this->charsetConverter = $charsetConverter;
|
||||
$this->name = (!empty($nameParts)) ? $this->getNameFromParts($nameParts) : '';
|
||||
parent::__construct($logger, $charsetConverter, $valueParts);
|
||||
\array_unshift($this->children, ...$nameParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the string 'name' representation of this part constructed from
|
||||
* the child name parts passed to it.
|
||||
*
|
||||
* @param HeaderParts[] $parts
|
||||
*/
|
||||
protected function getNameFromParts(array $parts) : string
|
||||
{
|
||||
return \array_reduce($this->filterIgnoredSpaces($parts), fn ($c, $p) => $c . $p->getValue(), '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the name/value part.
|
||||
*/
|
||||
public function getName() : string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
protected function validate() : void
|
||||
{
|
||||
if ($this->value === '') {
|
||||
$this->addError('NameValuePart value is empty', LogLevel::NOTICE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the ZBateson\MailMimeParser project.
|
||||
*
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
*/
|
||||
|
||||
namespace ZBateson\MailMimeParser\Header\Part;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ZBateson\MbWrapper\MbWrapper;
|
||||
|
||||
/**
|
||||
* Represents a name/value parameter part of a header.
|
||||
*
|
||||
* @author Zaahid Bateson
|
||||
*/
|
||||
class ParameterPart extends NameValuePart
|
||||
{
|
||||
/**
|
||||
* @var string the RFC-1766 language tag if set.
|
||||
*/
|
||||
protected ?string $language = null;
|
||||
|
||||
/**
|
||||
* @var string charset of content if set.
|
||||
*/
|
||||
protected ?string $charset = null;
|
||||
|
||||
/**
|
||||
* @var int the zero-based index of the part if part of a 'continuation' in
|
||||
* an RFC-2231 split parameter.
|
||||
*/
|
||||
protected ?int $index = null;
|
||||
|
||||
/**
|
||||
* @var bool true if the part is an RFC-2231 encoded part, and the value
|
||||
* needs to be decoded.
|
||||
*/
|
||||
protected bool $encoded = false;
|
||||
|
||||
/**
|
||||
* @param HeaderPart[] $nameParts
|
||||
*/
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
MbWrapper $charsetConverter,
|
||||
array $nameParts,
|
||||
ContainerPart $valuePart
|
||||
) {
|
||||
parent::__construct($logger, $charsetConverter, $nameParts, $valuePart->children);
|
||||
}
|
||||
|
||||
protected function getNameFromParts(array $parts) : string
|
||||
{
|
||||
$name = parent::getNameFromParts($parts);
|
||||
if (\preg_match('~^\s*([^\*]+)\*(\d*)(\*)?$~', $name, $matches)) {
|
||||
$name = $matches[1];
|
||||
$this->index = ($matches[2] !== '') ? (int) ($matches[2]) : null;
|
||||
$this->encoded = (($matches[2] === '') || !empty($matches[3]));
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
protected function decodePartValue(string $value, ?string $charset = null) : string
|
||||
{
|
||||
if ($charset !== null) {
|
||||
return $this->convertEncoding(\rawurldecode($value), $charset, true);
|
||||
}
|
||||
return $this->convertEncoding(\rawurldecode($value));
|
||||
}
|
||||
|
||||
protected function getValueFromParts(array $parts) : string
|
||||
{
|
||||
$value = parent::getValueFromParts($parts);
|
||||
if ($this->encoded && \preg_match('~^([^\']*)\'?([^\']*)\'?(.*)$~', $value, $matches)) {
|
||||
$this->charset = (!empty($matches[1]) && !empty($matches[3])) ? $matches[1] : $this->charset;
|
||||
$this->language = (!empty($matches[2])) ? $matches[2] : $this->language;
|
||||
$ev = (empty($matches[3])) ? $matches[1] : $matches[3];
|
||||
// only if it's not part of a SplitParameterPart
|
||||
if ($this->index === null) {
|
||||
// subsequent parts are decoded as a SplitParameterPart since only
|
||||
// the first part are supposed to have charset/language fields
|
||||
return $this->decodePartValue($ev, $this->charset);
|
||||
}
|
||||
return $ev;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the charset if the part is an RFC-2231 part with a charset set.
|
||||
*/
|
||||
public function getCharset() : ?string
|
||||
{
|
||||
return $this->charset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RFC-1766 (or subset) language tag, if the parameter is an
|
||||
* RFC-2231 part with a language tag set.
|
||||
*
|
||||
* @return ?string the language if set, or null if not
|
||||
*/
|
||||
public function getLanguage() : ?string
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
public function isUrlEncoded() : bool
|
||||
{
|
||||
return $this->encoded;
|
||||
}
|
||||
|
||||
public function getIndex() : ?int
|
||||
{
|
||||
return $this->index;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the ZBateson\MailMimeParser project.
|
||||
*
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
*/
|
||||
|
||||
namespace ZBateson\MailMimeParser\Header\Part;
|
||||
|
||||
/**
|
||||
* A quoted literal header string part. The value of the part is stripped of CR
|
||||
* and LF characters, and whitespace between two adjacent MimeTokens is removed.
|
||||
*
|
||||
* @author Zaahid Bateson
|
||||
*/
|
||||
class QuotedLiteralPart extends ContainerPart
|
||||
{
|
||||
/**
|
||||
* Strips spaces found between two adjacent MimeToken parts.
|
||||
* Other whitespace is returned as-is.
|
||||
*
|
||||
* @param HeaderPart[] $parts
|
||||
* @return HeaderPart[]
|
||||
*/
|
||||
protected function filterIgnoredSpaces(array $parts) : array
|
||||
{
|
||||
$filtered = \array_reduce(
|
||||
\array_keys($parts),
|
||||
function($carry, $key) use ($parts) {
|
||||
$cur = $parts[$key];
|
||||
$last = ($carry !== null) ? \end($carry) : null;
|
||||
$next = (count($parts) > $key + 1) ? $parts[$key + 1] : null;
|
||||
if ($last !== null && $next !== null && $cur->isSpace && (
|
||||
$last->canIgnoreSpacesAfter
|
||||
&& $next->canIgnoreSpacesBefore
|
||||
&& $last instanceof MimeToken
|
||||
&& $next instanceof MimeToken
|
||||
)) {
|
||||
return $carry;
|
||||
}
|
||||
return \array_merge($carry ?? [], [$cur]);
|
||||
}
|
||||
);
|
||||
return $filtered;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the ZBateson\MailMimeParser project.
|
||||
*
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
*/
|
||||
|
||||
namespace ZBateson\MailMimeParser\Header\Part;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ZBateson\MbWrapper\MbWrapper;
|
||||
|
||||
/**
|
||||
* Holds extra information about a parsed Received header part, for FROM and BY
|
||||
* parts, namely: ehlo name, hostname, and address.
|
||||
*
|
||||
* The parsed parts would be mapped as follows:
|
||||
*
|
||||
* FROM ehlo name (hostname [address]), for example: FROM computer (domain.com
|
||||
* [1.2.3.4]) would contain "computer" for getEhloName(), domain.com for
|
||||
* getHostname and 1.2.3.4 for getAddress().
|
||||
*
|
||||
* This doesn't change if the ehlo name is an address, it is still returned in
|
||||
* getEhloName(), and not in getAddress(). Additionally square brackets are not
|
||||
* stripped from getEhloName() if its an address. For example: "FROM [1.2.3.4]"
|
||||
* would return "[1.2.3.4]" in a call to getEhloName().
|
||||
*
|
||||
* For further information on how the header's parsed, check the documentation
|
||||
* for {@see \ZBateson\MailMimeParser\Header\Consumer\Received\DomainConsumer}.
|
||||
*
|
||||
* @author Zaahid Bateson
|
||||
*/
|
||||
class ReceivedDomainPart extends ReceivedPart
|
||||
{
|
||||
/**
|
||||
* @var string The name used to identify the server in the EHLO line.
|
||||
*/
|
||||
protected ?string $ehloName = null;
|
||||
|
||||
/**
|
||||
* @var string The hostname.
|
||||
*/
|
||||
protected ?string $hostname = null;
|
||||
|
||||
/**
|
||||
* @var string The address.
|
||||
*/
|
||||
protected ?string $address = null;
|
||||
|
||||
/**
|
||||
* @param HeaderPart[] $children
|
||||
*/
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
MbWrapper $charsetConverter,
|
||||
string $name,
|
||||
array $children
|
||||
) {
|
||||
parent::__construct($logger, $charsetConverter, $name, $children);
|
||||
|
||||
$this->ehloName = ($this->value !== '') ? $this->value : null;
|
||||
$cps = $this->getComments();
|
||||
$commentPart = (!empty($cps)) ? $cps[0] : null;
|
||||
|
||||
$pattern = '~^(\[(IPv[64])?(?P<addr1>[a-f\d\.\:]+)\])?\s*(helo=)?(?P<name>[a-z0-9\-]+[a-z0-9\-\.]+)?\s*(\[(IPv[64])?(?P<addr2>[a-f\d\.\:]+)\])?$~i';
|
||||
if ($commentPart !== null && \preg_match($pattern, $commentPart->getComment(), $matches)) {
|
||||
$this->value .= ' (' . $commentPart->getComment() . ')';
|
||||
$this->hostname = (!empty($matches['name'])) ? $matches['name'] : null;
|
||||
$this->address = (!empty($matches['addr1'])) ? $matches['addr1'] : ((!empty($matches['addr2'])) ? $matches['addr2'] : null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name used to identify the server in the first part of the
|
||||
* extended-domain line.
|
||||
*
|
||||
* Note that this is not necessarily the name used in the EHLO line to an
|
||||
* SMTP server, since implementations differ so much, not much can be
|
||||
* guaranteed except the position it was parsed in.
|
||||
*/
|
||||
public function getEhloName() : ?string
|
||||
{
|
||||
return $this->ehloName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hostname of the server, or whatever string in the hostname
|
||||
* position when parsing (but never an address).
|
||||
*/
|
||||
public function getHostname() : ?string
|
||||
{
|
||||
return $this->hostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the address of the server, or whatever string that looks like an
|
||||
* address in the address position when parsing (but never a hostname).
|
||||
*/
|
||||
public function getAddress() : ?string
|
||||
{
|
||||
return $this->address;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the ZBateson\MailMimeParser project.
|
||||
*
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
*/
|
||||
|
||||
namespace ZBateson\MailMimeParser\Header\Part;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ZBateson\MbWrapper\MbWrapper;
|
||||
|
||||
/**
|
||||
* Represents one parameter in a parsed 'Received' header, e.g. the FROM or VIA
|
||||
* part.
|
||||
*
|
||||
* Note that FROM and BY actually get parsed into a sub-class,
|
||||
* ReceivedDomainPart which keeps track of other sub-parts that can be parsed
|
||||
* from them.
|
||||
*
|
||||
* @author Zaahid Bateson
|
||||
*/
|
||||
class ReceivedPart extends NameValuePart
|
||||
{
|
||||
/**
|
||||
* @param HeaderPart[] $children
|
||||
*/
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
MbWrapper $charsetConverter,
|
||||
string $name,
|
||||
array $children
|
||||
) {
|
||||
parent::__construct($logger, $charsetConverter, [], $children);
|
||||
$this->name = $name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the ZBateson\MailMimeParser project.
|
||||
*
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
*/
|
||||
|
||||
namespace ZBateson\MailMimeParser\Header\Part;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ZBateson\MbWrapper\MbWrapper;
|
||||
|
||||
/**
|
||||
* Holds a running value for an RFC-2231 split header parameter.
|
||||
*
|
||||
* ParameterConsumer creates SplitParameterTokens when a split header parameter
|
||||
* is first found, and adds subsequent split parts to an already created one if
|
||||
* the parameter name matches.
|
||||
*
|
||||
* @author Zaahid Bateson
|
||||
*/
|
||||
class SplitParameterPart extends ParameterPart
|
||||
{
|
||||
/**
|
||||
* @var HeaderPartFactory used to create combined MimeToken parts.
|
||||
*/
|
||||
protected HeaderPartFactory $partFactory;
|
||||
|
||||
/**
|
||||
* Initializes a SplitParameterToken.
|
||||
*
|
||||
* @param ParameterPart[] $children
|
||||
*/
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
MbWrapper $charsetConverter,
|
||||
HeaderPartFactory $headerPartFactory,
|
||||
array $children
|
||||
) {
|
||||
$this->partFactory = $headerPartFactory;
|
||||
NameValuePart::__construct($logger, $charsetConverter, [$children[0]], $children);
|
||||
$this->children = $children;
|
||||
}
|
||||
|
||||
protected function getNameFromParts(array $parts) : string
|
||||
{
|
||||
return $parts[0]->getName();
|
||||
}
|
||||
|
||||
private function getMimeTokens(string $value) : array
|
||||
{
|
||||
$pattern = MimeToken::MIME_PART_PATTERN;
|
||||
// remove whitespace between two adjacent mime encoded parts
|
||||
$normed = \preg_replace("/($pattern)\\s+(?=$pattern)/", '$1', $value);
|
||||
// with PREG_SPLIT_DELIM_CAPTURE, matched and unmatched parts are returned
|
||||
$aMimeParts = \preg_split("/($pattern)/", $normed, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||
return \array_map(
|
||||
fn ($p) => (\preg_match("/$pattern/", $p)) ? $this->partFactory->newMimeToken($p) : $this->partFactory->newToken($p, true, true),
|
||||
$aMimeParts
|
||||
);
|
||||
}
|
||||
|
||||
private function combineAdjacentUnencodedParts(array $parts) : array
|
||||
{
|
||||
$runningValue = '';
|
||||
$returnedParts = [];
|
||||
foreach ($parts as $part) {
|
||||
if (!$part->encoded) {
|
||||
$runningValue .= $part->value;
|
||||
continue;
|
||||
}
|
||||
if (!empty($runningValue)) {
|
||||
$returnedParts = \array_merge($returnedParts, $this->getMimeTokens($runningValue));
|
||||
$runningValue = '';
|
||||
}
|
||||
$returnedParts[] = $part;
|
||||
}
|
||||
if (!empty($runningValue)) {
|
||||
$returnedParts = \array_merge($returnedParts, $this->getMimeTokens($runningValue));
|
||||
}
|
||||
return $returnedParts;
|
||||
}
|
||||
|
||||
protected function getValueFromParts(array $parts) : string
|
||||
{
|
||||
$sorted = $parts;
|
||||
\usort($sorted, fn ($a, $b) => $a->index <=> $b->index);
|
||||
|
||||
$first = $sorted[0];
|
||||
$this->language = $first->language;
|
||||
$charset = $this->charset = $first->charset;
|
||||
|
||||
$combined = $this->combineAdjacentUnencodedParts($sorted);
|
||||
|
||||
return \implode('', \array_map(
|
||||
fn ($p) => ($p instanceof ParameterPart && $p->encoded)
|
||||
? $this->decodePartValue($p->getValue(), ($p->charset === null) ? $charset : $p->charset)
|
||||
: $p->getValue(),
|
||||
$combined
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the ZBateson\MailMimeParser project.
|
||||
*
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
*/
|
||||
|
||||
namespace ZBateson\MailMimeParser\Header\Part;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ZBateson\MbWrapper\MbWrapper;
|
||||
|
||||
/**
|
||||
* Specialized token for subjects that preserves whitespace, except for new
|
||||
* lines.
|
||||
*
|
||||
* New lines are either discarded if followed by a whitespace as should happen
|
||||
* with folding whitespace, or replaced by a single space character if somehow
|
||||
* aren't followed by whitespace.
|
||||
*
|
||||
* @author Zaahid Bateson
|
||||
*/
|
||||
class SubjectToken extends Token
|
||||
{
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
MbWrapper $charsetConverter,
|
||||
string $value
|
||||
) {
|
||||
parent::__construct($logger, $charsetConverter, $value, true);
|
||||
$this->value = \preg_replace(['/(\r|\n)+(\s)\s*/', '/(\r|\n)+/'], ['$2', ' '], $value);
|
||||
$this->isSpace = (\preg_match('/^\s*$/m', $this->value) === 1);
|
||||
$this->canIgnoreSpacesBefore = $this->canIgnoreSpacesAfter = $this->isSpace;
|
||||
}
|
||||
|
||||
public function getValue() : string
|
||||
{
|
||||
return $this->convertEncoding($this->value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of the ZBateson\MailMimeParser project.
|
||||
*
|
||||
* @license http://opensource.org/licenses/bsd-license.php BSD
|
||||
*/
|
||||
|
||||
namespace ZBateson\MailMimeParser\Header\Part;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ZBateson\MbWrapper\MbWrapper;
|
||||
|
||||
/**
|
||||
* Holds a string value token that will require additional processing by a
|
||||
* consumer prior to returning to a client.
|
||||
*
|
||||
* A Token is meant to hold a value for further processing -- for instance when
|
||||
* consuming an address list header (like From or To) -- before it's known what
|
||||
* type of IHeaderPart it is (could be an email address, could be a name, or
|
||||
* could be a group.)
|
||||
*
|
||||
* @author Zaahid Bateson
|
||||
*/
|
||||
class Token extends HeaderPart
|
||||
{
|
||||
/**
|
||||
* @var string the raw value of the part.
|
||||
*/
|
||||
protected string $rawValue;
|
||||
|
||||
public function __construct(
|
||||
LoggerInterface $logger,
|
||||
MbWrapper $charsetConverter,
|
||||
string $value,
|
||||
bool $isLiteral = false,
|
||||
bool $preserveSpaces = false
|
||||
) {
|
||||
parent::__construct($logger, $charsetConverter, $value);
|
||||
$this->rawValue = $value;
|
||||
if (!$isLiteral) {
|
||||
$this->value = \preg_replace(['/(\r|\n)+(\s)/', '/(\r|\n)+/'], ['$2', ' '], $value);
|
||||
if (!$preserveSpaces) {
|
||||
$this->value = \preg_replace('/^\s+$/m', ' ', $this->value);
|
||||
}
|
||||
}
|
||||
$this->isSpace = ($this->value === '' || (!$isLiteral && \preg_match('/^\s*$/m', $this->value) === 1));
|
||||
$this->canIgnoreSpacesBefore = $this->canIgnoreSpacesAfter = $this->isSpace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the part's representative value after any necessary processing
|
||||
* has been performed. For the raw value, call getRawValue().
|
||||
*/
|
||||
public function getValue() : string
|
||||
{
|
||||
return $this->convertEncoding($this->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the part's raw value.
|
||||
*/
|
||||
public function getRawValue() : string
|
||||
{
|
||||
return $this->rawValue;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user