|
|
@@ -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.
|
|
|
*
|
|
|
@@ -756,7 +768,7 @@ class PHPMailer
|
|
|
*
|
|
|
* @var string
|
|
|
*/
|
|
|
- const VERSION = '6.9.3';
|
|
|
+ const VERSION = '6.10.0';
|
|
|
|
|
|
/**
|
|
|
* Error severity: message only, continue processing.
|
|
|
@@ -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,6 +1175,15 @@ 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',
|
|
|
@@ -1362,6 +1386,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 +1457,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;
|
|
|
@@ -1565,9 +1608,26 @@ class PHPMailer
|
|
|
$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) {
|
|
|
@@ -2058,6 +2118,11 @@ class PHPMailer
|
|
|
if (!$this->smtpConnect($this->SMTPOptions)) {
|
|
|
throw new Exception($this->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($this->lang('no_smtputf8'), self::STOP_CRITICAL);
|
|
|
+ }
|
|
|
//Sender already validated in preSend()
|
|
|
if ('' === $this->Sender) {
|
|
|
$smtp_from = $this->From;
|
|
|
@@ -2159,6 +2224,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';
|
|
|
}
|
|
|
@@ -2356,6 +2422,7 @@ 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',
|
|
|
];
|
|
|
if (empty($lang_path)) {
|
|
|
//Calculate an absolute path so it can work if CWD is not here
|
|
|
@@ -2870,7 +2937,9 @@ class PHPMailer
|
|
|
$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;
|
|
|
@@ -3507,7 +3576,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
|
|
|
@@ -3516,6 +3586,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':
|
|
|
@@ -4180,7 +4255,7 @@ 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 .= ' ' . $this->lang('smtp_error') . $lasterror['error'];
|
|
|
if (!empty($lasterror['detail'])) {
|
|
|
$msg .= ' ' . $this->lang('smtp_detail') . $lasterror['detail'];
|
|
|
}
|
|
|
@@ -4267,6 +4342,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.
|
|
|
*
|