This commit is contained in:
Hanson.xyz Dev
2026-01-04 17:50:08 -06:00
parent 7e45ce0756
commit acc8ac87a0
4131 changed files with 232562 additions and 250244 deletions
+245
View File
@@ -0,0 +1,245 @@
<?php
/**
* PHPMailer - PHP email creation and transport class.
* PHP Version 5.5.
*
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
*
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
* @author Brent R. Matzelle (original founder)
* @copyright 2012 - 2023 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*/
namespace PHPMailer\PHPMailer;
/**
* Configure PHPMailer with DSN string.
*
* @see https://en.wikipedia.org/wiki/Data_source_name
*
* @author Oleg Voronkovich <oleg-voronkovich@yandex.ru>
*/
class DSNConfigurator
{
/**
* Create new PHPMailer instance configured by DSN.
*
* @param string $dsn DSN
* @param bool $exceptions Should we throw external exceptions?
*
* @return PHPMailer
*/
public static function mailer($dsn, $exceptions = null)
{
static $configurator = null;
if (null === $configurator) {
$configurator = new DSNConfigurator();
}
return $configurator->configure(new PHPMailer($exceptions), $dsn);
}
/**
* Configure PHPMailer instance with DSN string.
*
* @param PHPMailer $mailer PHPMailer instance
* @param string $dsn DSN
*
* @return PHPMailer
*/
public function configure(PHPMailer $mailer, $dsn)
{
$config = $this->parseDSN($dsn);
$this->applyConfig($mailer, $config);
return $mailer;
}
/**
* Parse DSN string.
*
* @param string $dsn DSN
*
* @throws Exception If DSN is malformed
*
* @return array Configuration
*/
private function parseDSN($dsn)
{
$config = $this->parseUrl($dsn);
if (false === $config || !isset($config['scheme']) || !isset($config['host'])) {
throw new Exception('Malformed DSN');
}
if (isset($config['query'])) {
parse_str($config['query'], $config['query']);
}
return $config;
}
/**
* Apply configuration to mailer.
*
* @param PHPMailer $mailer PHPMailer instance
* @param array $config Configuration
*
* @throws Exception If scheme is invalid
*/
private function applyConfig(PHPMailer $mailer, $config)
{
switch ($config['scheme']) {
case 'mail':
$mailer->isMail();
break;
case 'sendmail':
$mailer->isSendmail();
break;
case 'qmail':
$mailer->isQmail();
break;
case 'smtp':
case 'smtps':
$mailer->isSMTP();
$this->configureSMTP($mailer, $config);
break;
default:
throw new Exception(
sprintf(
'Invalid scheme: "%s". Allowed values: "mail", "sendmail", "qmail", "smtp", "smtps".',
$config['scheme']
)
);
}
if (isset($config['query'])) {
$this->configureOptions($mailer, $config['query']);
}
}
/**
* Configure SMTP.
*
* @param PHPMailer $mailer PHPMailer instance
* @param array $config Configuration
*/
private function configureSMTP($mailer, $config)
{
$isSMTPS = 'smtps' === $config['scheme'];
if ($isSMTPS) {
$mailer->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
}
$mailer->Host = $config['host'];
if (isset($config['port'])) {
$mailer->Port = $config['port'];
} elseif ($isSMTPS) {
$mailer->Port = SMTP::DEFAULT_SECURE_PORT;
}
$mailer->SMTPAuth = isset($config['user']) || isset($config['pass']);
if (isset($config['user'])) {
$mailer->Username = $config['user'];
}
if (isset($config['pass'])) {
$mailer->Password = $config['pass'];
}
}
/**
* Configure options.
*
* @param PHPMailer $mailer PHPMailer instance
* @param array $options Options
*
* @throws Exception If option is unknown
*/
private function configureOptions(PHPMailer $mailer, $options)
{
$allowedOptions = get_object_vars($mailer);
unset($allowedOptions['Mailer']);
unset($allowedOptions['SMTPAuth']);
unset($allowedOptions['Username']);
unset($allowedOptions['Password']);
unset($allowedOptions['Hostname']);
unset($allowedOptions['Port']);
unset($allowedOptions['ErrorInfo']);
$allowedOptions = \array_keys($allowedOptions);
foreach ($options as $key => $value) {
if (!in_array($key, $allowedOptions)) {
throw new Exception(
sprintf(
'Unknown option: "%s". Allowed values: "%s"',
$key,
implode('", "', $allowedOptions)
)
);
}
switch ($key) {
case 'AllowEmpty':
case 'SMTPAutoTLS':
case 'SMTPKeepAlive':
case 'SingleTo':
case 'UseSendmailOptions':
case 'do_verp':
case 'DKIM_copyHeaderFields':
$mailer->$key = (bool) $value;
break;
case 'Priority':
case 'SMTPDebug':
case 'WordWrap':
$mailer->$key = (int) $value;
break;
default:
$mailer->$key = $value;
break;
}
}
}
/**
* Parse a URL.
* Wrapper for the built-in parse_url function to work around a bug in PHP 5.5.
*
* @param string $url URL
*
* @return array|false
*/
protected function parseUrl($url)
{
if (\PHP_VERSION_ID >= 50600 || false === strpos($url, '?')) {
return parse_url($url);
}
$chunks = explode('?', $url);
if (is_array($chunks)) {
$result = parse_url($chunks[0]);
if (is_array($result)) {
$result['query'] = $chunks[1];
}
return $result;
}
return false;
}
}
+139
View File
@@ -0,0 +1,139 @@
<?php
/**
* PHPMailer - PHP email creation and transport class.
* PHP Version 5.5.
*
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
*
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
* @author Brent R. Matzelle (original founder)
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*/
namespace PHPMailer\PHPMailer;
use League\OAuth2\Client\Grant\RefreshToken;
use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Token\AccessToken;
/**
* OAuth - OAuth2 authentication wrapper class.
* Uses the oauth2-client package from the League of Extraordinary Packages.
*
* @see https://oauth2-client.thephpleague.com
*
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
*/
class OAuth implements OAuthTokenProvider
{
/**
* An instance of the League OAuth Client Provider.
*
* @var AbstractProvider
*/
protected $provider;
/**
* The current OAuth access token.
*
* @var AccessToken
*/
protected $oauthToken;
/**
* The user's email address, usually used as the login ID
* and also the from address when sending email.
*
* @var string
*/
protected $oauthUserEmail = '';
/**
* The client secret, generated in the app definition of the service you're connecting to.
*
* @var string
*/
protected $oauthClientSecret = '';
/**
* The client ID, generated in the app definition of the service you're connecting to.
*
* @var string
*/
protected $oauthClientId = '';
/**
* The refresh token, used to obtain new AccessTokens.
*
* @var string
*/
protected $oauthRefreshToken = '';
/**
* OAuth constructor.
*
* @param array $options Associative array containing
* `provider`, `userName`, `clientSecret`, `clientId` and `refreshToken` elements
*/
public function __construct($options)
{
$this->provider = $options['provider'];
$this->oauthUserEmail = $options['userName'];
$this->oauthClientSecret = $options['clientSecret'];
$this->oauthClientId = $options['clientId'];
$this->oauthRefreshToken = $options['refreshToken'];
}
/**
* Get a new RefreshToken.
*
* @return RefreshToken
*/
protected function getGrant()
{
return new RefreshToken();
}
/**
* Get a new AccessToken.
*
* @return AccessToken
*/
protected function getToken()
{
return $this->provider->getAccessToken(
$this->getGrant(),
['refresh_token' => $this->oauthRefreshToken]
);
}
/**
* Generate a base64-encoded OAuth token.
*
* @return string
*/
public function getOauth64()
{
//Get a new token if it's not available or has expired
if (null === $this->oauthToken || $this->oauthToken->hasExpired()) {
$this->oauthToken = $this->getToken();
}
return base64_encode(
'user=' .
$this->oauthUserEmail .
"\001auth=Bearer " .
$this->oauthToken .
"\001\001"
);
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
/**
* PHPMailer - PHP email creation and transport class.
* PHP Version 5.5.
*
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
*
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
* @author Brent R. Matzelle (original founder)
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*/
namespace PHPMailer\PHPMailer;
/**
* OAuthTokenProvider - OAuth2 token provider interface.
* Provides base64 encoded OAuth2 auth strings for SMTP authentication.
*
* @see OAuth
* @see SMTP::authenticate()
*
* @author Peter Scopes (pdscopes)
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
*/
interface OAuthTokenProvider
{
/**
* Generate a base64-encoded OAuth token ensuring that the access token has not expired.
* The string to be base 64 encoded should be in the form:
* "user=<user_email_address>\001auth=Bearer <access_token>\001\001"
*
* @return string
*/
public function getOauth64();
}
+362 -152
View File
@@ -561,9 +561,9 @@ class PHPMailer
* string $body the email body
* string $from email address of sender
* string $extra extra information of possible use
* "smtp_transaction_id' => last smtp transaction id
* 'smtp_transaction_id' => last smtp transaction id
*
* @var string
* @var callable|callable-string
*/
public $action_function = '';
@@ -580,6 +580,10 @@ class PHPMailer
* May be a callable to inject your own validator, but there are several built-in validators.
* The default validator uses PHP's FILTER_VALIDATE_EMAIL filter_var option.
*
* If CharSet is UTF8, the validator is left at the default value,
* and you send to addresses that use non-ASCII local parts, then
* PHPMailer automatically changes to the 'eai' validator.
*
* @see PHPMailer::validateAddress()
*
* @var string|callable
@@ -659,6 +663,14 @@ class PHPMailer
*/
protected $ReplyToQueue = [];
/**
* Whether the need for SMTPUTF8 has been detected. Set by
* preSend() if necessary.
*
* @var bool
*/
public $UseSMTPUTF8 = false;
/**
* The array of attachments.
*
@@ -699,7 +711,7 @@ class PHPMailer
*
* @var array
*/
protected $language = [];
protected static $language = [];
/**
* The number of errors encountered.
@@ -756,7 +768,7 @@ class PHPMailer
*
* @var string
*/
const VERSION = '6.9.3';
const VERSION = '7.0.0';
/**
* Error severity: message only, continue processing.
@@ -864,7 +876,7 @@ class PHPMailer
private function mailPassthru($to, $subject, $body, $header, $params)
{
//Check overloading of mail function to avoid double-encoding
if ((int)ini_get('mbstring.func_overload') & 1) { // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.mbstring_func_overloadDeprecated
if ((int)ini_get('mbstring.func_overload') & 1) {
$subject = $this->secureHeader($subject);
} else {
$subject = $this->encodeHeader($this->secureHeader($subject));
@@ -1090,7 +1102,7 @@ class PHPMailer
//At-sign is missing.
$error_message = sprintf(
'%s (%s): %s',
$this->lang('invalid_address'),
self::lang('invalid_address'),
$kind,
$address
);
@@ -1110,19 +1122,22 @@ class PHPMailer
$params = [$kind, $address, $name];
//Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
//Domain is assumed to be whatever is after the last @ symbol in the address
if (static::idnSupported() && $this->has8bitChars(substr($address, ++$pos))) {
if ('Reply-To' !== $kind) {
if (!array_key_exists($address, $this->RecipientsQueue)) {
$this->RecipientsQueue[$address] = $params;
if ($this->has8bitChars(substr($address, ++$pos))) {
if (static::idnSupported()) {
if ('Reply-To' !== $kind) {
if (!array_key_exists($address, $this->RecipientsQueue)) {
$this->RecipientsQueue[$address] = $params;
return true;
}
} elseif (!array_key_exists($address, $this->ReplyToQueue)) {
$this->ReplyToQueue[$address] = $params;
return true;
}
} elseif (!array_key_exists($address, $this->ReplyToQueue)) {
$this->ReplyToQueue[$address] = $params;
return true;
}
//We have an 8-bit domain, but we are missing the necessary extensions to support it
//Or we are already sending to this address
return false;
}
@@ -1160,10 +1175,19 @@ class PHPMailer
*/
protected function addAnAddress($kind, $address, $name = '')
{
if (
self::$validator === 'php' &&
((bool) preg_match('/[\x80-\xFF]/', $address))
) {
//The caller has not altered the validator and is sending to an address
//with UTF-8, so assume that they want UTF-8 support instead of failing
$this->CharSet = self::CHARSET_UTF8;
self::$validator = 'eai';
}
if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) {
$error_message = sprintf(
'%s: %s',
$this->lang('Invalid recipient kind'),
self::lang('Invalid recipient kind'),
$kind
);
$this->setError($error_message);
@@ -1177,7 +1201,7 @@ class PHPMailer
if (!static::validateAddress($address)) {
$error_message = sprintf(
'%s (%s): %s',
$this->lang('invalid_address'),
self::lang('invalid_address'),
$kind,
$address
);
@@ -1196,12 +1220,16 @@ class PHPMailer
return true;
}
} elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) {
$this->ReplyTo[strtolower($address)] = [$address, $name];
} else {
foreach ($this->ReplyTo as $replyTo) {
if (0 === strcasecmp($replyTo[0], $address)) {
return false;
}
}
$this->ReplyTo[] = [$address, $name];
return true;
}
return false;
}
@@ -1214,15 +1242,18 @@ class PHPMailer
* @see https://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
*
* @param string $addrstr The address list string
* @param bool $useimap Whether to use the IMAP extension to parse the list
* @param null $useimap Deprecated argument since 6.11.0.
* @param string $charset The charset to use when decoding the address list string.
*
* @return array
*/
public static function parseAddresses($addrstr, $useimap = true, $charset = self::CHARSET_ISO88591)
public static function parseAddresses($addrstr, $useimap = null, $charset = self::CHARSET_ISO88591)
{
if ($useimap !== null) {
trigger_error(self::lang('deprecated_argument'), E_USER_DEPRECATED);
}
$addresses = [];
if ($useimap && function_exists('imap_rfc822_parse_adrlist')) {
if (function_exists('imap_rfc822_parse_adrlist')) {
//Use this built-in parser if it's available
$list = imap_rfc822_parse_adrlist($addrstr, '');
// Clear any potential IMAP errors to get rid of notices being thrown at end of script.
@@ -1232,20 +1263,13 @@ class PHPMailer
'.SYNTAX-ERROR.' !== $address->host &&
static::validateAddress($address->mailbox . '@' . $address->host)
) {
//Decode the name part if it's present and encoded
//Decode the name part if it's present and maybe encoded
if (
property_exists($address, 'personal') &&
//Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled
defined('MB_CASE_UPPER') &&
preg_match('/^=\?.*\?=$/s', $address->personal)
property_exists($address, 'personal')
&& is_string($address->personal)
&& $address->personal !== ''
) {
$origCharset = mb_internal_encoding();
mb_internal_encoding($charset);
//Undo any RFC2047-encoded spaces-as-underscores
$address->personal = str_replace('_', '=20', $address->personal);
//Decode the name
$address->personal = mb_decode_mimeheader($address->personal);
mb_internal_encoding($origCharset);
$address->personal = static::decodeHeader($address->personal, $charset);
}
$addresses[] = [
@@ -1256,40 +1280,51 @@ class PHPMailer
}
} else {
//Use this simpler parser
$list = explode(',', $addrstr);
foreach ($list as $address) {
$address = trim($address);
//Is there a separate name part?
if (strpos($address, '<') === false) {
//No separate name, just use the whole thing
if (static::validateAddress($address)) {
$addresses[] = [
'name' => '',
'address' => $address,
];
}
} else {
list($name, $email) = explode('<', $address);
$email = trim(str_replace('>', '', $email));
$name = trim($name);
if (static::validateAddress($email)) {
//Check for a Mbstring constant rather than using extension_loaded, which is sometimes disabled
//If this name is encoded, decode it
if (defined('MB_CASE_UPPER') && preg_match('/^=\?.*\?=$/s', $name)) {
$origCharset = mb_internal_encoding();
mb_internal_encoding($charset);
//Undo any RFC2047-encoded spaces-as-underscores
$name = str_replace('_', '=20', $name);
//Decode the name
$name = mb_decode_mimeheader($name);
mb_internal_encoding($origCharset);
}
$addresses[] = [
//Remove any surrounding quotes and spaces from the name
'name' => trim($name, '\'" '),
'address' => $email,
];
}
$addresses = static::parseSimplerAddresses($addrstr, $charset);
}
return $addresses;
}
/**
* Parse a string containing one or more RFC822-style comma-separated email addresses
* with the form "display name <address>" into an array of name/address pairs.
* Uses a simpler parser that does not require the IMAP extension but doesnt support
* the full RFC822 spec. For full RFC822 support, use the PHP IMAP extension.
*
* @param string $addrstr The address list string
* @param string $charset The charset to use when decoding the address list string.
*
* @return array
*/
protected static function parseSimplerAddresses($addrstr, $charset)
{
// Emit a runtime notice to recommend using the IMAP extension for full RFC822 parsing
trigger_error(self::lang('imap_recommended'), E_USER_NOTICE);
$addresses = [];
$list = explode(',', $addrstr);
foreach ($list as $address) {
$address = trim($address);
//Is there a separate name part?
if (strpos($address, '<') === false) {
//No separate name, just use the whole thing
if (static::validateAddress($address)) {
$addresses[] = [
'name' => '',
'address' => $address,
];
}
} else {
$parsed = static::parseEmailString($address);
$email = $parsed['email'];
if (static::validateAddress($email)) {
$name = static::decodeHeader($parsed['name'], $charset);
$addresses[] = [
//Remove any surrounding quotes and spaces from the name
'name' => trim($name, '\'" '),
'address' => $email,
];
}
}
}
@@ -1297,6 +1332,42 @@ class PHPMailer
return $addresses;
}
/**
* Parse a string containing an email address with an optional name
* and divide it into a name and email address.
*
* @param string $input The email with name.
*
* @return array{name: string, email: string}
*/
private static function parseEmailString($input)
{
$input = trim((string)$input);
if ($input === '') {
return ['name' => '', 'email' => ''];
}
$pattern = '/^\s*(?:(?:"([^"]*)"|\'([^\']*)\'|([^<]*?))\s*)?<\s*([^>]+)\s*>\s*$/';
if (preg_match($pattern, $input, $matches)) {
$name = '';
// Double quotes including special scenarios.
if (isset($matches[1]) && $matches[1] !== '') {
$name = $matches[1];
// Single quotes including special scenarios.
} elseif (isset($matches[2]) && $matches[2] !== '') {
$name = $matches[2];
// Simplest scenario, name and email are in the format "Name <email>".
} elseif (isset($matches[3])) {
$name = trim($matches[3]);
}
return ['name' => $name, 'email' => trim($matches[4])];
}
return ['name' => '', 'email' => $input];
}
/**
* Set the From and FromName properties.
*
@@ -1310,6 +1381,10 @@ class PHPMailer
*/
public function setFrom($address, $name = '', $auto = true)
{
if (is_null($name)) {
//Helps avoid a deprecation warning in the preg_replace() below
$name = '';
}
$address = trim((string)$address);
$name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
//Don't validate now addresses with IDN. Will be done in send().
@@ -1321,7 +1396,7 @@ class PHPMailer
) {
$error_message = sprintf(
'%s (From): %s',
$this->lang('invalid_address'),
self::lang('invalid_address'),
$address
);
$this->setError($error_message);
@@ -1362,6 +1437,7 @@ class PHPMailer
* * `pcre` Use old PCRE implementation;
* * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
* * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
* * `eai` Use a pattern similar to the HTML5 spec for 'email' and to firefox, extended to support EAI (RFC6530).
* * `noregex` Don't use a regex: super fast, really dumb.
* Alternatively you may pass in a callable to inject your own validator, for example:
*
@@ -1432,6 +1508,24 @@ class PHPMailer
'[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
$address
);
case 'eai':
/*
* This is the pattern used in the HTML5 spec for validation of 'email' type
* form input elements (as above), modified to accept Unicode email addresses.
* This is also more lenient than Firefox' html5 spec, in order to make the regex faster.
* 'eai' is an acronym for Email Address Internationalization.
* This validator is selected automatically if you attempt to use recipient addresses
* that contain Unicode characters in the local part.
*
* @see https://html.spec.whatwg.org/#e-mail-state-(type=email)
* @see https://en.wikipedia.org/wiki/International_email
*/
return (bool) preg_match(
'/^[-\p{L}\p{N}\p{M}.!#$%&\'*+\/=?^_`{|}~]+@[\p{L}\p{N}\p{M}](?:[\p{L}\p{N}\p{M}-]{0,61}' .
'[\p{L}\p{N}\p{M}])?(?:\.[\p{L}\p{N}\p{M}]' .
'(?:[-\p{L}\p{N}\p{M}]{0,61}[\p{L}\p{N}\p{M}])?)*$/usD',
$address
);
case 'php':
default:
return filter_var($address, FILTER_VALIDATE_EMAIL) !== false;
@@ -1489,11 +1583,9 @@ class PHPMailer
);
} elseif (defined('INTL_IDNA_VARIANT_2003')) {
//Fall back to this old, deprecated/removed encoding
// phpcs:ignore PHPCompatibility.Constants.RemovedConstants.intl_idna_variant_2003Deprecated
$punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VARIANT_2003);
} else {
//Fall back to a default we don't know about
// phpcs:ignore PHPCompatibility.ParameterValues.NewIDNVariantDefault.NotSet
$punycode = idn_to_ascii($domain, $errorcode);
}
if (false !== $punycode) {
@@ -1560,20 +1652,37 @@ class PHPMailer
&& ini_get('mail.add_x_header') === '1'
&& stripos(PHP_OS, 'WIN') === 0
) {
trigger_error($this->lang('buggy_php'), E_USER_WARNING);
trigger_error(self::lang('buggy_php'), E_USER_WARNING);
}
try {
$this->error_count = 0; //Reset errors
$this->mailHeader = '';
//The code below tries to support full use of Unicode,
//while remaining compatible with legacy SMTP servers to
//the greatest degree possible: If the message uses
//Unicode in the local parts of any addresses, it is sent
//using SMTPUTF8. If not, it it sent using
//punycode-encoded domains and plain SMTP.
if (
static::CHARSET_UTF8 === strtolower($this->CharSet) &&
($this->anyAddressHasUnicodeLocalPart($this->RecipientsQueue) ||
$this->anyAddressHasUnicodeLocalPart(array_keys($this->all_recipients)) ||
$this->anyAddressHasUnicodeLocalPart($this->ReplyToQueue) ||
$this->addressHasUnicodeLocalPart($this->From))
) {
$this->UseSMTPUTF8 = true;
}
//Dequeue recipient and Reply-To addresses with IDN
foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
$params[1] = $this->punyencodeAddress($params[1]);
if (!$this->UseSMTPUTF8) {
$params[1] = $this->punyencodeAddress($params[1]);
}
call_user_func_array([$this, 'addAnAddress'], $params);
}
if (count($this->to) + count($this->cc) + count($this->bcc) < 1) {
throw new Exception($this->lang('provide_address'), self::STOP_CRITICAL);
throw new Exception(self::lang('provide_address'), self::STOP_CRITICAL);
}
//Validate From, Sender, and ConfirmReadingTo addresses
@@ -1590,7 +1699,7 @@ class PHPMailer
if (!static::validateAddress($this->{$address_kind})) {
$error_message = sprintf(
'%s (%s): %s',
$this->lang('invalid_address'),
self::lang('invalid_address'),
$address_kind,
$this->{$address_kind}
);
@@ -1612,7 +1721,7 @@ class PHPMailer
$this->setMessageType();
//Refuse to send an empty message unless we are specifically allowing it
if (!$this->AllowEmpty && empty($this->Body)) {
throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
throw new Exception(self::lang('empty_message'), self::STOP_CRITICAL);
}
//Trim subject consistently
@@ -1751,8 +1860,10 @@ class PHPMailer
} else {
$sendmailFmt = '%s -oi -f%s -t';
}
} elseif ($this->Mailer === 'qmail') {
$sendmailFmt = '%s';
} else {
//allow sendmail to choose a default envelope sender. It may
//Allow sendmail to choose a default envelope sender. It may
//seem preferable to force it to use the From header as with
//SMTP, but that introduces new problems (see
//<https://github.com/PHPMailer/PHPMailer/issues/2298>), and
@@ -1770,33 +1881,35 @@ class PHPMailer
foreach ($this->SingleToArray as $toAddr) {
$mail = @popen($sendmail, 'w');
if (!$mail) {
throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
$this->edebug("To: {$toAddr}");
fwrite($mail, 'To: ' . $toAddr . "\n");
fwrite($mail, $header);
fwrite($mail, $body);
$result = pclose($mail);
$addrinfo = static::parseAddresses($toAddr, true, $this->CharSet);
$this->doCallback(
($result === 0),
[[$addrinfo['address'], $addrinfo['name']]],
$this->cc,
$this->bcc,
$this->Subject,
$body,
$this->From,
[]
);
$addrinfo = static::parseAddresses($toAddr, null, $this->CharSet);
foreach ($addrinfo as $addr) {
$this->doCallback(
($result === 0),
[[$addr['address'], $addr['name']]],
$this->cc,
$this->bcc,
$this->Subject,
$body,
$this->From,
[]
);
}
$this->edebug("Result: " . ($result === 0 ? 'true' : 'false'));
if (0 !== $result) {
throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
}
} else {
$mail = @popen($sendmail, 'w');
if (!$mail) {
throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
fwrite($mail, $header);
fwrite($mail, $body);
@@ -1813,7 +1926,7 @@ class PHPMailer
);
$this->edebug("Result: " . ($result === 0 ? 'true' : 'false'));
if (0 !== $result) {
throw new Exception($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
throw new Exception(self::lang('execute') . $this->Sendmail, self::STOP_CRITICAL);
}
}
@@ -1952,17 +2065,19 @@ class PHPMailer
if ($this->SingleTo && count($toArr) > 1) {
foreach ($toArr as $toAddr) {
$result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
$addrinfo = static::parseAddresses($toAddr, true, $this->CharSet);
$this->doCallback(
$result,
[[$addrinfo['address'], $addrinfo['name']]],
$this->cc,
$this->bcc,
$this->Subject,
$body,
$this->From,
[]
);
$addrinfo = static::parseAddresses($toAddr, null, $this->CharSet);
foreach ($addrinfo as $addr) {
$this->doCallback(
$result,
[[$addr['address'], $addr['name']]],
$this->cc,
$this->bcc,
$this->Subject,
$body,
$this->From,
[]
);
}
}
} else {
$result = $this->mailPassthru($to, $this->Subject, $body, $header, $params);
@@ -1972,7 +2087,7 @@ class PHPMailer
ini_set('sendmail_from', $old_from);
}
if (!$result) {
throw new Exception($this->lang('instantiate'), self::STOP_CRITICAL);
throw new Exception(self::lang('instantiate'), self::STOP_CRITICAL);
}
return true;
@@ -2058,7 +2173,12 @@ class PHPMailer
$header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
$bad_rcpt = [];
if (!$this->smtpConnect($this->SMTPOptions)) {
throw new Exception($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
throw new Exception(self::lang('smtp_connect_failed'), self::STOP_CRITICAL);
}
//If we have recipient addresses that need Unicode support,
//but the server doesn't support it, stop here
if ($this->UseSMTPUTF8 && !$this->smtp->getServerExt('SMTPUTF8')) {
throw new Exception(self::lang('no_smtputf8'), self::STOP_CRITICAL);
}
//Sender already validated in preSend()
if ('' === $this->Sender) {
@@ -2070,7 +2190,7 @@ class PHPMailer
$this->smtp->xclient($this->SMTPXClient);
}
if (!$this->smtp->mail($smtp_from)) {
$this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
$this->setError(self::lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
throw new Exception($this->ErrorInfo, self::STOP_CRITICAL);
}
@@ -2092,7 +2212,7 @@ class PHPMailer
//Only send the DATA command if we have viable recipients
if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->data($header . $body)) {
throw new Exception($this->lang('data_not_accepted'), self::STOP_CRITICAL);
throw new Exception(self::lang('data_not_accepted'), self::STOP_CRITICAL);
}
$smtp_transaction_id = $this->smtp->getLastTransactionID();
@@ -2123,7 +2243,7 @@ class PHPMailer
foreach ($bad_rcpt as $bad) {
$errstr .= $bad['to'] . ': ' . $bad['error'];
}
throw new Exception($this->lang('recipients_failed') . $errstr, self::STOP_CONTINUE);
throw new Exception(self::lang('recipients_failed') . $errstr, self::STOP_CONTINUE);
}
return true;
@@ -2161,6 +2281,7 @@ class PHPMailer
$this->smtp->setDebugLevel($this->SMTPDebug);
$this->smtp->setDebugOutput($this->Debugoutput);
$this->smtp->setVerp($this->do_verp);
$this->smtp->setSMTPUTF8($this->UseSMTPUTF8);
if ($this->Host === null) {
$this->Host = 'localhost';
}
@@ -2176,7 +2297,7 @@ class PHPMailer
$hostinfo
)
) {
$this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hostentry));
$this->edebug(self::lang('invalid_hostentry') . ' ' . trim($hostentry));
//Not a valid host entry
continue;
}
@@ -2188,7 +2309,7 @@ class PHPMailer
//Check the host name is a valid name or IP address before trying to use it
if (!static::isValidHost($hostinfo[2])) {
$this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]);
$this->edebug(self::lang('invalid_host') . ' ' . $hostinfo[2]);
continue;
}
$prefix = '';
@@ -2208,7 +2329,7 @@ class PHPMailer
if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SMTPS === $secure) {
//Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled
if (!$sslext) {
throw new Exception($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL);
throw new Exception(self::lang('extension_missing') . 'openssl', self::STOP_CRITICAL);
}
}
$host = $hostinfo[2];
@@ -2260,7 +2381,7 @@ class PHPMailer
$this->oauth
)
) {
throw new Exception($this->lang('authenticate'));
throw new Exception(self::lang('authenticate'));
}
return true;
@@ -2310,7 +2431,7 @@ class PHPMailer
*
* @return bool Returns true if the requested language was loaded, false otherwise.
*/
public function setLanguage($langcode = 'en', $lang_path = '')
public static function setLanguage($langcode = 'en', $lang_path = '')
{
//Backwards compatibility for renamed language codes
$renamed_langcodes = [
@@ -2358,6 +2479,10 @@ class PHPMailer
'smtp_detail' => 'Detail: ',
'smtp_error' => 'SMTP server error: ',
'variable_set' => 'Cannot set or reset variable: ',
'no_smtputf8' => 'Server does not support SMTPUTF8 needed to send to Unicode addresses',
'imap_recommended' => 'Using simplified address parser is not recommended. ' .
'Install the PHP IMAP extension for full RFC822 parsing.',
'deprecated_argument' => 'Argument $useimap is deprecated',
];
if (empty($lang_path)) {
//Calculate an absolute path so it can work if CWD is not here
@@ -2424,7 +2549,7 @@ class PHPMailer
}
}
}
$this->language = $PHPMAILER_LANG;
self::$language = $PHPMAILER_LANG;
return $foundlang; //Returns false if language not found
}
@@ -2436,11 +2561,11 @@ class PHPMailer
*/
public function getTranslations()
{
if (empty($this->language)) {
$this->setLanguage(); // Set the default language.
if (empty(self::$language)) {
self::setLanguage(); // Set the default language.
}
return $this->language;
return self::$language;
}
/**
@@ -2863,16 +2988,14 @@ class PHPMailer
//Create unique IDs and preset boundaries
$this->setBoundaries();
if ($this->sign_key_file) {
$body .= $this->getMailMIME() . static::$LE;
}
$this->setWordWrap();
$bodyEncoding = $this->Encoding;
$bodyCharSet = $this->CharSet;
//Can we do a 7-bit downgrade?
if (static::ENCODING_8BIT === $bodyEncoding && !$this->has8bitChars($this->Body)) {
if ($this->UseSMTPUTF8) {
$bodyEncoding = static::ENCODING_8BIT;
} elseif (static::ENCODING_8BIT === $bodyEncoding && !$this->has8bitChars($this->Body)) {
$bodyEncoding = static::ENCODING_7BIT;
//All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
$bodyCharSet = static::CHARSET_ASCII;
@@ -2896,6 +3019,12 @@ class PHPMailer
if (static::ENCODING_BASE64 !== $altBodyEncoding && static::hasLineLongerThanMax($this->AltBody)) {
$altBodyEncoding = static::ENCODING_QUOTED_PRINTABLE;
}
if ($this->sign_key_file) {
$this->Encoding = $bodyEncoding;
$body .= $this->getMailMIME() . static::$LE;
}
//Use this as a preamble in all multipart message types
$mimepre = '';
switch ($this->message_type) {
@@ -3077,12 +3206,12 @@ class PHPMailer
if ($this->isError()) {
$body = '';
if ($this->exceptions) {
throw new Exception($this->lang('empty_message'), self::STOP_CRITICAL);
throw new Exception(self::lang('empty_message'), self::STOP_CRITICAL);
}
} elseif ($this->sign_key_file) {
try {
if (!defined('PKCS7_TEXT')) {
throw new Exception($this->lang('extension_missing') . 'openssl');
throw new Exception(self::lang('extension_missing') . 'openssl');
}
$file = tempnam(sys_get_temp_dir(), 'srcsign');
@@ -3120,7 +3249,7 @@ class PHPMailer
$body = $parts[1];
} else {
@unlink($signed);
throw new Exception($this->lang('signing') . openssl_error_string());
throw new Exception(self::lang('signing') . openssl_error_string());
}
} catch (Exception $exc) {
$body = '';
@@ -3265,7 +3394,7 @@ class PHPMailer
) {
try {
if (!static::fileIsAccessible($path)) {
throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
throw new Exception(self::lang('file_access') . $path, self::STOP_CONTINUE);
}
//If a MIME type is not specified, try to work it out from the file name
@@ -3278,7 +3407,7 @@ class PHPMailer
$name = $filename;
}
if (!$this->validateEncoding($encoding)) {
throw new Exception($this->lang('encoding') . $encoding);
throw new Exception(self::lang('encoding') . $encoding);
}
$this->attachment[] = [
@@ -3439,11 +3568,11 @@ class PHPMailer
{
try {
if (!static::fileIsAccessible($path)) {
throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
throw new Exception(self::lang('file_open') . $path, self::STOP_CONTINUE);
}
$file_buffer = file_get_contents($path);
if (false === $file_buffer) {
throw new Exception($this->lang('file_open') . $path, self::STOP_CONTINUE);
throw new Exception(self::lang('file_open') . $path, self::STOP_CONTINUE);
}
$file_buffer = $this->encodeString($file_buffer, $encoding);
@@ -3496,9 +3625,9 @@ class PHPMailer
$encoded = $this->encodeQP($str);
break;
default:
$this->setError($this->lang('encoding') . $encoding);
$this->setError(self::lang('encoding') . $encoding);
if ($this->exceptions) {
throw new Exception($this->lang('encoding') . $encoding);
throw new Exception(self::lang('encoding') . $encoding);
}
break;
}
@@ -3509,7 +3638,8 @@ class PHPMailer
/**
* Encode a header value (not including its label) optimally.
* Picks shortest of Q, B, or none. Result includes folding if needed.
* See RFC822 definitions for phrase, comment and text positions.
* See RFC822 definitions for phrase, comment and text positions,
* and RFC2047 for inline encodings.
*
* @param string $str The header value to encode
* @param string $position What context the string will be used in
@@ -3518,6 +3648,11 @@ class PHPMailer
*/
public function encodeHeader($str, $position = 'text')
{
$position = strtolower($position);
if ($this->UseSMTPUTF8 && !("comment" === $position)) {
return trim(static::normalizeBreaks($str));
}
$matchcount = 0;
switch (strtolower($position)) {
case 'phrase':
@@ -3598,6 +3733,42 @@ class PHPMailer
return trim(static::normalizeBreaks($encoded));
}
/**
* Decode an RFC2047-encoded header value
* Attempts multiple strategies so it works even when the mbstring extension is disabled.
*
* @param string $value The header value to decode
* @param string $charset The target charset to convert to, defaults to ISO-8859-1 for BC
*
* @return string The decoded header value
*/
public static function decodeHeader($value, $charset = self::CHARSET_ISO88591)
{
if (!is_string($value) || $value === '') {
return '';
}
// Detect the presence of any RFC2047 encoded-words
$hasEncodedWord = (bool) preg_match('/=\?.*\?=/s', $value);
if ($hasEncodedWord && defined('MB_CASE_UPPER')) {
$origCharset = mb_internal_encoding();
// Always decode to UTF-8 to provide a consistent, modern output encoding.
mb_internal_encoding($charset);
if (PHP_VERSION_ID < 80300) {
// Undo any RFC2047-encoded spaces-as-underscores.
$value = str_replace('_', '=20', $value);
} else {
// PHP 8.3+ already interprets underscores as spaces. Remove additional
// linear whitespace between adjacent encoded words to avoid double spacing.
$value = preg_replace('/(\?=)\s+(=\?)/', '$1$2', $value);
}
// Decode the header value
$value = mb_decode_mimeheader($value);
mb_internal_encoding($origCharset);
}
return $value;
}
/**
* Check if a string contains multi-byte characters.
*
@@ -3767,7 +3938,7 @@ class PHPMailer
}
if (!$this->validateEncoding($encoding)) {
throw new Exception($this->lang('encoding') . $encoding);
throw new Exception(self::lang('encoding') . $encoding);
}
//Append to $attachment array
@@ -3826,7 +3997,7 @@ class PHPMailer
) {
try {
if (!static::fileIsAccessible($path)) {
throw new Exception($this->lang('file_access') . $path, self::STOP_CONTINUE);
throw new Exception(self::lang('file_access') . $path, self::STOP_CONTINUE);
}
//If a MIME type is not specified, try to work it out from the file name
@@ -3835,7 +4006,7 @@ class PHPMailer
}
if (!$this->validateEncoding($encoding)) {
throw new Exception($this->lang('encoding') . $encoding);
throw new Exception(self::lang('encoding') . $encoding);
}
$filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
@@ -3901,7 +4072,7 @@ class PHPMailer
}
if (!$this->validateEncoding($encoding)) {
throw new Exception($this->lang('encoding') . $encoding);
throw new Exception(self::lang('encoding') . $encoding);
}
//Append to $attachment array
@@ -4158,7 +4329,7 @@ class PHPMailer
}
if (strpbrk($name . $value, "\r\n") !== false) {
if ($this->exceptions) {
throw new Exception($this->lang('invalid_header'));
throw new Exception(self::lang('invalid_header'));
}
return false;
@@ -4182,15 +4353,15 @@ class PHPMailer
if ('smtp' === $this->Mailer && null !== $this->smtp) {
$lasterror = $this->smtp->getError();
if (!empty($lasterror['error'])) {
$msg .= $this->lang('smtp_error') . $lasterror['error'];
$msg .= ' ' . self::lang('smtp_error') . $lasterror['error'];
if (!empty($lasterror['detail'])) {
$msg .= ' ' . $this->lang('smtp_detail') . $lasterror['detail'];
$msg .= ' ' . self::lang('smtp_detail') . $lasterror['detail'];
}
if (!empty($lasterror['smtp_code'])) {
$msg .= ' ' . $this->lang('smtp_code') . $lasterror['smtp_code'];
$msg .= ' ' . self::lang('smtp_code') . $lasterror['smtp_code'];
}
if (!empty($lasterror['smtp_code_ex'])) {
$msg .= ' ' . $this->lang('smtp_code_ex') . $lasterror['smtp_code_ex'];
$msg .= ' ' . self::lang('smtp_code_ex') . $lasterror['smtp_code_ex'];
}
}
}
@@ -4269,6 +4440,45 @@ class PHPMailer
return filter_var('https://' . $host, FILTER_VALIDATE_URL) !== false;
}
/**
* Check whether the supplied address uses Unicode in the local part.
*
* @return bool
*/
protected function addressHasUnicodeLocalPart($address)
{
return (bool) preg_match('/[\x80-\xFF].*@/', $address);
}
/**
* Check whether any of the supplied addresses use Unicode in the local part.
*
* @return bool
*/
protected function anyAddressHasUnicodeLocalPart($addresses)
{
foreach ($addresses as $address) {
if (is_array($address)) {
$address = $address[0];
}
if ($this->addressHasUnicodeLocalPart($address)) {
return true;
}
}
return false;
}
/**
* Check whether the message requires SMTPUTF8 based on what's known so far.
*
* @return bool
*/
public function needsSMTPUTF8()
{
return $this->UseSMTPUTF8;
}
/**
* Get an error message in the current language.
*
@@ -4276,21 +4486,21 @@ class PHPMailer
*
* @return string
*/
protected function lang($key)
protected static function lang($key)
{
if (count($this->language) < 1) {
$this->setLanguage(); //Set the default language
if (count(self::$language) < 1) {
self::setLanguage(); //Set the default language
}
if (array_key_exists($key, $this->language)) {
if (array_key_exists($key, self::$language)) {
if ('smtp_connect_failed' === $key) {
//Include a link to troubleshooting docs on SMTP connection failure.
//This is by far the biggest cause of support questions
//but it's usually not PHPMailer's fault.
return $this->language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
return self::$language[$key] . ' https://github.com/PHPMailer/PHPMailer/wiki/Troubleshooting';
}
return $this->language[$key];
return self::$language[$key];
}
//Return the key as a fallback
@@ -4305,7 +4515,7 @@ class PHPMailer
*/
private function getSmtpErrorMessage($base_key)
{
$message = $this->lang($base_key);
$message = self::lang($base_key);
$error = $this->smtp->getError();
if (!empty($error['error'])) {
$message .= ' ' . $error['error'];
@@ -4349,7 +4559,7 @@ class PHPMailer
//Ensure name is not empty, and that neither name nor value contain line breaks
if (empty($name) || strpbrk($name . $value, "\r\n") !== false) {
if ($this->exceptions) {
throw new Exception($this->lang('invalid_header'));
throw new Exception(self::lang('invalid_header'));
}
return false;
@@ -4742,7 +4952,7 @@ class PHPMailer
return true;
}
$this->setError($this->lang('variable_set') . $name);
$this->setError(self::lang('variable_set') . $name);
return false;
}
@@ -4880,7 +5090,7 @@ class PHPMailer
{
if (!defined('PKCS7_TEXT')) {
if ($this->exceptions) {
throw new Exception($this->lang('extension_missing') . 'openssl');
throw new Exception(self::lang('extension_missing') . 'openssl');
}
return '';
+469
View File
@@ -0,0 +1,469 @@
<?php
/**
* PHPMailer POP-Before-SMTP Authentication Class.
* PHP Version 5.5.
*
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
*
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
* @author Brent R. Matzelle (original founder)
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*/
namespace PHPMailer\PHPMailer;
/**
* PHPMailer POP-Before-SMTP Authentication Class.
* Specifically for PHPMailer to use for RFC1939 POP-before-SMTP authentication.
* 1) This class does not support APOP authentication.
* 2) Opening and closing lots of POP3 connections can be quite slow. If you need
* to send a batch of emails then just perform the authentication once at the start,
* and then loop through your mail sending script. Providing this process doesn't
* take longer than the verification period lasts on your POP3 server, you should be fine.
* 3) This is really ancient technology; you should only need to use it to talk to very old systems.
* 4) This POP3 class is deliberately lightweight and incomplete, implementing just
* enough to do authentication.
* If you want a more complete class there are other POP3 classes for PHP available.
*
* @author Richard Davey (original author) <rich@corephp.co.uk>
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
*/
class POP3
{
/**
* The POP3 PHPMailer Version number.
*
* @var string
*/
const VERSION = '7.0.0';
/**
* Default POP3 port number.
*
* @var int
*/
const DEFAULT_PORT = 110;
/**
* Default timeout in seconds.
*
* @var int
*/
const DEFAULT_TIMEOUT = 30;
/**
* POP3 class debug output mode.
* Debug output level.
* Options:
* @see POP3::DEBUG_OFF: No output
* @see POP3::DEBUG_SERVER: Server messages, connection/server errors
* @see POP3::DEBUG_CLIENT: Client and Server messages, connection/server errors
*
* @var int
*/
public $do_debug = self::DEBUG_OFF;
/**
* POP3 mail server hostname.
*
* @var string
*/
public $host;
/**
* POP3 port number.
*
* @var int
*/
public $port;
/**
* POP3 Timeout Value in seconds.
*
* @var int
*/
public $tval;
/**
* POP3 username.
*
* @var string
*/
public $username;
/**
* POP3 password.
*
* @var string
*/
public $password;
/**
* Resource handle for the POP3 connection socket.
*
* @var resource
*/
protected $pop_conn;
/**
* Are we connected?
*
* @var bool
*/
protected $connected = false;
/**
* Error container.
*
* @var array
*/
protected $errors = [];
/**
* Line break constant.
*/
const LE = "\r\n";
/**
* Debug level for no output.
*
* @var int
*/
const DEBUG_OFF = 0;
/**
* Debug level to show server -> client messages
* also shows clients connection errors or errors from server
*
* @var int
*/
const DEBUG_SERVER = 1;
/**
* Debug level to show client -> server and server -> client messages.
*
* @var int
*/
const DEBUG_CLIENT = 2;
/**
* Simple static wrapper for all-in-one POP before SMTP.
*
* @param string $host The hostname to connect to
* @param int|bool $port The port number to connect to
* @param int|bool $timeout The timeout value
* @param string $username
* @param string $password
* @param int $debug_level
*
* @return bool
*/
public static function popBeforeSmtp(
$host,
$port = false,
$timeout = false,
$username = '',
$password = '',
$debug_level = 0
) {
$pop = new self();
return $pop->authorise($host, $port, $timeout, $username, $password, $debug_level);
}
/**
* Authenticate with a POP3 server.
* A connect, login, disconnect sequence
* appropriate for POP-before SMTP authorisation.
*
* @param string $host The hostname to connect to
* @param int|bool $port The port number to connect to
* @param int|bool $timeout The timeout value
* @param string $username
* @param string $password
* @param int $debug_level
*
* @return bool
*/
public function authorise($host, $port = false, $timeout = false, $username = '', $password = '', $debug_level = 0)
{
$this->host = $host;
//If no port value provided, use default
if (false === $port) {
$this->port = static::DEFAULT_PORT;
} else {
$this->port = (int) $port;
}
//If no timeout value provided, use default
if (false === $timeout) {
$this->tval = static::DEFAULT_TIMEOUT;
} else {
$this->tval = (int) $timeout;
}
$this->do_debug = $debug_level;
$this->username = $username;
$this->password = $password;
//Reset the error log
$this->errors = [];
//Connect
$result = $this->connect($this->host, $this->port, $this->tval);
if ($result) {
$login_result = $this->login($this->username, $this->password);
if ($login_result) {
$this->disconnect();
return true;
}
}
//We need to disconnect regardless of whether the login succeeded
$this->disconnect();
return false;
}
/**
* Connect to a POP3 server.
*
* @param string $host
* @param int|bool $port
* @param int $tval
*
* @return bool
*/
public function connect($host, $port = false, $tval = 30)
{
//Are we already connected?
if ($this->connected) {
return true;
}
//On Windows this will raise a PHP Warning error if the hostname doesn't exist.
//Rather than suppress it with @fsockopen, capture it cleanly instead
set_error_handler(function () {
call_user_func_array([$this, 'catchWarning'], func_get_args());
});
if (false === $port) {
$port = static::DEFAULT_PORT;
}
//Connect to the POP3 server
$errno = 0;
$errstr = '';
$this->pop_conn = fsockopen(
$host, //POP3 Host
$port, //Port #
$errno, //Error Number
$errstr, //Error Message
$tval
); //Timeout (seconds)
//Restore the error handler
restore_error_handler();
//Did we connect?
if (false === $this->pop_conn) {
//It would appear not...
$this->setError(
"Failed to connect to server $host on port $port. errno: $errno; errstr: $errstr"
);
return false;
}
//Increase the stream time-out
stream_set_timeout($this->pop_conn, $tval, 0);
//Get the POP3 server response
$pop3_response = $this->getResponse();
//Check for the +OK
if ($this->checkResponse($pop3_response)) {
//The connection is established and the POP3 server is talking
$this->connected = true;
return true;
}
return false;
}
/**
* Log in to the POP3 server.
* Does not support APOP (RFC 2828, 4949).
*
* @param string $username
* @param string $password
*
* @return bool
*/
public function login($username = '', $password = '')
{
if (!$this->connected) {
$this->setError('Not connected to POP3 server');
return false;
}
if (empty($username)) {
$username = $this->username;
}
if (empty($password)) {
$password = $this->password;
}
//Send the Username
$this->sendString("USER $username" . static::LE);
$pop3_response = $this->getResponse();
if ($this->checkResponse($pop3_response)) {
//Send the Password
$this->sendString("PASS $password" . static::LE);
$pop3_response = $this->getResponse();
if ($this->checkResponse($pop3_response)) {
return true;
}
}
return false;
}
/**
* Disconnect from the POP3 server.
*/
public function disconnect()
{
// If could not connect at all, no need to disconnect
if ($this->pop_conn === false) {
return;
}
$this->sendString('QUIT' . static::LE);
// RFC 1939 shows POP3 server sending a +OK response to the QUIT command.
// Try to get it. Ignore any failures here.
try {
$this->getResponse();
} catch (Exception $e) {
//Do nothing
}
//The QUIT command may cause the daemon to exit, which will kill our connection
//So ignore errors here
try {
@fclose($this->pop_conn);
} catch (Exception $e) {
//Do nothing
}
// Clean up attributes.
$this->connected = false;
$this->pop_conn = false;
}
/**
* Get a response from the POP3 server.
*
* @param int $size The maximum number of bytes to retrieve
*
* @return string
*/
protected function getResponse($size = 128)
{
$response = fgets($this->pop_conn, $size);
if ($this->do_debug >= self::DEBUG_SERVER) {
echo 'Server -> Client: ', $response;
}
return $response;
}
/**
* Send raw data to the POP3 server.
*
* @param string $string
*
* @return int
*/
protected function sendString($string)
{
if ($this->pop_conn) {
if ($this->do_debug >= self::DEBUG_CLIENT) { //Show client messages when debug >= 2
echo 'Client -> Server: ', $string;
}
return fwrite($this->pop_conn, $string, strlen($string));
}
return 0;
}
/**
* Checks the POP3 server response.
* Looks for for +OK or -ERR.
*
* @param string $string
*
* @return bool
*/
protected function checkResponse($string)
{
if (strpos($string, '+OK') !== 0) {
$this->setError("Server reported an error: $string");
return false;
}
return true;
}
/**
* Add an error to the internal error store.
* Also display debug output if it's enabled.
*
* @param string $error
*/
protected function setError($error)
{
$this->errors[] = $error;
if ($this->do_debug >= self::DEBUG_SERVER) {
echo '<pre>';
foreach ($this->errors as $e) {
print_r($e);
}
echo '</pre>';
}
}
/**
* Get an array of error messages, if any.
*
* @return array
*/
public function getErrors()
{
return $this->errors;
}
/**
* POP3 connection error handler.
*
* @param int $errno
* @param string $errstr
* @param string $errfile
* @param int $errline
*/
protected function catchWarning($errno, $errstr, $errfile, $errline)
{
$this->setError(
'Connecting to the POP3 server raised a PHP warning:' .
"errno: $errno errstr: $errstr; errfile: $errfile; errline: $errline"
);
}
}
+87 -8
View File
@@ -35,7 +35,7 @@ class SMTP
*
* @var string
*/
const VERSION = '6.9.3';
const VERSION = '7.0.0';
/**
* SMTP line break constant.
@@ -159,6 +159,15 @@ class SMTP
*/
public $do_verp = false;
/**
* Whether to use SMTPUTF8.
*
* @see https://www.rfc-editor.org/rfc/rfc6531
*
* @var bool
*/
public $do_smtputf8 = false;
/**
* The timeout value for connection, in seconds.
* Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2.
@@ -196,6 +205,7 @@ class SMTP
'Haraka' => '/[\d]{3} Message Queued \((.*)\)/',
'ZoneMTA' => '/[\d]{3} Message queued as (.*)/',
'Mailjet' => '/[\d]{3} OK queued as (.*)/',
'Gsmtp' => '/[\d]{3} 2\.0\.0 OK (.*) - gsmtp/',
];
/**
@@ -624,10 +634,41 @@ class SMTP
return false;
}
$oauth = $OAuth->getOauth64();
//Start authentication
if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
return false;
/*
* An SMTP command line can have a maximum length of 512 bytes, including the command name,
* so the base64-encoded OAUTH token has a maximum length of:
* 512 - 13 (AUTH XOAUTH2) - 2 (CRLF) = 497 bytes
* If the token is longer than that, the command and the token must be sent separately as described in
* https://www.rfc-editor.org/rfc/rfc4954#section-4
*/
if ($oauth === '') {
//Sending an empty auth token is legitimate, but it must be encoded as '='
//to indicate it's not a 2-part command
if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 =', 235)) {
return false;
}
} elseif (strlen($oauth) <= 497) {
//Authenticate using a token in the initial-response part
if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
return false;
}
} else {
//The token is too long, so we need to send it in two parts.
//Send the auth command without a token and expect a 334
if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2', 334)) {
return false;
}
//Send the token
if (!$this->sendCommand('OAuth TOKEN', $oauth, [235, 334])) {
return false;
}
//If the server answers with 334, send an empty line and wait for a 235
if (
substr($this->last_reply, 0, 3) === '334'
&& $this->sendCommand('AUTH End', '', 235)
) {
return false;
}
}
break;
default:
@@ -913,7 +954,15 @@ class SMTP
* $from. Returns true if successful or false otherwise. If True
* the mail transaction is started and then one or more recipient
* commands may be called followed by a data command.
* Implements RFC 821: MAIL <SP> FROM:<reverse-path> <CRLF>.
* Implements RFC 821: MAIL <SP> FROM:<reverse-path> <CRLF> and
* two extensions, namely XVERP and SMTPUTF8.
*
* The server's EHLO response is not checked. If use of either
* extensions is enabled even though the server does not support
* that, mail submission will fail.
*
* XVERP is documented at https://www.postfix.org/VERP_README.html
* and SMTPUTF8 is specified in RFC 6531.
*
* @param string $from Source address of this message
*
@@ -922,10 +971,11 @@ class SMTP
public function mail($from)
{
$useVerp = ($this->do_verp ? ' XVERP' : '');
$useSmtputf8 = ($this->do_smtputf8 ? ' SMTPUTF8' : '');
return $this->sendCommand(
'MAIL FROM',
'MAIL FROM:<' . $from . '>' . $useVerp,
'MAIL FROM:<' . $from . '>' . $useSmtputf8 . $useVerp,
250
);
}
@@ -1291,7 +1341,16 @@ class SMTP
//stream_select returns false when the `select` system call is interrupted
//by an incoming signal, try the select again
if (stripos($message, 'interrupted system call') !== false) {
if (
stripos($message, 'interrupted system call') !== false ||
(
// on applications with a different locale than english, the message above is not found because
// it's translated. So we also check for the SOCKET_EINTR constant which is defined under
// Windows and UNIX-like platforms (if available on the platform).
defined('SOCKET_EINTR') &&
stripos($message, 'stream_select(): Unable to select [' . SOCKET_EINTR . ']') !== false
)
) {
$this->edebug(
'SMTP -> get_lines(): retrying stream_select',
self::DEBUG_LOWLEVEL
@@ -1364,6 +1423,26 @@ class SMTP
return $this->do_verp;
}
/**
* Enable or disable use of SMTPUTF8.
*
* @param bool $enabled
*/
public function setSMTPUTF8($enabled = false)
{
$this->do_smtputf8 = $enabled;
}
/**
* Get SMTPUTF8 use.
*
* @return bool
*/
public function getSMTPUTF8()
{
return $this->do_smtputf8;
}
/**
* Set error messages and codes.
*