* @author Jim Jagielski (jimjag)
`, appropriate for browser output
* * `error_log` Output to error log as configured in php.ini
* By default PHPMailer will use `echo` if run from a `cli` or `cli-server` SAPI, `html` otherwise.
* Alternatively, you can provide a callable expecting two params: a message string and the debug level:
*
* ```php
* $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";};
* ```
*
* Alternatively, you can pass in an instance of a PSR-3 compatible logger, though only `debug`
* level output is used:
*
* ```php
* $mail->Debugoutput = new myPsr3Logger;
* ```
*
* @see SMTP::$Debugoutput
*
* @var string|callable|\Psr\Log\LoggerInterface
*/
public $Debugoutput = 'echo';
/**
* Whether to keep the SMTP connection open after each message.
* If this is set to true then the connection will remain open after a send,
* and closing the connection will require an explicit call to smtpClose().
* It's a good idea to use this if you are sending multiple messages as it reduces overhead.
* See the mailing list example for how to use it.
*
* @var bool
*/
public $SMTPKeepAlive = false;
/**
* Whether to split multiple to addresses into multiple messages
* or send them all in one message.
* Only supported in `mail` and `sendmail` transports, not in SMTP.
*
* @var bool
*
* @deprecated 6.0.0 PHPMailer isn't a mailing list manager!
*/
public $SingleTo = false;
/**
* Storage for addresses when SingleTo is enabled.
*
* @var array
*/
protected $SingleToArray = [];
/**
* Whether to generate VERP addresses on send.
* Only applicable when sending via SMTP.
*
* @see https://en.wikipedia.org/wiki/Variable_envelope_return_path
* @see https://www.postfix.org/VERP_README.html Postfix VERP info
*
* @var bool
*/
public $do_verp = false;
/**
* Whether to allow sending messages with an empty body.
*
* @var bool
*/
public $AllowEmpty = false;
/**
* DKIM selector.
*
* @var string
*/
public $DKIM_selector = '';
/**
* DKIM Identity.
* Usually the email address used as the source of the email.
*
* @var string
*/
public $DKIM_identity = '';
/**
* DKIM passphrase.
* Used if your key is encrypted.
*
* @var string
*/
public $DKIM_passphrase = '';
/**
* DKIM signing domain name.
*
* @example 'example.com'
*
* @var string
*/
public $DKIM_domain = '';
/**
* DKIM Copy header field values for diagnostic use.
*
* @var bool
*/
public $DKIM_copyHeaderFields = true;
/**
* DKIM Extra signing headers.
*
* @example ['List-Unsubscribe', 'List-Help']
*
* @var array
*/
public $DKIM_extraHeaders = [];
/**
* DKIM private key file path.
*
* @var string
*/
public $DKIM_private = '';
/**
* DKIM private key string.
*
* If set, takes precedence over `$DKIM_private`.
*
* @var string
*/
public $DKIM_private_string = '';
/**
* Callback Action function name.
*
* The function that handles the result of the send email action.
* It is called out by send() for each email sent.
*
* Value can be any php callable: https://www.php.net/is_callable
*
* Parameters:
* bool $result result of the send action
* array $to email addresses of the recipients
* array $cc cc email addresses
* array $bcc bcc email addresses
* string $subject the subject
* 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
*
* @var string
*/
public $action_function = '';
/**
* What to put in the X-Mailer header.
* Options: An empty string for PHPMailer default, whitespace/null for none, or a string to use.
*
* @var string|null
*/
public $XMailer = '';
/**
* Which validator to use by default when validating email addresses.
* 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.
*
* @see PHPMailer::validateAddress()
*
* @var string|callable
*/
public static $validator = 'php';
/**
* An instance of the SMTP sender class.
*
* @var SMTP
*/
protected $smtp;
/**
* The array of 'to' names and addresses.
*
* @var array
*/
protected $to = [];
/**
* The array of 'cc' names and addresses.
*
* @var array
*/
protected $cc = [];
/**
* The array of 'bcc' names and addresses.
*
* @var array
*/
protected $bcc = [];
/**
* The array of reply-to names and addresses.
*
* @var array
*/
protected $ReplyTo = [];
/**
* An array of all kinds of addresses.
* Includes all of $to, $cc, $bcc.
*
* @see PHPMailer::$to
* @see PHPMailer::$cc
* @see PHPMailer::$bcc
*
* @var array
*/
protected $all_recipients = [];
/**
* An array of names and addresses queued for validation.
* In send(), valid and non duplicate entries are moved to $all_recipients
* and one of $to, $cc, or $bcc.
* This array is used only for addresses with IDN.
*
* @see PHPMailer::$to
* @see PHPMailer::$cc
* @see PHPMailer::$bcc
* @see PHPMailer::$all_recipients
*
* @var array
*/
protected $RecipientsQueue = [];
/**
* An array of reply-to names and addresses queued for validation.
* In send(), valid and non duplicate entries are moved to $ReplyTo.
* This array is used only for addresses with IDN.
*
* @see PHPMailer::$ReplyTo
*
* @var array
*/
protected $ReplyToQueue = [];
/**
* The array of attachments.
*
* @var array
*/
protected $attachment = [];
/**
* The array of custom headers.
*
* @var array
*/
protected $CustomHeader = [];
/**
* The most recent Message-ID (including angular brackets).
*
* @var string
*/
protected $lastMessageID = '';
/**
* The message's MIME type.
*
* @var string
*/
protected $message_type = '';
/**
* The array of MIME boundary strings.
*
* @var array
*/
protected $boundary = [];
/**
* The array of available text strings for the current language.
*
* @var array
*/
protected $language = [];
/**
* The number of errors encountered.
*
* @var int
*/
protected $error_count = 0;
/**
* The S/MIME certificate file path.
*
* @var string
*/
protected $sign_cert_file = '';
/**
* The S/MIME key file path.
*
* @var string
*/
protected $sign_key_file = '';
/**
* The optional S/MIME extra certificates ("CA Chain") file path.
*
* @var string
*/
protected $sign_extracerts_file = '';
/**
* The S/MIME password for the key.
* Used only if the key is encrypted.
*
* @var string
*/
protected $sign_key_pass = '';
/**
* Whether to throw exceptions for errors.
*
* @var bool
*/
protected $exceptions = false;
/**
* Unique ID used for message ID and boundaries.
*
* @var string
*/
protected $uniqueid = '';
/**
* The PHPMailer Version number.
*
* @var string
*/
const VERSION = '6.9.2';
/**
* Error severity: message only, continue processing.
*
* @var int
*/
const STOP_MESSAGE = 0;
/**
* Error severity: message, likely ok to continue processing.
*
* @var int
*/
const STOP_CONTINUE = 1;
/**
* Error severity: message, plus full stop, critical error reached.
*
* @var int
*/
const STOP_CRITICAL = 2;
/**
* The SMTP standard CRLF line break.
* If you want to change line break format, change static::$LE, not this.
*/
const CRLF = "\r\n";
/**
* "Folding White Space" a white space string used for line folding.
*/
const FWS = ' ';
/**
* SMTP RFC standard line ending; Carriage Return, Line Feed.
*
* @var string
*/
protected static $LE = self::CRLF;
/**
* The maximum line length supported by mail().
*
* Background: mail() will sometimes corrupt messages
* with headers longer than 65 chars, see #818.
*
* @var int
*/
const MAIL_MAX_LINE_LENGTH = 63;
/**
* The maximum line length allowed by RFC 2822 section 2.1.1.
*
* @var int
*/
const MAX_LINE_LENGTH = 998;
/**
* The lower maximum line length allowed by RFC 2822 section 2.1.1.
* This length does NOT include the line break
* 76 means that lines will be 77 or 78 chars depending on whether
* the line break format is LF or CRLF; both are valid.
*
* @var int
*/
const STD_LINE_LENGTH = 76;
/**
* Constructor.
*
* @param bool $exceptions Should we throw external exceptions?
*/
public function __construct($exceptions = null)
{
if (null !== $exceptions) {
$this->exceptions = (bool) $exceptions;
}
//Pick an appropriate debug output format automatically
$this->Debugoutput = (strpos(PHP_SAPI, 'cli') !== false ? 'echo' : 'html');
}
/**
* Destructor.
*/
public function __destruct()
{
//Close any open SMTP connection nicely
$this->smtpClose();
}
/**
* Call mail() in a safe_mode-aware fashion.
* Also, unless sendmail_path points to sendmail (or something that
* claims to be sendmail), don't pass params (not a perfect fix,
* but it will do).
*
* @param string $to To
* @param string $subject Subject
* @param string $body Message Body
* @param string $header Additional Header(s)
* @param string|null $params Params
*
* @return bool
*/
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
$subject = $this->secureHeader($subject);
} else {
$subject = $this->encodeHeader($this->secureHeader($subject));
}
//Calling mail() with null params breaks
$this->edebug('Sending with mail()');
$this->edebug('Sendmail path: ' . ini_get('sendmail_path'));
$this->edebug("Envelope sender: {$this->Sender}");
$this->edebug("To: {$to}");
$this->edebug("Subject: {$subject}");
$this->edebug("Headers: {$header}");
if (!$this->UseSendmailOptions || null === $params) {
$result = @mail($to, $subject, $body, $header);
} else {
$this->edebug("Additional params: {$params}");
$result = @mail($to, $subject, $body, $header, $params);
}
$this->edebug('Result: ' . ($result ? 'true' : 'false'));
return $result;
}
/**
* Output debugging info via a user-defined method.
* Only generates output if debug output is enabled.
*
* @see PHPMailer::$Debugoutput
* @see PHPMailer::$SMTPDebug
*
* @param string $str
*/
protected function edebug($str)
{
if ($this->SMTPDebug <= 0) {
return;
}
//Is this a PSR-3 logger?
if ($this->Debugoutput instanceof \Psr\Log\LoggerInterface) {
$this->Debugoutput->debug(rtrim($str, "\r\n"));
return;
}
//Avoid clash with built-in function names
if (is_callable($this->Debugoutput) && !in_array($this->Debugoutput, ['error_log', 'html', 'echo'])) {
call_user_func($this->Debugoutput, $str, $this->SMTPDebug);
return;
}
switch ($this->Debugoutput) {
case 'error_log':
//Don't output, just log
/** @noinspection ForgottenDebugOutputInspection */
error_log($str);
break;
case 'html':
//Cleans up output a bit for a better looking, HTML-safe output
echo htmlentities(
preg_replace('/[\r\n]+/', '', $str),
ENT_QUOTES,
'UTF-8'
), "
\n";
break;
case 'echo':
default:
//Normalize line breaks
$str = preg_replace('/\r\n|\r/m', "\n", $str);
echo gmdate('Y-m-d H:i:s'),
"\t",
//Trim trailing space
trim(
//Indent for readability, except for trailing break
str_replace(
"\n",
"\n \t ",
trim($str)
)
),
"\n";
}
}
/**
* Sets message type to HTML or plain.
*
* @param bool $isHtml True for HTML mode
*/
public function isHTML($isHtml = true)
{
if ($isHtml) {
$this->ContentType = static::CONTENT_TYPE_TEXT_HTML;
} else {
$this->ContentType = static::CONTENT_TYPE_PLAINTEXT;
}
}
/**
* Send messages using SMTP.
*/
public function isSMTP()
{
$this->Mailer = 'smtp';
}
/**
* Send messages using PHP's mail() function.
*/
public function isMail()
{
$this->Mailer = 'mail';
}
/**
* Send messages using $Sendmail.
*/
public function isSendmail()
{
$ini_sendmail_path = ini_get('sendmail_path');
if (false === stripos($ini_sendmail_path, 'sendmail')) {
$this->Sendmail = '/usr/sbin/sendmail';
} else {
$this->Sendmail = $ini_sendmail_path;
}
$this->Mailer = 'sendmail';
}
/**
* Send messages using qmail.
*/
public function isQmail()
{
$ini_sendmail_path = ini_get('sendmail_path');
if (false === stripos($ini_sendmail_path, 'qmail')) {
$this->Sendmail = '/var/qmail/bin/qmail-inject';
} else {
$this->Sendmail = $ini_sendmail_path;
}
$this->Mailer = 'qmail';
}
/**
* Add a "To" address.
*
* @param string $address The email address to send to
* @param string $name
*
* @throws Exception
*
* @return bool true on success, false if address already used or invalid in some way
*/
public function addAddress($address, $name = '')
{
return $this->addOrEnqueueAnAddress('to', $address, $name);
}
/**
* Add a "CC" address.
*
* @param string $address The email address to send to
* @param string $name
*
* @throws Exception
*
* @return bool true on success, false if address already used or invalid in some way
*/
public function addCC($address, $name = '')
{
return $this->addOrEnqueueAnAddress('cc', $address, $name);
}
/**
* Add a "BCC" address.
*
* @param string $address The email address to send to
* @param string $name
*
* @throws Exception
*
* @return bool true on success, false if address already used or invalid in some way
*/
public function addBCC($address, $name = '')
{
return $this->addOrEnqueueAnAddress('bcc', $address, $name);
}
/**
* Add a "Reply-To" address.
*
* @param string $address The email address to reply to
* @param string $name
*
* @throws Exception
*
* @return bool true on success, false if address already used or invalid in some way
*/
public function addReplyTo($address, $name = '')
{
return $this->addOrEnqueueAnAddress('Reply-To', $address, $name);
}
/**
* Add an address to one of the recipient arrays or to the ReplyTo array. Because PHPMailer
* can't validate addresses with an IDN without knowing the PHPMailer::$CharSet (that can still
* be modified after calling this function), addition of such addresses is delayed until send().
* Addresses that have been added already return false, but do not throw exceptions.
*
* @param string $kind One of 'to', 'cc', 'bcc', or 'Reply-To'
* @param string $address The email address
* @param string $name An optional username associated with the address
*
* @throws Exception
*
* @return bool true on success, false if address already used or invalid in some way
*/
protected function addOrEnqueueAnAddress($kind, $address, $name)
{
$pos = false;
if ($address !== null) {
$address = trim($address);
$pos = strrpos($address, '@');
}
if (false === $pos) {
//At-sign is missing.
$error_message = sprintf(
'%s (%s): %s',
$this->lang('invalid_address'),
$kind,
$address
);
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
throw new Exception($error_message);
}
return false;
}
if ($name !== null && is_string($name)) {
$name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
} else {
$name = '';
}
$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;
return true;
}
} elseif (!array_key_exists($address, $this->ReplyToQueue)) {
$this->ReplyToQueue[$address] = $params;
return true;
}
return false;
}
//Immediately add standard addresses without IDN.
return call_user_func_array([$this, 'addAnAddress'], $params);
}
/**
* Set the boundaries to use for delimiting MIME parts.
* If you override this, ensure you set all 3 boundaries to unique values.
* The default boundaries include a "=_" sequence which cannot occur in quoted-printable bodies,
* as suggested by https://www.rfc-editor.org/rfc/rfc2045#section-6.7
*
* @return void
*/
public function setBoundaries()
{
$this->uniqueid = $this->generateId();
$this->boundary[1] = 'b1=_' . $this->uniqueid;
$this->boundary[2] = 'b2=_' . $this->uniqueid;
$this->boundary[3] = 'b3=_' . $this->uniqueid;
}
/**
* Add an address to one of the recipient arrays or to the ReplyTo array.
* Addresses that have been added already return false, but do not throw exceptions.
*
* @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
* @param string $address The email address to send, resp. to reply to
* @param string $name
*
* @throws Exception
*
* @return bool true on success, false if address already used or invalid in some way
*/
protected function addAnAddress($kind, $address, $name = '')
{
if (!in_array($kind, ['to', 'cc', 'bcc', 'Reply-To'])) {
$error_message = sprintf(
'%s: %s',
$this->lang('Invalid recipient kind'),
$kind
);
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
throw new Exception($error_message);
}
return false;
}
if (!static::validateAddress($address)) {
$error_message = sprintf(
'%s (%s): %s',
$this->lang('invalid_address'),
$kind,
$address
);
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
throw new Exception($error_message);
}
return false;
}
if ('Reply-To' !== $kind) {
if (!array_key_exists(strtolower($address), $this->all_recipients)) {
$this->{$kind}[] = [$address, $name];
$this->all_recipients[strtolower($address)] = true;
return true;
}
} elseif (!array_key_exists(strtolower($address), $this->ReplyTo)) {
$this->ReplyTo[strtolower($address)] = [$address, $name];
return true;
}
return false;
}
/**
* Parse and validate a string containing one or more RFC822-style comma-separated email addresses
* of the form "display name " into an array of name/address pairs.
* Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
* Note that quotes in the name part are removed.
*
* @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 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)
{
$addresses = [];
if ($useimap && 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.
imap_errors();
foreach ($list as $address) {
if (
'.SYNTAX-ERROR.' !== $address->host &&
static::validateAddress($address->mailbox . '@' . $address->host)
) {
//Decode the name part if it's present and 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)
) {
$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);
}
$addresses[] = [
'name' => (property_exists($address, 'personal') ? $address->personal : ''),
'address' => $address->mailbox . '@' . $address->host,
];
}
}
} 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,
];
}
}
}
}
return $addresses;
}
/**
* Set the From and FromName properties.
*
* @param string $address
* @param string $name
* @param bool $auto Whether to also set the Sender address, defaults to true
*
* @throws Exception
*
* @return bool
*/
public function setFrom($address, $name = '', $auto = true)
{
$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().
$pos = strrpos($address, '@');
if (
(false === $pos)
|| ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnSupported())
&& !static::validateAddress($address))
) {
$error_message = sprintf(
'%s (From): %s',
$this->lang('invalid_address'),
$address
);
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
throw new Exception($error_message);
}
return false;
}
$this->From = $address;
$this->FromName = $name;
if ($auto && empty($this->Sender)) {
$this->Sender = $address;
}
return true;
}
/**
* Return the Message-ID header of the last email.
* Technically this is the value from the last time the headers were created,
* but it's also the message ID of the last sent message except in
* pathological cases.
*
* @return string
*/
public function getLastMessageID()
{
return $this->lastMessageID;
}
/**
* Check that a string looks like an email address.
* Validation patterns supported:
* * `auto` Pick best pattern automatically;
* * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0;
* * `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.
* * `noregex` Don't use a regex: super fast, really dumb.
* Alternatively you may pass in a callable to inject your own validator, for example:
*
* ```php
* PHPMailer::validateAddress('user@example.com', function($address) {
* return (strpos($address, '@') !== false);
* });
* ```
*
* You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.
*
* @param string $address The email address to check
* @param string|callable $patternselect Which pattern to use
*
* @return bool
*/
public static function validateAddress($address, $patternselect = null)
{
if (null === $patternselect) {
$patternselect = static::$validator;
}
//Don't allow strings as callables, see SECURITY.md and CVE-2021-3603
if (is_callable($patternselect) && !is_string($patternselect)) {
return call_user_func($patternselect, $address);
}
//Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
if (strpos($address, "\n") !== false || strpos($address, "\r") !== false) {
return false;
}
switch ($patternselect) {
case 'pcre': //Kept for BC
case 'pcre8':
/*
* A more complex and more permissive version of the RFC5322 regex on which FILTER_VALIDATE_EMAIL
* is based.
* In addition to the addresses allowed by filter_var, also permits:
* * dotless domains: `a@b`
* * comments: `1234 @ local(blah) .machine .example`
* * quoted elements: `'"test blah"@example.org'`
* * numeric TLDs: `a@b.123`
* * unbracketed IPv4 literals: `a@192.168.0.1`
* * IPv6 literals: 'first.last@[IPv6:a1::]'
* Not all of these will necessarily work for sending!
*
* @copyright 2009-2010 Michael Rushton
* Feel free to use and redistribute this code. But please keep this copyright notice.
*/
return (bool) preg_match(
'/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' .
'((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' .
'(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' .
'([!#-\'*+\/-9=?^-~-]+|"(?>(?2)(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\x7F]))*' .
'(?2)")(?>(?1)\.(?1)(?4))*(?1)@(?!(?1)[a-z0-9-]{64,})(?1)(?>([a-z0-9](?>[a-z0-9-]*[a-z0-9])?)' .
'(?>(?1)\.(?!(?1)[a-z0-9-]{64,})(?1)(?5)){0,126}|\[(?:(?>IPv6:(?>([a-f0-9]{1,4})(?>:(?6)){7}' .
'|(?!(?:.*[a-f0-9][:\]]){8,})((?6)(?>:(?6)){0,6})?::(?7)?))|(?>(?>IPv6:(?>(?6)(?>:(?6)){5}:' .
'|(?!(?:.*[a-f0-9]:){6,})(?8)?::(?>((?6)(?>:(?6)){0,4}):)?))?(25[0-5]|2[0-4][0-9]|1[0-9]{2}' .
'|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD',
$address
);
case 'html5':
/*
* This is the pattern used in the HTML5 spec for validation of 'email' type form input elements.
*
* @see https://html.spec.whatwg.org/#e-mail-state-(type=email)
*/
return (bool) preg_match(
'/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' .
'[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD',
$address
);
case 'php':
default:
return filter_var($address, FILTER_VALIDATE_EMAIL) !== false;
}
}
/**
* Tells whether IDNs (Internationalized Domain Names) are supported or not. This requires the
* `intl` and `mbstring` PHP extensions.
*
* @return bool `true` if required functions for IDN support are present
*/
public static function idnSupported()
{
return function_exists('idn_to_ascii') && function_exists('mb_convert_encoding');
}
/**
* Converts IDN in given email address to its ASCII form, also known as punycode, if possible.
* Important: Address must be passed in same encoding as currently set in PHPMailer::$CharSet.
* This function silently returns unmodified address if:
* - No conversion is necessary (i.e. domain name is not an IDN, or is already in ASCII form)
* - Conversion to punycode is impossible (e.g. required PHP functions are not available)
* or fails for any reason (e.g. domain contains characters not allowed in an IDN).
*
* @see PHPMailer::$CharSet
*
* @param string $address The email address to convert
*
* @return string The encoded address in ASCII form
*/
public function punyencodeAddress($address)
{
//Verify we have required functions, CharSet, and at-sign.
$pos = strrpos($address, '@');
if (
!empty($this->CharSet) &&
false !== $pos &&
static::idnSupported()
) {
$domain = substr($address, ++$pos);
//Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $this->CharSet)) {
//Convert the domain from whatever charset it's in to UTF-8
$domain = mb_convert_encoding($domain, self::CHARSET_UTF8, $this->CharSet);
//Ignore IDE complaints about this line - method signature changed in PHP 5.4
$errorcode = 0;
if (defined('INTL_IDNA_VARIANT_UTS46')) {
//Use the current punycode standard (appeared in PHP 7.2)
$punycode = idn_to_ascii(
$domain,
\IDNA_DEFAULT | \IDNA_USE_STD3_RULES | \IDNA_CHECK_BIDI |
\IDNA_CHECK_CONTEXTJ | \IDNA_NONTRANSITIONAL_TO_ASCII,
\INTL_IDNA_VARIANT_UTS46
);
} 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) {
return substr($address, 0, $pos) . $punycode;
}
}
}
return $address;
}
/**
* Create a message and send it.
* Uses the sending method specified by $Mailer.
*
* @throws Exception
*
* @return bool false on error - See the ErrorInfo property for details of the error
*/
public function send()
{
try {
if (!$this->preSend()) {
return false;
}
return $this->postSend();
} catch (Exception $exc) {
$this->mailHeader = '';
$this->setError($exc->getMessage());
if ($this->exceptions) {
throw $exc;
}
return false;
}
}
/**
* Prepare a message for sending.
*
* @throws Exception
*
* @return bool
*/
public function preSend()
{
if (
'smtp' === $this->Mailer
|| ('mail' === $this->Mailer && (\PHP_VERSION_ID >= 80000 || stripos(PHP_OS, 'WIN') === 0))
) {
//SMTP mandates RFC-compliant line endings
//and it's also used with mail() on Windows
static::setLE(self::CRLF);
} else {
//Maintain backward compatibility with legacy Linux command line mailers
static::setLE(PHP_EOL);
}
//Check for buggy PHP versions that add a header with an incorrect line break
if (
'mail' === $this->Mailer
&& ((\PHP_VERSION_ID >= 70000 && \PHP_VERSION_ID < 70017)
|| (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70103))
&& ini_get('mail.add_x_header') === '1'
&& stripos(PHP_OS, 'WIN') === 0
) {
trigger_error($this->lang('buggy_php'), E_USER_WARNING);
}
try {
$this->error_count = 0; //Reset errors
$this->mailHeader = '';
//Dequeue recipient and Reply-To addresses with IDN
foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
$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);
}
//Validate From, Sender, and ConfirmReadingTo addresses
foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) {
if ($this->{$address_kind} === null) {
$this->{$address_kind} = '';
continue;
}
$this->{$address_kind} = trim($this->{$address_kind});
if (empty($this->{$address_kind})) {
continue;
}
$this->{$address_kind} = $this->punyencodeAddress($this->{$address_kind});
if (!static::validateAddress($this->{$address_kind})) {
$error_message = sprintf(
'%s (%s): %s',
$this->lang('invalid_address'),
$address_kind,
$this->{$address_kind}
);
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
throw new Exception($error_message);
}
return false;
}
}
//Set whether the message is multipart/alternative
if ($this->alternativeExists()) {
$this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE;
}
$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);
}
//Trim subject consistently
$this->Subject = trim($this->Subject);
//Create body before headers in case body makes changes to headers (e.g. altering transfer encoding)
$this->MIMEHeader = '';
$this->MIMEBody = $this->createBody();
//createBody may have added some headers, so retain them
$tempheaders = $this->MIMEHeader;
$this->MIMEHeader = $this->createHeader();
$this->MIMEHeader .= $tempheaders;
//To capture the complete message when using mail(), create
//an extra header list which createHeader() doesn't fold in
if ('mail' === $this->Mailer) {
if (count($this->to) > 0) {
$this->mailHeader .= $this->addrAppend('To', $this->to);
} else {
$this->mailHeader .= $this->headerLine('To', 'undisclosed-recipients:;');
}
$this->mailHeader .= $this->headerLine(
'Subject',
$this->encodeHeader($this->secureHeader($this->Subject))
);
}
//Sign with DKIM if enabled
if (
!empty($this->DKIM_domain)
&& !empty($this->DKIM_selector)
&& (!empty($this->DKIM_private_string)
|| (!empty($this->DKIM_private)
&& static::isPermittedPath($this->DKIM_private)
&& file_exists($this->DKIM_private)
)
)
) {
$header_dkim = $this->DKIM_Add(
$this->MIMEHeader . $this->mailHeader,
$this->encodeHeader($this->secureHeader($this->Subject)),
$this->MIMEBody
);
$this->MIMEHeader = static::stripTrailingWSP($this->MIMEHeader) . static::$LE .
static::normalizeBreaks($header_dkim) . static::$LE;
}
return true;
} catch (Exception $exc) {
$this->setError($exc->getMessage());
if ($this->exceptions) {
throw $exc;
}
return false;
}
}
/**
* Actually send a message via the selected mechanism.
*
* @throws Exception
*
* @return bool
*/
public function postSend()
{
try {
//Choose the mailer and send through it
switch ($this->Mailer) {
case 'sendmail':
case 'qmail':
return $this->sendmailSend($this->MIMEHeader, $this->MIMEBody);
case 'smtp':
return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
case 'mail':
return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
default:
$sendMethod = $this->Mailer . 'Send';
if (method_exists($this, $sendMethod)) {
return $this->{$sendMethod}($this->MIMEHeader, $this->MIMEBody);
}
return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
}
} catch (Exception $exc) {
$this->setError($exc->getMessage());
$this->edebug($exc->getMessage());
if ($this->Mailer === 'smtp' && $this->SMTPKeepAlive == true && $this->smtp->connected()) {
$this->smtp->reset();
}
if ($this->exceptions) {
throw $exc;
}
}
return false;
}
/**
* Send mail using the $Sendmail program.
*
* @see PHPMailer::$Sendmail
*
* @param string $header The message headers
* @param string $body The message body
*
* @throws Exception
*
* @return bool
*/
protected function sendmailSend($header, $body)
{
if ($this->Mailer === 'qmail') {
$this->edebug('Sending with qmail');
} else {
$this->edebug('Sending with sendmail');
}
$header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
//This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
//A space after `-f` is optional, but there is a long history of its presence
//causing problems, so we don't use one
//Exim docs: https://www.exim.org/exim-html-current/doc/html/spec_html/ch-the_exim_command_line.html
//Sendmail docs: https://www.sendmail.org/~ca/email/man/sendmail.html
//Example problem: https://www.drupal.org/node/1057954
//PHP 5.6 workaround
$sendmail_from_value = ini_get('sendmail_from');
if (empty($this->Sender) && !empty($sendmail_from_value)) {
//PHP config has a sender address we can use
$this->Sender = ini_get('sendmail_from');
}
//CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be escaped.
if (!empty($this->Sender) && static::validateAddress($this->Sender) && self::isShellSafe($this->Sender)) {
if ($this->Mailer === 'qmail') {
$sendmailFmt = '%s -f%s';
} else {
$sendmailFmt = '%s -oi -f%s -t';
}
} else {
//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
//