N°5809 Update laminas/laminas-mail from 2.16.0 to 2.22.0

This commit is contained in:
Pierre Goiffon
2024-01-25 17:24:43 +01:00
parent f3d3ec6ef7
commit 0f39ea8ac7
146 changed files with 4426 additions and 8899 deletions

View File

@@ -5,10 +5,19 @@ namespace Laminas\Mail;
use Laminas\Validator\EmailAddress as EmailAddressValidator;
use Laminas\Validator\Hostname;
use function array_shift;
use function is_string;
use function preg_match;
use function sprintf;
use function trim;
class Address implements Address\AddressInterface
{
/** @var null|string */
protected $comment;
/** @var string */
protected $email;
/** @var null|string */
protected $name;
/**
@@ -149,7 +158,7 @@ class Address implements Address\AddressInterface
*/
private function constructName()
{
$name = $this->getName();
$name = $this->getName();
$comment = $this->getComment();
if ($comment === null || $comment === '') {

View File

@@ -4,24 +4,43 @@ namespace Laminas\Mail;
use Countable;
use Iterator;
use Laminas\Mail\Address\AddressInterface;
use ReturnTypeWillChange;
use function count;
use function current;
use function gettype;
use function is_int;
use function is_numeric;
use function is_object;
use function is_string;
use function key;
use function next;
use function reset;
use function sprintf;
use function strtolower;
use function var_export;
/**
* @implements Iterator<string, AddressInterface>
* @final
*/
class AddressList implements Countable, Iterator
{
/**
* List of Address objects we're managing
*
* @var array
* @var array<string, AddressInterface>
*/
protected $addresses = [];
/**
* Add an address to the list
*
* @param string|Address\AddressInterface $emailOrAddress
* @param string|AddressInterface $emailOrAddress
* @param null|string $name
* @throws Exception\InvalidArgumentException
* @return AddressList
* @return $this
*/
public function add($emailOrAddress, $name = null)
{
@@ -29,12 +48,12 @@ class AddressList implements Countable, Iterator
$emailOrAddress = $this->createAddress($emailOrAddress, $name);
}
if (! $emailOrAddress instanceof Address\AddressInterface) {
if (! $emailOrAddress instanceof AddressInterface) {
throw new Exception\InvalidArgumentException(sprintf(
'%s expects an email address or %s\Address object as its first argument; received "%s"',
__METHOD__,
__NAMESPACE__,
(is_object($emailOrAddress) ? get_class($emailOrAddress) : gettype($emailOrAddress))
is_object($emailOrAddress) ? $emailOrAddress::class : gettype($emailOrAddress)
));
}
@@ -56,7 +75,7 @@ class AddressList implements Countable, Iterator
*
* @param array $addresses
* @throws Exception\RuntimeException
* @return AddressList
* @return $this
*/
public function addMany(array $addresses)
{
@@ -69,7 +88,7 @@ class AddressList implements Countable, Iterator
if (! is_string($key)) {
throw new Exception\RuntimeException(sprintf(
'Invalid key type in provided addresses array ("%s")',
(is_object($key) ? get_class($key) : var_export($key, 1))
is_object($key) ? $key::class : var_export($key, true)
));
}
@@ -86,7 +105,7 @@ class AddressList implements Countable, Iterator
* @param string $address
* @param null|string $comment Comment associated with the address, if any.
* @throws Exception\InvalidArgumentException
* @return AddressList
* @return $this
*/
public function addFromString($address, $comment = null)
{
@@ -97,8 +116,7 @@ class AddressList implements Countable, Iterator
/**
* Merge another address list into this one
*
* @param AddressList $addressList
* @return AddressList
* @return $this
*/
public function merge(self $addressList)
{
@@ -124,7 +142,7 @@ class AddressList implements Countable, Iterator
* Get an address by email
*
* @param string $email
* @return bool|Address\AddressInterface
* @return false|AddressInterface
*/
public function get($email)
{
@@ -167,9 +185,10 @@ class AddressList implements Countable, Iterator
/**
* Rewind iterator
*
* @return mixed the value of the first addresses element, or false if the addresses is
* empty.
* @see addresses
*
* @return false|AddressInterface the value of the first addresses element, or false if the addresses is
* empty.
*/
#[ReturnTypeWillChange]
public function rewind()
@@ -180,7 +199,7 @@ class AddressList implements Countable, Iterator
/**
* Return current item in iteration
*
* @return Address
* @return AddressInterface
*/
#[ReturnTypeWillChange]
public function current()
@@ -202,9 +221,10 @@ class AddressList implements Countable, Iterator
/**
* Move to next item
*
* @return mixed the addresses value in the next place that's pointed to by the
* internal array pointer, or false if there are no more elements.
* @see addresses
*
* @return false|AddressInterface the addresses value in the next place that's pointed to by the
* internal array pointer, or false if there are no more elements.
*/
#[ReturnTypeWillChange]
public function next()
@@ -221,7 +241,7 @@ class AddressList implements Countable, Iterator
public function valid()
{
$key = key($this->addresses);
return ($key !== null && $key !== false);
return $key !== null && $key !== false;
}
/**

View File

@@ -25,8 +25,8 @@ class ConfigProvider
{
return [
// Legacy Zend Framework aliases
'aliases' => [
\Zend\Mail\Protocol\SmtpPluginManager::class => Protocol\SmtpPluginManager::class,
'aliases' => [
'Zend\Mail\Protocol\SmtpPluginManager' => Protocol\SmtpPluginManager::class,
],
'factories' => [
Protocol\SmtpPluginManager::class => Protocol\SmtpPluginManagerFactory::class,

View File

@@ -2,6 +2,8 @@
namespace Laminas\Mail\Exception;
interface ExceptionInterface
use Throwable;
interface ExceptionInterface extends Throwable
{
}

View File

@@ -6,7 +6,39 @@ use Laminas\Mail\Address;
use Laminas\Mail\AddressList;
use Laminas\Mail\Headers;
use Laminas\Mail\Storage\Exception\RuntimeException;
use Throwable;
use function array_filter;
use function array_map;
use function assert;
use function idn_to_ascii;
use function implode;
use function in_array;
use function is_array;
use function is_string;
use function preg_match;
use function preg_match_all;
use function preg_replace;
use function sprintf;
use function str_contains;
use function str_replace;
use function strtolower;
use function trim;
use const IDNA_DEFAULT;
use const IDNA_ERROR_BIDI;
use const IDNA_ERROR_CONTEXTJ;
use const IDNA_ERROR_DISALLOWED;
use const IDNA_ERROR_DOMAIN_NAME_TOO_LONG;
use const IDNA_ERROR_EMPTY_LABEL;
use const IDNA_ERROR_HYPHEN_3_4;
use const IDNA_ERROR_INVALID_ACE_LABEL;
use const IDNA_ERROR_LABEL_HAS_DOT;
use const IDNA_ERROR_LABEL_TOO_LONG;
use const IDNA_ERROR_LEADING_COMBINING_MARK;
use const IDNA_ERROR_LEADING_HYPHEN;
use const IDNA_ERROR_PUNYCODE;
use const IDNA_ERROR_TRAILING_HYPHEN;
use const INTL_IDNA_VARIANT_UTS46;
/**
* Base class for headers composing address lists (to, from, cc, bcc, reply-to)
@@ -14,29 +46,25 @@ use Throwable;
abstract class AbstractAddressList implements HeaderInterface
{
private const IDNA_ERROR_MAP = [
IDNA_ERROR_EMPTY_LABEL => 'empty label',
IDNA_ERROR_LABEL_TOO_LONG => 'label too long',
IDNA_ERROR_DOMAIN_NAME_TOO_LONG => 'domain name too long',
IDNA_ERROR_LEADING_HYPHEN => 'leading hyphen',
IDNA_ERROR_TRAILING_HYPHEN => 'trailing hyphen',
IDNA_ERROR_HYPHEN_3_4 => 'consecutive hyphens',
IDNA_ERROR_EMPTY_LABEL => 'empty label',
IDNA_ERROR_LABEL_TOO_LONG => 'label too long',
IDNA_ERROR_DOMAIN_NAME_TOO_LONG => 'domain name too long',
IDNA_ERROR_LEADING_HYPHEN => 'leading hyphen',
IDNA_ERROR_TRAILING_HYPHEN => 'trailing hyphen',
IDNA_ERROR_HYPHEN_3_4 => 'consecutive hyphens',
IDNA_ERROR_LEADING_COMBINING_MARK => 'leading combining mark',
IDNA_ERROR_DISALLOWED => 'disallowed',
IDNA_ERROR_PUNYCODE => 'invalid punycode encoding',
IDNA_ERROR_LABEL_HAS_DOT => 'has dot',
IDNA_ERROR_INVALID_ACE_LABEL => 'label not in ASCII encoding',
IDNA_ERROR_BIDI => 'fails bidirectional criteria',
IDNA_ERROR_CONTEXTJ => 'one or more characters fail CONTEXTJ rule',
IDNA_ERROR_DISALLOWED => 'disallowed',
IDNA_ERROR_PUNYCODE => 'invalid punycode encoding',
IDNA_ERROR_LABEL_HAS_DOT => 'has dot',
IDNA_ERROR_INVALID_ACE_LABEL => 'label not in ASCII encoding',
IDNA_ERROR_BIDI => 'fails bidirectional criteria',
IDNA_ERROR_CONTEXTJ => 'one or more characters fail CONTEXTJ rule',
];
/**
* @var AddressList
*/
/** @var AddressList */
protected $addressList;
/**
* @var string Normalized field name
*/
/** @var string Normalized field name */
protected $fieldName;
/**
@@ -46,40 +74,42 @@ abstract class AbstractAddressList implements HeaderInterface
*/
protected $encoding = 'ASCII';
/**
* @var string lower case field name
*/
/** @var string lower case field name */
protected static $type;
/** @var string[] lower case aliases for the field name */
protected static $typeAliases = [];
/**
* @param string $headerLine
* @return static
*/
public static function fromString($headerLine)
{
list($fieldName, $fieldValue) = GenericHeader::splitHeaderLine($headerLine);
if (strtolower($fieldName) !== static::$type) {
[$fieldName, $fieldValue] = GenericHeader::splitHeaderLine($headerLine);
if ((strtolower($fieldName) !== static::$type) && ! in_array(strtolower($fieldName), static::$typeAliases)) {
throw new Exception\InvalidArgumentException(sprintf(
'Invalid header line for "%s" string',
__CLASS__
self::class
));
}
// split value on ","
$fieldValue = str_replace(Headers::FOLDING, ' ', $fieldValue);
$fieldValue = preg_replace('/[^:]+:([^;]*);/', '$1,', $fieldValue);
$values = ListParser::parse($fieldValue);
$values = ListParser::parse($fieldValue);
$wasEncoded = false;
$addresses = array_map(
function ($value) use (&$wasEncoded) {
$addresses = array_map(
static function ($value) use (&$wasEncoded): ?Address {
$decodedValue = HeaderWrap::mimeDecodeValue($value);
$wasEncoded = $wasEncoded || ($decodedValue !== $value);
$value = trim($decodedValue);
$comments = self::getComments($value);
$value = self::stripComments($value);
$value = preg_replace(
$wasEncoded = $wasEncoded || ($decodedValue !== $value);
$value = trim($decodedValue);
$comments = self::getComments($value);
$value = self::stripComments($value);
$value = preg_replace(
[
'#(?<!\\\)"(.*)(?<!\\\)"#', // quoted-text
'#(?<!\\\)"(.*)(?<!\\\)"#', // quoted-text
'#\\\([\x01-\x09\x0b\x0c\x0e-\x7f])#', // quoted-pair
],
[
@@ -88,12 +118,11 @@ abstract class AbstractAddressList implements HeaderInterface
],
$value
);
return empty($value) ? null : Address::fromString($value, $comments);
},
$values
);
$addresses = array_filter($addresses);
$addresses = array_filter($addresses);
$header = new static();
if ($wasEncoded) {
@@ -109,6 +138,9 @@ abstract class AbstractAddressList implements HeaderInterface
return $header;
}
/**
* @return string
*/
public function getFieldName()
{
return $this->fieldName;
@@ -116,18 +148,21 @@ abstract class AbstractAddressList implements HeaderInterface
/**
* Safely convert UTF-8 encoded domain name to ASCII
*
* @param string $domainName the UTF-8 encoded email
* @return string
*/
protected function idnToAscii($domainName): string
{
/** @psalm-var string|false $ascii */
$ascii = idn_to_ascii($domainName, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $conversionInfo);
if (false !== $ascii) {
if (is_string($ascii)) {
return $ascii;
}
$messages = [];
$errors = (int) $conversionInfo['errors'];
assert(is_array($conversionInfo));
/* @psalm-var array{errors: numeric-string} $conversionInfo */
$errors = (int) $conversionInfo['errors'];
foreach (self::IDNA_ERROR_MAP as $flag => $message) {
if (($flag & $errors) === $flag) {
@@ -141,6 +176,9 @@ abstract class AbstractAddressList implements HeaderInterface
));
}
/**
* @inheritDoc
*/
public function getFieldValue($format = HeaderInterface::FORMAT_RAW)
{
$emails = [];
@@ -151,12 +189,13 @@ abstract class AbstractAddressList implements HeaderInterface
$name = $address->getName();
// quote $name if value requires so
if (! empty($name) && (false !== strpos($name, ',') || false !== strpos($name, ';'))) {
if (! empty($name) && (str_contains($name, ',') || str_contains($name, ';'))) {
// FIXME: what if name contains double quote?
$name = sprintf('"%s"', $name);
}
if ($format === HeaderInterface::FORMAT_ENCODED
if (
$format === HeaderInterface::FORMAT_ENCODED
&& 'ASCII' !== $encoding
) {
if (! empty($name)) {
@@ -166,7 +205,7 @@ abstract class AbstractAddressList implements HeaderInterface
if (preg_match('/^(.+)@([^@]+)$/', $email, $matches)) {
$localPart = $matches[1];
$hostname = $this->idnToAscii($matches[2]);
$email = sprintf('%s@%s', $localPart, $hostname);
$email = sprintf('%s@%s', $localPart, $hostname);
}
}
@@ -187,12 +226,19 @@ abstract class AbstractAddressList implements HeaderInterface
return implode(',' . Headers::FOLDING, $emails);
}
/**
* @param string $encoding
* @return self
*/
public function setEncoding($encoding)
{
$this->encoding = $encoding;
return $this;
}
/**
* @return string
*/
public function getEncoding()
{
return $this->encoding;
@@ -200,8 +246,6 @@ abstract class AbstractAddressList implements HeaderInterface
/**
* Set address list for this header
*
* @param AddressList $addressList
*/
public function setAddressList(AddressList $addressList)
{
@@ -221,11 +265,14 @@ abstract class AbstractAddressList implements HeaderInterface
return $this->addressList;
}
/**
* @return string
*/
public function toString()
{
$name = $this->getFieldName();
$value = $this->getFieldValue(HeaderInterface::FORMAT_ENCODED);
return (empty($value)) ? '' : sprintf('%s: %s', $name, $value);
return empty($value) ? '' : sprintf('%s: %s', $name, $value);
}
/**

View File

@@ -4,13 +4,9 @@ namespace Laminas\Mail\Header;
class Bcc extends AbstractAddressList
{
/**
* @var string
*/
/** @var string */
protected $fieldName = 'Bcc';
/**
* @var string
*/
/** @var string */
protected static $type = 'bcc';
}

View File

@@ -4,6 +4,8 @@ namespace Laminas\Mail\Header;
class Cc extends AbstractAddressList
{
/** @var string */
protected $fieldName = 'Cc';
/** @var string */
protected static $type = 'cc';
}

View File

@@ -5,6 +5,21 @@ namespace Laminas\Mail\Header;
use Laminas\Mail\Headers;
use Laminas\Mime\Mime;
use function count;
use function explode;
use function gettype;
use function in_array;
use function is_numeric;
use function mb_strlen;
use function mb_substr;
use function sprintf;
use function str_replace;
use function strlen;
use function strpos;
use function strtolower;
use function trim;
use function var_export;
class ContentDisposition implements UnstructuredInterface
{
/**
@@ -14,9 +29,7 @@ class ContentDisposition implements UnstructuredInterface
*/
public const MAX_PARAMETER_LENGTH = 76;
/**
* @var string
*/
/** @var string */
protected $disposition = 'inline';
/**
@@ -26,9 +39,7 @@ class ContentDisposition implements UnstructuredInterface
*/
protected $encoding = 'ASCII';
/**
* @var array
*/
/** @var array */
protected $parameters = [];
/**
@@ -36,32 +47,32 @@ class ContentDisposition implements UnstructuredInterface
*/
public static function fromString($headerLine)
{
list($name, $value) = GenericHeader::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
[$name, $value] = GenericHeader::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
// check to ensure proper header type for this factory
if (strtolower($name) !== 'content-disposition') {
if (! in_array(strtolower($name), ['contentdisposition', 'content_disposition', 'content-disposition'])) {
throw new Exception\InvalidArgumentException('Invalid header line for Content-Disposition string');
}
$value = str_replace(Headers::FOLDING, ' ', $value);
$value = str_replace(Headers::FOLDING, ' ', $value);
$parts = explode(';', $value, 2);
$header = new static();
$header->setDisposition($parts[0]);
if (isset($parts[1])) {
$values = ListParser::parse(trim($parts[1]), [';', '=']);
$length = count($values);
$values = ListParser::parse(trim($parts[1]), [';', '=']);
$length = count($values);
$continuedValues = [];
for ($i = 0; $i < $length; $i += 2) {
$value = $values[$i + 1];
$value = trim($value, "'\" \t\n\r\0\x0B");
$name = trim($values[$i], "'\" \t\n\r\0\x0B");
$name = trim($values[$i], "'\" \t\n\r\0\x0B");
if (strpos($name, '*')) {
list($name, $count) = explode('*', $name);
[$name, $count] = explode('*', $name);
// allow optional count:
// Content-Disposition: attachment; filename*=UTF-8''%64%61%61%6D%69%2D%6D%C3%B5%72%76%2E%6A%70%67
if ($count === "") {
@@ -69,11 +80,11 @@ class ContentDisposition implements UnstructuredInterface
}
if (! is_numeric($count)) {
$type = gettype($count);
$value = var_export($count, 1);
$type = gettype($count);
$value = var_export($count, true);
throw new Exception\InvalidArgumentException(sprintf(
"Invalid header line for Content-Disposition string".
" - count expected to be numeric, got %s with value %s",
"Invalid header line for Content-Disposition string"
. " - count expected to be numeric, got %s with value %s",
$type,
$value
));
@@ -92,8 +103,8 @@ class ContentDisposition implements UnstructuredInterface
for ($i = 0, $iMax = count($values); $i < $iMax; $i++) {
if (! isset($values[$i])) {
throw new Exception\InvalidArgumentException(
'Invalid header line for Content-Disposition string - incomplete continuation'.
'; HeaderLine: '.$headerLine
'Invalid header line for Content-Disposition string - incomplete continuation'
. '; HeaderLine: ' . $headerLine
);
}
$value .= $values[$i];
@@ -126,7 +137,7 @@ class ContentDisposition implements UnstructuredInterface
foreach ($this->parameters as $attribute => $value) {
$valueIsEncoded = false;
if (HeaderInterface::FORMAT_ENCODED === $format && ! Mime::isPrintable($value)) {
$value = $this->getEncodedValue($value);
$value = $this->getEncodedValue($value);
$valueIsEncoded = true;
}
@@ -152,13 +163,13 @@ class ContentDisposition implements UnstructuredInterface
$value = HeaderWrap::mimeDecodeValue($value);
}
$i = 0;
$i = 0;
$fullLength = mb_strlen($value, 'UTF-8');
while ($fullLength > 0) {
$attributePart = $attribute . '*' . $i++ . '="';
$attLen = mb_strlen($attributePart, 'UTF-8');
$attLen = mb_strlen($attributePart, 'UTF-8');
$subPos = 1;
$subPos = 1;
$valuePart = '';
while ($subPos <= $fullLength) {
$sub = mb_substr($value, 0, $subPos, 'UTF-8');
@@ -173,9 +184,9 @@ class ContentDisposition implements UnstructuredInterface
$valuePart = $sub;
}
$value = mb_substr($value, $subPos, null, 'UTF-8');
$value = mb_substr($value, $subPos, null, 'UTF-8');
$fullLength = mb_strlen($value, 'UTF-8');
$result .= ';' . Headers::FOLDING . $attributePart . $valuePart . '"';
$result .= ';' . Headers::FOLDING . $attributePart . $valuePart . '"';
}
}
}
@@ -190,9 +201,9 @@ class ContentDisposition implements UnstructuredInterface
protected function getEncodedValue($value)
{
$configuredEncoding = $this->encoding;
$this->encoding = 'UTF-8';
$value = HeaderWrap::wrap($value, $this);
$this->encoding = $configuredEncoding;
$this->encoding = 'UTF-8';
$value = HeaderWrap::wrap($value, $this);
$this->encoding = $configuredEncoding;
return $value;
}
@@ -253,7 +264,7 @@ class ContentDisposition implements UnstructuredInterface
*/
public function setParameter($name, $value)
{
$name = strtolower($name);
$name = strtolower($name);
if (! HeaderValue::isValid($name)) {
throw new Exception\InvalidArgumentException(

View File

@@ -2,11 +2,17 @@
namespace Laminas\Mail\Header;
use function implode;
use function in_array;
use function sprintf;
use function strtolower;
class ContentTransferEncoding implements HeaderInterface
{
/**
* Allowed Content-Transfer-Encoding parameters specified by RFC 1521
* (reduced set)
*
* @var array
*/
protected static $allowedTransferEncodings = [
@@ -21,23 +27,28 @@ class ContentTransferEncoding implements HeaderInterface
*/
];
/**
* @var string
*/
/** @var string */
protected $transferEncoding;
/**
* @var array
*/
/** @var array */
protected $parameters = [];
/**
* @param string $headerLine
* @return static
*/
public static function fromString($headerLine)
{
list($name, $value) = GenericHeader::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
[$name, $value] = GenericHeader::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
// check to ensure proper header type for this factory
if (strtolower($name) !== 'content-transfer-encoding') {
if (
! in_array(
strtolower($name),
['contenttransferencoding', 'content_transfer_encoding', 'content-transfer-encoding']
)
) {
throw new Exception\InvalidArgumentException('Invalid header line for Content-Transfer-Encoding string');
}
@@ -47,27 +58,43 @@ class ContentTransferEncoding implements HeaderInterface
return $header;
}
/**
* @return string
*/
public function getFieldName()
{
return 'Content-Transfer-Encoding';
}
/**
* @inheritDoc
*/
public function getFieldValue($format = HeaderInterface::FORMAT_RAW)
{
return $this->transferEncoding;
}
/**
* @param string $encoding
* @return self
*/
public function setEncoding($encoding)
{
// Header must be always in US-ASCII
return $this;
}
/**
* @return string
*/
public function getEncoding()
{
return 'ASCII';
}
/**
* @return string
*/
public function toString()
{
return 'Content-Transfer-Encoding: ' . $this->getFieldValue();
@@ -87,7 +114,7 @@ class ContentTransferEncoding implements HeaderInterface
if (! in_array($transferEncoding, static::$allowedTransferEncodings)) {
throw new Exception\InvalidArgumentException(sprintf(
'%s expects one of "'. implode(', ', static::$allowedTransferEncodings) . '"; received "%s"',
'%s expects one of "' . implode(', ', static::$allowedTransferEncodings) . '"; received "%s"',
__METHOD__,
(string) $transferEncoding
));

View File

@@ -5,11 +5,19 @@ namespace Laminas\Mail\Header;
use Laminas\Mail\Headers;
use Laminas\Mime\Mime;
use function count;
use function explode;
use function implode;
use function in_array;
use function preg_match;
use function sprintf;
use function str_replace;
use function strtolower;
use function trim;
class ContentType implements UnstructuredInterface
{
/**
* @var string
*/
/** @var string */
protected $type;
/**
@@ -19,22 +27,24 @@ class ContentType implements UnstructuredInterface
*/
protected $encoding = 'ASCII';
/**
* @var array
*/
/** @var array */
protected $parameters = [];
/**
* @param string $headerLine
* @return static
*/
public static function fromString($headerLine)
{
list($name, $value) = GenericHeader::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
[$name, $value] = GenericHeader::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
// check to ensure proper header type for this factory
if (strtolower($name) !== 'content-type') {
if (! in_array(strtolower($name), ['contenttype', 'content_type', 'content-type'])) {
throw new Exception\InvalidArgumentException('Invalid header line for Content-Type string');
}
$value = str_replace(Headers::FOLDING, ' ', $value);
$value = str_replace(Headers::FOLDING, ' ', $value);
$parts = explode(';', $value, 2);
$header = new static();
@@ -54,11 +64,17 @@ class ContentType implements UnstructuredInterface
return $header;
}
/**
* @return string
*/
public function getFieldName()
{
return 'Content-Type';
}
/**
* @inheritDoc
*/
public function getFieldValue($format = HeaderInterface::FORMAT_RAW)
{
$prepared = $this->type;
@@ -70,7 +86,7 @@ class ContentType implements UnstructuredInterface
foreach ($this->parameters as $attribute => $value) {
if (HeaderInterface::FORMAT_ENCODED === $format && ! Mime::isPrintable($value)) {
$this->encoding = 'UTF-8';
$value = HeaderWrap::wrap($value, $this);
$value = HeaderWrap::wrap($value, $this);
$this->encoding = 'ASCII';
}
@@ -80,17 +96,27 @@ class ContentType implements UnstructuredInterface
return implode(';' . Headers::FOLDING, $values);
}
/**
* @param string $encoding
* @return self
*/
public function setEncoding($encoding)
{
$this->encoding = $encoding;
return $this;
}
/**
* @return string
*/
public function getEncoding()
{
return $this->encoding;
}
/**
* @return string
*/
public function toString()
{
return 'Content-Type: ' . $this->getFieldValue(HeaderInterface::FORMAT_ENCODED);
@@ -132,8 +158,8 @@ class ContentType implements UnstructuredInterface
* @param string $name
* @param string $value
* @return ContentType
* @throws Exception\InvalidArgumentException for parameter names that do not follow RFC 2822
* @throws Exception\InvalidArgumentException for parameter values that do not follow RFC 2822
* @throws Exception\InvalidArgumentException For parameter names that do not follow RFC 2822.
* @throws Exception\InvalidArgumentException For parameter values that do not follow RFC 2822.
*/
public function addParameter($name, $value)
{

View File

@@ -2,31 +2,36 @@
namespace Laminas\Mail\Header;
use function strtolower;
/**
* @todo Add accessors for setting date from DateTime, Laminas\Date, or a string
*/
class Date implements HeaderInterface
{
/**
* @var string
*/
/** @var string */
protected $value;
/**
* @param string $headerLine
* @return static
*/
public static function fromString($headerLine)
{
list($name, $value) = GenericHeader::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
[$name, $value] = GenericHeader::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
// check to ensure proper header type for this factory
if (strtolower($name) !== 'date') {
throw new Exception\InvalidArgumentException('Invalid header line for Date string');
}
$header = new static($value);
return $header;
return new static($value);
}
/**
* @param string $value
*/
public function __construct($value)
{
if (! HeaderValue::isValid($value)) {
@@ -35,27 +40,43 @@ class Date implements HeaderInterface
$this->value = $value;
}
/**
* @return string
*/
public function getFieldName()
{
return 'Date';
}
/**
* @inheritDoc
*/
public function getFieldValue($format = HeaderInterface::FORMAT_RAW)
{
return $this->value;
}
/**
* @param string $encoding
* @return self
*/
public function setEncoding($encoding)
{
// This header must be always in US-ASCII
return $this;
}
/**
* @return string
*/
public function getEncoding()
{
return 'ASCII';
}
/**
* @return string
*/
public function toString()
{
return 'Date: ' . $this->getFieldValue();

View File

@@ -4,6 +4,8 @@ namespace Laminas\Mail\Header;
class From extends AbstractAddressList
{
/** @var string */
protected $fieldName = 'From';
/** @var string */
protected static $type = 'from';
}

View File

@@ -5,16 +5,20 @@ namespace Laminas\Mail\Header;
use Laminas\Mail\Header\Exception\InvalidArgumentException;
use Laminas\Mime\Mime;
use function count;
use function explode;
use function is_string;
use function ltrim;
use function str_replace;
use function strtoupper;
use function ucwords;
class GenericHeader implements HeaderInterface, UnstructuredInterface
{
/**
* @var string
*/
/** @var string */
protected $fieldName;
/**
* @var string
*/
/** @var string */
protected $fieldValue = '';
/**
@@ -30,11 +34,9 @@ class GenericHeader implements HeaderInterface, UnstructuredInterface
*/
public static function fromString($headerLine)
{
list($name, $value) = self::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
$header = new static($name, $value);
return $header;
[$name, $value] = self::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
return new static($name, $value);
}
/**
@@ -42,21 +44,21 @@ class GenericHeader implements HeaderInterface, UnstructuredInterface
*
* @param string $headerLine
* @return string[] `name` in the first index and `value` in the second.
* @throws Exception\InvalidArgumentException If header does not match with the format ``name:value``
* @throws InvalidArgumentException If header does not match with the format ``name:value``.
*/
public static function splitHeaderLine($headerLine)
{
$parts = explode(':', $headerLine, 2);
if (count($parts) !== 2) {
throw new Exception\InvalidArgumentException('Header must match with the format "name:value"');
throw new InvalidArgumentException('Header must match with the format "name:value"');
}
if (! HeaderName::isValid($parts[0])) {
throw new Exception\InvalidArgumentException('Invalid header name detected');
throw new InvalidArgumentException('Invalid header name detected');
}
if (! HeaderValue::isValid($parts[1])) {
throw new Exception\InvalidArgumentException('Invalid header value detected');
throw new InvalidArgumentException('Invalid header value detected');
}
$parts[1] = ltrim($parts[1]);
@@ -93,14 +95,14 @@ class GenericHeader implements HeaderInterface, UnstructuredInterface
public function setFieldName($fieldName)
{
if (! is_string($fieldName) || empty($fieldName)) {
throw new Exception\InvalidArgumentException('Header name must be a string');
throw new InvalidArgumentException('Header name must be a string');
}
// Pre-filter to normalize valid characters, change underscore to dash
$fieldName = str_replace(' ', '-', ucwords(str_replace(['_', '-'], ' ', $fieldName)));
if (! HeaderName::isValid($fieldName)) {
throw new Exception\InvalidArgumentException(
throw new InvalidArgumentException(
'Header name must be composed of printable US-ASCII characters, except colon.'
);
}
@@ -109,6 +111,9 @@ class GenericHeader implements HeaderInterface, UnstructuredInterface
return $this;
}
/**
* @return string
*/
public function getFieldName()
{
return $this->fieldName;
@@ -126,7 +131,7 @@ class GenericHeader implements HeaderInterface, UnstructuredInterface
$fieldValue = (string) $fieldValue;
if (! HeaderWrap::canBeEncoded($fieldValue)) {
throw new Exception\InvalidArgumentException(
throw new InvalidArgumentException(
'Header value must be composed of printable US-ASCII characters and valid folding sequences.'
);
}
@@ -137,6 +142,9 @@ class GenericHeader implements HeaderInterface, UnstructuredInterface
return $this;
}
/**
* @inheritDoc
*/
public function getFieldValue($format = HeaderInterface::FORMAT_RAW)
{
if (HeaderInterface::FORMAT_ENCODED === $format) {
@@ -146,6 +154,10 @@ class GenericHeader implements HeaderInterface, UnstructuredInterface
return $this->fieldValue;
}
/**
* @param string $encoding
* @return self
*/
public function setEncoding($encoding)
{
if ($encoding === $this->encoding) {
@@ -173,6 +185,9 @@ class GenericHeader implements HeaderInterface, UnstructuredInterface
return $this;
}
/**
* @return string
*/
public function getEncoding()
{
if (! $this->encoding) {
@@ -182,6 +197,9 @@ class GenericHeader implements HeaderInterface, UnstructuredInterface
return $this->encoding;
}
/**
* @return string
*/
public function toString()
{
$name = $this->getFieldName();

View File

@@ -2,15 +2,23 @@
namespace Laminas\Mail\Header;
use function explode;
use function implode;
use function strpos;
/**
* Generic class for Headers with multiple occurs in the same message
*/
class GenericMultiHeader extends GenericHeader implements MultipleHeadersInterface
{
/**
* @param string $headerLine
* @return array|GenericHeader|GenericMultiHeader|static
*/
public static function fromString($headerLine)
{
list($fieldName, $fieldValue) = GenericHeader::splitHeaderLine($headerLine);
$fieldValue = HeaderWrap::mimeDecodeValue($fieldValue);
[$fieldName, $fieldValue] = GenericHeader::splitHeaderLine($headerLine);
$fieldValue = HeaderWrap::mimeDecodeValue($fieldValue);
if (strpos($fieldValue, ',')) {
$headers = [];

View File

@@ -16,15 +16,16 @@ interface HeaderInterface
*
* @var bool
*/
public const FORMAT_RAW = false;
public const FORMAT_RAW = false;
/**
* Factory to generate a header object from a string
*
* @see http://tools.ietf.org/html/rfc2822#section-2.2
*
* @param string $headerLine
* @return static
* @throws Exception\InvalidArgumentException If the header does not match with RFC 2822 definition.
* @see http://tools.ietf.org/html/rfc2822#section-2.2
*/
public static function fromString($headerLine);
@@ -38,7 +39,7 @@ interface HeaderInterface
/**
* Retrieve header value
*
* @param bool $format Return the value in Mime::Encoded or in Raw format
* @param HeaderInterface::FORMAT_* $format Return the value in Mime::Encoded or in Raw format
* @return string
*/
public function getFieldValue($format = self::FORMAT_RAW);

View File

@@ -9,9 +9,7 @@ use Laminas\Loader\PluginClassLoader;
*/
class HeaderLoader extends PluginClassLoader
{
/**
* @var array Pre-aliased Header plugins
*/
/** @var array Pre-aliased Header plugins */
protected $plugins = [
'bcc' => Bcc::class,
'cc' => Cc::class,

View File

@@ -4,15 +4,15 @@ declare(strict_types=1);
namespace Laminas\Mail\Header;
use function strtolower;
/**
* Plugin Class Loader implementation for HTTP headers
*/
final class HeaderLocator implements HeaderLocatorInterface
{
/**
* @var array Pre-aliased Header plugins
*/
private $plugins = [
/** @var array Pre-aliased Header plugins */
private array $plugins = [
'bcc' => Bcc::class,
'cc' => Cc::class,
'contentdisposition' => ContentDisposition::class,

View File

@@ -2,6 +2,9 @@
namespace Laminas\Mail\Header;
use function ord;
use function strlen;
final class HeaderName
{
/**
@@ -15,6 +18,7 @@ final class HeaderName
* Filter the header name according to RFC 2822
*
* @see http://www.rfc-base.org/txt/rfc-2822.txt (section 2.2)
*
* @param string $name
* @return string
*/

View File

@@ -2,6 +2,10 @@
namespace Laminas\Mail\Header;
use function in_array;
use function ord;
use function strlen;
final class HeaderValue
{
/**
@@ -15,6 +19,7 @@ final class HeaderValue
* Filter the header value according to RFC 2822
*
* @see http://www.rfc-base.org/txt/rfc-2822.txt (section 2.2)
*
* @param string $value
* @return string
*/
@@ -44,7 +49,7 @@ final class HeaderValue
}
$result .= "\r\n ";
$i += 2;
$i += 2;
continue;
}
@@ -58,6 +63,7 @@ final class HeaderValue
* Determine if the header value contains any invalid characters.
*
* @see http://www.rfc-base.org/txt/rfc-2822.txt (section 2.2)
*
* @param string $value
* @return bool
*/

View File

@@ -5,17 +5,35 @@ namespace Laminas\Mail\Header;
use Laminas\Mail\Headers;
use Laminas\Mime\Mime;
use function array_reduce;
use function explode;
use function extension_loaded;
use function iconv_mime_decode;
use function iconv_mime_encode;
use function imap_mime_header_decode;
use function imap_utf8;
use function implode;
use function str_contains;
use function str_pad;
use function str_starts_with;
use function strlen;
use function strpos;
use function substr;
use function wordwrap;
use const ICONV_MIME_DECODE_CONTINUE_ON_ERROR;
/**
* Utility class used for creating wrapped or MIME-encoded versions of header
* values.
*/
// phpcs:ignore WebimpressCodingStandard.NamingConventions.AbstractClass.Prefix
abstract class HeaderWrap
{
/**
* Wrap a long header line
*
* @param string $value
* @param HeaderInterface $header
* @return string
*/
public static function wrap($value, HeaderInterface $header)
@@ -34,23 +52,33 @@ abstract class HeaderWrap
* Wrap at 78 characters or before, based on whitespace.
*
* @param string $value
* @param HeaderInterface $header
* @return string
*/
protected static function wrapUnstructuredHeader($value, HeaderInterface $header)
{
$encoding = $header->getEncoding();
$headerNameColonSize = strlen($header->getFieldName() . ': ');
$encoding = $header->getEncoding();
if ($encoding == 'ASCII') {
return wordwrap($value, 78, Headers::FOLDING);
/*
* Before folding the header line, it is necessary to calculate the length of the
* entire header (including the name and colon). We need to put a stub at the
* beginning of the value so that the folding is performed correctly.
*/
$headerLine = str_pad('0', $headerNameColonSize, '0') . $value;
$foldedHeaderLine = wordwrap($headerLine, 78, Headers::FOLDING);
// Remove the stub and return the header folded value.
return substr($foldedHeaderLine, $headerNameColonSize);
}
return static::mimeEncodeValue($value, $encoding, 78);
return static::mimeEncodeValue($value, $encoding, 78, $headerNameColonSize);
}
/**
* Wrap a structured header line
*
* @param string $value
* @param StructuredInterface $header
* @return string
*/
protected static function wrapStructuredHeader($value, StructuredInterface $header)
@@ -76,14 +104,19 @@ abstract class HeaderWrap
* Performs quoted-printable encoding on a value, setting maximum
* line-length to 998.
*
* @param string $value
* @param string $encoding
* @param int $lineLength maximum line-length, by default 998
* @param string $value
* @param string $encoding
* @param int $lineLength Maximum line-length, by default 998
* @param positive-int|0 $firstLineGapSize When folding a line, it is necessary to calculate
* the length of the entire line (together with the
* header name). Therefore, you can specify the header
* name and colon length in this argument to fold the
* string properly.
* @return string Returns the mime encode value without the last line ending
*/
public static function mimeEncodeValue($value, $encoding, $lineLength = 998)
public static function mimeEncodeValue($value, $encoding, $lineLength = 998, $firstLineGapSize = 0)
{
return Mime::encodeQuotedPrintableHeader($value, $encoding, $lineLength, Headers::EOL);
return Mime::encodeQuotedPrintableHeader($value, $encoding, $lineLength, Headers::EOL, $firstLineGapSize);
}
/**
@@ -109,9 +142,7 @@ abstract class HeaderWrap
if (self::isNotDecoded($value, $decodedValue) && extension_loaded('imap')) {
return array_reduce(
imap_mime_header_decode(imap_utf8($value)),
function ($accumulator, $headerPart) {
return $accumulator . $headerPart->text;
},
static fn($accumulator, $headerPart) => $accumulator . $headerPart->text,
''
);
}
@@ -119,11 +150,11 @@ abstract class HeaderWrap
return $decodedValue;
}
private static function isNotDecoded($originalValue, $value)
private static function isNotDecoded(string $originalValue, string $value): bool
{
return 0 === strpos($value, '=?')
return str_starts_with($value, '=?')
&& strlen($value) - 2 === strpos($value, '?=')
&& false !== strpos($originalValue, $value);
&& str_contains($originalValue, $value);
}
/**
@@ -138,18 +169,18 @@ abstract class HeaderWrap
// "test" -> 4
// "x-test: =?ISO-8859-1?B?dGVzdA==?=" -> 33
// 8 +2 +3 +3 -> 16
$charset = 'UTF-8';
$charset = 'UTF-8';
$lineLength = strlen($value) * 4 + strlen($charset) + 16;
$preferences = [
'scheme' => 'Q',
'input-charset' => $charset,
'scheme' => 'Q',
'input-charset' => $charset,
'output-charset' => $charset,
'line-length' => $lineLength,
'line-length' => $lineLength,
];
$encoded = iconv_mime_encode('x-test', $value, $preferences);
return (false !== $encoded);
return false !== $encoded;
}
}

View File

@@ -4,24 +4,27 @@ namespace Laminas\Mail\Header;
use Laminas\Mail\Headers;
use function array_map;
use function explode;
use function implode;
use function preg_match;
use function sprintf;
use function strtolower;
use function trim;
/**
* @see https://tools.ietf.org/html/rfc5322#section-3.6.4
*/
// phpcs:ignore WebimpressCodingStandard.NamingConventions.AbstractClass.Prefix
abstract class IdentificationField implements HeaderInterface
{
/**
* @var string lower case field name
*/
/** @var string lower case field name */
protected static $type;
/**
* @var string[]
*/
/** @var string[] */
protected $messageIds;
/**
* @var string
*/
/** @var string */
protected $fieldName;
/**
@@ -30,11 +33,11 @@ abstract class IdentificationField implements HeaderInterface
*/
public static function fromString($headerLine)
{
list($name, $value) = GenericHeader::splitHeaderLine($headerLine);
[$name, $value] = GenericHeader::splitHeaderLine($headerLine);
if (strtolower($name) !== static::$type) {
throw new Exception\InvalidArgumentException(sprintf(
'Invalid header line for "%s" string',
__CLASS__
self::class
));
}
@@ -69,14 +72,11 @@ abstract class IdentificationField implements HeaderInterface
}
/**
* @param bool $format
* @return string
* @inheritDoc
*/
public function getFieldValue($format = HeaderInterface::FORMAT_RAW)
{
return implode(Headers::FOLDING, array_map(function ($id) {
return sprintf('<%s>', $id);
}, $this->messageIds));
return implode(Headers::FOLDING, array_map(static fn($id) => sprintf('<%s>', $id), $this->messageIds));
}
/**
@@ -114,7 +114,8 @@ abstract class IdentificationField implements HeaderInterface
public function setIds($ids)
{
foreach ($ids as $id) {
if (! HeaderValue::isValid($id)
if (
! HeaderValue::isValid($id)
|| preg_match("/[\r\n]/", $id)
) {
throw new Exception\InvalidArgumentException('Invalid ID detected');

View File

@@ -4,6 +4,8 @@ namespace Laminas\Mail\Header;
class InReplyTo extends IdentificationField
{
/** @var string */
protected $fieldName = 'In-Reply-To';
/** @var string */
protected static $type = 'in-reply-to';
}

View File

@@ -3,6 +3,7 @@
namespace Laminas\Mail\Header;
use function in_array;
use function strlen;
/**
* @internal
@@ -35,14 +36,14 @@ class ListParser
// If we are in an escape sequence, append the character and continue.
if ($inEscape) {
$currentValue .= $char;
$inEscape = false;
$inEscape = false;
continue;
}
// If we are not in a quoted string, and have a delimiter, append
// the current value to the list, and reset the current value.
if (in_array($char, $delims, true) && ! $inQuote) {
$values [] = $currentValue;
$values [] = $currentValue;
$currentValue = '';
continue;
}
@@ -66,7 +67,7 @@ class ListParser
// we reset our quote status and the currently opened quote
// delimiter.
if ($char === $currentQuoteDelim) {
$inQuote = false;
$inQuote = false;
$currentQuoteDelim = null;
continue;
}
@@ -78,7 +79,7 @@ class ListParser
}
// Otherwise, we're starting a quoted string.
$inQuote = true;
$inQuote = true;
$currentQuoteDelim = $char;
}

View File

@@ -2,17 +2,29 @@
namespace Laminas\Mail\Header;
use function getmypid;
use function mt_rand;
use function php_uname;
use function preg_match;
use function sha1;
use function sprintf;
use function strtolower;
use function time;
use function trim;
class MessageId implements HeaderInterface
{
/**
* @var string
*/
/** @var string */
protected $messageId;
/**
* @param string $headerLine
* @return static
*/
public static function fromString($headerLine)
{
list($name, $value) = GenericHeader::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
[$name, $value] = GenericHeader::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
// check to ensure proper header type for this factory
if (strtolower($name) !== 'message-id') {
@@ -25,27 +37,43 @@ class MessageId implements HeaderInterface
return $header;
}
/**
* @return string
*/
public function getFieldName()
{
return 'Message-ID';
}
/**
* @inheritDoc
*/
public function getFieldValue($format = HeaderInterface::FORMAT_RAW)
{
return $this->messageId;
}
/**
* @param string $encoding
* @return self
*/
public function setEncoding($encoding)
{
// This header must be always in US-ASCII
return $this;
}
/**
* @return string
*/
public function getEncoding()
{
return 'ASCII';
}
/**
* @return string
*/
public function toString()
{
return 'Message-ID: ' . $this->getFieldValue();
@@ -65,7 +93,8 @@ class MessageId implements HeaderInterface
$id = trim($id, '<>');
}
if (! HeaderValue::isValid($id)
if (
! HeaderValue::isValid($id)
|| preg_match("/[\r\n]/", $id)
) {
throw new Exception\InvalidArgumentException('Invalid ID detected');

View File

@@ -2,20 +2,26 @@
namespace Laminas\Mail\Header;
use function in_array;
use function preg_match;
use function strtolower;
class MimeVersion implements HeaderInterface
{
/**
* @var string Version string
*/
/** @var string Version string */
protected $version = '1.0';
/**
* @param string $headerLine
* @return static
*/
public static function fromString($headerLine)
{
list($name, $value) = GenericHeader::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
[$name, $value] = GenericHeader::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
// check to ensure proper header type for this factory
if (strtolower($name) !== 'mime-version') {
if (! in_array(strtolower($name), ['mimeversion', 'mime_version', 'mime-version'])) {
throw new Exception\InvalidArgumentException('Invalid header line for MIME-Version string');
}
@@ -28,27 +34,43 @@ class MimeVersion implements HeaderInterface
return $header;
}
/**
* @return string
*/
public function getFieldName()
{
return 'MIME-Version';
}
/**
* @inheritDoc
*/
public function getFieldValue($format = HeaderInterface::FORMAT_RAW)
{
return $this->version;
}
/**
* @param string $encoding
* @return self
*/
public function setEncoding($encoding)
{
// This header must be always in US-ASCII
return $this;
}
/**
* @return string
*/
public function getEncoding()
{
return 'ASCII';
}
/**
* @return string
*/
public function toString()
{
return 'MIME-Version: ' . $this->getFieldValue();

View File

@@ -4,31 +4,37 @@ namespace Laminas\Mail\Header;
use Laminas\Mail\Headers;
use function implode;
use function strtolower;
/**
* @todo Allow setting date from DateTime, Laminas\Date, or string
*/
class Received implements HeaderInterface, MultipleHeadersInterface
{
/**
* @var string
*/
/** @var string */
protected $value;
/**
* @param string $headerLine
* @return static
*/
public static function fromString($headerLine)
{
list($name, $value) = GenericHeader::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
[$name, $value] = GenericHeader::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
// check to ensure proper header type for this factory
if (strtolower($name) !== 'received') {
throw new Exception\InvalidArgumentException('Invalid header line for Received string');
}
$header = new static($value);
return $header;
return new static($value);
}
/**
* @param string $value
*/
public function __construct($value = '')
{
if (! HeaderValue::isValid($value)) {
@@ -37,27 +43,43 @@ class Received implements HeaderInterface, MultipleHeadersInterface
$this->value = $value;
}
/**
* @return string
*/
public function getFieldName()
{
return 'Received';
}
/**
* @inheritDoc
*/
public function getFieldValue($format = HeaderInterface::FORMAT_RAW)
{
return $this->value;
}
/**
* @param string $encoding
* @return self
*/
public function setEncoding($encoding)
{
// This header must be always in US-ASCII
return $this;
}
/**
* @return string
*/
public function getEncoding()
{
return 'ASCII';
}
/**
* @return string
*/
public function toString()
{
return 'Received: ' . $this->getFieldValue();

View File

@@ -4,6 +4,8 @@ namespace Laminas\Mail\Header;
class References extends IdentificationField
{
/** @var string */
protected $fieldName = 'References';
/** @var string */
protected static $type = 'references';
}

View File

@@ -4,6 +4,10 @@ namespace Laminas\Mail\Header;
class ReplyTo extends AbstractAddressList
{
/** @var string */
protected $fieldName = 'Reply-To';
/** @var string */
protected static $type = 'reply-to';
/** @var string[] */
protected static $typeAliases = ['replyto', 'reply_to'];
}

View File

@@ -3,8 +3,17 @@
namespace Laminas\Mail\Header;
use Laminas\Mail;
use Laminas\Mail\Address\AddressInterface;
use Laminas\Mime\Mime;
use function gettype;
use function is_object;
use function is_string;
use function preg_match;
use function sprintf;
use function strtolower;
use function trim;
/**
* Sender header class methods.
*
@@ -13,9 +22,7 @@ use Laminas\Mime\Mime;
*/
class Sender implements HeaderInterface
{
/**
* @var \Laminas\Mail\Address\AddressInterface
*/
/** @var AddressInterface */
protected $address;
/**
@@ -25,21 +32,26 @@ class Sender implements HeaderInterface
*/
protected $encoding;
/**
* @param string $headerLine
* @return static
*/
public static function fromString($headerLine)
{
list($name, $value) = GenericHeader::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
[$name, $value] = GenericHeader::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
// check to ensure proper header type for this factory
if (strtolower($name) !== 'sender') {
throw new Exception\InvalidArgumentException('Invalid header name for Sender string');
}
$header = new static();
$header = new static();
/**
* matches the header value so that the email must be enclosed by < > when a name is present
* 'name' and 'email' capture groups correspond respectively to 'display-name' and 'addr-spec' in the ABNF
*
* @see https://tools.ietf.org/html/rfc5322#section-3.4
*/
$hasMatches = preg_match(
@@ -63,11 +75,17 @@ class Sender implements HeaderInterface
return $header;
}
/**
* @return string
*/
public function getFieldName()
{
return 'Sender';
}
/**
* @inheritDoc
*/
public function getFieldValue($format = HeaderInterface::FORMAT_RAW)
{
if (! $this->address instanceof Mail\Address\AddressInterface) {
@@ -81,7 +99,7 @@ class Sender implements HeaderInterface
if ($format == HeaderInterface::FORMAT_ENCODED) {
$encoding = $this->getEncoding();
if ('ASCII' !== $encoding) {
$name = HeaderWrap::mimeEncodeValue($name, $encoding);
$name = HeaderWrap::mimeEncodeValue($name, $encoding);
}
}
$email = sprintf('%s %s', $name, $email);
@@ -90,12 +108,19 @@ class Sender implements HeaderInterface
return $email;
}
/**
* @param string $encoding
* @return self
*/
public function setEncoding($encoding)
{
$this->encoding = $encoding;
return $this;
}
/**
* @return string
*/
public function getEncoding()
{
if (! $this->encoding) {
@@ -107,6 +132,9 @@ class Sender implements HeaderInterface
return $this->encoding;
}
/**
* @return string
*/
public function toString()
{
return 'Sender: ' . $this->getFieldValue(HeaderInterface::FORMAT_ENCODED);
@@ -115,7 +143,7 @@ class Sender implements HeaderInterface
/**
* Set the address used in this header
*
* @param string|\Laminas\Mail\Address\AddressInterface $emailOrAddress
* @param string|AddressInterface $emailOrAddress
* @param null|string $name
* @throws Exception\InvalidArgumentException
* @return Sender
@@ -128,7 +156,7 @@ class Sender implements HeaderInterface
throw new Exception\InvalidArgumentException(sprintf(
'%s expects a string or AddressInterface object; received "%s"',
__METHOD__,
(is_object($emailOrAddress) ? get_class($emailOrAddress) : gettype($emailOrAddress))
is_object($emailOrAddress) ? $emailOrAddress::class : gettype($emailOrAddress)
));
}
$this->address = $emailOrAddress;
@@ -138,7 +166,7 @@ class Sender implements HeaderInterface
/**
* Retrieve the internal address from this header
*
* @return \Laminas\Mail\Address\AddressInterface|null
* @return AddressInterface|null
*/
public function getAddress()
{

View File

@@ -4,6 +4,9 @@ namespace Laminas\Mail\Header;
use Laminas\Mime\Mime;
use function strtolower;
use function strtoupper;
/**
* Subject header class methods.
*
@@ -12,9 +15,7 @@ use Laminas\Mime\Mime;
*/
class Subject implements UnstructuredInterface
{
/**
* @var string
*/
/** @var string */
protected $subject = '';
/**
@@ -24,10 +25,14 @@ class Subject implements UnstructuredInterface
*/
protected $encoding;
/**
* @param string $headerLine
* @return static
*/
public static function fromString($headerLine)
{
list($name, $value) = GenericHeader::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
[$name, $value] = GenericHeader::splitHeaderLine($headerLine);
$value = HeaderWrap::mimeDecodeValue($value);
// check to ensure proper header type for this factory
if (strtolower($name) !== 'subject') {
@@ -40,11 +45,17 @@ class Subject implements UnstructuredInterface
return $header;
}
/**
* @return string
*/
public function getFieldName()
{
return 'Subject';
}
/**
* @inheritDoc
*/
public function getFieldValue($format = HeaderInterface::FORMAT_RAW)
{
if (HeaderInterface::FORMAT_ENCODED === $format) {
@@ -54,6 +65,10 @@ class Subject implements UnstructuredInterface
return $this->subject;
}
/**
* @param string $encoding
* @return self
*/
public function setEncoding($encoding)
{
if ($encoding === $this->encoding) {
@@ -81,6 +96,9 @@ class Subject implements UnstructuredInterface
return $this;
}
/**
* @return string
*/
public function getEncoding()
{
if (! $this->encoding) {
@@ -90,6 +108,10 @@ class Subject implements UnstructuredInterface
return $this->encoding;
}
/**
* @param string $subject
* @return self
*/
public function setSubject($subject)
{
$subject = (string) $subject;
@@ -106,6 +128,9 @@ class Subject implements UnstructuredInterface
return $this;
}
/**
* @return string
*/
public function toString()
{
return 'Subject: ' . $this->getFieldValue(HeaderInterface::FORMAT_ENCODED);

View File

@@ -4,6 +4,8 @@ namespace Laminas\Mail\Header;
class To extends AbstractAddressList
{
/** @var string */
protected $fieldName = 'To';
/** @var string */
protected static $type = 'to';
}

View File

@@ -10,13 +10,40 @@ use Iterator;
use Laminas\Loader\PluginClassLocator;
use Laminas\Mail\Header\GenericHeader;
use Laminas\Mail\Header\HeaderInterface;
use Laminas\Mail\Header\HeaderLocatorInterface;
use ReturnTypeWillChange;
use Traversable;
use function array_keys;
use function array_shift;
use function assert;
use function count;
use function current;
use function explode;
use function gettype;
use function in_array;
use function is_array;
use function is_int;
use function is_object;
use function is_string;
use function key;
use function next;
use function preg_match;
use function reset;
use function sprintf;
use function str_replace;
use function strtolower;
use function trigger_error;
use function trim;
use const E_USER_DEPRECATED;
/**
* Basic mail headers collection functionality
*
* Handles aggregation of headers
*
* @implements Iterator<int, HeaderInterface>
*/
class Headers implements Countable, Iterator
{
@@ -26,10 +53,7 @@ class Headers implements Countable, Iterator
/** @var string Start of Line when folding */
public const FOLDING = "\r\n ";
/**
* @var null|Header\HeaderLocatorInterface
*/
private $headerLocator;
private ?HeaderLocatorInterface $headerLocator = null;
/**
* @todo Remove for 3.0.0.
@@ -37,14 +61,10 @@ class Headers implements Countable, Iterator
*/
protected $pluginClassLoader;
/**
* @var array key names for $headers array
*/
/** @var list<string> key names for $headers array */
protected $headersKeys = [];
/**
* @var Header\HeaderInterface[] instances
*/
/** @var list<HeaderInterface> instances */
protected $headers = [];
/**
@@ -62,18 +82,18 @@ class Headers implements Countable, Iterator
* will be lazy loaded)
*
* @param string $string
* @param string $EOL EOL string; defaults to {@link EOL}
* @throws Exception\RuntimeException
* @param string $eol EOL string; defaults to {@link EOL}
* @return Headers
* @throws Exception\RuntimeException
*/
public static function fromString($string, $EOL = self::EOL)
public static function fromString($string, $eol = self::EOL)
{
$headers = new static();
$currentLine = '';
$emptyLine = 0;
// iterate the header lines, some might be continuations
$lines = explode($EOL, $string);
$lines = explode($eol, $string);
$total = count($lines);
for ($i = 0; $i < $total; $i += 1) {
$line = $lines[$i];
@@ -128,6 +148,7 @@ class Headers implements Countable, Iterator
* Set an alternate PluginClassLocator implementation for loading header classes.
*
* @deprecated since 2.12.0
*
* @todo Remove for version 3.0.0
* @return $this
*/
@@ -137,7 +158,7 @@ class Headers implements Countable, Iterator
@trigger_error(sprintf(
'Since laminas/laminas-mail 2.12.0: Usage of %s is deprecated; use %s::setHeaderLocator() instead',
__METHOD__,
__CLASS__
self::class
), E_USER_DEPRECATED);
$this->pluginClassLoader = $pluginClassLoader;
@@ -150,6 +171,7 @@ class Headers implements Countable, Iterator
* Lazyloads a Header\HeaderLoader if necessary.
*
* @deprecated since 2.12.0
*
* @todo Remove for version 3.0.0
* @return PluginClassLocator
*/
@@ -159,7 +181,7 @@ class Headers implements Countable, Iterator
@trigger_error(sprintf(
'Since laminas/laminas-mail 2.12.0: Usage of %s is deprecated; use %s::getHeaderLocator() instead',
__METHOD__,
__CLASS__
self::class
), E_USER_DEPRECATED);
if (! $this->pluginClassLoader) {
@@ -174,11 +196,14 @@ class Headers implements Countable, Iterator
*
* Lazyloads a Header\HeaderLocator instance if necessary.
*/
public function getHeaderLocator(): Header\HeaderLocatorInterface
public function getHeaderLocator(): HeaderLocatorInterface
{
if (! $this->headerLocator) {
$this->setHeaderLocator(new Header\HeaderLocator());
}
assert($this->headerLocator instanceof HeaderLocatorInterface);
return $this->headerLocator;
}
@@ -186,7 +211,7 @@ class Headers implements Countable, Iterator
* @todo Return self when we update to 7.4 or later as minimum PHP version.
* @return $this
*/
public function setHeaderLocator(Header\HeaderLocatorInterface $headerLocator)
public function setHeaderLocator(HeaderLocatorInterface $headerLocator)
{
$this->headerLocator = $headerLocator;
return $this;
@@ -231,7 +256,7 @@ class Headers implements Countable, Iterator
if (! is_array($headers) && ! $headers instanceof Traversable) {
throw new Exception\InvalidArgumentException(sprintf(
'Expected array or Traversable; received "%s"',
(is_object($headers) ? get_class($headers) : gettype($headers))
is_object($headers) ? $headers::class : gettype($headers)
));
}
@@ -271,9 +296,9 @@ class Headers implements Countable, Iterator
throw new Exception\InvalidArgumentException(sprintf(
'%s expects its first argument to be a string; received "%s"',
__METHOD__,
(is_object($headerFieldNameOrLine)
? get_class($headerFieldNameOrLine)
: gettype($headerFieldNameOrLine))
is_object($headerFieldNameOrLine)
? $headerFieldNameOrLine::class
: gettype($headerFieldNameOrLine)
));
}
@@ -288,7 +313,7 @@ class Headers implements Countable, Iterator
$this->addHeader(Header\GenericMultiHeader::fromString($headerFieldNameOrLine . ':' . $i));
}
} else {
$this->addHeader(Header\GenericHeader::fromString($headerFieldNameOrLine . ':' . $fieldValue));
$this->addHeader(GenericHeader::fromString($headerFieldNameOrLine . ':' . $fieldValue));
}
return $this;
@@ -297,14 +322,13 @@ class Headers implements Countable, Iterator
/**
* Add a Header\Interface to this container, for raw values see {@link addHeaderLine()} and {@link addHeaders()}
*
* @param Header\HeaderInterface $header
* @return Headers
*/
public function addHeader(Header\HeaderInterface $header)
public function addHeader(HeaderInterface $header)
{
$key = $this->normalizeFieldName($header->getFieldName());
$key = $this->normalizeFieldName($header->getFieldName());
$this->headersKeys[] = $key;
$this->headers[] = $header;
$this->headers[] = $header;
if ($this->getEncoding() !== 'ASCII') {
$header->setEncoding($this->getEncoding());
}
@@ -314,7 +338,7 @@ class Headers implements Countable, Iterator
/**
* Remove a Header from the container
*
* @param string|Header\HeaderInterface field name or specific header instance to remove
* @param string|HeaderInterface $instanceOrFieldName field name or specific header instance to remove
* @return bool
*/
public function removeHeader($instanceOrFieldName)
@@ -323,8 +347,8 @@ class Headers implements Countable, Iterator
throw new Exception\InvalidArgumentException(sprintf(
'%s requires a string or %s instance; received %s',
__METHOD__,
Header\HeaderInterface::class,
is_object($instanceOrFieldName) ? get_class($instanceOrFieldName) : gettype($instanceOrFieldName)
HeaderInterface::class,
is_object($instanceOrFieldName) ? $instanceOrFieldName::class : gettype($instanceOrFieldName)
));
}
@@ -333,7 +357,7 @@ class Headers implements Countable, Iterator
}
if (is_string($instanceOrFieldName)) {
$key = $this->normalizeFieldName($instanceOrFieldName);
$key = $this->normalizeFieldName($instanceOrFieldName);
$indexes = array_keys($this->headersKeys, $key, true);
}
@@ -365,13 +389,13 @@ class Headers implements Countable, Iterator
* Get all headers of a certain name/type
*
* @param string $name
* @return bool|ArrayIterator|Header\HeaderInterface Returns false if there is no headers with $name in this
* @return false|ArrayIterator|HeaderInterface Returns false if there is no headers with $name in this
* contain, an ArrayIterator if the header is a MultipleHeadersInterface instance and finally returns
* HeaderInterface for the rest of cases.
*/
public function get($name)
{
$key = $this->normalizeFieldName($name);
$key = $this->normalizeFieldName($name);
$results = [];
foreach (array_keys($this->headersKeys, $key, true) as $index) {
@@ -409,7 +433,6 @@ class Headers implements Countable, Iterator
/**
* Advance the pointer for this object as an iterator
*
*/
#[ReturnTypeWillChange]
public function next()
@@ -436,12 +459,11 @@ class Headers implements Countable, Iterator
#[ReturnTypeWillChange]
public function valid()
{
return (current($this->headers) !== false);
return current($this->headers) !== false;
}
/**
* Reset the internal pointer for this object as an iterator
*
*/
#[ReturnTypeWillChange]
public function rewind()
@@ -452,7 +474,7 @@ class Headers implements Countable, Iterator
/**
* Return the current value for this iterator, lazy loading it if need be
*
* @return Header\HeaderInterface
* @return HeaderInterface
*/
#[ReturnTypeWillChange]
public function current()
@@ -500,13 +522,12 @@ class Headers implements Countable, Iterator
* Return the headers container as an array
*
* @param bool $format Return the values in Mime::Encoded or in Raw format
* @return array
* @return array<string, list<string>|string>
* @todo determine how to produce single line headers, if they are supported
*/
public function toArray($format = Header\HeaderInterface::FORMAT_RAW)
public function toArray($format = HeaderInterface::FORMAT_RAW)
{
$headers = [];
/* @var $header Header\HeaderInterface */
foreach ($this->headers as $header) {
if ($header instanceof Header\MultipleHeadersInterface) {
$name = $header->getFieldName();
@@ -528,6 +549,7 @@ class Headers implements Countable, Iterator
*/
public function forceLoading()
{
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedForeach
foreach ($this as $item) {
// $item should now be loaded
}
@@ -538,11 +560,11 @@ class Headers implements Countable, Iterator
* Create Header object from header line
*
* @param string $headerLine
* @return Header\HeaderInterface|Header\HeaderInterface[]
* @return HeaderInterface|HeaderInterface[]
*/
public function loadHeader($headerLine)
{
list($name) = Header\GenericHeader::splitHeaderLine($headerLine);
[$name] = GenericHeader::splitHeaderLine($headerLine);
/** @var HeaderInterface $class */
$class = $this->resolveHeaderClass($name);
@@ -550,14 +572,14 @@ class Headers implements Countable, Iterator
}
/**
* @param $index
* @param array-key $index
* @return mixed
*/
protected function lazyLoadHeader($index)
{
$current = $this->headers[$index];
$key = $this->headersKeys[$index];
$key = $this->headersKeys[$index];
/** @var GenericHeader $class */
$class = $this->resolveHeaderClass($key);
@@ -595,13 +617,13 @@ class Headers implements Countable, Iterator
/**
* @param string $key
* @return string
* @return null|string
*/
private function resolveHeaderClass($key)
{
if ($this->pluginClassLoader) {
return $this->pluginClassLoader->load($key) ?: Header\GenericHeader::class;
return $this->pluginClassLoader->load($key) ?: GenericHeader::class;
}
return $this->getHeaderLocator()->get($key, Header\GenericHeader::class);
return $this->getHeaderLocator()->get($key, GenericHeader::class);
}
}

View File

@@ -2,6 +2,7 @@
namespace Laminas\Mail;
use ArrayIterator;
use Laminas\Mail\Header\Bcc;
use Laminas\Mail\Header\Cc;
use Laminas\Mail\Header\ContentType;
@@ -13,6 +14,16 @@ use Laminas\Mail\Header\To;
use Laminas\Mime;
use Traversable;
use function array_shift;
use function count;
use function date;
use function gettype;
use function is_array;
use function is_object;
use function is_string;
use function method_exists;
use function sprintf;
class Message
{
/**
@@ -22,9 +33,7 @@ class Message
*/
protected $body;
/**
* @var Headers
*/
/** @var Headers */
protected $headers;
/**
@@ -78,7 +87,6 @@ class Message
/**
* Compose headers
*
* @param Headers $headers
* @return Message
*/
public function setHeaders(Headers $headers)
@@ -297,11 +305,9 @@ class Message
/**
* setSender
*
* @param mixed $emailOrAddress
* @param mixed $name
* @return Message
*/
public function setSender($emailOrAddress, $name = null)
public function setSender(mixed $emailOrAddress, mixed $name = null)
{
/** @var Sender $header */
$header = $this->getHeaderByName('sender', Sender::class);
@@ -385,7 +391,7 @@ class Message
. ' object of type "%s" received',
__METHOD__,
Mime\Message::class,
get_class($body)
$body::class
));
}
}
@@ -402,7 +408,7 @@ class Message
// Multipart content headers
if ($this->body->isMultiPart()) {
$mime = $this->body->getMime();
$mime = $this->body->getMime();
/** @var ContentType $header */
$header = $this->getHeaderByName('content-type', ContentType::class);
@@ -451,7 +457,7 @@ class Message
*
* @param string $headerName
* @param string $headerClass
* @return Header\HeaderInterface|\ArrayIterator header instance or collection of headers
* @return Header\HeaderInterface|ArrayIterator header instance or collection of headers
*/
protected function getHeaderByName($headerName, $headerClass)
{
@@ -492,7 +498,7 @@ class Message
if (! $header instanceof Header\AbstractAddressList) {
throw new Exception\DomainException(sprintf(
'Cannot grab address list from header of type "%s"; not an AbstractAddressList implementation',
get_class($header)
$header::class
));
}
return $header->getAddressList();
@@ -503,7 +509,6 @@ class Message
*
* Proxied to this from addFrom, addTo, addCc, addBcc, and addReplyTo.
*
* @param AddressList $addressList
* @param string|Address\AddressInterface|array|AddressList|Traversable $emailOrAddressOrList
* @param null|string $name
* @param string $callingMethod
@@ -526,7 +531,7 @@ class Message
'%s expects a string, AddressInterface, array, AddressList, or Traversable as its first argument;'
. ' received "%s"',
$callingMethod,
(is_object($emailOrAddressOrList) ? get_class($emailOrAddressOrList) : gettype($emailOrAddressOrList))
is_object($emailOrAddressOrList) ? $emailOrAddressOrList::class : gettype($emailOrAddressOrList)
));
}
@@ -566,9 +571,9 @@ class Message
$headers = null;
$content = null;
Mime\Decode::splitMessage($rawMessage, $headers, $content, Headers::EOL);
if ($headers->has('mime-version')) {
// if ($headers->has('mime-version')) {
// todo - restore body to mime\message
}
// }
$message->setHeaders($headers);
$message->setBody($content);
return $message;

View File

@@ -4,6 +4,15 @@ namespace Laminas\Mail;
use Traversable;
use function gettype;
use function is_array;
use function is_object;
use function method_exists;
use function sprintf;
use function str_replace;
use function strtr;
use function ucwords;
class MessageFactory
{
/**
@@ -16,7 +25,7 @@ class MessageFactory
throw new Exception\InvalidArgumentException(sprintf(
'"%s" expects an array or Traversable; received "%s"',
__METHOD__,
(is_object($options) ? get_class($options) : gettype($options))
is_object($options) ? $options::class : gettype($options)
));
}

View File

@@ -3,6 +3,28 @@
namespace Laminas\Mail\Protocol;
use Laminas\Validator;
use Laminas\Validator\ValidatorChain;
use function array_shift;
use function count;
use function fclose;
use function fgets;
use function fwrite;
use function implode;
use function in_array;
use function is_array;
use function is_resource;
use function preg_split;
use function restore_error_handler;
use function set_error_handler;
use function sprintf;
use function str_starts_with;
use function stream_get_meta_data;
use function stream_set_timeout;
use function stream_socket_client;
use const E_WARNING;
use const PREG_SPLIT_DELIM_CAPTURE;
/**
* Provides low-level methods for concrete adapters to communicate with a
@@ -24,60 +46,57 @@ abstract class AbstractProtocol
/**
* Maximum of the transaction log
*
* @var int
*/
protected $maximumLog = 64;
/**
* Hostname or IP address of remote server
*
* @var string
*/
protected $host;
/**
* Port number of connection
* @var int
*/
protected $port;
/**
* Instance of Laminas\Validator\ValidatorChain to check hostnames
* @var \Laminas\Validator\ValidatorChain
*
* @var ValidatorChain
*/
protected $validHost;
/**
* Socket connection resource
*
* @var null|resource
*/
protected $socket;
/**
* Last request sent to server
*
* @var string
*/
protected $request;
/**
* Array of server responses to last request
*
* @var array
*/
protected $response;
/**
* Log of mail requests and server responses for a session
* @var array
*/
private $log = [];
private array $log = [];
/**
* Constructor.
*
* @param string $host OPTIONAL Hostname of remote connection (default: 127.0.0.1)
* @param int $port OPTIONAL Port number (default: null)
* @throws Exception\RuntimeException
*/
public function __construct($host = '127.0.0.1', $port = null)
public function __construct($host = '127.0.0.1', protected $port = null)
{
$this->validHost = new Validator\ValidatorChain();
$this->validHost->attach(new Validator\Hostname(Validator\Hostname::ALLOW_ALL));
@@ -87,12 +106,10 @@ abstract class AbstractProtocol
}
$this->host = $host;
$this->port = $port;
}
/**
* Class destructor to cleanup open resources
*
*/
public function __destruct()
{
@@ -159,7 +176,6 @@ abstract class AbstractProtocol
/**
* Reset the transaction log
*
*/
public function resetLog()
{
@@ -187,6 +203,7 @@ abstract class AbstractProtocol
* An example $remote string may be 'tcp://mail.example.com:25' or 'ssh://hostname.com:2222'
*
* @deprecated Since 1.12.0. Implementations should use the ProtocolTrait::setupSocket() method instead.
*
* @todo Remove for 3.0.0.
* @param string $remote Remote
* @throws Exception\RuntimeException
@@ -200,7 +217,7 @@ abstract class AbstractProtocol
// open connection
set_error_handler(
function ($error, $message = '') {
static function ($error, $message = '') {
throw new Exception\RuntimeException(sprintf('Could not open socket: %s', $message), $error);
},
E_WARNING
@@ -224,7 +241,6 @@ abstract class AbstractProtocol
/**
* Disconnect from remote host and free resource
*
*/
// @codingStandardsIgnoreLine PSR2.Methods.MethodDeclaration.Underscore
protected function _disconnect()
@@ -316,15 +332,15 @@ abstract class AbstractProtocol
protected function _expect($code, $timeout = null)
{
$this->response = [];
$errMsg = '';
$errMsg = '';
if (! is_array($code)) {
$code = [$code];
}
do {
$this->response[] = $result = $this->_receive($timeout);
list($cmd, $more, $msg) = preg_split('/([\s-]+)/', $result, 2, PREG_SPLIT_DELIM_CAPTURE);
$this->response[] = $result = $this->_receive($timeout);
[$cmd, $more, $msg] = preg_split('/([\s-]+)/', $result, 2, PREG_SPLIT_DELIM_CAPTURE);
if ($errMsg !== '') {
$errMsg .= ' ' . $msg;
@@ -333,7 +349,7 @@ abstract class AbstractProtocol
}
// The '-' message prefix indicates an information string instead of a response string.
} while (strpos($more, '-') === 0);
} while (str_starts_with($more, '-'));
if ($errMsg !== '') {
throw new Exception\RuntimeException($errMsg);

View File

@@ -2,6 +2,40 @@
namespace Laminas\Mail\Protocol;
use Laminas\Mail\Protocol\Exception\ExceptionInterface;
use function array_merge;
use function array_pop;
use function array_push;
use function array_search;
use function array_shift;
use function count;
use function current;
use function explode;
use function fclose;
use function fgets;
use function func_get_args;
use function func_num_args;
use function fwrite;
use function implode;
use function is_array;
use function is_numeric;
use function key;
use function next;
use function preg_match;
use function rtrim;
use function str_contains;
use function str_replace;
use function str_starts_with;
use function stream_socket_enable_crypto;
use function strlen;
use function strpos;
use function strtolower;
use function substr;
use function trim;
use const INF;
class Imap
{
use ProtocolTrait;
@@ -11,13 +45,12 @@ class Imap
*/
public const TIMEOUT_CONNECTION = 30;
/**
* @var null|resource
*/
/** @var null|resource */
protected $socket;
/**
* counter for request tag
*
* @var int
*/
protected $tagCount = 0;
@@ -29,7 +62,7 @@ class Imap
* @param int|null $port port of IMAP server, null for default (143 or 993 for ssl)
* @param string|bool $ssl use ssl? 'SSL', 'TLS' or false
* @param bool $novalidatecert set to true to skip SSL certificate validation
* @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface
* @throws ExceptionInterface
*/
public function __construct($host = '', $port = null, $ssl = false, $novalidatecert = false)
{
@@ -60,7 +93,7 @@ class Imap
public function connect($host, $port = null, $ssl = false)
{
$transport = 'tcp';
$isTls = false;
$isTls = false;
if ($ssl) {
$ssl = strtolower($ssl);
@@ -123,7 +156,7 @@ class Imap
protected function assumedNextLine($start)
{
$line = $this->nextLine();
return strpos($line, $start) === 0;
return str_starts_with($line, $start);
}
/**
@@ -137,7 +170,7 @@ class Imap
$line = $this->nextLine();
// separate tag from line
list($tag, $line) = explode(' ', $line, 2);
[$tag, $line] = explode(' ', $line, 2);
return $line;
}
@@ -151,7 +184,7 @@ class Imap
protected function decodeLine($line)
{
$tokens = [];
$stack = [];
$stack = [];
/*
We start to decode the response here. The understood tokens are:
@@ -177,18 +210,18 @@ class Imap
while ($token[0] == '(') {
array_push($stack, $tokens);
$tokens = [];
$token = substr($token, 1);
$token = substr($token, 1);
}
if ($token[0] == '"') {
if (preg_match('%^\(*"((.|\\\\|\\")*?)" *%', $line, $matches)) {
$tokens[] = $matches[1];
$line = substr($line, strlen($matches[0]));
$line = substr($line, strlen($matches[0]));
continue;
}
}
if ($token[0] == '{') {
$endPos = strpos($token, '}');
$chars = substr($token, 1, $endPos - 1);
$chars = substr($token, 1, $endPos - 1);
if (is_numeric($chars)) {
$token = '';
while (strlen($token) < $chars) {
@@ -196,43 +229,43 @@ class Imap
}
$line = '';
if (strlen($token) > $chars) {
$line = substr($token, $chars);
$line = substr($token, $chars);
$token = substr($token, 0, $chars);
} else {
$line .= $this->nextLine();
}
$tokens[] = $token;
$line = trim($line) . ' ';
$line = trim($line) . ' ';
continue;
}
}
if ($stack && $token[strlen($token) - 1] == ')') {
// closing braces are not separated by spaces, so we need to count them
$braces = strlen($token);
$token = rtrim($token, ')');
$token = rtrim($token, ')');
// only count braces if more than one
$braces -= strlen($token) + 1;
// only add if token had more than just closing braces
if (rtrim($token) != '') {
$tokens[] = rtrim($token);
}
$token = $tokens;
$token = $tokens;
$tokens = array_pop($stack);
// special handline if more than one closing brace
while ($braces-- > 0) {
$tokens[] = $token;
$token = $tokens;
$tokens = array_pop($stack);
$token = $tokens;
$tokens = array_pop($stack);
}
}
$tokens[] = $token;
$line = substr($line, $pos + 1);
$line = substr($line, $pos + 1);
}
// maybe the server forgot to send some closing braces
while ($stack) {
$child = $tokens;
$tokens = array_pop($stack);
$child = $tokens;
$tokens = array_pop($stack);
$tokens[] = $child;
}
@@ -274,7 +307,7 @@ class Imap
*/
public function readResponse($tag, $dontParse = false)
{
$lines = [];
$lines = [];
$tokens = null; // define $tokens variable before first use
while (! $this->readLine($tokens, $tag, $dontParse)) {
$lines[] = $tokens;
@@ -287,7 +320,7 @@ class Imap
// last line has response code
if ($tokens[0] == 'OK') {
return $lines ? $lines : true;
return $lines ?: true;
} elseif ($tokens[0] == 'NO') {
return false;
}
@@ -341,9 +374,7 @@ class Imap
{
$tag = null; // define $tag variable before first use
$this->sendRequest($command, $tokens, $tag);
$response = $this->readResponse($tag, $dontParse);
return $response;
return $this->readResponse($tag, $dontParse);
}
/**
@@ -356,7 +387,7 @@ class Imap
public function escapeString($string)
{
if (func_num_args() < 2) {
if (strpos($string, "\n") !== false) {
if (str_contains($string, "\n")) {
return ['{' . strlen($string) . '}', $string];
}
@@ -412,7 +443,7 @@ class Imap
if ($this->socket) {
try {
$result = $this->requestAndResponse('LOGOUT', [], true);
} catch (Exception\ExceptionInterface $e) {
} catch (Exception\ExceptionInterface) {
// ignoring exception
}
fclose($this->socket);
@@ -425,7 +456,7 @@ class Imap
* Get capabilities from IMAP server
*
* @return array list of capabilities
* @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface
* @throws ExceptionInterface
*/
public function capability()
{
@@ -450,7 +481,7 @@ class Imap
* @param string $box which folder to change to or examine
* @return bool|array false if error, array with returned information
* otherwise (flags, exists, recent, uidvalidity)
* @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface
* @throws ExceptionInterface
*/
public function examineOrSelect($command = 'EXAMINE', $box = 'INBOX')
{
@@ -489,7 +520,7 @@ class Imap
*
* @param string $box change to this folder
* @return bool|array see examineOrselect()
* @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface
* @throws ExceptionInterface
*/
public function select($box = 'INBOX')
{
@@ -501,7 +532,7 @@ class Imap
*
* @param string $box examine this folder
* @return bool|array see examineOrselect()
* @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface
* @throws ExceptionInterface
*/
public function examine($box = 'INBOX')
{
@@ -535,7 +566,7 @@ class Imap
$set = (int) $from . ':' . (int) $to;
}
$items = (array) $items;
$items = (array) $items;
$itemList = $this->escapeList($items);
$tag = null; // define $tag variable before first use
@@ -593,6 +624,7 @@ class Imap
// if we want only one message we can ignore everything else and just return
if ($to === null && ! is_array($from) && ($uid ? $tokens[2][$uidKey] == $from : $tokens[0] == $from)) {
// we still need to read all lines
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedWhile
while (! $this->readLine($tokens, $tag)) {
}
return $data;
@@ -615,12 +647,12 @@ class Imap
* @param string $reference mailbox reference for list
* @param string $mailbox mailbox name match with wildcards
* @return array mailboxes that matched $mailbox as array(globalName => array('delim' => .., 'flags' => ..))
* @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface
* @throws ExceptionInterface
*/
public function listMailbox($reference = '', $mailbox = '*')
{
$result = [];
$list = $this->requestAndResponse('LIST', $this->escapeString($reference, $mailbox));
$list = $this->requestAndResponse('LIST', $this->escapeString($reference, $mailbox));
if (! $list || $list === true) {
return $result;
}
@@ -645,7 +677,7 @@ class Imap
* @param string|null $mode '+' to add flags, '-' to remove flags, everything else sets the flags as given
* @param bool $silent if false the return values are the new flags for the wanted messages
* @return bool|array new flags if $silent is false, else true or false depending on success
* @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface
* @throws ExceptionInterface
*/
public function store(array $flags, $from, $to = null, $mode = null, $silent = true)
{
@@ -658,7 +690,7 @@ class Imap
}
$flags = $this->escapeList($flags);
$set = (int) $from;
$set = (int) $from;
if ($to !== null) {
$set .= ':' . ($to == INF ? '*' : (int) $to);
}
@@ -689,11 +721,11 @@ class Imap
* @param array $flags flags for new message
* @param string $date date for new message
* @return bool success
* @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface
* @throws ExceptionInterface
*/
public function append($folder, $message, $flags = null, $date = null)
{
$tokens = [];
$tokens = [];
$tokens[] = $this->escapeString($folder);
if ($flags !== null) {
$tokens[] = $this->escapeList($flags);
@@ -710,14 +742,14 @@ class Imap
* copy message set from current folder to other folder
*
* @param string $folder destination folder
* @param $from
* @param int $from
* @param int|null $to if null only one message ($from) is fetched, else it's the
* last message, INF means last message available
* @return bool success
*/
public function copy($folder, $from, $to = null)
{
$set = (int) $from;
$set = (string) $from;
if ($to !== null) {
$set .= ':' . ($to == INF ? '*' : (int) $to);
}

View File

@@ -2,8 +2,23 @@
namespace Laminas\Mail\Protocol;
use Laminas\Mail\Protocol\Pop3\Response;
use Laminas\Stdlib\ErrorHandler;
use function explode;
use function fclose;
use function fgets;
use function fwrite;
use function is_string;
use function md5;
use function rtrim;
use function stream_socket_enable_crypto;
use function strpos;
use function strtok;
use function strtolower;
use function substr;
use function trim;
class Pop3
{
use ProtocolTrait;
@@ -15,17 +30,17 @@ class Pop3
/**
* saves if server supports top
*
* @var null|bool
*/
public $hasTop = null;
public $hasTop;
/**
* @var null|resource
*/
/** @var null|resource */
protected $socket;
/**
* greeting timestamp for apop
*
* @var null|string
*/
protected $timestamp;
@@ -67,7 +82,7 @@ class Pop3
public function connect($host, $port = null, $ssl = false)
{
$transport = 'tcp';
$isTls = false;
$isTls = false;
if ($ssl) {
$ssl = strtolower($ssl);
@@ -136,6 +151,37 @@ class Pop3
* @return string response
*/
public function readResponse($multiline = false)
{
$response = $this->readRemoteResponse();
if ($response->status() != '+OK') {
throw new Exception\RuntimeException('last request failed');
}
$message = $response->message();
if ($multiline) {
$message = '';
$line = fgets($this->socket);
while ($line && rtrim($line, "\r\n") != '.') {
if ($line[0] == '.') {
$line = substr($line, 1);
}
$message .= $line;
$line = fgets($this->socket);
}
}
return $message;
}
/**
* read a response
* return extracted status / message from response
* @throws Exception\RuntimeException
*/
protected function readRemoteResponse(): Response
{
ErrorHandler::start();
$result = fgets($this->socket);
@@ -146,29 +192,13 @@ class Pop3
$result = trim($result);
if (strpos($result, ' ')) {
list($status, $message) = explode(' ', $result, 2);
[$status, $message] = explode(' ', $result, 2);
} else {
$status = $result;
$status = $result;
$message = '';
}
if ($status != '+OK') {
throw new Exception\RuntimeException('last request failed');
}
if ($multiline) {
$message = '';
$line = fgets($this->socket);
while ($line && rtrim($line, "\r\n") != '.') {
if ($line[0] == '.') {
$line = substr($line, 1);
}
$message .= $line;
$line = fgets($this->socket);
}
}
return $message;
return new Response($status, $message);
}
/**
@@ -176,6 +206,7 @@ class Pop3
*
* @see sendRequest()
* @see readResponse()
*
* @param string $request request
* @param bool $multiline multiline response?
* @return string result from readResponse()
@@ -194,7 +225,7 @@ class Pop3
if ($this->socket) {
try {
$this->request('QUIT');
} catch (Exception\ExceptionInterface $e) {
} catch (Exception\ExceptionInterface) {
// ignore error - we're closing the socket anyway
}
@@ -227,7 +258,7 @@ class Pop3
try {
$this->request("APOP $user " . md5($this->timestamp . $password));
return;
} catch (Exception\ExceptionInterface $e) {
} catch (Exception\ExceptionInterface) {
// ignore
}
}
@@ -245,10 +276,10 @@ class Pop3
public function status(&$messages, &$octets)
{
$messages = 0;
$octets = 0;
$result = $this->request('STAT');
$octets = 0;
$result = $this->request('STAT');
list($messages, $octets) = explode(' ', $result);
[$messages, $octets] = explode(' ', $result);
}
/**
@@ -262,17 +293,17 @@ class Pop3
if ($msgno !== null) {
$result = $this->request("LIST $msgno");
list(, $result) = explode(' ', $result);
[, $result] = explode(' ', $result);
return (int) $result;
}
$result = $this->request('LIST', true);
$result = $this->request('LIST', true);
$messages = [];
$line = strtok($result, "\n");
$line = strtok($result, "\n");
while ($line) {
list($no, $size) = explode(' ', trim($line));
[$no, $size] = explode(' ', trim($line));
$messages[(int) $no] = (int) $size;
$line = strtok("\n");
$line = strtok("\n");
}
return $messages;
@@ -289,19 +320,19 @@ class Pop3
if ($msgno !== null) {
$result = $this->request("UIDL $msgno");
list(, $result) = explode(' ', $result);
[, $result] = explode(' ', $result);
return $result;
}
$result = $this->request('UIDL', true);
$result = explode("\n", $result);
$result = explode("\n", $result);
$messages = [];
foreach ($result as $line) {
if (! $line) {
continue;
}
list($no, $id) = explode(' ', trim($line), 2);
[$no, $id] = explode(' ', trim($line), 2);
$messages[(int) $no] = $id;
}
@@ -333,7 +364,7 @@ class Pop3
}
$this->hasTop = true;
$lines = (! $lines || $lines < 1) ? 0 : (int) $lines;
$lines = ! $lines || $lines < 1 ? 0 : (int) $lines;
try {
$result = $this->request("TOP $msgno $lines", true);
@@ -357,8 +388,7 @@ class Pop3
*/
public function retrieve($msgno)
{
$result = $this->request("RETR $msgno", true);
return $result;
return $this->request("RETR $msgno", true);
}
/**
@@ -372,7 +402,7 @@ class Pop3
/**
* Make a DELE count to remove a message
*
* @param $msgno
* @param int $msgno
*/
public function delete($msgno)
{

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Laminas\Mail\Protocol\Pop3;
/**
* POP3 response value object
*
* @internal
*/
final class Response
{
/** @var string $status */
private $status;
/** @var string $message */
private $message;
public function __construct(string $status, string $message)
{
$this->status = $status;
$this->message = $message;
}
public function status(): string
{
return $this->status;
}
public function message(): string
{
return $this->message;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Laminas\Mail\Protocol\Pop3\Xoauth2;
use Laminas\Mail\Protocol\Exception\RuntimeException;
use Laminas\Mail\Protocol\Pop3;
use Laminas\Mail\Protocol\Xoauth2\Xoauth2;
/**
* @final
*/
class Microsoft extends Pop3
{
protected const AUTH_INITIALIZE_REQUEST = 'AUTH XOAUTH2';
protected const AUTH_RESPONSE_INITIALIZED_OK = '+';
/**
* @param string $user the target mailbox to access
* @param string $password OAUTH2 accessToken
* @param bool $tryApop obsolete parameter not used here
*/
public function login($user, $password, $tryApop = true): void
{
$this->sendRequest(self::AUTH_INITIALIZE_REQUEST);
$response = $this->readRemoteResponse();
if ($response->status() != self::AUTH_RESPONSE_INITIALIZED_OK) {
throw new RuntimeException($response->message());
}
$this->request(Xoauth2::encodeXoauth2Sasl($user, $password));
}
}

View File

@@ -4,6 +4,17 @@ namespace Laminas\Mail\Protocol;
use Laminas\Stdlib\ErrorHandler;
use function defined;
use function sprintf;
use function stream_context_create;
use function stream_set_timeout;
use function stream_socket_client;
use const STREAM_CLIENT_CONNECT;
use const STREAM_CRYPTO_METHOD_TLS_CLIENT;
use const STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
use const STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
/**
* https://bugs.php.net/bug.php?id=69195
*/
@@ -11,6 +22,7 @@ trait ProtocolTrait
{
/**
* If set to true, do not validate the SSL certificate
*
* @var null|bool
*/
protected $novalidatecert;
@@ -45,8 +57,6 @@ trait ProtocolTrait
/**
* Should we validate SSL certificate?
*
* @return bool
*/
public function validateCert(): bool
{
@@ -94,7 +104,7 @@ trait ProtocolTrait
STREAM_CLIENT_CONNECT,
stream_context_create($this->prepareSocketOptions())
);
$error = ErrorHandler::stop();
$error = ErrorHandler::stop();
if (! $socket) {
throw new Exception\RuntimeException(sprintf(

View File

@@ -5,6 +5,23 @@ namespace Laminas\Mail\Protocol;
use Generator;
use Laminas\Mail\Headers;
use function array_key_exists;
use function array_replace_recursive;
use function chunk_split;
use function fclose;
use function fgets;
use function fopen;
use function fwrite;
use function implode;
use function ini_get;
use function is_array;
use function rewind;
use function rtrim;
use function stream_socket_enable_crypto;
use function strlen;
use function strtolower;
use function substr;
/**
* SMTP implementation of Laminas\Mail\Protocol\AbstractProtocol
*
@@ -18,6 +35,7 @@ class Smtp extends AbstractProtocol
/**
* RFC 5322 section-2.2.3 specifies maximum of 998 bytes per line.
* This may not be exceeded.
*
* @see https://tools.ietf.org/html/rfc5322#section-2.2.3
*/
public const SMTP_LINE_LIMIT = 998;
@@ -69,7 +87,7 @@ class Smtp extends AbstractProtocol
*
* @var bool
*/
protected $data = null;
protected $data;
/**
* Whether or not send QUIT command
@@ -79,8 +97,6 @@ class Smtp extends AbstractProtocol
protected $useCompleteQuit = true;
/**
* Constructor.
*
* The first argument may be an array of all options. If so, it must include
* the 'host' and 'port' keys in order to ensure that all required values
* are present.
@@ -90,7 +106,7 @@ class Smtp extends AbstractProtocol
* @param null|array $config
* @throws Exception\InvalidArgumentException
*/
public function __construct($host = '127.0.0.1', $port = null, array $config = null)
public function __construct($host = '127.0.0.1', $port = null, ?array $config = null)
{
// Did we receive a configuration array?
if (is_array($host)) {
@@ -129,7 +145,7 @@ class Smtp extends AbstractProtocol
case 'ssl':
$this->transport = 'ssl';
$this->secure = 'ssl';
$this->secure = 'ssl';
if ($port === null) {
$port = 465;
}
@@ -177,10 +193,7 @@ class Smtp extends AbstractProtocol
/**
* Read $data as lines terminated by "\n"
*
* @param string $data
* @param int $chunkSize
* @return Generator|string[]
* @author Elan Ruusamäe <glen@pld-linux.org>
*/
private static function chunkedReader(string $data, int $chunkSize = 4096): Generator
{
@@ -305,7 +318,7 @@ class Smtp extends AbstractProtocol
* Send EHLO or HELO depending on capabilities of smtp host
*
* @param string $host The client hostname or IP address (default: 127.0.0.1)
* @throws \Exception|Exception\ExceptionInterface
* @throws Exception\ExceptionInterface
*/
protected function ehlo($host)
{
@@ -313,7 +326,7 @@ class Smtp extends AbstractProtocol
try {
$this->_send('EHLO ' . $host);
$this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
} catch (Exception\ExceptionInterface $e) {
} catch (Exception\ExceptionInterface) {
$this->_send('HELO ' . $host);
$this->_expect(250, 300); // Timeout set for 5 minutes as per RFC 2821 4.5.3.2
}
@@ -388,7 +401,7 @@ class Smtp extends AbstractProtocol
// Add "-1" to stay within limits,
// because Headers::FOLDING includes a byte for space character after \r\n
$chunks = chunk_split($line, self::SMTP_LINE_LIMIT - 1, Headers::FOLDING);
$line = substr($chunks, 0, -strlen(Headers::FOLDING));
$line = substr($chunks, 0, -strlen(Headers::FOLDING));
}
$this->_send($line);
@@ -420,7 +433,6 @@ class Smtp extends AbstractProtocol
* Issues the NOOP command end validates answer
*
* Not used by Laminas\Mail, could be used to keep a connection alive or check if it is still open.
*
*/
public function noop()
{
@@ -443,7 +455,6 @@ class Smtp extends AbstractProtocol
/**
* Issues the QUIT command and clears the current session
*
*/
public function quit()
{
@@ -475,7 +486,6 @@ class Smtp extends AbstractProtocol
/**
* Closes connection
*
*/
public function disconnect()
{
@@ -488,7 +498,6 @@ class Smtp extends AbstractProtocol
// @codingStandardsIgnoreLine PSR2.Methods.MethodDeclaration.Underscore
protected function _disconnect()
{
// Make sure the session gets closed
$this->quit();
parent::_disconnect();
@@ -496,7 +505,6 @@ class Smtp extends AbstractProtocol
/**
* Start mail session
*
*/
protected function startSession()
{
@@ -505,7 +513,6 @@ class Smtp extends AbstractProtocol
/**
* Stop mail session
*
*/
protected function stopSession()
{

View File

@@ -5,24 +5,23 @@ namespace Laminas\Mail\Protocol\Smtp\Auth;
use Laminas\Crypt\Hmac;
use Laminas\Mail\Protocol\Smtp;
use function array_replace_recursive;
use function base64_decode;
use function base64_encode;
use function is_array;
/**
* Performs CRAM-MD5 authentication
*/
class Crammd5 extends Smtp
{
/**
* @var string
*/
/** @var string */
protected $username;
/**
* @var string
*/
/** @var string */
protected $password;
/**
* Constructor.
*
* All parameters may be passed as an array to the first argument of the
* constructor. If so,
*
@@ -67,7 +66,7 @@ class Crammd5 extends Smtp
$this->_send('AUTH CRAM-MD5');
$challenge = $this->_expect(334);
$challenge = base64_decode($challenge);
$digest = $this->hmacMd5($this->getPassword(), $challenge);
$digest = $this->hmacMd5($this->getPassword(), $challenge);
$this->_send(base64_encode($this->getUsername() . ' ' . $digest));
$this->_expect(235);
$this->auth = true;

View File

@@ -4,6 +4,10 @@ namespace Laminas\Mail\Protocol\Smtp\Auth;
use Laminas\Mail\Protocol\Smtp;
use function array_replace_recursive;
use function base64_encode;
use function is_array;
/**
* Performs LOGIN authentication
*/
@@ -24,8 +28,6 @@ class Login extends Smtp
protected $password;
/**
* Constructor.
*
* @param string $host (Default: 127.0.0.1)
* @param int $port (Default: null)
* @param array $config Auth-specific parameters
@@ -58,7 +60,6 @@ class Login extends Smtp
/**
* Perform LOGIN authentication with supplied credentials
*
*/
public function auth()
{

View File

@@ -4,6 +4,10 @@ namespace Laminas\Mail\Protocol\Smtp\Auth;
use Laminas\Mail\Protocol\Smtp;
use function array_replace_recursive;
use function base64_encode;
use function is_array;
/**
* Performs PLAIN authentication
*/
@@ -24,8 +28,6 @@ class Plain extends Smtp
protected $password;
/**
* Constructor.
*
* @param string $host (Default: 127.0.0.1)
* @param int $port (Default: null)
* @param array $config Auth-specific parameters
@@ -58,7 +60,6 @@ class Plain extends Smtp
/**
* Perform PLAIN authentication with supplied credentials
*
*/
public function auth()
{

View File

@@ -0,0 +1,123 @@
<?php
namespace Laminas\Mail\Protocol\Smtp\Auth;
use Laminas\Mail\Protocol\Smtp;
use Laminas\Mail\Protocol\Xoauth2\Xoauth2 as Xoauth2AuthEncoder;
use function array_replace_recursive;
use function is_array;
/**
* Performs Xoauth2 authentication
*
* @psalm-suppress PropertyNotSetInConstructor
*/
final class Xoauth2 extends Smtp
{
/**
* SMTP username
*
* @var string
*/
protected $username;
/**
* Xoauth2 access token
*
* @var string
*/
protected $accessToken;
/**
* @param string|array $host (Default: 127.0.0.1)
* @param int|null $port (Default: null)
* @param array|null $config Auth-specific parameters
*/
public function __construct($host = '127.0.0.1', $port = null, ?array $config = null)
{
// Did we receive a configuration array?
$origConfig = $config;
if (is_array($host)) {
// Merge config array with principal array, if provided
if (is_array($config)) {
$config = array_replace_recursive($host, $config);
} else {
$config = $host;
}
}
if (is_array($config)) {
if (isset($config['username'])) {
$this->setUsername((string) $config['username']);
}
if (isset($config['access_token'])) {
$this->setAccessToken((string) $config['access_token']);
}
}
// Call parent with original arguments
parent::__construct($host, $port, $origConfig);
}
/**
* Perform XOAUTH2 authentication with supplied credentials
*
* @return void
*/
public function auth()
{
// Ensure AUTH has not already been initiated.
parent::auth();
$this->_send('AUTH XOAUTH2');
$this->_expect('334');
$this->_send(Xoauth2AuthEncoder::encodeXoauth2Sasl($this->getUsername(), $this->getAccessToken()));
$this->_expect('235');
$this->auth = true;
}
/**
* Set value for username
*
* @param string $username
* @return Xoauth2
*/
public function setUsername($username)
{
$this->username = $username;
return $this;
}
/**
* Get username
*
* @return string
*/
public function getUsername()
{
return $this->username;
}
/**
* Set value for access token
*
* @param string $token
* @return Xoauth2
*/
public function setAccessToken($token)
{
$this->accessToken = $token;
return $this;
}
/**
* Get access token
*
* @return string
*/
public function getAccessToken()
{
return $this->accessToken;
}
}

View File

@@ -3,19 +3,33 @@
namespace Laminas\Mail\Protocol;
use Laminas\ServiceManager\AbstractPluginManager;
use Laminas\ServiceManager\ConfigInterface;
use Laminas\ServiceManager\Exception\InvalidServiceException;
use Laminas\ServiceManager\Factory\InvokableFactory;
use function gettype;
use function is_object;
use function sprintf;
/**
* Plugin manager implementation for SMTP extensions.
*
* Enforces that SMTP extensions retrieved are instances of Smtp. Additionally,
* it registers a number of default extensions available.
*
* @link ConfigInterface
*
* @psalm-import-type FactoriesConfigurationType from ConfigInterface
*
* @extends AbstractPluginManager<Smtp>
* @final
*/
class SmtpPluginManager extends AbstractPluginManager
{
/**
* Service aliases
*
* @var array<array-key, class-string>
*/
protected $aliases = [
'crammd5' => Smtp\Auth\Crammd5::class,
@@ -27,61 +41,58 @@ class SmtpPluginManager extends AbstractPluginManager
'Login' => Smtp\Auth\Login::class,
'plain' => Smtp\Auth\Plain::class,
'Plain' => Smtp\Auth\Plain::class,
'xoauth2' => Smtp\Auth\Xoauth2::class,
'Xoauth2' => Smtp\Auth\Xoauth2::class,
'smtp' => Smtp::class,
'Smtp' => Smtp::class,
'SMTP' => Smtp::class,
// Legacy Zend Framework aliases
\Zend\Mail\Protocol\Smtp\Auth\Crammd5::class => Smtp\Auth\Crammd5::class,
\Zend\Mail\Protocol\Smtp\Auth\Login::class => Smtp\Auth\Login::class,
\Zend\Mail\Protocol\Smtp\Auth\Plain::class => Smtp\Auth\Plain::class,
\Zend\Mail\Protocol\Smtp::class => Smtp::class,
'Zend\Mail\Protocol\Smtp\Auth\Crammd5' => Smtp\Auth\Crammd5::class,
'Zend\Mail\Protocol\Smtp\Auth\Login' => Smtp\Auth\Login::class,
'Zend\Mail\Protocol\Smtp\Auth\Plain' => Smtp\Auth\Plain::class,
'Zend\Mail\Protocol\Smtp' => Smtp::class,
// v2 normalized FQCNs
'zendmailprotocolsmtpauthcrammd5' => Smtp\Auth\Crammd5::class,
'zendmailprotocolsmtpauthlogin' => Smtp\Auth\Login::class,
'zendmailprotocolsmtpauthplain' => Smtp\Auth\Plain::class,
'zendmailprotocolsmtp' => Smtp::class,
'zendmailprotocolsmtpauthcrammd5' => Smtp\Auth\Crammd5::class,
'zendmailprotocolsmtpauthlogin' => Smtp\Auth\Login::class,
'zendmailprotocolsmtpauthplain' => Smtp\Auth\Plain::class,
'zendmailprotocolsmtp' => Smtp::class,
'laminasmailprotocolsmtpauthcrammd5' => Smtp\Auth\Crammd5::class,
'laminasmailprotocolsmtpauthlogin' => Smtp\Auth\Login::class,
'laminasmailprotocolsmtpauthplain' => Smtp\Auth\Plain::class,
'laminasmailprotocolsmtp' => Smtp::class,
];
/**
* Service factories
*
* @var array
* @var FactoriesConfigurationType
*/
protected $factories = [
Smtp\Auth\Crammd5::class => InvokableFactory::class,
Smtp\Auth\Login::class => InvokableFactory::class,
Smtp\Auth\Plain::class => InvokableFactory::class,
Smtp\Auth\Xoauth2::class => InvokableFactory::class,
Smtp::class => InvokableFactory::class,
// v2 normalized service names
'laminasmailprotocolsmtpauthcrammd5' => InvokableFactory::class,
'laminasmailprotocolsmtpauthlogin' => InvokableFactory::class,
'laminasmailprotocolsmtpauthplain' => InvokableFactory::class,
'laminasmailprotocolsmtp' => InvokableFactory::class,
];
/**
* Plugins must be an instance of the Smtp class
*
* @var string
* @var class-string<Smtp>
*/
protected $instanceOf = Smtp::class;
/**
* Validate a retrieved plugin instance (v3).
*
* @param object|array $instance
* @throws InvalidServiceException
* {@inheritDoc}
*/
public function validate($instance)
public function validate(mixed $instance)
{
if (! $instance instanceof $this->instanceOf) {
throw new InvalidServiceException(sprintf(
'Plugin of type %s is invalid; must extend %s',
(is_object($instance) ? get_class($instance) : gettype($instance)),
is_object($instance) ? $instance::class : gettype($instance),
$this->instanceOf
));
}
@@ -90,10 +101,12 @@ class SmtpPluginManager extends AbstractPluginManager
/**
* Validate a retrieved plugin instance (v2).
*
* @deprecated
*
* @param object $plugin
* @throws Exception\InvalidArgumentException
*/
public function validatePlugin($plugin)
public function validatePlugin(mixed $plugin)
{
try {
$this->validate($plugin);

View File

@@ -2,25 +2,34 @@
namespace Laminas\Mail\Protocol;
// phpcs:ignore WebimpressCodingStandard.PHP.CorrectClassNameCase.Invalid
use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\FactoryInterface;
use Laminas\ServiceManager\ServiceLocatorInterface;
use Laminas\ServiceManager\ServiceManager;
/**
* @link ServiceManager
*
* @psalm-import-type ServiceManagerConfiguration from ServiceManager
*/
class SmtpPluginManagerFactory implements FactoryInterface
{
/**
* laminas-servicemanager v2 support for invocation options.
*
* @param array
* @var array
* @psalm-var ServiceManagerConfiguration
*/
protected $creationOptions;
/**
* {@inheritDoc}
*
* @psalm-param ServiceManagerConfiguration $options
* @return SmtpPluginManager
*/
public function __invoke(ContainerInterface $container, $name, array $options = null)
public function __invoke(ContainerInterface $container, $name, ?array $options = null)
{
return new SmtpPluginManager($container, $options ?: []);
}
@@ -38,7 +47,7 @@ class SmtpPluginManagerFactory implements FactoryInterface
/**
* laminas-servicemanager v2 support for invocation options.
*
* @param array $options
* @psalm-param ServiceManagerConfiguration $options
* @return void
*/
public function setCreationOptions(array $options)

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Laminas\Mail\Protocol\Xoauth2;
use function base64_encode;
use function chr;
use function sprintf;
/**
* @internal
*/
final class Xoauth2
{
/**
* encodes accessToken and target mailbox to Xoauth2 SASL base64 encoded string
*/
public static function encodeXoauth2Sasl(string $targetMailbox, string $accessToken): string
{
return base64_encode(
sprintf(
"user=%s%sauth=Bearer %s%s%s",
$targetMailbox,
chr(0x01),
$accessToken,
chr(0x01),
chr(0x01)
)
);
}
}

View File

@@ -4,9 +4,14 @@ namespace Laminas\Mail\Storage;
use ArrayAccess;
use Countable;
use Laminas\Mail\Storage\Message;
use ReturnTypeWillChange;
use SeekableIterator;
use function str_starts_with;
use function strtolower;
use function substr;
abstract class AbstractStorage implements
ArrayAccess,
Countable,
@@ -14,6 +19,7 @@ abstract class AbstractStorage implements
{
/**
* class capabilities with default values
*
* @var array
*/
protected $has = [
@@ -27,19 +33,22 @@ abstract class AbstractStorage implements
/**
* current iteration position
*
* @var int
*/
protected $iterationPos = 0;
/**
* maximum iteration position (= message count)
*
* @var null|int
*/
protected $iterationMax = null;
protected $iterationMax;
/**
* used message class, change it in an extended class to extend the returned message class
* @var string
*
* @var class-string<Message\MessageInterface>
*/
protected $messageClass = Message::class;
@@ -54,13 +63,13 @@ abstract class AbstractStorage implements
*
* @param string $var property name
* @throws Exception\InvalidArgumentException
* @return bool supported or not
* @return null|bool supported or not
*/
public function __get($var)
{
if (strpos($var, 'has') === 0) {
if (str_starts_with($var, 'has')) {
$var = strtolower(substr($var, 3));
return isset($this->has[$var]) ? $this->has[$var] : null;
return $this->has[$var] ?? null;
}
throw new Exception\InvalidArgumentException($var . ' not found');
@@ -95,8 +104,8 @@ abstract class AbstractStorage implements
/**
* Get a message with headers and body
*
* @param $id int number of message
* @return Message
* @param int $id number of message
* @return Message\MessageInterface
*/
abstract public function getMessage($id);
@@ -149,7 +158,7 @@ abstract class AbstractStorage implements
/**
* delete a message from current box/folder
*
* @param $id
* @param int $id message number
*/
abstract public function removeMessage($id);
@@ -202,7 +211,7 @@ abstract class AbstractStorage implements
if ($this->getMessage($id)) {
return true;
}
} catch (Exception\ExceptionInterface $e) {
} catch (Exception\ExceptionInterface) {
}
return false;
@@ -212,7 +221,7 @@ abstract class AbstractStorage implements
* ArrayAccess::offsetGet()
*
* @param int $id
* @return \Laminas\Mail\Storage\Message message object
* @return Message message object
*/
#[ReturnTypeWillChange]
public function offsetGet($id)
@@ -223,12 +232,10 @@ abstract class AbstractStorage implements
/**
* ArrayAccess::offsetSet()
*
* @param mixed $id
* @param mixed $value
* @throws Exception\RuntimeException
*/
#[ReturnTypeWillChange]
public function offsetSet($id, $value)
public function offsetSet(mixed $id, mixed $value)
{
throw new Exception\RuntimeException('cannot write mail messages via array access');
}

View File

@@ -4,48 +4,39 @@ namespace Laminas\Mail\Storage;
use RecursiveIterator;
use ReturnTypeWillChange;
use Stringable;
class Folder implements RecursiveIterator
use function current;
use function key;
use function next;
use function reset;
class Folder implements RecursiveIterator, Stringable
{
/**
* subfolders of folder array(localName => \Laminas\Mail\Storage\Folder folder)
* @var array
*/
protected $folders;
/**
* local name (name of folder in parent folder)
* @var string
*/
protected $localName;
/**
* global name (absolute name of folder)
*
* @var string
*/
protected $globalName;
/**
* folder is selectable if folder is able to hold messages, otherwise it is a parent folder
* @var bool
*/
protected $selectable = true;
/**
* create a new mail folder instance
*
* @param string $localName name of folder in current subdirectory
* @param string $localName local name (name of folder in parent folder)
* @param string $globalName absolute name of folder
* @param bool $selectable if true folder holds messages, if false it's
* just a parent for subfolders (Default: true)
* @param array $folders init with given instances of Folder as subfolders
* @param array<string, Folder> $folders subfolders of
* folder array(localName => \Laminas\Mail\Storage\Folder folder)
*/
public function __construct($localName, $globalName = '', $selectable = true, array $folders = [])
{
$this->localName = $localName;
$this->globalName = $globalName ? $globalName : $localName;
$this->selectable = $selectable;
$this->folders = $folders;
public function __construct(
protected $localName,
$globalName = '',
protected $selectable = true,
protected array $folders = []
) {
$this->globalName = $globalName ?: $localName;
}
/**
@@ -63,7 +54,7 @@ class Folder implements RecursiveIterator
/**
* implements RecursiveIterator::getChildren()
*
* @return \Laminas\Mail\Storage\Folder same as self::current()
* @return Folder same as self::current()
*/
#[ReturnTypeWillChange]
public function getChildren()
@@ -105,7 +96,7 @@ class Folder implements RecursiveIterator
/**
* implements Iterator::current()
*
* @return \Laminas\Mail\Storage\Folder current folder
* @return Folder current folder
*/
#[ReturnTypeWillChange]
public function current()
@@ -127,7 +118,7 @@ class Folder implements RecursiveIterator
*
* @param string $name wanted subfolder
* @throws Exception\InvalidArgumentException
* @return \Laminas\Mail\Storage\Folder folder named $folder
* @return Folder folder named $folder
*/
public function __get($name)
{
@@ -142,7 +133,7 @@ class Folder implements RecursiveIterator
* add or replace subfolder named $name
*
* @param string $name local name of subfolder
* @param \Laminas\Mail\Storage\Folder $folder instance for new subfolder
* @param Folder $folder instance for new subfolder
*/
public function __set($name, self $folder)
{
@@ -164,7 +155,7 @@ class Folder implements RecursiveIterator
*
* @return string global name of folder
*/
public function __toString()
public function __toString(): string
{
return (string) $this->getGlobalName();
}

View File

@@ -2,13 +2,16 @@
namespace Laminas\Mail\Storage\Folder;
use Laminas\Mail\Storage\Exception\ExceptionInterface;
use Laminas\Mail\Storage\Folder;
interface FolderInterface
{
/**
* get root folder or given folder
*
* @param string $rootFolder get folder structure for given folder, else root
* @return FolderInterface root or wanted folder
* @return Folder root or wanted folder
*/
public function getFolders($rootFolder = null);
@@ -17,16 +20,16 @@ interface FolderInterface
*
* folder must be selectable!
*
* @param FolderInterface|string $globalName global name of folder or instance for subfolder
* @throws \Laminas\Mail\Storage\Exception\ExceptionInterface
* @param Folder|string $globalName global name of folder or instance for subfolder
* @throws ExceptionInterface
*/
public function selectFolder($globalName);
/**
* get Laminas\Mail\Storage\Folder instance for current folder
*
* @return FolderInterface instance of current folder
* @throws \Laminas\Mail\Storage\Exception\ExceptionInterface
* @return string instance of current folder
* @throws ExceptionInterface
*/
public function getCurrentFolder();
}

View File

@@ -4,31 +4,55 @@ namespace Laminas\Mail\Storage\Folder;
use Laminas\Mail\Storage;
use Laminas\Mail\Storage\Exception;
use Laminas\Mail\Storage\Exception\InvalidArgumentException;
use Laminas\Mail\Storage\Folder;
use Laminas\Mail\Storage\ParamsNormalizer;
use Laminas\Stdlib\ErrorHandler;
use function array_pop;
use function array_push;
use function closedir;
use function explode;
use function is_dir;
use function opendir;
use function readdir;
use function rtrim;
use function sort;
use function str_contains;
use function str_starts_with;
use function strlen;
use function substr;
use function trim;
use const DIRECTORY_SEPARATOR;
use const E_WARNING;
class Maildir extends Storage\Maildir implements FolderInterface
{
/**
* root folder for folder structure
*
* @var Storage\Folder
*/
protected $rootFolder;
/**
* rootdir of folder structure
*
* @var string
*/
protected $rootdir;
/**
* name of current folder
*
* @var string
*/
protected $currentFolder;
/**
* delim char for subfolders
*
* @var string
*/
protected $delim;
@@ -42,7 +66,7 @@ class Maildir extends Storage\Maildir implements FolderInterface
* - delim delim char for folder structure, default is '.'
* - folder initial selected folder, default is 'INBOX'
*
* @param $params array mail reader specific parameters
* @param object|array $params mail reader specific parameters
* @throws Exception\InvalidArgumentException
*/
public function __construct($params)
@@ -61,14 +85,14 @@ class Maildir extends Storage\Maildir implements FolderInterface
$this->rootdir = rtrim($dirname, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
$delim = $params['delim'] ?? '.';
$delim = $params['delim'] ?? '.';
$this->delim = (string) $delim;
$folder = $params['folder'] ?? 'INBOX';
$this->buildFolderTree();
$this->selectFolder((string) $folder);
$this->has['top'] = true;
$this->has['top'] = true;
$this->has['flags'] = true;
}
@@ -82,7 +106,7 @@ class Maildir extends Storage\Maildir implements FolderInterface
*/
protected function buildFolderTree()
{
$this->rootFolder = new Storage\Folder('/', '/', false);
$this->rootFolder = new Storage\Folder('/', '/', false);
$this->rootFolder->INBOX = new Storage\Folder('INBOX', 'INBOX', true);
ErrorHandler::start(E_WARNING);
@@ -106,27 +130,27 @@ class Maildir extends Storage\Maildir implements FolderInterface
closedir($dh);
sort($dirs);
$stack = [null];
$folderStack = [null];
$stack = [null];
$folderStack = [null];
$parentFolder = $this->rootFolder;
$parent = '.';
$parent = '.';
foreach ($dirs as $dir) {
do {
if (strpos($dir, $parent) === 0) {
$local = substr($dir, strlen($parent));
if (strpos($local, $this->delim) !== false) {
if (str_starts_with($dir, $parent)) {
$local = substr($dir, strlen((string) $parent));
if (str_contains($local, $this->delim)) {
throw new Exception\RuntimeException('error while reading maildir');
}
array_push($stack, $parent);
$parent = $dir . $this->delim;
$folder = new Storage\Folder($local, substr($dir, 1), true);
$parent = $dir . $this->delim;
$folder = new Storage\Folder($local, substr($dir, 1), true);
$parentFolder->$local = $folder;
array_push($folderStack, $parentFolder);
$parentFolder = $folder;
break;
} elseif ($stack) {
$parent = array_pop($stack);
$parent = array_pop($stack);
$parentFolder = array_pop($folderStack);
}
} while ($stack);
@@ -140,8 +164,8 @@ class Maildir extends Storage\Maildir implements FolderInterface
* get root folder or given folder
*
* @param string $rootFolder get folder structure for given folder, else root
* @throws \Laminas\Mail\Storage\Exception\InvalidArgumentException
* @return \Laminas\Mail\Storage\Folder root or wanted folder
* @throws InvalidArgumentException
* @return Folder root or wanted folder
*/
public function getFolders($rootFolder = null)
{
@@ -150,15 +174,15 @@ class Maildir extends Storage\Maildir implements FolderInterface
}
// rootdir is same as INBOX in maildir
if (strpos($rootFolder, 'INBOX' . $this->delim) === 0) {
if (str_starts_with($rootFolder, 'INBOX' . $this->delim)) {
$rootFolder = substr($rootFolder, 6);
}
$currentFolder = $this->rootFolder;
$subname = trim($rootFolder, $this->delim);
$subname = trim($rootFolder, $this->delim);
while ($currentFolder) {
if (false !== strpos($subname, $this->delim)) {
list($entry, $subname) = explode($this->delim, $subname, 2);
if (str_contains($subname, $this->delim)) {
[$entry, $subname] = explode($this->delim, $subname, 2);
} else {
$entry = $subname;
$subname = null;
@@ -214,7 +238,7 @@ class Maildir extends Storage\Maildir implements FolderInterface
/**
* get Storage\Folder instance for current folder
*
* @return Storage\Folder instance of current folder
* @return string instance of current folder
*/
public function getCurrentFolder()
{

View File

@@ -7,22 +7,40 @@ use Laminas\Mail\Storage\Exception;
use Laminas\Mail\Storage\ParamsNormalizer;
use Laminas\Stdlib\ErrorHandler;
use function array_merge;
use function closedir;
use function explode;
use function is_dir;
use function is_file;
use function opendir;
use function readdir;
use function rtrim;
use function sprintf;
use function str_contains;
use function trim;
use const DIRECTORY_SEPARATOR;
use const E_WARNING;
class Mbox extends Storage\Mbox implements FolderInterface
{
/**
* Storage\Folder root folder for folder structure
*
* @var Storage\Folder
*/
protected $rootFolder;
/**
* rootdir of folder structure
*
* @var string
*/
protected $rootdir;
/**
* name of current folder
*
* @var string
*/
protected $currentFolder;
@@ -38,7 +56,7 @@ class Mbox extends Storage\Mbox implements FolderInterface
* - dirname rootdir of mbox structure
* - folder initial selected folder, default is 'INBOX'
*
* @param $params array|object Array, iterable object, or stdClass object
* @param array|object $params Array, iterable object, or stdClass object
* with reader specific parameters
* @throws Exception\InvalidArgumentException
*/
@@ -61,7 +79,7 @@ class Mbox extends Storage\Mbox implements FolderInterface
}
$this->rootdir = rtrim($dirname, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
$folder = $params['folder'] ?? 'INBOX';
$folder = $params['folder'] ?? 'INBOX';
$this->buildFolderTree($this->rootdir);
$this->selectFolder((string) $folder);
@@ -84,7 +102,7 @@ class Mbox extends Storage\Mbox implements FolderInterface
{
if (! $parentFolder) {
$this->rootFolder = new Storage\Folder('/', '/', false);
$parentFolder = $this->rootFolder;
$parentFolder = $this->rootFolder;
}
ErrorHandler::start(E_WARNING);
@@ -99,15 +117,15 @@ class Mbox extends Storage\Mbox implements FolderInterface
continue;
}
$absoluteEntry = $currentDir . $entry;
$globalName = $parentGlobalName . DIRECTORY_SEPARATOR . $entry;
$globalName = $parentGlobalName . DIRECTORY_SEPARATOR . $entry;
if (is_file($absoluteEntry) && $this->isMboxFile($absoluteEntry)) {
$parentFolder->$entry = new Storage\Folder($entry, $globalName);
continue;
}
if (! is_dir($absoluteEntry) /* || $entry == '.' || $entry == '..' */) {
if (! is_dir($absoluteEntry)) { /* || $entry == '.' || $entry == '..' */
continue;
}
$folder = new Storage\Folder($entry, $globalName, false);
$folder = new Storage\Folder($entry, $globalName, false);
$parentFolder->$entry = $folder;
$this->buildFolderTree($absoluteEntry . DIRECTORY_SEPARATOR, $folder, $globalName);
}
@@ -129,10 +147,10 @@ class Mbox extends Storage\Mbox implements FolderInterface
}
$currentFolder = $this->rootFolder;
$subname = trim($rootFolder, DIRECTORY_SEPARATOR);
$subname = trim($rootFolder, DIRECTORY_SEPARATOR);
while ($currentFolder) {
if (false !== strpos($subname, DIRECTORY_SEPARATOR)) {
list($entry, $subname) = explode(DIRECTORY_SEPARATOR, $subname, 2);
if (str_contains($subname, DIRECTORY_SEPARATOR)) {
[$entry, $subname] = explode(DIRECTORY_SEPARATOR, $subname, 2);
} else {
$entry = $subname;
$subname = null;
@@ -188,7 +206,7 @@ class Mbox extends Storage\Mbox implements FolderInterface
/**
* get Storage\Folder instance for current folder
*
* @return Storage\Folder instance of current folder
* @return string instance of current folder
* @throws Exception\ExceptionInterface
*/
public function getCurrentFolder()

View File

@@ -5,6 +5,20 @@ namespace Laminas\Mail\Storage;
use Laminas\Mail;
use Laminas\Mail\Protocol;
use function array_key_exists;
use function array_pop;
use function array_push;
use function count;
use function in_array;
use function is_string;
use function ksort;
use function str_starts_with;
use function strrpos;
use function substr;
use const INF;
use const SORT_STRING;
class Imap extends AbstractStorage implements Folder\FolderInterface, Writable\WritableInterface
{
// TODO: with an internal cache we could optimize this class, or create an extra class with
@@ -12,24 +26,28 @@ class Imap extends AbstractStorage implements Folder\FolderInterface, Writable\W
/**
* protocol handler
*
* @var null|Protocol\Imap
*/
protected $protocol;
/**
* name of current folder
*
* @var string
*/
protected $currentFolder = '';
/**
* IMAP folder delimiter character
*
* @var null|string
*/
protected $delimiter;
/**
* IMAP flags to constants translation
*
* @var array
*/
protected static $knownFlags = [
@@ -44,6 +62,7 @@ class Imap extends AbstractStorage implements Folder\FolderInterface, Writable\W
/**
* IMAP flags to search criteria
*
* @var array
*/
protected static $searchFlags = [
@@ -110,7 +129,7 @@ class Imap extends AbstractStorage implements Folder\FolderInterface, Writable\W
*/
public function getMessage($id)
{
$data = $this->protocol->fetch(['FLAGS', 'RFC822.HEADER'], $id);
$data = $this->protocol->fetch(['FLAGS', 'RFC822.HEADER'], $id);
$header = $data['RFC822.HEADER'];
$flags = [];
@@ -121,13 +140,12 @@ class Imap extends AbstractStorage implements Folder\FolderInterface, Writable\W
return new $this->messageClass(['handler' => $this, 'id' => $id, 'headers' => $header, 'flags' => $flags]);
}
/*
/**
* Get raw header of message or part
*
* @param int $id number of message
* @param null|array|string $part path to part or null for message header
* @param int $topLines include this many lines with header (after an empty line)
* @param int $topLines include this many lines with header (after an empty line)
* @return string raw header
* @throws Exception\RuntimeException
* @throws Protocol\Exception\RuntimeException
@@ -143,7 +161,7 @@ class Imap extends AbstractStorage implements Folder\FolderInterface, Writable\W
return $this->protocol->fetch('RFC822.HEADER', $id);
}
/*
/**
* Get raw content of message or part
*
* @param int $id number of message
@@ -331,16 +349,16 @@ class Imap extends AbstractStorage implements Folder\FolderInterface, Writable\W
}
ksort($folders, SORT_STRING);
$root = new Folder('/', '/', false);
$stack = [null];
$folderStack = [null];
$root = new Folder('/', '/', false);
$stack = [null];
$folderStack = [null];
$parentFolder = $root;
$parent = '';
$parent = '';
foreach ($folders as $globalName => $data) {
do {
if (! $parent || strpos($globalName, $parent) === 0) {
$pos = strrpos($globalName, $data['delim']);
if (! $parent || str_starts_with($globalName, ! is_string($parent) ? (string) $parent : $parent)) {
$pos = strrpos($globalName, (string) $data['delim']);
if ($pos === false) {
$localName = $globalName;
} else {
@@ -349,15 +367,15 @@ class Imap extends AbstractStorage implements Folder\FolderInterface, Writable\W
$selectable = ! $data['flags'] || ! in_array('\\Noselect', $data['flags']);
array_push($stack, $parent);
$parent = $globalName . $data['delim'];
$folder = new Folder($localName, $globalName, $selectable);
$parent = $globalName . $data['delim'];
$folder = new Folder($localName, $globalName, $selectable);
$parentFolder->$localName = $folder;
array_push($folderStack, $parentFolder);
$parentFolder = $folder;
$parentFolder = $folder;
$this->delimiter = $data['delim'];
break;
} elseif ($stack) {
$parent = array_pop($stack);
$parent = array_pop($stack);
$parentFolder = array_pop($folderStack);
}
} while ($stack);
@@ -380,7 +398,7 @@ class Imap extends AbstractStorage implements Folder\FolderInterface, Writable\W
*/
public function selectFolder($globalName)
{
$this->currentFolder = $globalName;
$this->currentFolder = (string) $globalName;
if (! $this->protocol->select($this->currentFolder)) {
$this->currentFolder = '';
throw new Exception\RuntimeException('cannot change folder, maybe it does not exist');
@@ -390,7 +408,7 @@ class Imap extends AbstractStorage implements Folder\FolderInterface, Writable\W
/**
* get Folder instance for current folder
*
* @return Folder instance of current folder
* @return string instance of current folder
*/
public function getCurrentFolder()
{

View File

@@ -3,18 +3,50 @@
namespace Laminas\Mail\Storage;
use Laminas\Mail;
use Laminas\Mail\Storage\Exception\ExceptionInterface;
use Laminas\Mail\Storage\Message\File;
use Laminas\Stdlib\ErrorHandler;
use function array_flip;
use function closedir;
use function count;
use function ctype_digit;
use function explode;
use function fclose;
use function feof;
use function fgets;
use function file_exists;
use function filesize;
use function fopen;
use function is_array;
use function is_dir;
use function is_file;
use function is_subclass_of;
use function opendir;
use function readdir;
use function sprintf;
use function str_contains;
use function strcmp;
use function stream_get_contents;
use function strlen;
use function substr;
use function trim;
use function usort;
use const E_WARNING;
class Maildir extends AbstractStorage
{
/**
* used message class, change it in an extended class to extend the returned message class
* @var string
*
* @var class-string<Mail\Storage\Message\MessageInterface>
*/
protected $messageClass = Message\File::class;
protected $messageClass = File::class;
/**
* data of found message files in maildir dir
*
* @var array
*/
protected $files = [];
@@ -121,14 +153,15 @@ class Maildir extends AbstractStorage
* Fetch a message
*
* @param int $id number of message
* @return \Laminas\Mail\Storage\Message\File
* @throws \Laminas\Mail\Storage\Exception\ExceptionInterface
* @return File
* @throws ExceptionInterface
*/
public function getMessage($id)
{
// TODO that's ugly, would be better to let the message class decide
if (\trim($this->messageClass, '\\') === Message\File::class
|| is_subclass_of($this->messageClass, Message\File::class)
if (
trim($this->messageClass, '\\') === File::class
|| is_subclass_of($this->messageClass, File::class)
) {
return new $this->messageClass([
'file' => $this->getFileData($id, 'filename'),
@@ -144,7 +177,7 @@ class Maildir extends AbstractStorage
]);
}
/*
/**
* Get raw header of message or part
*
* @param int $id number of message
@@ -175,7 +208,7 @@ class Maildir extends AbstractStorage
return $content;
}
/*
/**
* Get raw content of message or part
*
* @param int $id number of message
@@ -209,7 +242,7 @@ class Maildir extends AbstractStorage
* Supported parameters are:
* - dirname dirname of mbox file
*
* @param $params array|object Array, iterable object, or stdClass object
* @param array|object $params Array, iterable object, or stdClass object
* with reader specific parameters
* @throws Exception\InvalidArgumentException
*/
@@ -221,7 +254,7 @@ class Maildir extends AbstractStorage
throw new Exception\InvalidArgumentException('no dirname provided in params');
}
$dirname = (string) $params['dirname'] ;
$dirname = (string) $params['dirname'];
if (! is_dir($dirname)) {
throw new Exception\InvalidArgumentException(sprintf('Maildir "%s" is not a directory', $dirname));
@@ -231,7 +264,7 @@ class Maildir extends AbstractStorage
throw new Exception\InvalidArgumentException('invalid maildir given');
}
$this->has['top'] = true;
$this->has['top'] = true;
$this->has['flags'] = true;
$this->openMaildir($dirname);
}
@@ -299,15 +332,15 @@ class Maildir extends AbstractStorage
continue;
}
if (false !== strpos($entry, ':')) {
list($uniq, $info) = explode(':', $entry, 2);
if (str_contains($entry, ':')) {
[$uniq, $info] = explode(':', $entry, 2);
} else {
$uniq = $entry;
$info = '';
}
if (false !== strpos($uniq, ',')) {
list(, $size) = explode(',', $uniq, 2);
if (str_contains($uniq, ',')) {
[, $size] = explode(',', $uniq, 2);
} else {
$size = '';
}
@@ -320,8 +353,8 @@ class Maildir extends AbstractStorage
$size = null;
}
if (false !== strpos($info, ',')) {
list($version, $flags) = explode(',', $info, 2);
if (str_contains($info, ',')) {
[$version, $flags] = explode(',', $info, 2);
} else {
$version = $info;
$flags = '';
@@ -332,9 +365,9 @@ class Maildir extends AbstractStorage
}
$namedFlags = $defaultFlags;
$length = strlen($flags);
$length = strlen($flags);
for ($i = 0; $i < $length; ++$i) {
$flag = $flags[$i];
$flag = $flags[$i];
$namedFlags[$flag] = static::$knownFlags[$flag] ?? $flag;
}
@@ -350,15 +383,12 @@ class Maildir extends AbstractStorage
$this->files[] = $data;
}
\usort($this->files, function ($a, $b): int {
return \strcmp($a['filename'], $b['filename']);
});
usort($this->files, static fn($a, $b): int => strcmp($a['filename'], $b['filename']));
}
/**
* Close resource for mail lib. If you need to control, when the resource
* is closed. Otherwise the destructor would call this.
*
*/
public function close()
{
@@ -378,7 +408,7 @@ class Maildir extends AbstractStorage
/**
* stub for not supported message deletion
*
* @param $id
* @param int $id
* @throws Exception\RuntimeException
*/
public function removeMessage($id)

View File

@@ -2,52 +2,81 @@
namespace Laminas\Mail\Storage;
use Laminas\Mail\Storage\Exception\ExceptionInterface;
use Laminas\Mail\Storage\Message\File;
use Laminas\Mail\Storage\Message\MessageInterface;
use Laminas\Stdlib\ErrorHandler;
use function array_combine;
use function count;
use function fclose;
use function fgets;
use function filemtime;
use function fopen;
use function fseek;
use function ftell;
use function is_dir;
use function is_resource;
use function is_subclass_of;
use function range;
use function str_starts_with;
use function stream_get_contents;
use function strlen;
use function strtolower;
use function trim;
use const E_WARNING;
class Mbox extends AbstractStorage
{
/**
* file handle to mbox file
*
* @var null|resource
*/
protected $fh;
/**
* filename of mbox file for __wakeup
*
* @var string
*/
protected $filename;
/**
* modification date of mbox file for __wakeup
*
* @var int
*/
protected $filemtime;
/**
* start and end position of messages as array('start' => start, 'separator' => headersep, 'end' => end)
*
* @var array
*/
protected $positions;
/**
* used message class, change it in an extended class to extend the returned message class
* @var string
*
* @var class-string<MessageInterface>
*/
protected $messageClass = Message\File::class;
protected $messageClass = File::class;
/**
* end of Line for messages
*
* @var string|null
*/
// phpcs:ignore WebimpressCodingStandard.NamingConventions.ValidVariableName.NotCamelCapsProperty
protected $messageEOL;
/**
* Count messages all messages in current box
*
* @return int number of messages
* @throws \Laminas\Mail\Storage\Exception\ExceptionInterface
* @throws ExceptionInterface
*/
public function countMessages()
{
@@ -95,21 +124,23 @@ class Mbox extends AbstractStorage
* Fetch a message
*
* @param int $id number of message
* @return \Laminas\Mail\Storage\Message\File
* @throws \Laminas\Mail\Storage\Exception\ExceptionInterface
* @return File
* @throws ExceptionInterface
*/
public function getMessage($id)
{
// TODO that's ugly, would be better to let the message class decide
if (is_subclass_of($this->messageClass, Message\File::class)
|| strtolower($this->messageClass) === strtolower(Message\File::class)) {
if (
is_subclass_of($this->messageClass, File::class)
|| strtolower($this->messageClass) === strtolower(File::class)
) {
// TODO top/body lines
$messagePos = $this->getPos($id);
$messageClassParams = [
'file' => $this->fh,
'file' => $this->fh,
'startPos' => $messagePos['start'],
'endPos' => $messagePos['end'],
'endPos' => $messagePos['end'],
];
if (isset($this->messageEOL)) {
@@ -138,7 +169,7 @@ class Mbox extends AbstractStorage
return new $this->messageClass(['handler' => $this, 'id' => $id, 'headers' => $message]);
}
/*
/**
* Get raw header of message or part
*
* @param int $id number of message
@@ -146,7 +177,7 @@ class Mbox extends AbstractStorage
* @param int $topLines include this many lines with header (after an empty line)
* @return string raw header
* @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface
* @throws \Laminas\Mail\Storage\Exception\ExceptionInterface
* @throws ExceptionInterface
*/
public function getRawHeader($id, $part = null, $topLines = 0)
{
@@ -159,14 +190,14 @@ class Mbox extends AbstractStorage
return stream_get_contents($this->fh, $messagePos['separator'] - $messagePos['start'], $messagePos['start']);
}
/*
/**
* Get raw content of message or part
*
* @param int $id number of message
* @param null|array|string $part path to part or null for message content
* @return string raw content
* @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface
* @throws \Laminas\Mail\Storage\Exception\ExceptionInterface
* @throws ExceptionInterface
*/
public function getRawContent($id, $part = null)
{
@@ -183,7 +214,7 @@ class Mbox extends AbstractStorage
* Supported parameters are:
* - filename filename of mbox file
*
* @param $params array|object|Config mail reader specific parameters
* @param array|object|Config $params mail reader specific parameters
* @throws Exception\InvalidArgumentException
*/
public function __construct($params)
@@ -228,7 +259,7 @@ class Mbox extends AbstractStorage
$result = false;
$line = fgets($file) ?: '';
if (strpos($line, 'From ') === 0) {
if (str_starts_with($line, 'From ')) {
$result = true;
}
@@ -260,11 +291,11 @@ class Mbox extends AbstractStorage
ErrorHandler::start();
$this->fh = fopen($filename, 'r');
$error = ErrorHandler::stop();
$error = ErrorHandler::stop();
if (! $this->fh) {
throw new Exception\RuntimeException('cannot open mbox file', 0, $error);
}
$this->filename = $filename;
$this->filename = $filename;
$this->filemtime = filemtime($this->filename);
if (! $this->isMboxFile($this->fh, false)) {
@@ -276,13 +307,13 @@ class Mbox extends AbstractStorage
$messagePos = ['start' => ftell($this->fh), 'separator' => 0, 'end' => 0];
while (($line = fgets($this->fh)) !== false) {
if (strpos($line, 'From ') === 0) {
if (str_starts_with($line, 'From ')) {
$messagePos['end'] = ftell($this->fh) - strlen($line) - 2; // + newline
if (! $messagePos['separator']) {
$messagePos['separator'] = $messagePos['end'];
}
$this->positions[] = $messagePos;
$messagePos = ['start' => ftell($this->fh), 'separator' => 0, 'end' => 0];
$messagePos = ['start' => ftell($this->fh), 'separator' => 0, 'end' => 0];
}
if (! $messagePos['separator'] && ! trim($line)) {
$messagePos['separator'] = ftell($this->fh);
@@ -299,7 +330,6 @@ class Mbox extends AbstractStorage
/**
* Close resource for mail lib. If you need to control, when the resource
* is closed. Otherwise the destructor would call this.
*
*/
public function close()
{
@@ -322,7 +352,7 @@ class Mbox extends AbstractStorage
/**
* stub for not supported message deletion
*
* @param $id
* @param int $id message number
* @throws Exception\RuntimeException
*/
public function removeMessage($id)
@@ -339,7 +369,7 @@ class Mbox extends AbstractStorage
*
* @param int|null $id message number
* @return array|string message number for given message or all messages as array
* @throws \Laminas\Mail\Storage\Exception\ExceptionInterface
* @throws ExceptionInterface
*/
public function getUniqueId($id = null)
{
@@ -361,7 +391,7 @@ class Mbox extends AbstractStorage
*
* @param string $id unique id
* @return int message number
* @throws \Laminas\Mail\Storage\Exception\ExceptionInterface
* @throws ExceptionInterface
*/
public function getNumberByUniqueId($id)
{

View File

@@ -4,10 +4,17 @@ namespace Laminas\Mail\Storage;
use Laminas\Stdlib\ErrorHandler;
use function array_combine;
use function file_get_contents;
use function is_resource;
use function ltrim;
use function stream_get_contents;
class Message extends Part implements Message\MessageInterface
{
/**
* flags for this message
*
* @var array
*/
protected $flags = [];
@@ -28,7 +35,7 @@ class Message extends Part implements Message\MessageInterface
if (! is_resource($params['file'])) {
ErrorHandler::start();
$params['raw'] = file_get_contents($params['file']);
$error = ErrorHandler::stop();
$error = ErrorHandler::stop();
if ($params['raw'] === false) {
throw new Exception\RuntimeException('could not open file', 0, $error);
}

View File

@@ -2,12 +2,16 @@
namespace Laminas\Mail\Storage\Message;
use Laminas\Mail\Storage\Exception\ExceptionInterface;
use Laminas\Mail\Storage\Part;
use function array_combine;
class File extends Part\File implements MessageInterface
{
/**
* flags for this message
*
* @var array
*/
protected $flags = [];
@@ -19,7 +23,7 @@ class File extends Part\File implements MessageInterface
* - flags array with flags for message, keys are ignored, use constants defined in Laminas\Mail\Storage
*
* @param array $params
* @throws \Laminas\Mail\Storage\Exception\ExceptionInterface
* @throws ExceptionInterface
*/
public function __construct(array $params)
{

View File

@@ -5,16 +5,22 @@ namespace Laminas\Mail\Storage;
use Traversable;
use Webmozart\Assert\Assert;
use function get_object_vars;
use function gettype;
use function is_array;
use function is_object;
use function iterator_to_array;
use function sprintf;
/**
* @internal
*/
final class ParamsNormalizer
{
/**
* @param mixed $params
* @return array<string, mixed>
*/
public static function normalizeParams($params): array
public static function normalizeParams(mixed $params): array
{
if ($params instanceof Traversable) {
$params = iterator_to_array($params);

View File

@@ -2,58 +2,81 @@
namespace Laminas\Mail\Storage;
use ArrayIterator;
use Laminas\Mail\Header\HeaderInterface;
use Laminas\Mail\Headers;
use Laminas\Mime;
use Laminas\Mime\Exception\RuntimeException;
use RecursiveIterator;
use ReturnTypeWillChange;
use Stringable;
class Part implements RecursiveIterator, Part\PartInterface
use function array_map;
use function count;
use function current;
use function implode;
use function is_array;
use function iterator_to_array;
use function preg_replace;
use function stripos;
use function strlen;
use function strtolower;
use function trim;
class Part implements RecursiveIterator, Part\PartInterface, Stringable
{
/**
* Headers of the part
*
* @var Headers|null
*/
protected $headers;
/**
* raw part body
*
* @var null|string
*/
protected $content;
/**
* toplines as fetched with headers
*
* @var string
*/
protected $topLines = '';
/**
* parts of multipart message
*
* @var array
*/
protected $parts = [];
/**
* count of parts of a multipart message
*
* @var null|int
*/
protected $countParts;
/**
* current position of iterator
*
* @var int
*/
protected $iterationPos = 1;
/**
* mail handler, if late fetch is active
*
* @var null|AbstractStorage
*/
protected $mail;
/**
* message number for mail handler
*
* @var int
*/
protected $messageNum = 0;
@@ -87,7 +110,7 @@ class Part implements RecursiveIterator, Part\PartInterface
$this->messageNum = $params['id'];
}
$params['strict'] = $params['strict'] ?? false;
$params['strict'] ??= false;
if (isset($params['raw'])) {
Mime\Decode::splitMessage(
@@ -124,7 +147,7 @@ class Part implements RecursiveIterator, Part\PartInterface
{
try {
return stripos($this->contentType, 'multipart/') === 0;
} catch (Exception\ExceptionInterface $e) {
} catch (Exception\ExceptionInterface) {
return false;
}
}
@@ -211,10 +234,10 @@ class Part implements RecursiveIterator, Part\PartInterface
throw new Exception\RuntimeException('part not found');
}
if ($this->mail && $this->mail->hasFetchPart) {
// if ($this->mail && $this->mail->hasFetchPart) {
// TODO: fetch part
// return
}
// }
$this->cacheContent();
@@ -241,10 +264,10 @@ class Part implements RecursiveIterator, Part\PartInterface
return $this->countParts;
}
if ($this->mail && $this->mail->hasFetchPart) {
// if ($this->mail && $this->mail->hasFetchPart) {
// TODO: fetch part
// return
}
// }
$this->cacheContent();
@@ -264,7 +287,7 @@ class Part implements RecursiveIterator, Part\PartInterface
{
if (null === $this->headers) {
if ($this->mail) {
$part = $this->mail->getRawHeader($this->messageNum);
$part = $this->mail->getRawHeader($this->messageNum);
$this->headers = Headers::fromString($part);
} else {
$this->headers = new Headers();
@@ -288,14 +311,14 @@ class Part implements RecursiveIterator, Part\PartInterface
* @param string $name name of header, matches case-insensitive, but camel-case is replaced with dashes
* @param string $format change type of return value to 'string' or 'array'
* @throws Exception\InvalidArgumentException
* @return string|array|HeaderInterface|\ArrayIterator value of header in wanted or internal format
* @return string|array|HeaderInterface|ArrayIterator value of header in wanted or internal format
*/
public function getHeader($name, $format = null)
{
$header = $this->getHeaders()->get($name);
if ($header === false) {
$lowerName = strtolower(preg_replace('%([a-z])([A-Z])%', '\1-\2', $name));
$header = $this->getHeaders()->get($lowerName);
$header = $this->getHeaders()->get($lowerName);
if ($header === false) {
throw new Exception\InvalidArgumentException(
"Header with Name $name or $lowerName not found"
@@ -310,9 +333,8 @@ class Part implements RecursiveIterator, Part\PartInterface
} else {
$return = trim(implode(
Mime\Mime::LINEEND,
array_map(static function ($header): string {
return $header->getFieldValue(HeaderInterface::FORMAT_RAW);
}, iterator_to_array($header))
array_map(static fn($header): string
=> $header->getFieldValue(HeaderInterface::FORMAT_RAW), iterator_to_array($header))
), Mime\Mime::LINEEND);
}
break;
@@ -346,7 +368,7 @@ class Part implements RecursiveIterator, Part\PartInterface
* @param string $wantedPart the wanted part, default is first, if null an array with all parts is returned
* @param string $firstName key name for the first part
* @return string|array wanted part or all parts as array($firstName => firstPart, partname => value)
* @throws \Laminas\Mime\Exception\RuntimeException
* @throws RuntimeException
*/
public function getHeaderField($name, $wantedPart = '0', $firstName = '0')
{
@@ -376,7 +398,7 @@ class Part implements RecursiveIterator, Part\PartInterface
*
* @see Part::hasHeader
*
* @param string
* @param string $name
* @return bool
*/
public function __isset($name)
@@ -389,7 +411,7 @@ class Part implements RecursiveIterator, Part\PartInterface
*
* @return string content
*/
public function __toString()
public function __toString(): string
{
return $this->getContent();
}

View File

@@ -5,10 +5,26 @@ namespace Laminas\Mail\Storage\Part;
use Laminas\Mail\Headers;
use Laminas\Mail\Storage\Part;
use function count;
use function feof;
use function fgets;
use function fopen;
use function fread;
use function fseek;
use function ftell;
use function is_resource;
use function stream_copy_to_stream;
use function trim;
use const SEEK_END;
class File extends Part
{
/** @var array */
protected $contentPos = [];
/** @var array */
protected $partPos = [];
/** @var resource */
protected $fh;
/**
@@ -31,13 +47,14 @@ class File extends Part
}
if (! is_resource($params['file'])) {
$this->fh = fopen($params['file'], 'r');
$fh = fopen($params['file'], 'r');
} else {
$this->fh = $params['file'];
$fh = $params['file'];
}
if (! $this->fh) {
if (! $fh) {
throw new Exception\RuntimeException('could not open file');
}
$this->fh = $fh;
if (isset($params['startPos'])) {
fseek($this->fh, $params['startPos']);
}
@@ -70,7 +87,7 @@ class File extends Part
}
$part = [];
$pos = $this->contentPos[0];
$pos = $this->contentPos[0];
fseek($this->fh, $pos);
while (! feof($this->fh) && ($endPos === null || $pos < $endPos)) {
$line = fgets($this->fh);
@@ -82,18 +99,18 @@ class File extends Part
}
$lastPos = $pos;
$pos = ftell($this->fh);
$line = trim($line);
$pos = ftell($this->fh);
$line = trim($line);
if ($line == '--' . $boundary) {
if ($part) {
// not first part
$part[1] = $lastPos;
$part[1] = $lastPos;
$this->partPos[] = $part;
}
$part = [$pos];
} elseif ($line == '--' . $boundary . '--') {
$part[1] = $lastPos;
$part[1] = $lastPos;
$this->partPos[] = $part;
break;
}
@@ -146,9 +163,9 @@ class File extends Part
}
return new static([
'file' => $this->fh,
'file' => $this->fh,
'startPos' => $this->partPos[$num][0],
'endPos' => $this->partPos[$num][1],
'endPos' => $this->partPos[$num][1],
]);
}
}

View File

@@ -102,6 +102,7 @@ interface PartInterface extends RecursiveIterator
* This getter is short for PartInterface::getHeader($name, 'string')
*
* @see PartInterface::getHeader()
*
* @param string $name header name
* @return string value of header
* @throws Exception\ExceptionInterface

View File

@@ -4,12 +4,23 @@ namespace Laminas\Mail\Storage;
use Laminas\Mail\Exception as MailException;
use Laminas\Mail\Protocol;
use Laminas\Mail\Protocol\Exception\RuntimeException;
use Laminas\Mail\Storage\Exception\ExceptionInterface;
use Laminas\Mail\Storage\Exception\InvalidArgumentException;
use Laminas\Mail\Storage\Message;
use Laminas\Mime;
use function array_combine;
use function array_key_exists;
use function is_string;
use function range;
use function strtolower;
class Pop3 extends AbstractStorage
{
/**
* protocol handler
*
* @var null|\Laminas\Mail\Protocol\Pop3
*/
protected $protocol;
@@ -18,7 +29,7 @@ class Pop3 extends AbstractStorage
* Count messages all messages in current box
*
* @return int number of messages
* @throws \Laminas\Mail\Storage\Exception\ExceptionInterface
* @throws ExceptionInterface
* @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface
*/
public function countMessages()
@@ -38,7 +49,7 @@ class Pop3 extends AbstractStorage
*/
public function getSize($id = 0)
{
$id = $id ? $id : null;
$id = $id ?: null;
return $this->protocol->getList($id);
}
@@ -46,23 +57,23 @@ class Pop3 extends AbstractStorage
* Fetch a message
*
* @param int $id number of message
* @return \Laminas\Mail\Storage\Message
* @return Message
* @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface
*/
public function getMessage($id)
{
$bodyLines = 0;
$message = $this->protocol->top($id, $bodyLines, true);
$message = $this->protocol->top($id, $bodyLines, true);
return new $this->messageClass([
'handler' => $this,
'id' => $id,
'headers' => $message,
'handler' => $this,
'id' => $id,
'headers' => $message,
'noToplines' => $bodyLines < 1,
]);
}
/*
/**
* Get raw header of message or part
*
* @param int $id number of message
@@ -70,7 +81,7 @@ class Pop3 extends AbstractStorage
* @param int $topLines include this many lines with header (after an empty line)
* @return string raw header
* @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface
* @throws \Laminas\Mail\Storage\Exception\ExceptionInterface
* @throws ExceptionInterface
*/
public function getRawHeader($id, $part = null, $topLines = 0)
{
@@ -82,14 +93,14 @@ class Pop3 extends AbstractStorage
return $this->protocol->top($id, 0, true);
}
/*
/**
* Get raw content of message or part
*
* @param int $id number of message
* @param null|array|string $part path to part or null for message content
* @return string raw content
* @throws \Laminas\Mail\Protocol\Exception\ExceptionInterface
* @throws \Laminas\Mail\Storage\Exception\ExceptionInterface
* @throws ExceptionInterface
*/
public function getRawContent($id, $part = null)
{
@@ -117,8 +128,8 @@ class Pop3 extends AbstractStorage
*
* @param array|object|Protocol\Pop3 $params mail reader specific
* parameters or configured Pop3 protocol object
* @throws \Laminas\Mail\Storage\Exception\InvalidArgumentException
* @throws \Laminas\Mail\Protocol\Exception\RuntimeException
* @throws InvalidArgumentException
* @throws RuntimeException
*/
public function __construct($params)
{
@@ -134,7 +145,7 @@ class Pop3 extends AbstractStorage
$params = ParamsNormalizer::normalizeParams($params);
if (! isset($params['user'])) {
throw new Exception\InvalidArgumentException('need at least user in params');
throw new InvalidArgumentException('need at least user in params');
}
$host = $params['host'] ?? 'localhost';
@@ -172,7 +183,7 @@ class Pop3 extends AbstractStorage
/**
* Keep the server busy.
*
* @throws \Laminas\Mail\Protocol\Exception\RuntimeException
* @throws RuntimeException
*/
public function noop()
{
@@ -185,7 +196,7 @@ class Pop3 extends AbstractStorage
* identify the message.
*
* @param int $id number of message
* @throws \Laminas\Mail\Protocol\Exception\RuntimeException
* @throws RuntimeException
*/
public function removeMessage($id)
{
@@ -199,7 +210,7 @@ class Pop3 extends AbstractStorage
*
* @param int|null $id message number
* @return array|string message number for given message or all messages as array
* @throws \Laminas\Mail\Storage\Exception\ExceptionInterface
* @throws ExceptionInterface
*/
public function getUniqueId($id = null)
{
@@ -225,7 +236,7 @@ class Pop3 extends AbstractStorage
* as parameter and use this method to translate it to message number right before calling removeMessage()
*
* @param string $id unique id
* @throws Exception\InvalidArgumentException
* @throws InvalidArgumentException
* @return int message number
*/
public function getNumberByUniqueId($id)
@@ -241,7 +252,7 @@ class Pop3 extends AbstractStorage
}
}
throw new Exception\InvalidArgumentException('unique id not found');
throw new InvalidArgumentException('unique id not found');
}
/**
@@ -249,8 +260,9 @@ class Pop3 extends AbstractStorage
* retrieved if Top wasn't needed/tried yet.
*
* @see AbstractStorage::__get()
*
* @param string $var
* @return string
* @return null|string
*/
public function __get($var)
{
@@ -264,7 +276,7 @@ class Pop3 extends AbstractStorage
// need to make a real call, because not all server are honest in their capas
try {
$this->protocol->top(1, 0, false);
} catch (MailException\ExceptionInterface $e) {
} catch (MailException\ExceptionInterface) {
// ignoring error
}
}
@@ -276,7 +288,7 @@ class Pop3 extends AbstractStorage
$id = null;
try {
$id = $this->protocol->uniqueid(1);
} catch (MailException\ExceptionInterface $e) {
} catch (MailException\ExceptionInterface) {
// ignoring error
}
$this->has['uniqueid'] = (bool) $id;

View File

@@ -5,10 +5,64 @@ namespace Laminas\Mail\Storage\Writable;
use Laminas\Mail\Exception as MailException;
use Laminas\Mail\Storage;
use Laminas\Mail\Storage\Exception as StorageException;
use Laminas\Mail\Storage\Exception\ExceptionInterface;
use Laminas\Mail\Storage\Exception\InvalidArgumentException;
use Laminas\Mail\Storage\Exception\RuntimeException;
use Laminas\Mail\Storage\Folder;
use Laminas\Stdlib\ErrorHandler;
use RecursiveIteratorIterator;
use function array_flip;
use function array_keys;
use function array_search;
use function array_values;
use function closedir;
use function copy;
use function dirname;
use function explode;
use function fclose;
use function fgets;
use function file_exists;
use function file_put_contents;
use function filemtime;
use function filesize;
use function fopen;
use function fread;
use function fwrite;
use function get_resource_type;
use function getmypid;
use function implode;
use function is_array;
use function is_dir;
use function is_file;
use function is_numeric;
use function is_resource;
use function link;
use function microtime;
use function mkdir;
use function opendir;
use function php_uname;
use function readdir;
use function rename;
use function rmdir;
use function rtrim;
use function sleep;
use function str_contains;
use function str_starts_with;
use function stream_copy_to_stream;
use function strlen;
use function strpos;
use function strrpos;
use function strtok;
use function substr;
use function time;
use function trim;
use function unlink;
use const DIRECTORY_SEPARATOR;
use const E_WARNING;
use const FILE_APPEND;
class Maildir extends Folder\Maildir implements WritableInterface
{
// TODO: init maildir (+ constructor option create if not found)
@@ -26,8 +80,8 @@ class Maildir extends Folder\Maildir implements WritableInterface
* If the given dir is already a valid maildir this will not fail.
*
* @param string $dir directory for the new maildir (may already exist)
* @throws \Laminas\Mail\Storage\Exception\RuntimeException
* @throws \Laminas\Mail\Storage\Exception\InvalidArgumentException
* @throws RuntimeException
* @throws InvalidArgumentException
*/
public static function initMaildir($dir)
{
@@ -69,8 +123,8 @@ class Maildir extends Folder\Maildir implements WritableInterface
* Additional parameters are (see parent for more):
* - create if true a new maildir is create if none exists
*
* @param $params array mail reader specific parameters
* @throws \Laminas\Mail\Storage\Exception\ExceptionInterface
* @param array|object $params mail reader specific parameters
* @throws ExceptionInterface
*/
public function __construct($params)
{
@@ -78,7 +132,8 @@ class Maildir extends Folder\Maildir implements WritableInterface
$params = (object) $params;
}
if (! empty($params->create)
if (
! empty($params->create)
&& isset($params->dirname)
&& ! file_exists($params->dirname . DIRECTORY_SEPARATOR . 'cur')
) {
@@ -95,8 +150,8 @@ class Maildir extends Folder\Maildir implements WritableInterface
* may be used as parent or which chars may be used in the folder name
*
* @param string $name global name of folder, local name if $parentFolder is set
* @param string|\Laminas\Mail\Storage\Folder $parentFolder parent of new folder, else root folder is parent
* @throws \Laminas\Mail\Storage\Exception\RuntimeException
* @param string|Folder $parentFolder parent of new folder, else root folder is parent
* @throws RuntimeException
* @return string only used internally (new created maildir)
*/
public function createFolder($name, $parentFolder = null)
@@ -115,25 +170,26 @@ class Maildir extends Folder\Maildir implements WritableInterface
$exists = null;
try {
$exists = $this->getFolders($folder);
} catch (MailException\ExceptionInterface $e) {
} catch (MailException\ExceptionInterface) {
// ok
}
if ($exists) {
throw new StorageException\RuntimeException('folder already exists');
}
if (strpos($folder, $this->delim . $this->delim) !== false) {
if (str_contains($folder, $this->delim . $this->delim)) {
throw new StorageException\RuntimeException('invalid name - folder parts may not be empty');
}
if (strpos($folder, 'INBOX' . $this->delim) === 0) {
if (str_starts_with($folder, 'INBOX' . $this->delim)) {
$folder = substr($folder, 6);
}
$fulldir = $this->rootdir . '.' . $folder;
// check if we got tricked and would create a dir outside of the rootdir or not as direct child
if (strpos($folder, DIRECTORY_SEPARATOR) !== false || strpos($folder, '/') !== false
if (
str_contains($folder, DIRECTORY_SEPARATOR) || str_contains($folder, '/')
|| dirname($fulldir) . DIRECTORY_SEPARATOR != $this->rootdir
) {
throw new StorageException\RuntimeException('invalid name - no directory separator allowed in folder name');
@@ -146,7 +202,7 @@ class Maildir extends Folder\Maildir implements WritableInterface
$parent = substr($folder, 0, strrpos($folder, $this->delim));
try {
$this->getFolders($parent);
} catch (MailException\ExceptionInterface $e) {
} catch (MailException\ExceptionInterface) {
// does not - create parent folder
$this->createFolder($parent);
}
@@ -176,7 +232,7 @@ class Maildir extends Folder\Maildir implements WritableInterface
* remove a folder
*
* @param string|Folder $name name or instance of folder
* @throws \Laminas\Mail\Storage\Exception\RuntimeException
* @throws RuntimeException
*/
public function removeFolder($name)
{
@@ -191,7 +247,7 @@ class Maildir extends Folder\Maildir implements WritableInterface
}
$name = trim($name, $this->delim);
if (strpos($name, 'INBOX' . $this->delim) === 0) {
if (str_starts_with($name, 'INBOX' . $this->delim)) {
$name = substr($name, 6);
}
@@ -249,9 +305,9 @@ class Maildir extends Folder\Maildir implements WritableInterface
*
* The new name has the same restrictions as in createFolder()
*
* @param string|\Laminas\Mail\Storage\Folder $oldName name or instance of folder
* @param string|Folder $oldName name or instance of folder
* @param string $newName new global name of folder
* @throws \Laminas\Mail\Storage\Exception\RuntimeException
* @throws RuntimeException
*/
public function renameFolder($oldName, $newName)
{
@@ -262,16 +318,16 @@ class Maildir extends Folder\Maildir implements WritableInterface
}
$oldName = trim($oldName, $this->delim);
if (strpos($oldName, 'INBOX' . $this->delim) === 0) {
if (str_starts_with($oldName, 'INBOX' . $this->delim)) {
$oldName = substr($oldName, 6);
}
$newName = trim($newName, $this->delim);
if (strpos($newName, 'INBOX' . $this->delim) === 0) {
if (str_starts_with($newName, 'INBOX' . $this->delim)) {
$newName = substr($newName, 6);
}
if (strpos($newName, $oldName . $this->delim) === 0) {
if (str_starts_with($newName, $oldName . $this->delim)) {
throw new StorageException\RuntimeException('new folder cannot be a child of old folder');
}
@@ -325,7 +381,7 @@ class Maildir extends Folder\Maildir implements WritableInterface
*/
protected function createUniqueId()
{
$id = '';
$id = '';
$id .= microtime(true);
$id .= '.' . getmypid();
$id .= '.' . php_uname('n');
@@ -340,7 +396,7 @@ class Maildir extends Folder\Maildir implements WritableInterface
* you should close the returned filehandle!
*
* @param string $folder name of current folder without leading .
* @throws \Laminas\Mail\Storage\Exception\RuntimeException
* @throws RuntimeException
* @return array array('dirname' => dir of maildir folder, 'uniq' => unique id, 'filename' => name of create file
* 'handle' => file opened for writing)
*/
@@ -385,10 +441,10 @@ class Maildir extends Folder\Maildir implements WritableInterface
}
return [
'dirname' => $this->rootdir . '.' . $folder,
'uniq' => $uniq,
'dirname' => $this->rootdir . '.' . $folder,
'uniq' => $uniq,
'filename' => $tmpdir . $uniq,
'handle' => $fh,
'handle' => $fh,
];
}
@@ -414,7 +470,7 @@ class Maildir extends Folder\Maildir implements WritableInterface
if (! isset($wantedFlags[$flag])) {
continue;
}
$info .= $char;
$info .= $char;
$flags[$char] = $flag;
unset($wantedFlags[$flag]);
}
@@ -449,7 +505,7 @@ class Maildir extends Folder\Maildir implements WritableInterface
$folder = $this->currentFolder;
}
if (! ($folder instanceof Folder)) {
if (! $folder instanceof Folder) {
$folder = $this->getFolders($folder);
}
@@ -472,7 +528,7 @@ class Maildir extends Folder\Maildir implements WritableInterface
if ($size !== false) {
$info = ',S=' . $size . $info;
}
$newFilename = $tempFile['dirname'] . DIRECTORY_SEPARATOR;
$newFilename = $tempFile['dirname'] . DIRECTORY_SEPARATOR;
$newFilename .= $recent ? 'new' : 'cur';
$newFilename .= DIRECTORY_SEPARATOR . $tempFile['uniq'] . $info;
@@ -492,8 +548,8 @@ class Maildir extends Folder\Maildir implements WritableInterface
}
$this->files[] = [
'uniq' => $tempFile['uniq'],
'flags' => $flags,
'uniq' => $tempFile['uniq'],
'flags' => $flags,
'filename' => $newFilename,
];
if ($this->quota) {
@@ -505,8 +561,8 @@ class Maildir extends Folder\Maildir implements WritableInterface
* copy an existing message
*
* @param int $id number of message
* @param string|\Laminas\Mail\Storage\Folder $folder name or instance of targer folder
* @throws \Laminas\Mail\Storage\Exception\RuntimeException
* @param string|Folder $folder name or instance of targer folder
* @throws RuntimeException
*/
public function copyMessage($id, $folder)
{
@@ -514,7 +570,7 @@ class Maildir extends Folder\Maildir implements WritableInterface
throw new StorageException\RuntimeException('storage is over quota!');
}
if (! ($folder instanceof Folder)) {
if (! $folder instanceof Folder) {
$folder = $this->getFolders($folder);
}
@@ -558,12 +614,13 @@ class Maildir extends Folder\Maildir implements WritableInterface
throw $exception;
}
if ($folder->getGlobalName() == $this->currentFolder
if (
$folder->getGlobalName() == $this->currentFolder
|| ($this->currentFolder == 'INBOX' && $folder->getGlobalName() == '/')
) {
$this->files[] = [
'uniq' => $tempFile['uniq'],
'flags' => $flags,
'uniq' => $tempFile['uniq'],
'flags' => $flags,
'filename' => $newFile,
];
}
@@ -577,16 +634,17 @@ class Maildir extends Folder\Maildir implements WritableInterface
* move an existing message
*
* @param int $id number of message
* @param string|\Laminas\Mail\Storage\Folder $folder name or instance of targer folder
* @throws \Laminas\Mail\Storage\Exception\RuntimeException
* @param string|Folder $folder name or instance of targer folder
* @throws RuntimeException
*/
public function moveMessage($id, $folder)
{
if (! ($folder instanceof Folder)) {
if (! $folder instanceof Folder) {
$folder = $this->getFolders($folder);
}
if ($folder->getGlobalName() == $this->currentFolder
if (
$folder->getGlobalName() == $this->currentFolder
|| ($this->currentFolder == 'INBOX' && $folder->getGlobalName() == '/')
) {
throw new StorageException\RuntimeException('target is current folder');
@@ -641,7 +699,7 @@ class Maildir extends Folder\Maildir implements WritableInterface
*
* @param int $id number of message
* @param array $flags new flags for message
* @throws \Laminas\Mail\Storage\Exception\RuntimeException
* @throws RuntimeException
*/
public function setFlags($id, $flags)
{
@@ -672,8 +730,8 @@ class Maildir extends Folder\Maildir implements WritableInterface
/**
* stub for not supported message deletion
*
* @param $id
* @throws \Laminas\Mail\Storage\Exception\RuntimeException
* @param int $id
* @throws RuntimeException
*/
public function removeMessage($id)
{
@@ -716,8 +774,9 @@ class Maildir extends Folder\Maildir implements WritableInterface
* get currently set quota
*
* @see \Laminas\Mail\Storage\Writable\Maildir::setQuota()
*
* @param bool $fromStorage
* @throws \Laminas\Mail\Storage\Exception\RuntimeException
* @throws RuntimeException
* @return bool|array
*/
public function getQuota($fromStorage = false)
@@ -748,7 +807,8 @@ class Maildir extends Folder\Maildir implements WritableInterface
/**
* @see http://www.inter7.com/courierimap/README.maildirquota.html "Calculating maildirsize"
* @throws \Laminas\Mail\Storage\Exception\RuntimeException
*
* @throws RuntimeException
* @return array
*/
protected function calculateMaildirsize()
@@ -842,7 +902,7 @@ class Maildir extends Folder\Maildir implements WritableInterface
}
return [
'size' => $totalSize,
'size' => $totalSize,
'count' => $messages,
'quota' => $quota,
];
@@ -850,6 +910,7 @@ class Maildir extends Folder\Maildir implements WritableInterface
/**
* @see http://www.inter7.com/courierimap/README.maildirquota.html "Calculating the quota for a Maildir++"
*
* @param bool $forceRecalc
* @return array
*/
@@ -859,7 +920,8 @@ class Maildir extends Folder\Maildir implements WritableInterface
$totalSize = 0;
$messages = 0;
$maildirsize = '';
if (! $forceRecalc
if (
! $forceRecalc
&& file_exists($this->rootdir . 'maildirsize')
&& filesize($this->rootdir . 'maildirsize') < 5120
) {
@@ -874,10 +936,10 @@ class Maildir extends Folder\Maildir implements WritableInterface
}
}
if (! $fh) {
$result = $this->calculateMaildirsize();
$result = $this->calculateMaildirsize();
$totalSize = $result['size'];
$messages = $result['count'];
$quota = $result['quota'];
$messages = $result['count'];
$quota = $result['quota'];
} else {
$maildirsize = explode("\n", $maildirsize);
if (is_array($this->quota)) {
@@ -895,9 +957,9 @@ class Maildir extends Folder\Maildir implements WritableInterface
}
unset($maildirsize[0]);
foreach ($maildirsize as $line) {
list($size, $count) = explode(' ', trim($line));
$totalSize += $size;
$messages += $count;
[$size, $count] = explode(' ', trim($line));
$totalSize += $size;
$messages += $count;
}
}
@@ -909,10 +971,10 @@ class Maildir extends Folder\Maildir implements WritableInterface
// Also we're using local time to calculate the 15 minute offset. Touching a file just for known the
// local time of the file storage isn't worth the hassle.
if ($overQuota && ($maildirsize || filemtime($this->rootdir . 'maildirsize') > time() - 900)) {
$result = $this->calculateMaildirsize();
$totalSize = $result['size'];
$messages = $result['count'];
$quota = $result['quota'];
$result = $this->calculateMaildirsize();
$totalSize = $result['size'];
$messages = $result['count'];
$quota = $result['quota'];
$overQuota = false;
$overQuota = $overQuota || (isset($quota['size']) && $totalSize > $quota['size']);
$overQuota = $overQuota || (isset($quota['count']) && $messages > $quota['count']);
@@ -924,20 +986,23 @@ class Maildir extends Folder\Maildir implements WritableInterface
}
return [
'size' => $totalSize,
'count' => $messages,
'quota' => $quota,
'size' => $totalSize,
'count' => $messages,
'quota' => $quota,
'over_quota' => $overQuota,
];
}
/**
* @param int $size
* @param int $count
* @return void
*/
protected function addQuotaEntry($size, $count = 1)
{
if (! file_exists($this->rootdir . 'maildirsize')) {
// if (! file_exists($this->rootdir . 'maildirsize')) {
// TODO: should get file handler from calculateQuota
}
$size = (int) $size;
$count = (int) $count;
// }
file_put_contents($this->rootdir . 'maildirsize', "$size $count\n", FILE_APPEND);
}
@@ -945,6 +1010,7 @@ class Maildir extends Folder\Maildir implements WritableInterface
* check if storage is currently over quota
*
* @see calculateQuota()
*
* @param bool $detailedResponse return known data of quota and current size and message count
* @param bool $forceRecalc
* @return bool|array over quota state or detailed response

View File

@@ -6,14 +6,10 @@ use Laminas\Stdlib\AbstractOptions;
class Envelope extends AbstractOptions
{
/**
* @var string|null
*/
/** @var string|null */
protected $from;
/**
* @var string|null
*/
/** @var string|null */
protected $to;
/**

View File

@@ -5,18 +5,24 @@ namespace Laminas\Mail\Transport;
use Laminas\Stdlib\ArrayUtils;
use Traversable;
use function class_exists;
use function gettype;
use function is_array;
use function is_object;
use function sprintf;
use function strtolower;
// phpcs:ignore WebimpressCodingStandard.NamingConventions.AbstractClass.Prefix
abstract class Factory
{
/**
* @var array Known transport types
*/
/** @var array Known transport types */
protected static $classMap = [
'file' => File::class,
'inmemory' => InMemory::class,
'memory' => InMemory::class,
'null' => InMemory::class,
'sendmail' => Sendmail::class,
'smtp' => Smtp::class,
'file' => File::class,
'inmemory' => InMemory::class,
'memory' => InMemory::class,
'null' => InMemory::class,
'sendmail' => Sendmail::class,
'smtp' => Smtp::class,
];
/**
@@ -35,7 +41,7 @@ abstract class Factory
throw new Exception\InvalidArgumentException(sprintf(
'%s expects an array or Traversable argument; received "%s"',
__METHOD__,
(is_object($spec) ? get_class($spec) : gettype($spec))
is_object($spec) ? $spec::class : gettype($spec)
));
}

View File

@@ -4,6 +4,11 @@ namespace Laminas\Mail\Transport;
use Laminas\Mail\Message;
use function file_put_contents;
use function sprintf;
use const DIRECTORY_SEPARATOR;
/**
* File transport
*
@@ -11,9 +16,7 @@ use Laminas\Mail\Message;
*/
class File implements TransportInterface
{
/**
* @var FileOptions
*/
/** @var FileOptions */
protected $options;
/**
@@ -28,7 +31,7 @@ class File implements TransportInterface
*
* @param null|FileOptions $options OPTIONAL (Default: null)
*/
public function __construct(FileOptions $options = null)
public function __construct(?FileOptions $options = null)
{
if (! $options instanceof FileOptions) {
$options = new FileOptions();
@@ -46,8 +49,6 @@ class File implements TransportInterface
/**
* Sets options
*
* @param FileOptions $options
*/
public function setOptions(FileOptions $options)
{
@@ -57,9 +58,7 @@ class File implements TransportInterface
/**
* Saves e-mail message to a file
*
* @param Message $message
* @throws Exception\RuntimeException on not writable target directory or
* on file_put_contents() failure
* @throws Exception\RuntimeException On not writable target directory or on file_put_contents() failure.
*/
public function send(Message $message)
{

View File

@@ -3,25 +3,32 @@
namespace Laminas\Mail\Transport;
use Laminas\Mail\Exception;
use Laminas\Mail\Exception\InvalidArgumentException;
use Laminas\Stdlib\AbstractOptions;
use function gettype;
use function is_callable;
use function is_dir;
use function is_object;
use function is_writable;
use function mt_rand;
use function sprintf;
use function sys_get_temp_dir;
use function time;
class FileOptions extends AbstractOptions
{
/**
* @var string Path to stored mail files
*/
/** @var string Path to stored mail files */
protected $path;
/**
* @var callable
*/
/** @var callable */
protected $callback;
/**
* Set path to stored mail files
*
* @param string $path
* @throws \Laminas\Mail\Exception\InvalidArgumentException
* @throws InvalidArgumentException
* @return FileOptions
*/
public function setPath($path)
@@ -56,7 +63,7 @@ class FileOptions extends AbstractOptions
* Set callback used to generate a file name
*
* @param callable $callback
* @throws \Laminas\Mail\Exception\InvalidArgumentException
* @throws InvalidArgumentException
* @return FileOptions
*/
public function setCallback($callback)
@@ -65,7 +72,7 @@ class FileOptions extends AbstractOptions
throw new Exception\InvalidArgumentException(sprintf(
'%s expects a valid callback; received "%s"',
__METHOD__,
(is_object($callback) ? get_class($callback) : gettype($callback))
is_object($callback) ? $callback::class : gettype($callback)
));
}
$this->callback = $callback;
@@ -80,9 +87,7 @@ class FileOptions extends AbstractOptions
public function getCallback()
{
if (null === $this->callback) {
$this->setCallback(function () {
return 'LaminasMail_' . time() . '_' . mt_rand() . '.eml';
});
$this->setCallback(static fn() => 'LaminasMail_' . time() . '_' . mt_rand() . '.eml');
}
return $this->callback;
}

View File

@@ -13,15 +13,11 @@ use Laminas\Mail\Message;
*/
class InMemory implements TransportInterface
{
/**
* @var null|Message
*/
/** @var null|Message */
protected $lastMessage;
/**
* Takes the last message and saves it for testing.
*
* @param Message $message
*/
public function send(Message $message)
{

View File

@@ -5,8 +5,32 @@ namespace Laminas\Mail\Transport;
use Laminas\Mail;
use Laminas\Mail\Address\AddressInterface;
use Laminas\Mail\Header\HeaderInterface;
use Laminas\Mail\Transport\Exception\InvalidArgumentException;
use Laminas\Mail\Transport\Exception\RuntimeException;
use Traversable;
use function count;
use function escapeshellarg;
use function gettype;
use function implode;
use function is_array;
use function is_callable;
use function is_object;
use function is_string;
use function mail;
use function preg_match;
use function restore_error_handler;
use function set_error_handler;
use function sprintf;
use function str_contains;
use function str_replace;
use function strtoupper;
use function substr;
use function trim;
use const PHP_OS;
use const PHP_VERSION_ID;
/**
* Class for sending email via the PHP internal mail() function
*/
@@ -28,18 +52,15 @@ class Sendmail implements TransportInterface
/**
* error information
*
* @var string
*/
protected $errstr;
/**
* @var string
*/
/** @var string */
protected $operatingSystem;
/**
* Constructor.
*
* @param null|string|array|Traversable $parameters OPTIONAL (Default: null)
*/
public function __construct($parameters = null)
@@ -56,7 +77,7 @@ class Sendmail implements TransportInterface
* Used to populate the additional_parameters argument to mail()
*
* @param null|string|array|Traversable $parameters
* @throws \Laminas\Mail\Transport\Exception\InvalidArgumentException
* @throws InvalidArgumentException
* @return Sendmail
*/
public function setParameters($parameters)
@@ -67,10 +88,10 @@ class Sendmail implements TransportInterface
}
if (! is_array($parameters) && ! $parameters instanceof Traversable) {
throw new Exception\InvalidArgumentException(sprintf(
throw new InvalidArgumentException(sprintf(
'%s expects a string, array, or Traversable object of parameters; received "%s"',
__METHOD__,
(is_object($parameters) ? get_class($parameters) : gettype($parameters))
is_object($parameters) ? $parameters::class : gettype($parameters)
));
}
@@ -89,16 +110,16 @@ class Sendmail implements TransportInterface
* Primarily for testing purposes, but could be used to curry arguments.
*
* @param callable $callable
* @throws \Laminas\Mail\Transport\Exception\InvalidArgumentException
* @throws InvalidArgumentException
* @return Sendmail
*/
public function setCallable($callable)
{
if (! is_callable($callable)) {
throw new Exception\InvalidArgumentException(sprintf(
throw new InvalidArgumentException(sprintf(
'%s expects a callable argument; received "%s"',
__METHOD__,
(is_object($callable) ? get_class($callable) : gettype($callable))
is_object($callable) ? $callable::class : gettype($callable)
));
}
$this->callable = $callable;
@@ -107,8 +128,6 @@ class Sendmail implements TransportInterface
/**
* Send a message
*
* @param \Laminas\Mail\Message $message
*/
public function send(Mail\Message $message)
{
@@ -133,8 +152,7 @@ class Sendmail implements TransportInterface
/**
* Prepare recipients list
*
* @param \Laminas\Mail\Message $message
* @throws \Laminas\Mail\Transport\Exception\RuntimeException
* @throws RuntimeException
* @return string
*/
protected function prepareRecipients(Mail\Message $message)
@@ -143,7 +161,7 @@ class Sendmail implements TransportInterface
$hasTo = $headers->has('to');
if (! $hasTo && ! $headers->has('cc') && ! $headers->has('bcc')) {
throw new Exception\RuntimeException(
throw new RuntimeException(
'Invalid email; contains no at least one of "To", "Cc", and "Bcc" header'
);
}
@@ -156,7 +174,7 @@ class Sendmail implements TransportInterface
$to = $headers->get('to');
$list = $to->getAddressList();
if (0 == count($list)) {
throw new Exception\RuntimeException('Invalid "To" header; contains no addresses');
throw new RuntimeException('Invalid "To" header; contains no addresses');
}
// If not on Windows, return normal string
@@ -176,7 +194,6 @@ class Sendmail implements TransportInterface
/**
* Prepare the subject line string
*
* @param \Laminas\Mail\Message $message
* @return string
*/
protected function prepareSubject(Mail\Message $message)
@@ -192,7 +209,6 @@ class Sendmail implements TransportInterface
/**
* Prepare the body string
*
* @param \Laminas\Mail\Message $message
* @return string
*/
protected function prepareBody(Mail\Message $message)
@@ -211,7 +227,6 @@ class Sendmail implements TransportInterface
/**
* Prepare the textual representation of headers
*
* @param \Laminas\Mail\Message $message
* @return string
*/
protected function prepareHeaders(Mail\Message $message)
@@ -225,8 +240,8 @@ class Sendmail implements TransportInterface
$from = $headers->get('From');
if ($from) {
foreach ($from->getAddressList() as $address) {
if (strpos($address->getEmail(), '\\"') !== false) {
throw new Exception\RuntimeException('Potential code injection in From header');
if (str_contains($address->getEmail(), '\\"')) {
throw new RuntimeException('Potential code injection in From header');
}
}
}
@@ -239,7 +254,6 @@ class Sendmail implements TransportInterface
* Basically, overrides the MAIL FROM envelope with either the Sender or
* From address.
*
* @param \Laminas\Mail\Message $message
* @return string
*/
protected function prepareParameters(Mail\Message $message)
@@ -255,16 +269,14 @@ class Sendmail implements TransportInterface
$sender = $message->getSender();
if ($sender instanceof AddressInterface) {
$parameters .= ' -f' . \escapeshellarg($sender->getEmail());
return $parameters;
return $parameters . ' -f' . escapeshellarg($sender->getEmail());
}
$from = $message->getFrom();
if (count($from)) {
$from->rewind();
$sender = $from->current();
$parameters .= ' -f' . \escapeshellarg($sender->getEmail());
return $parameters;
$sender = $from->current();
return $parameters . ' -f' . escapeshellarg($sender->getEmail());
}
return $parameters;
@@ -277,8 +289,8 @@ class Sendmail implements TransportInterface
* @param string $subject
* @param string $message
* @param string $headers
* @param $parameters
* @throws \Laminas\Mail\Transport\Exception\RuntimeException
* @param null|string $parameters
* @throws RuntimeException
*/
public function mailHandler($to, $subject, $message, $headers, $parameters)
{
@@ -295,7 +307,7 @@ class Sendmail implements TransportInterface
if (empty($errstr)) {
$errstr = 'Unknown error';
}
throw new Exception\RuntimeException('Unable to send mail: ' . $errstr);
throw new RuntimeException('Unable to send mail: ' . $errstr);
}
}
@@ -309,7 +321,7 @@ class Sendmail implements TransportInterface
* @param array $errcontext
* @return bool always true
*/
public function handleMailErrors($errno, $errstr, $errfile = null, $errline = null, array $errcontext = null)
public function handleMailErrors($errno, $errstr, $errfile = null, $errline = null, ?array $errcontext = null)
{
$this->errstr = $errstr;
return true;
@@ -325,6 +337,6 @@ class Sendmail implements TransportInterface
if (! $this->operatingSystem) {
$this->operatingSystem = strtoupper(substr(PHP_OS, 0, 3));
}
return ($this->operatingSystem == 'WIN');
return $this->operatingSystem == 'WIN';
}
}

View File

@@ -9,6 +9,11 @@ use Laminas\Mail\Protocol;
use Laminas\Mail\Protocol\Exception as ProtocolException;
use Laminas\ServiceManager\ServiceManager;
use function array_unique;
use function count;
use function sprintf;
use function time;
/**
* SMTP connection object
*
@@ -16,29 +21,19 @@ use Laminas\ServiceManager\ServiceManager;
*/
class Smtp implements TransportInterface
{
/**
* @var SmtpOptions
*/
/** @var SmtpOptions */
protected $options;
/**
* @var Envelope|null
*/
/** @var Envelope|null */
protected $envelope;
/**
* @var Protocol\Smtp
*/
/** @var null|Protocol\Smtp */
protected $connection;
/**
* @var bool
*/
/** @var bool */
protected $autoDisconnect = true;
/**
* @var Protocol\SmtpPluginManager
*/
/** @var Protocol\SmtpPluginManager */
protected $plugins;
/**
@@ -49,11 +44,9 @@ class Smtp implements TransportInterface
protected $connectedTime;
/**
* Constructor.
*
* @param SmtpOptions $options Optional
*/
public function __construct(SmtpOptions $options = null)
public function __construct(?SmtpOptions $options = null)
{
if (! $options instanceof SmtpOptions) {
$options = new SmtpOptions();
@@ -64,7 +57,6 @@ class Smtp implements TransportInterface
/**
* Set options
*
* @param SmtpOptions $options
* @return Smtp
*/
public function setOptions(SmtpOptions $options)
@@ -85,8 +77,6 @@ class Smtp implements TransportInterface
/**
* Set options
*
* @param Envelope $envelope
*/
public function setEnvelope(Envelope $envelope)
{
@@ -106,7 +96,6 @@ class Smtp implements TransportInterface
/**
* Set plugin manager for obtaining SMTP protocol connection
*
* @param Protocol\SmtpPluginManager $plugins
* @throws Exception\InvalidArgumentException
* @return Smtp
*/
@@ -155,10 +144,9 @@ class Smtp implements TransportInterface
* Return an SMTP connection
*
* @param string $name
* @param array|null $options
* @return Protocol\Smtp
*/
public function plugin($name, array $options = null)
public function plugin($name, ?array $options = null)
{
return $this->getPluginManager()->get($name, $options);
}
@@ -168,30 +156,30 @@ class Smtp implements TransportInterface
*/
public function __destruct()
{
if (! $this->getConnection() instanceof Protocol\Smtp) {
$connection = $this->getConnection();
if (! $connection instanceof Protocol\Smtp) {
return;
}
try {
$this->getConnection()->quit();
} catch (ProtocolException\ExceptionInterface $e) {
$connection->quit();
} catch (ProtocolException\ExceptionInterface) {
// ignore
}
if ($this->autoDisconnect) {
$this->getConnection()->disconnect();
$connection->disconnect();
}
}
/**
* Sets the connection protocol instance
*
* @param Protocol\AbstractProtocol $connection
*/
public function setConnection(Protocol\AbstractProtocol $connection)
{
$this->connection = $connection;
if (($connection instanceof Protocol\Smtp)
if (
$connection instanceof Protocol\Smtp
&& ($this->getOptions()->getConnectionTimeLimit() !== null)
) {
$connection->setUseCompleteQuit(false);
@@ -201,12 +189,13 @@ class Smtp implements TransportInterface
/**
* Gets the connection protocol instance
*
* @return Protocol\Smtp
* @return null|Protocol\Smtp
*/
public function getConnection()
{
$timeLimit = $this->getOptions()->getConnectionTimeLimit();
if ($timeLimit !== null
if (
$timeLimit !== null
&& $this->connectedTime !== null
&& ((time() - $this->connectedTime) > $timeLimit)
) {
@@ -222,8 +211,9 @@ class Smtp implements TransportInterface
*/
public function disconnect()
{
if ($this->getConnection() instanceof Protocol\Smtp) {
$this->getConnection()->disconnect();
$connection = $this->getConnection();
if ($connection instanceof Protocol\Smtp) {
$connection->disconnect();
$this->connectedTime = null;
}
}
@@ -234,7 +224,6 @@ class Smtp implements TransportInterface
* The connection via the protocol adapter is made just-in-time to allow a
* developer to add a custom adapter if required before mail is sent.
*
* @param Message $message
* @throws Exception\RuntimeException
*/
public function send(Message $message)
@@ -242,7 +231,7 @@ class Smtp implements TransportInterface
// If sending multiple messages per session use existing adapter
$connection = $this->getConnection();
if (! ($connection instanceof Protocol\Smtp) || ! $connection->hasSession()) {
if (! $connection instanceof Protocol\Smtp || ! $connection->hasSession()) {
$connection = $this->connect();
} else {
// Reset connection to ensure reliable transaction
@@ -260,7 +249,7 @@ class Smtp implements TransportInterface
throw new Exception\RuntimeException(
sprintf(
'%s transport expects at least one recipient if the message has at least one header or body',
__CLASS__
self::class
)
);
}
@@ -280,7 +269,6 @@ class Smtp implements TransportInterface
/**
* Retrieve email address for envelope FROM
*
* @param Message $message
* @throws Exception\RuntimeException
* @return string
*/
@@ -300,7 +288,7 @@ class Smtp implements TransportInterface
// Per RFC 2822 3.6
throw new Exception\RuntimeException(sprintf(
'%s transport expects either a Sender or at least one From address in the Message; none provided',
__CLASS__
self::class
));
}
@@ -312,7 +300,6 @@ class Smtp implements TransportInterface
/**
* Prepare array of email address recipients
*
* @param Message $message
* @return array
*/
protected function prepareRecipients(Message $message)
@@ -339,7 +326,6 @@ class Smtp implements TransportInterface
/**
* Prepare header string from message
*
* @param Message $message
* @return string
*/
protected function prepareHeaders(Message $message)
@@ -352,7 +338,6 @@ class Smtp implements TransportInterface
/**
* Prepare body string from message
*
* @param Message $message
* @return string
*/
protected function prepareBody(Message $message)
@@ -368,10 +353,10 @@ class Smtp implements TransportInterface
protected function lazyLoadConnection()
{
// Check if authentication is required and determine required class
$options = $this->getOptions();
$config = $options->getConnectionConfig();
$config['host'] = $options->getHost();
$config['port'] = $options->getPort();
$options = $this->getOptions();
$config = $options->getConnectionConfig();
$config['host'] = $options->getHost();
$config['port'] = $options->getPort();
$this->setConnection($this->plugin($options->getConnectionClass(), $config));

View File

@@ -3,18 +3,20 @@
namespace Laminas\Mail\Transport;
use Laminas\Mail\Exception;
use Laminas\Mail\Exception\InvalidArgumentException;
use Laminas\Stdlib\AbstractOptions;
use function gettype;
use function is_object;
use function is_string;
use function sprintf;
class SmtpOptions extends AbstractOptions
{
/**
* @var string Local client hostname
*/
/** @var string Local client hostname */
protected $name = 'localhost';
/**
* @var string
*/
/** @var string */
protected $connectionClass = 'smtp';
/**
@@ -24,14 +26,10 @@ class SmtpOptions extends AbstractOptions
*/
protected $connectionConfig = [];
/**
* @var string Remote SMTP hostname or IP
*/
/** @var string Remote SMTP hostname or IP */
protected $host = '127.0.0.1';
/**
* @var int
*/
/** @var int */
protected $port = 25;
/**
@@ -57,7 +55,7 @@ class SmtpOptions extends AbstractOptions
*
* @todo hostname/IP validation
* @param string $name
* @throws \Laminas\Mail\Exception\InvalidArgumentException
* @throws InvalidArgumentException
* @return SmtpOptions
*/
public function setName($name)
@@ -65,7 +63,7 @@ class SmtpOptions extends AbstractOptions
if (! is_string($name) && $name !== null) {
throw new Exception\InvalidArgumentException(sprintf(
'Name must be a string or null; argument of type "%s" provided',
(is_object($name) ? get_class($name) : gettype($name))
is_object($name) ? $name::class : gettype($name)
));
}
$this->name = $name;
@@ -90,7 +88,7 @@ class SmtpOptions extends AbstractOptions
* Set connection class
*
* @param string $connectionClass the value to be set
* @throws \Laminas\Mail\Exception\InvalidArgumentException
* @throws InvalidArgumentException
* @return SmtpOptions
*/
public function setConnectionClass($connectionClass)
@@ -98,7 +96,7 @@ class SmtpOptions extends AbstractOptions
if (! is_string($connectionClass) && $connectionClass !== null) {
throw new Exception\InvalidArgumentException(sprintf(
'Connection class must be a string or null; argument of type "%s" provided',
(is_object($connectionClass) ? get_class($connectionClass) : gettype($connectionClass))
is_object($connectionClass) ? $connectionClass::class : gettype($connectionClass)
));
}
$this->connectionClass = $connectionClass;
@@ -118,7 +116,6 @@ class SmtpOptions extends AbstractOptions
/**
* Set connection configuration array
*
* @param array $connectionConfig
* @return SmtpOptions
*/
public function setConnectionConfig(array $connectionConfig)
@@ -164,7 +161,7 @@ class SmtpOptions extends AbstractOptions
* Set the port the SMTP server runs on
*
* @param int $port
* @throws \Laminas\Mail\Exception\InvalidArgumentException
* @throws InvalidArgumentException
* @return SmtpOptions
*/
public function setPort($port)

View File

@@ -12,8 +12,7 @@ interface TransportInterface
/**
* Send a mail message
*
* @param \Laminas\Mail\Message $message
* @return
* @return void
*/
public function send(Mail\Message $message);
}