"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "src/PHPMailer.php" between
PHPMailer-6.2.0.tar.gz and PHPMailer-6.3.0.tar.gz

About: PHPMailer is a PHP email transport class that features multiple file attachments, CCs, BCCs, REPLY-TOs, HTML messages, redundant SMTP servers, and word wrap, among others.

PHPMailer.php  (PHPMailer-6.2.0):PHPMailer.php  (PHPMailer-6.3.0)
skipping to change at line 751 skipping to change at line 751
* *
* @var string * @var string
*/ */
protected $uniqueid = ''; protected $uniqueid = '';
/** /**
* The PHPMailer Version number. * The PHPMailer Version number.
* *
* @var string * @var string
*/ */
const VERSION = '6.2.0'; const VERSION = '6.3.0';
/** /**
* Error severity: message only, continue processing. * Error severity: message only, continue processing.
* *
* @var int * @var int
*/ */
const STOP_MESSAGE = 0; const STOP_MESSAGE = 0;
/** /**
* Error severity: message, likely ok to continue processing. * Error severity: message, likely ok to continue processing.
skipping to change at line 865 skipping to change at line 865
*/ */
private function mailPassthru($to, $subject, $body, $header, $params) private function mailPassthru($to, $subject, $body, $header, $params)
{ {
//Check overloading of mail function to avoid double-encoding //Check overloading of mail function to avoid double-encoding
if (ini_get('mbstring.func_overload') & 1) { if (ini_get('mbstring.func_overload') & 1) {
$subject = $this->secureHeader($subject); $subject = $this->secureHeader($subject);
} else { } else {
$subject = $this->encodeHeader($this->secureHeader($subject)); $subject = $this->encodeHeader($this->secureHeader($subject));
} }
//Calling mail() with null params breaks //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) { if (!$this->UseSendmailOptions || null === $params) {
$result = @mail($to, $subject, $body, $header); $result = @mail($to, $subject, $body, $header);
} else { } else {
$this->edebug("Additional params: {$params}");
$result = @mail($to, $subject, $body, $header, $params); $result = @mail($to, $subject, $body, $header, $params);
} }
$this->edebug('Result: ' . ($result ? 'true' : 'false'));
return $result; return $result;
} }
/** /**
* Output debugging info via user-defined method. * Output debugging info via a user-defined method.
* Only generates output if SMTP debug output is enabled (@see SMTP::$do_deb * Only generates output if debug output is enabled.
ug).
* *
* @see PHPMailer::$Debugoutput * @see PHPMailer::$Debugoutput
* @see PHPMailer::$SMTPDebug * @see PHPMailer::$SMTPDebug
* *
* @param string $str * @param string $str
*/ */
protected function edebug($str) protected function edebug($str)
{ {
if ($this->SMTPDebug <= 0) { if ($this->SMTPDebug <= 0) {
return; return;
skipping to change at line 1073 skipping to change at line 1080
* @throws Exception * @throws Exception
* *
* @return bool true on success, false if address already used or invalid in some way * @return bool true on success, false if address already used or invalid in some way
*/ */
protected function addOrEnqueueAnAddress($kind, $address, $name) protected function addOrEnqueueAnAddress($kind, $address, $name)
{ {
$address = trim($address); $address = trim($address);
$name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and t rim $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and t rim
$pos = strrpos($address, '@'); $pos = strrpos($address, '@');
if (false === $pos) { if (false === $pos) {
// At-sign is missing. //At-sign is missing.
$error_message = sprintf( $error_message = sprintf(
'%s (%s): %s', '%s (%s): %s',
$this->lang('invalid_address'), $this->lang('invalid_address'),
$kind, $kind,
$address $address
); );
$this->setError($error_message); $this->setError($error_message);
$this->edebug($error_message); $this->edebug($error_message);
if ($this->exceptions) { if ($this->exceptions) {
throw new Exception($error_message); throw new Exception($error_message);
} }
return false; return false;
} }
$params = [$kind, $address, $name]; $params = [$kind, $address, $name];
// Enqueue addresses with IDN until we know the PHPMailer::$CharSet. //Enqueue addresses with IDN until we know the PHPMailer::$CharSet.
if (static::idnSupported() && $this->has8bitChars(substr($address, ++$po s))) { if (static::idnSupported() && $this->has8bitChars(substr($address, ++$po s))) {
if ('Reply-To' !== $kind) { if ('Reply-To' !== $kind) {
if (!array_key_exists($address, $this->RecipientsQueue)) { if (!array_key_exists($address, $this->RecipientsQueue)) {
$this->RecipientsQueue[$address] = $params; $this->RecipientsQueue[$address] = $params;
return true; return true;
} }
} elseif (!array_key_exists($address, $this->ReplyToQueue)) { } elseif (!array_key_exists($address, $this->ReplyToQueue)) {
$this->ReplyToQueue[$address] = $params; $this->ReplyToQueue[$address] = $params;
return true; return true;
} }
return false; return false;
} }
// Immediately add standard addresses without IDN. //Immediately add standard addresses without IDN.
return call_user_func_array([$this, 'addAnAddress'], $params); return call_user_func_array([$this, 'addAnAddress'], $params);
} }
/** /**
* Add an address to one of the recipient arrays or to the ReplyTo array. * 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 exc eptions. * Addresses that have been added already return false, but do not throw exc eptions.
* *
* @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo' * @param string $kind One of 'to', 'cc', 'bcc', or 'ReplyTo'
* @param string $address The email address to send, resp. to reply to * @param string $address The email address to send, resp. to reply to
* @param string $name * @param string $name
skipping to change at line 1194 skipping to change at line 1201
$addresses = []; $addresses = [];
if ($useimap && function_exists('imap_rfc822_parse_adrlist')) { if ($useimap && function_exists('imap_rfc822_parse_adrlist')) {
//Use this built-in parser if it's available //Use this built-in parser if it's available
$list = imap_rfc822_parse_adrlist($addrstr, ''); $list = imap_rfc822_parse_adrlist($addrstr, '');
foreach ($list as $address) { foreach ($list as $address) {
if ( if (
('.SYNTAX-ERROR.' !== $address->host) && static::validateAdd ress( ('.SYNTAX-ERROR.' !== $address->host) && static::validateAdd ress(
$address->mailbox . '@' . $address->host $address->mailbox . '@' . $address->host
) )
) { ) {
//Decode the name part if it's present and encoded
if (property_exists($address, 'personal') && preg_match('/^=
\?.*\?=$/', $address->personal)) {
$address->personal = mb_decode_mimeheader($address->pers
onal);
}
$addresses[] = [ $addresses[] = [
'name' => (property_exists($address, 'personal') ? $addr ess->personal : ''), 'name' => (property_exists($address, 'personal') ? $addr ess->personal : ''),
'address' => $address->mailbox . '@' . $address->host, 'address' => $address->mailbox . '@' . $address->host,
]; ];
} }
} }
} else { } else {
//Use this simpler parser //Use this simpler parser
$list = explode(',', $addrstr); $list = explode(',', $addrstr);
foreach ($list as $address) { foreach ($list as $address) {
skipping to change at line 1217 skipping to change at line 1229
//No separate name, just use the whole thing //No separate name, just use the whole thing
if (static::validateAddress($address)) { if (static::validateAddress($address)) {
$addresses[] = [ $addresses[] = [
'name' => '', 'name' => '',
'address' => $address, 'address' => $address,
]; ];
} }
} else { } else {
list($name, $email) = explode('<', $address); list($name, $email) = explode('<', $address);
$email = trim(str_replace('>', '', $email)); $email = trim(str_replace('>', '', $email));
$name = trim($name);
if (static::validateAddress($email)) { if (static::validateAddress($email)) {
//If this name is encoded, decode it
if (preg_match('/^=\?.*\?=$/', $name)) {
$name = mb_decode_mimeheader($name);
}
$addresses[] = [ $addresses[] = [
'name' => trim(str_replace(['"', "'"], '', $name)), //Remove any surrounding quotes and spaces from the
name
'name' => trim($name, '\'" '),
'address' => $email, 'address' => $email,
]; ];
} }
} }
} }
} }
return $addresses; return $addresses;
} }
skipping to change at line 1245 skipping to change at line 1263
* @param bool $auto Whether to also set the Sender address, defaults t o true * @param bool $auto Whether to also set the Sender address, defaults t o true
* *
* @throws Exception * @throws Exception
* *
* @return bool * @return bool
*/ */
public function setFrom($address, $name = '', $auto = true) public function setFrom($address, $name = '', $auto = true)
{ {
$address = trim($address); $address = trim($address);
$name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and t rim $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and t rim
// Don't validate now addresses with IDN. Will be done in send(). //Don't validate now addresses with IDN. Will be done in send().
$pos = strrpos($address, '@'); $pos = strrpos($address, '@');
if ( if (
(false === $pos) (false === $pos)
|| ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnS upported()) || ((!$this->has8bitChars(substr($address, ++$pos)) || !static::idnS upported())
&& !static::validateAddress($address)) && !static::validateAddress($address))
) { ) {
$error_message = sprintf( $error_message = sprintf(
'%s (From): %s', '%s (From): %s',
$this->lang('invalid_address'), $this->lang('invalid_address'),
$address $address
skipping to change at line 1398 skipping to change at line 1416
* or fails for any reason (e.g. domain contains characters not allowed in an IDN). * or fails for any reason (e.g. domain contains characters not allowed in an IDN).
* *
* @see PHPMailer::$CharSet * @see PHPMailer::$CharSet
* *
* @param string $address The email address to convert * @param string $address The email address to convert
* *
* @return string The encoded address in ASCII form * @return string The encoded address in ASCII form
*/ */
public function punyencodeAddress($address) public function punyencodeAddress($address)
{ {
// Verify we have required functions, CharSet, and at-sign. //Verify we have required functions, CharSet, and at-sign.
$pos = strrpos($address, '@'); $pos = strrpos($address, '@');
if ( if (
!empty($this->CharSet) && !empty($this->CharSet) &&
false !== $pos && false !== $pos &&
static::idnSupported() static::idnSupported()
) { ) {
$domain = substr($address, ++$pos); $domain = substr($address, ++$pos);
// Verify CharSet string is a valid one, and domain properly encoded in this CharSet. //Verify CharSet string is a valid one, and domain properly encoded in this CharSet.
if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $thi s->CharSet)) { if ($this->has8bitChars($domain) && @mb_check_encoding($domain, $thi s->CharSet)) {
$domain = mb_convert_encoding($domain, 'UTF-8', $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 chang ed in PHP 5.4 //Ignore IDE complaints about this line - method signature chang ed in PHP 5.4
$errorcode = 0; $errorcode = 0;
if (defined('INTL_IDNA_VARIANT_UTS46')) { if (defined('INTL_IDNA_VARIANT_UTS46')) {
$punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARI //Use the current punycode standard (appeared in PHP 7.2)
ANT_UTS46); $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VAR
IANT_UTS46);
} elseif (defined('INTL_IDNA_VARIANT_2003')) { } elseif (defined('INTL_IDNA_VARIANT_2003')) {
$punycode = idn_to_ascii($domain, $errorcode, INTL_IDNA_VARI //Fall back to this old, deprecated/removed encoding
ANT_2003); $punycode = idn_to_ascii($domain, $errorcode, \INTL_IDNA_VAR
IANT_2003);
} else { } else {
//Fall back to a default we don't know about
$punycode = idn_to_ascii($domain, $errorcode); $punycode = idn_to_ascii($domain, $errorcode);
} }
if (false !== $punycode) { if (false !== $punycode) {
return substr($address, 0, $pos) . $punycode; return substr($address, 0, $pos) . $punycode;
} }
} }
} }
return $address; return $address;
} }
skipping to change at line 1465 skipping to change at line 1487
* Prepare a message for sending. * Prepare a message for sending.
* *
* @throws Exception * @throws Exception
* *
* @return bool * @return bool
*/ */
public function preSend() public function preSend()
{ {
if ( if (
'smtp' === $this->Mailer 'smtp' === $this->Mailer
|| ('mail' === $this->Mailer && (PHP_VERSION_ID >= 80000 || stripos( PHP_OS, 'WIN') === 0)) || ('mail' === $this->Mailer && (\PHP_VERSION_ID >= 80000 || stripos (PHP_OS, 'WIN') === 0))
) { ) {
//SMTP mandates RFC-compliant line endings //SMTP mandates RFC-compliant line endings
//and it's also used with mail() on Windows //and it's also used with mail() on Windows
static::setLE(self::CRLF); static::setLE(self::CRLF);
} else { } else {
//Maintain backward compatibility with legacy Linux command line mai lers //Maintain backward compatibility with legacy Linux command line mai lers
static::setLE(PHP_EOL); static::setLE(PHP_EOL);
} }
//Check for buggy PHP versions that add a header with an incorrect line break //Check for buggy PHP versions that add a header with an incorrect line break
if ( if (
'mail' === $this->Mailer 'mail' === $this->Mailer
&& ((PHP_VERSION_ID >= 70000 && PHP_VERSION_ID < 70017) && ((\PHP_VERSION_ID >= 70000 && \PHP_VERSION_ID < 70017)
|| (PHP_VERSION_ID >= 70100 && PHP_VERSION_ID < 70103)) || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70103))
&& ini_get('mail.add_x_header') === '1' && ini_get('mail.add_x_header') === '1'
&& stripos(PHP_OS, 'WIN') === 0 && stripos(PHP_OS, 'WIN') === 0
) { ) {
trigger_error( trigger_error(
'Your version of PHP is affected by a bug that may result in cor rupted messages.' . 'Your version of PHP is affected by a bug that may result in cor rupted messages.' .
' To fix it, switch to sending using SMTP, disable the mail.add_ x_header option in' . ' To fix it, switch to sending using SMTP, disable the mail.add_ x_header option in' .
' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.', ' your php.ini, switch to MacOS or Linux, or upgrade your PHP to version 7.0.17+ or 7.1.3+.',
E_USER_WARNING E_USER_WARNING
); );
} }
try { try {
$this->error_count = 0; // Reset errors $this->error_count = 0; //Reset errors
$this->mailHeader = ''; $this->mailHeader = '';
// Dequeue recipient and Reply-To addresses with IDN //Dequeue recipient and Reply-To addresses with IDN
foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) { foreach (array_merge($this->RecipientsQueue, $this->ReplyToQueue) as $params) {
$params[1] = $this->punyencodeAddress($params[1]); $params[1] = $this->punyencodeAddress($params[1]);
call_user_func_array([$this, 'addAnAddress'], $params); call_user_func_array([$this, 'addAnAddress'], $params);
} }
if (count($this->to) + count($this->cc) + count($this->bcc) < 1) { if (count($this->to) + count($this->cc) + count($this->bcc) < 1) {
throw new Exception($this->lang('provide_address'), self::STOP_C RITICAL); throw new Exception($this->lang('provide_address'), self::STOP_C RITICAL);
} }
// Validate From, Sender, and ConfirmReadingTo addresses //Validate From, Sender, and ConfirmReadingTo addresses
foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) { foreach (['From', 'Sender', 'ConfirmReadingTo'] as $address_kind) {
$this->$address_kind = trim($this->$address_kind); $this->$address_kind = trim($this->$address_kind);
if (empty($this->$address_kind)) { if (empty($this->$address_kind)) {
continue; continue;
} }
$this->$address_kind = $this->punyencodeAddress($this->$address_ kind); $this->$address_kind = $this->punyencodeAddress($this->$address_ kind);
if (!static::validateAddress($this->$address_kind)) { if (!static::validateAddress($this->$address_kind)) {
$error_message = sprintf( $error_message = sprintf(
'%s (%s): %s', '%s (%s): %s',
$this->lang('invalid_address'), $this->lang('invalid_address'),
skipping to change at line 1527 skipping to change at line 1549
$this->setError($error_message); $this->setError($error_message);
$this->edebug($error_message); $this->edebug($error_message);
if ($this->exceptions) { if ($this->exceptions) {
throw new Exception($error_message); throw new Exception($error_message);
} }
return false; return false;
} }
} }
// Set whether the message is multipart/alternative //Set whether the message is multipart/alternative
if ($this->alternativeExists()) { if ($this->alternativeExists()) {
$this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE; $this->ContentType = static::CONTENT_TYPE_MULTIPART_ALTERNATIVE;
} }
$this->setMessageType(); $this->setMessageType();
// Refuse to send an empty message unless we are specifically allowi ng it //Refuse to send an empty message unless we are specifically allowin g it
if (!$this->AllowEmpty && empty($this->Body)) { if (!$this->AllowEmpty && empty($this->Body)) {
throw new Exception($this->lang('empty_message'), self::STOP_CRI TICAL); throw new Exception($this->lang('empty_message'), self::STOP_CRI TICAL);
} }
//Trim subject consistently //Trim subject consistently
$this->Subject = trim($this->Subject); $this->Subject = trim($this->Subject);
// Create body before headers in case body makes changes to headers (e.g. altering transfer encoding) //Create body before headers in case body makes changes to headers ( e.g. altering transfer encoding)
$this->MIMEHeader = ''; $this->MIMEHeader = '';
$this->MIMEBody = $this->createBody(); $this->MIMEBody = $this->createBody();
// createBody may have added some headers, so retain them //createBody may have added some headers, so retain them
$tempheaders = $this->MIMEHeader; $tempheaders = $this->MIMEHeader;
$this->MIMEHeader = $this->createHeader(); $this->MIMEHeader = $this->createHeader();
$this->MIMEHeader .= $tempheaders; $this->MIMEHeader .= $tempheaders;
// To capture the complete message when using mail(), create //To capture the complete message when using mail(), create
// an extra header list which createHeader() doesn't fold in //an extra header list which createHeader() doesn't fold in
if ('mail' === $this->Mailer) { if ('mail' === $this->Mailer) {
if (count($this->to) > 0) { if (count($this->to) > 0) {
$this->mailHeader .= $this->addrAppend('To', $this->to); $this->mailHeader .= $this->addrAppend('To', $this->to);
} else { } else {
$this->mailHeader .= $this->headerLine('To', 'undisclosed-re cipients:;'); $this->mailHeader .= $this->headerLine('To', 'undisclosed-re cipients:;');
} }
$this->mailHeader .= $this->headerLine( $this->mailHeader .= $this->headerLine(
'Subject', 'Subject',
$this->encodeHeader($this->secureHeader($this->Subject)) $this->encodeHeader($this->secureHeader($this->Subject))
); );
} }
// Sign with DKIM if enabled //Sign with DKIM if enabled
if ( if (
!empty($this->DKIM_domain) !empty($this->DKIM_domain)
&& !empty($this->DKIM_selector) && !empty($this->DKIM_selector)
&& (!empty($this->DKIM_private_string) && (!empty($this->DKIM_private_string)
|| (!empty($this->DKIM_private) || (!empty($this->DKIM_private)
&& static::isPermittedPath($this->DKIM_private) && static::isPermittedPath($this->DKIM_private)
&& file_exists($this->DKIM_private) && file_exists($this->DKIM_private)
) )
) )
) { ) {
skipping to change at line 1603 skipping to change at line 1625
/** /**
* Actually send a message via the selected mechanism. * Actually send a message via the selected mechanism.
* *
* @throws Exception * @throws Exception
* *
* @return bool * @return bool
*/ */
public function postSend() public function postSend()
{ {
try { try {
// Choose the mailer and send through it //Choose the mailer and send through it
switch ($this->Mailer) { switch ($this->Mailer) {
case 'sendmail': case 'sendmail':
case 'qmail': case 'qmail':
return $this->sendmailSend($this->MIMEHeader, $this->MIMEBod y); return $this->sendmailSend($this->MIMEHeader, $this->MIMEBod y);
case 'smtp': case 'smtp':
return $this->smtpSend($this->MIMEHeader, $this->MIMEBody); return $this->smtpSend($this->MIMEHeader, $this->MIMEBody);
case 'mail': case 'mail':
return $this->mailSend($this->MIMEHeader, $this->MIMEBody); return $this->mailSend($this->MIMEHeader, $this->MIMEBody);
default: default:
$sendMethod = $this->Mailer . 'Send'; $sendMethod = $this->Mailer . 'Send';
skipping to change at line 1648 skipping to change at line 1670
* *
* @param string $header The message headers * @param string $header The message headers
* @param string $body The message body * @param string $body The message body
* *
* @throws Exception * @throws Exception
* *
* @return bool * @return bool
*/ */
protected function sendmailSend($header, $body) 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; $header = static::stripTrailingWSP($header) . static::$LE . static::$LE;
//This sets the SMTP envelope sender which gets turned into a return-pat
// CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be e h header by the receiver
scaped. //A space after `-f` is optional, but there is a long history of its pre
if (!empty($this->Sender) && self::isShellSafe($this->Sender)) { sence
if ('qmail' === $this->Mailer) { //causing problems, so we don't use one
//Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch
-the_exim_command_line.html
//Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html
//Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html
//Example problem: https://www.drupal.org/node/1057954
//CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be es
caped.
if ('' === $this->Sender) {
$this->Sender = $this->From;
}
if (empty($this->Sender) && !empty(ini_get('sendmail_from'))) {
//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 es
caped.
//But sendmail requires this param, so fail without it
if (!empty($this->Sender) && static::validateAddress($this->Sender) && s
elf::isShellSafe($this->Sender)) {
if ($this->Mailer === 'qmail') {
$sendmailFmt = '%s -f%s'; $sendmailFmt = '%s -f%s';
} else { } else {
$sendmailFmt = '%s -oi -f%s -t'; $sendmailFmt = '%s -oi -f%s -t';
} }
} elseif ('qmail' === $this->Mailer) {
$sendmailFmt = '%s';
} else { } else {
$sendmailFmt = '%s -oi -t'; $this->edebug('Sender address unusable or missing: ' . $this->Sender
);
return false;
} }
$sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this ->Sender); $sendmail = sprintf($sendmailFmt, escapeshellcmd($this->Sendmail), $this ->Sender);
$this->edebug('Sendmail path: ' . $this->Sendmail);
$this->edebug('Sendmail command: ' . $sendmail);
$this->edebug('Envelope sender: ' . $this->Sender);
$this->edebug("Headers: {$header}");
if ($this->SingleTo) { if ($this->SingleTo) {
foreach ($this->SingleToArray as $toAddr) { foreach ($this->SingleToArray as $toAddr) {
$mail = @popen($sendmail, 'w'); $mail = @popen($sendmail, 'w');
if (!$mail) { if (!$mail) {
throw new Exception($this->lang('execute') . $this->Sendmail , self::STOP_CRITICAL); throw new Exception($this->lang('execute') . $this->Sendmail , self::STOP_CRITICAL);
} }
$this->edebug("To: {$toAddr}");
fwrite($mail, 'To: ' . $toAddr . "\n"); fwrite($mail, 'To: ' . $toAddr . "\n");
fwrite($mail, $header); fwrite($mail, $header);
fwrite($mail, $body); fwrite($mail, $body);
$result = pclose($mail); $result = pclose($mail);
$this->doCallback( $this->doCallback(
($result === 0), ($result === 0),
[$toAddr], [$toAddr],
$this->cc, $this->cc,
$this->bcc, $this->bcc,
$this->Subject, $this->Subject,
$body, $body,
$this->From, $this->From,
[] []
); );
$this->edebug("Result: " . ($result === 0 ? 'true' : 'false'));
if (0 !== $result) { if (0 !== $result) {
throw new Exception($this->lang('execute') . $this->Sendmail , self::STOP_CRITICAL); throw new Exception($this->lang('execute') . $this->Sendmail , self::STOP_CRITICAL);
} }
} }
} else { } else {
$mail = @popen($sendmail, 'w'); $mail = @popen($sendmail, 'w');
if (!$mail) { if (!$mail) {
throw new Exception($this->lang('execute') . $this->Sendmail, se lf::STOP_CRITICAL); throw new Exception($this->lang('execute') . $this->Sendmail, se lf::STOP_CRITICAL);
} }
fwrite($mail, $header); fwrite($mail, $header);
skipping to change at line 1707 skipping to change at line 1754
$this->doCallback( $this->doCallback(
($result === 0), ($result === 0),
$this->to, $this->to,
$this->cc, $this->cc,
$this->bcc, $this->bcc,
$this->Subject, $this->Subject,
$body, $body,
$this->From, $this->From,
[] []
); );
$this->edebug("Result: " . ($result === 0 ? 'true' : 'false'));
if (0 !== $result) { if (0 !== $result) {
throw new Exception($this->lang('execute') . $this->Sendmail, se lf::STOP_CRITICAL); throw new Exception($this->lang('execute') . $this->Sendmail, se lf::STOP_CRITICAL);
} }
} }
return true; return true;
} }
/** /**
* Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe s hell characters. * Fix CVE-2016-10033 and CVE-2016-10045 by disallowing potentially unsafe s hell characters.
* Note that escapeshellarg and escapeshellcmd are inadequate for our purpos es, especially on Windows. * Note that escapeshellarg and escapeshellcmd are inadequate for our purpos es, especially on Windows.
* *
* @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report * @see https://github.com/PHPMailer/PHPMailer/issues/924 CVE-2016-10045 bug report
* *
* @param string $string The string to be validated * @param string $string The string to be validated
* *
* @return bool * @return bool
*/ */
protected static function isShellSafe($string) protected static function isShellSafe($string)
{ {
// Future-proof //Future-proof
if ( if (
escapeshellcmd($string) !== $string escapeshellcmd($string) !== $string
|| !in_array(escapeshellarg($string), ["'$string'", "\"$string\""]) || !in_array(escapeshellarg($string), ["'$string'", "\"$string\""])
) { ) {
return false; return false;
} }
$length = strlen($string); $length = strlen($string);
for ($i = 0; $i < $length; ++$i) { for ($i = 0; $i < $length; ++$i) {
$c = $string[$i]; $c = $string[$i];
// All other characters have a special meaning in at least one commo //All other characters have a special meaning in at least one common
n shell, including = and +. shell, including = and +.
// Full stop (.) has a special meaning in cmd.exe, but its impact sh //Full stop (.) has a special meaning in cmd.exe, but its impact sho
ould be negligible here. uld be negligible here.
// Note that this does permit non-Latin alphanumeric characters base //Note that this does permit non-Latin alphanumeric characters based
d on the current locale. on the current locale.
if (!ctype_alnum($c) && strpos('@_-.', $c) === false) { if (!ctype_alnum($c) && strpos('@_-.', $c) === false) {
return false; return false;
} }
} }
return true; return true;
} }
/** /**
* Check whether a file path is of a permitted type. * Check whether a file path is of a permitted type.
skipping to change at line 1812 skipping to change at line 1860
$to = implode(', ', $toArr); $to = implode(', ', $toArr);
$params = null; $params = null;
//This sets the SMTP envelope sender which gets turned into a return-pat h header by the receiver //This sets the SMTP envelope sender which gets turned into a return-pat h header by the receiver
//A space after `-f` is optional, but there is a long history of its pre sence //A space after `-f` is optional, but there is a long history of its pre sence
//causing problems, so we don't use one //causing problems, so we don't use one
//Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch -the_exim_command_line.html //Exim docs: http://www.exim.org/exim-html-current/doc/html/spec_html/ch -the_exim_command_line.html
//Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html //Sendmail docs: http://www.sendmail.org/~ca/email/man/sendmail.html
//Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html //Qmail docs: http://www.qmail.org/man/man8/qmail-inject.html
//Example problem: https://www.drupal.org/node/1057954 //Example problem: https://www.drupal.org/node/1057954
// CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be e //CVE-2016-10033, CVE-2016-10045: Don't pass -f if characters will be es
scaped. caped.
if (!empty($this->Sender) && static::validateAddress($this->Sender) && s if ('' === $this->Sender) {
elf::isShellSafe($this->Sender)) { $this->Sender = $this->From;
$params = sprintf('-f%s', $this->Sender); }
if (empty($this->Sender) && !empty(ini_get('sendmail_from'))) {
//PHP config has a sender address we can use
$this->Sender = ini_get('sendmail_from');
} }
if (!empty($this->Sender) && static::validateAddress($this->Sender)) { if (!empty($this->Sender) && static::validateAddress($this->Sender)) {
if (self::isShellSafe($this->Sender)) {
$params = sprintf('-f%s', $this->Sender);
}
$old_from = ini_get('sendmail_from'); $old_from = ini_get('sendmail_from');
ini_set('sendmail_from', $this->Sender); ini_set('sendmail_from', $this->Sender);
} }
$result = false; $result = false;
if ($this->SingleTo && count($toArr) > 1) { if ($this->SingleTo && count($toArr) > 1) {
foreach ($toArr as $toAddr) { foreach ($toArr as $toAddr) {
$result = $this->mailPassthru($toAddr, $this->Subject, $body, $h eader, $params); $result = $this->mailPassthru($toAddr, $this->Subject, $body, $h eader, $params);
$this->doCallback($result, [$toAddr], $this->cc, $this->bcc, $th is->Subject, $body, $this->From, []); $this->doCallback($result, [$toAddr], $this->cc, $this->bcc, $th is->Subject, $body, $this->From, []);
} }
} else { } else {
skipping to change at line 1902 skipping to change at line 1957
$smtp_from = $this->From; $smtp_from = $this->From;
} else { } else {
$smtp_from = $this->Sender; $smtp_from = $this->Sender;
} }
if (!$this->smtp->mail($smtp_from)) { if (!$this->smtp->mail($smtp_from)) {
$this->setError($this->lang('from_failed') . $smtp_from . ' : ' . im plode(',', $this->smtp->getError())); $this->setError($this->lang('from_failed') . $smtp_from . ' : ' . im plode(',', $this->smtp->getError()));
throw new Exception($this->ErrorInfo, self::STOP_CRITICAL); throw new Exception($this->ErrorInfo, self::STOP_CRITICAL);
} }
$callbacks = []; $callbacks = [];
// Attempt to send to all recipients //Attempt to send to all recipients
foreach ([$this->to, $this->cc, $this->bcc] as $togroup) { foreach ([$this->to, $this->cc, $this->bcc] as $togroup) {
foreach ($togroup as $to) { foreach ($togroup as $to) {
if (!$this->smtp->recipient($to[0], $this->dsn)) { if (!$this->smtp->recipient($to[0], $this->dsn)) {
$error = $this->smtp->getError(); $error = $this->smtp->getError();
$bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']]; $bad_rcpt[] = ['to' => $to[0], 'error' => $error['detail']];
$isSent = false; $isSent = false;
} else { } else {
$isSent = true; $isSent = true;
} }
$callbacks[] = ['issent' => $isSent, 'to' => $to[0]]; $callbacks[] = ['issent' => $isSent, 'to' => $to[0]];
} }
} }
// Only send the DATA command if we have viable recipients //Only send the DATA command if we have viable recipients
if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->d ata($header . $body)) { if ((count($this->all_recipients) > count($bad_rcpt)) && !$this->smtp->d ata($header . $body)) {
throw new Exception($this->lang('data_not_accepted'), self::STOP_CRI TICAL); throw new Exception($this->lang('data_not_accepted'), self::STOP_CRI TICAL);
} }
$smtp_transaction_id = $this->smtp->getLastTransactionID(); $smtp_transaction_id = $this->smtp->getLastTransactionID();
if ($this->SMTPKeepAlive) { if ($this->SMTPKeepAlive) {
$this->smtp->reset(); $this->smtp->reset();
} else { } else {
$this->smtp->quit(); $this->smtp->quit();
skipping to change at line 1979 skipping to change at line 2034
{ {
if (null === $this->smtp) { if (null === $this->smtp) {
$this->smtp = $this->getSMTPInstance(); $this->smtp = $this->getSMTPInstance();
} }
//If no options are provided, use whatever is set in the instance //If no options are provided, use whatever is set in the instance
if (null === $options) { if (null === $options) {
$options = $this->SMTPOptions; $options = $this->SMTPOptions;
} }
// Already connected? //Already connected?
if ($this->smtp->connected()) { if ($this->smtp->connected()) {
return true; return true;
} }
$this->smtp->setTimeout($this->Timeout); $this->smtp->setTimeout($this->Timeout);
$this->smtp->setDebugLevel($this->SMTPDebug); $this->smtp->setDebugLevel($this->SMTPDebug);
$this->smtp->setDebugOutput($this->Debugoutput); $this->smtp->setDebugOutput($this->Debugoutput);
$this->smtp->setVerp($this->do_verp); $this->smtp->setVerp($this->do_verp);
$hosts = explode(';', $this->Host); $hosts = explode(';', $this->Host);
$lastexception = null; $lastexception = null;
skipping to change at line 2001 skipping to change at line 2056
foreach ($hosts as $hostentry) { foreach ($hosts as $hostentry) {
$hostinfo = []; $hostinfo = [];
if ( if (
!preg_match( !preg_match(
'/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/', '/^(?:(ssl|tls):\/\/)?(.+?)(?::(\d+))?$/',
trim($hostentry), trim($hostentry),
$hostinfo $hostinfo
) )
) { ) {
$this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hos tentry)); $this->edebug($this->lang('invalid_hostentry') . ' ' . trim($hos tentry));
// Not a valid host entry //Not a valid host entry
continue; continue;
} }
// $hostinfo[1]: optional ssl or tls prefix //$hostinfo[1]: optional ssl or tls prefix
// $hostinfo[2]: the hostname //$hostinfo[2]: the hostname
// $hostinfo[3]: optional port number //$hostinfo[3]: optional port number
// The host string prefix can temporarily override the current setti //The host string prefix can temporarily override the current settin
ng for SMTPSecure g for SMTPSecure
// If it's not specified, the default value is used //If it's not specified, the default value is used
//Check the host name is a valid name or IP address before trying to use it //Check the host name is a valid name or IP address before trying to use it
if (!static::isValidHost($hostinfo[2])) { if (!static::isValidHost($hostinfo[2])) {
$this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]); $this->edebug($this->lang('invalid_host') . ' ' . $hostinfo[2]);
continue; continue;
} }
$prefix = ''; $prefix = '';
$secure = $this->SMTPSecure; $secure = $this->SMTPSecure;
$tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure); $tls = (static::ENCRYPTION_STARTTLS === $this->SMTPSecure);
if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYP TION_SMTPS === $this->SMTPSecure)) { if ('ssl' === $hostinfo[1] || ('' === $hostinfo[1] && static::ENCRYP TION_SMTPS === $this->SMTPSecure)) {
$prefix = 'ssl://'; $prefix = 'ssl://';
$tls = false; // Can't have SSL and TLS at the same time $tls = false; //Can't have SSL and TLS at the same time
$secure = static::ENCRYPTION_SMTPS; $secure = static::ENCRYPTION_SMTPS;
} elseif ('tls' === $hostinfo[1]) { } elseif ('tls' === $hostinfo[1]) {
$tls = true; $tls = true;
// tls doesn't use a prefix //TLS doesn't use a prefix
$secure = static::ENCRYPTION_STARTTLS; $secure = static::ENCRYPTION_STARTTLS;
} }
//Do we need the OpenSSL extension? //Do we need the OpenSSL extension?
$sslext = defined('OPENSSL_ALGO_SHA256'); $sslext = defined('OPENSSL_ALGO_SHA256');
if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SM TPS === $secure) { if (static::ENCRYPTION_STARTTLS === $secure || static::ENCRYPTION_SM TPS === $secure) {
//Check for an OpenSSL constant rather than using extension_load ed, which is sometimes disabled //Check for an OpenSSL constant rather than using extension_load ed, which is sometimes disabled
if (!$sslext) { if (!$sslext) {
throw new Exception($this->lang('extension_missing') . 'open ssl', self::STOP_CRITICAL); throw new Exception($this->lang('extension_missing') . 'open ssl', self::STOP_CRITICAL);
} }
} }
skipping to change at line 2054 skipping to change at line 2109
} }
if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $op tions)) { if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $op tions)) {
try { try {
if ($this->Helo) { if ($this->Helo) {
$hello = $this->Helo; $hello = $this->Helo;
} else { } else {
$hello = $this->serverHostname(); $hello = $this->serverHostname();
} }
$this->smtp->hello($hello); $this->smtp->hello($hello);
//Automatically enable TLS encryption if: //Automatically enable TLS encryption if:
// * it's not disabled //* it's not disabled
// * we have openssl extension //* we have openssl extension
// * we are not already using SSL //* we are not already using SSL
// * the server offers STARTTLS //* the server offers STARTTLS
if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $t his->smtp->getServerExt('STARTTLS')) { if ($this->SMTPAutoTLS && $sslext && 'ssl' !== $secure && $t his->smtp->getServerExt('STARTTLS')) {
$tls = true; $tls = true;
} }
if ($tls) { if ($tls) {
if (!$this->smtp->startTLS()) { if (!$this->smtp->startTLS()) {
throw new Exception($this->lang('connect_host')); throw new Exception($this->lang('connect_host'));
} }
// We must resend EHLO after TLS negotiation //We must resend EHLO after TLS negotiation
$this->smtp->hello($hello); $this->smtp->hello($hello);
} }
if ( if (
$this->SMTPAuth && !$this->smtp->authenticate( $this->SMTPAuth && !$this->smtp->authenticate(
$this->Username, $this->Username,
$this->Password, $this->Password,
$this->AuthType, $this->AuthType,
$this->oauth $this->oauth
) )
) { ) {
throw new Exception($this->lang('authenticate')); throw new Exception($this->lang('authenticate'));
} }
return true; return true;
} catch (Exception $exc) { } catch (Exception $exc) {
$lastexception = $exc; $lastexception = $exc;
$this->edebug($exc->getMessage()); $this->edebug($exc->getMessage());
// We must have connected, but then failed TLS or Auth, so c lose connection nicely //We must have connected, but then failed TLS or Auth, so cl ose connection nicely
$this->smtp->quit(); $this->smtp->quit();
} }
} }
} }
// If we get here, all connection attempts have failed, so close connect ion hard //If we get here, all connection attempts have failed, so close connecti on hard
$this->smtp->close(); $this->smtp->close();
// As we've caught all exceptions, just report whatever the last one was //As we've caught all exceptions, just report whatever the last one was
if ($this->exceptions && null !== $lastexception) { if ($this->exceptions && null !== $lastexception) {
throw $lastexception; throw $lastexception;
} }
return false; return false;
} }
/** /**
* Close the active SMTP session if one exists. * Close the active SMTP session if one exists.
*/ */
skipping to change at line 2121 skipping to change at line 2176
* Returns false if it cannot load the language file. * Returns false if it cannot load the language file.
* The default language is English. * The default language is English.
* *
* @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr") * @param string $langcode ISO 639-1 2-character language code (e.g. French is "fr")
* @param string $lang_path Path to the language file directory, with traili ng separator (slash) * @param string $lang_path Path to the language file directory, with traili ng separator (slash)
* *
* @return bool * @return bool
*/ */
public function setLanguage($langcode = 'en', $lang_path = '') public function setLanguage($langcode = 'en', $lang_path = '')
{ {
// Backwards compatibility for renamed language codes //Backwards compatibility for renamed language codes
$renamed_langcodes = [ $renamed_langcodes = [
'br' => 'pt_br', 'br' => 'pt_br',
'cz' => 'cs', 'cz' => 'cs',
'dk' => 'da', 'dk' => 'da',
'no' => 'nb', 'no' => 'nb',
'se' => 'sv', 'se' => 'sv',
'rs' => 'sr', 'rs' => 'sr',
'tg' => 'tl', 'tg' => 'tl',
'am' => 'hy', 'am' => 'hy',
]; ];
if (array_key_exists($langcode, $renamed_langcodes)) { if (array_key_exists($langcode, $renamed_langcodes)) {
$langcode = $renamed_langcodes[$langcode]; $langcode = $renamed_langcodes[$langcode];
} }
// Define full set of translatable strings in English //Define full set of translatable strings in English
$PHPMAILER_LANG = [ $PHPMAILER_LANG = [
'authenticate' => 'SMTP Error: Could not authenticate.', 'authenticate' => 'SMTP Error: Could not authenticate.',
'connect_host' => 'SMTP Error: Could not connect to SMTP host.', 'connect_host' => 'SMTP Error: Could not connect to SMTP host.',
'data_not_accepted' => 'SMTP Error: data not accepted.', 'data_not_accepted' => 'SMTP Error: data not accepted.',
'empty_message' => 'Message body empty', 'empty_message' => 'Message body empty',
'encoding' => 'Unknown encoding: ', 'encoding' => 'Unknown encoding: ',
'execute' => 'Could not execute: ', 'execute' => 'Could not execute: ',
'file_access' => 'Could not access file: ', 'file_access' => 'Could not access file: ',
'file_open' => 'File Error: Could not open file: ', 'file_open' => 'File Error: Could not open file: ',
'from_failed' => 'The following From address failed: ', 'from_failed' => 'The following From address failed: ',
skipping to change at line 2162 skipping to change at line 2217
'mailer_not_supported' => ' mailer is not supported.', 'mailer_not_supported' => ' mailer is not supported.',
'provide_address' => 'You must provide at least one recipient email address.', 'provide_address' => 'You must provide at least one recipient email address.',
'recipients_failed' => 'SMTP Error: The following recipients failed: ', 'recipients_failed' => 'SMTP Error: The following recipients failed: ',
'signing' => 'Signing Error: ', 'signing' => 'Signing Error: ',
'smtp_connect_failed' => 'SMTP connect() failed.', 'smtp_connect_failed' => 'SMTP connect() failed.',
'smtp_error' => 'SMTP server error: ', 'smtp_error' => 'SMTP server error: ',
'variable_set' => 'Cannot set or reset variable: ', 'variable_set' => 'Cannot set or reset variable: ',
'extension_missing' => 'Extension missing: ', 'extension_missing' => 'Extension missing: ',
]; ];
if (empty($lang_path)) { if (empty($lang_path)) {
// Calculate an absolute path so it can work if CWD is not here //Calculate an absolute path so it can work if CWD is not here
$lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . D IRECTORY_SEPARATOR; $lang_path = dirname(__DIR__) . DIRECTORY_SEPARATOR . 'language' . D IRECTORY_SEPARATOR;
} }
//Validate $langcode //Validate $langcode
if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) { if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
$langcode = 'en'; $langcode = 'en';
} }
$foundlang = true; $foundlang = true;
$lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php'; $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
// There is no English translation file //There is no English translation file
if ('en' !== $langcode) { if ('en' !== $langcode) {
// Make sure language file path is readable //Make sure language file path is readable
if (!static::fileIsAccessible($lang_file)) { if (!static::fileIsAccessible($lang_file)) {
$foundlang = false; $foundlang = false;
} else { } else {
// Overwrite language-specific strings. //Overwrite language-specific strings.
// This way we'll never have missing translation keys. //This way we'll never have missing translation keys.
$foundlang = include $lang_file; $foundlang = include $lang_file;
} }
} }
$this->language = $PHPMAILER_LANG; $this->language = $PHPMAILER_LANG;
return (bool) $foundlang; // Returns false if language not found return (bool) $foundlang; //Returns false if language not found
} }
/** /**
* Get the array of strings for the current language. * Get the array of strings for the current language.
* *
* @return array * @return array
*/ */
public function getTranslations() public function getTranslations()
{ {
return $this->language; return $this->language;
skipping to change at line 2228 skipping to change at line 2283
/** /**
* Format an address for use in a message header. * Format an address for use in a message header.
* *
* @param array $addr A 2-element indexed array, element 0 containing an add ress, element 1 containing a name like * @param array $addr A 2-element indexed array, element 0 containing an add ress, element 1 containing a name like
* ['joe@example.com', 'Joe User'] * ['joe@example.com', 'Joe User']
* *
* @return string * @return string
*/ */
public function addrFormat($addr) public function addrFormat($addr)
{ {
if (empty($addr[1])) { // No name provided if (empty($addr[1])) { //No name provided
return $this->secureHeader($addr[0]); return $this->secureHeader($addr[0]);
} }
return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') .
' <' . $this->secureHeader($addr[0]) . '>'; ' <' . $this->secureHeader($addr[0]) . '>';
} }
/** /**
* Word-wrap message. * Word-wrap message.
* For use with mailers that do not automatically perform wrapping * For use with mailers that do not automatically perform wrapping
skipping to change at line 2255 skipping to change at line 2310
* *
* @return string * @return string
*/ */
public function wrapText($message, $length, $qp_mode = false) public function wrapText($message, $length, $qp_mode = false)
{ {
if ($qp_mode) { if ($qp_mode) {
$soft_break = sprintf(' =%s', static::$LE); $soft_break = sprintf(' =%s', static::$LE);
} else { } else {
$soft_break = static::$LE; $soft_break = static::$LE;
} }
// If utf-8 encoding is used, we will need to make sure we don't //If utf-8 encoding is used, we will need to make sure we don't
// split multibyte characters when we wrap //split multibyte characters when we wrap
$is_utf8 = static::CHARSET_UTF8 === strtolower($this->CharSet); $is_utf8 = static::CHARSET_UTF8 === strtolower($this->CharSet);
$lelen = strlen(static::$LE); $lelen = strlen(static::$LE);
$crlflen = strlen(static::$LE); $crlflen = strlen(static::$LE);
$message = static::normalizeBreaks($message); $message = static::normalizeBreaks($message);
//Remove a trailing line break //Remove a trailing line break
if (substr($message, -$lelen) === static::$LE) { if (substr($message, -$lelen) === static::$LE) {
$message = substr($message, 0, -$lelen); $message = substr($message, 0, -$lelen);
} }
skipping to change at line 2356 skipping to change at line 2411
* @return int * @return int
*/ */
public function utf8CharBoundary($encodedText, $maxLength) public function utf8CharBoundary($encodedText, $maxLength)
{ {
$foundSplitPos = false; $foundSplitPos = false;
$lookBack = 3; $lookBack = 3;
while (!$foundSplitPos) { while (!$foundSplitPos) {
$lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack) ; $lastChunk = substr($encodedText, $maxLength - $lookBack, $lookBack) ;
$encodedCharPos = strpos($lastChunk, '='); $encodedCharPos = strpos($lastChunk, '=');
if (false !== $encodedCharPos) { if (false !== $encodedCharPos) {
// Found start of encoded character byte within $lookBack block. //Found start of encoded character byte within $lookBack block.
// Check the encoded byte value (the 2 chars after the '=') //Check the encoded byte value (the 2 chars after the '=')
$hex = substr($encodedText, $maxLength - $lookBack + $encodedCha rPos + 1, 2); $hex = substr($encodedText, $maxLength - $lookBack + $encodedCha rPos + 1, 2);
$dec = hexdec($hex); $dec = hexdec($hex);
if ($dec < 128) { if ($dec < 128) {
// Single byte character. //Single byte character.
// If the encoded char was found at pos 0, it will fit //If the encoded char was found at pos 0, it will fit
// otherwise reduce maxLength to start of the encoded char //otherwise reduce maxLength to start of the encoded char
if ($encodedCharPos > 0) { if ($encodedCharPos > 0) {
$maxLength -= $lookBack - $encodedCharPos; $maxLength -= $lookBack - $encodedCharPos;
} }
$foundSplitPos = true; $foundSplitPos = true;
} elseif ($dec >= 192) { } elseif ($dec >= 192) {
// First byte of a multi byte character //First byte of a multi byte character
// Reduce maxLength to split at start of character //Reduce maxLength to split at start of character
$maxLength -= $lookBack - $encodedCharPos; $maxLength -= $lookBack - $encodedCharPos;
$foundSplitPos = true; $foundSplitPos = true;
} elseif ($dec < 192) { } elseif ($dec < 192) {
// Middle byte of a multi byte character, look further back //Middle byte of a multi byte character, look further back
$lookBack += 3; $lookBack += 3;
} }
} else { } else {
// No encoded character found //No encoded character found
$foundSplitPos = true; $foundSplitPos = true;
} }
} }
return $maxLength; return $maxLength;
} }
/** /**
* Apply word wrapping to the message body. * Apply word wrapping to the message body.
* Wraps the message body to the number of chars set in the WordWrap propert y. * Wraps the message body to the number of chars set in the WordWrap propert y.
skipping to change at line 2422 skipping to change at line 2477
* Assemble message headers. * Assemble message headers.
* *
* @return string The assembled headers * @return string The assembled headers
*/ */
public function createHeader() public function createHeader()
{ {
$result = ''; $result = '';
$result .= $this->headerLine('Date', '' === $this->MessageDate ? self::r fcDate() : $this->MessageDate); $result .= $this->headerLine('Date', '' === $this->MessageDate ? self::r fcDate() : $this->MessageDate);
// The To header is created automatically by mail(), so needs to be omit ted here //The To header is created automatically by mail(), so needs to be omitt ed here
if ('mail' !== $this->Mailer) { if ('mail' !== $this->Mailer) {
if ($this->SingleTo) { if ($this->SingleTo) {
foreach ($this->to as $toaddr) { foreach ($this->to as $toaddr) {
$this->SingleToArray[] = $this->addrFormat($toaddr); $this->SingleToArray[] = $this->addrFormat($toaddr);
} }
} elseif (count($this->to) > 0) { } elseif (count($this->to) > 0) {
$result .= $this->addrAppend('To', $this->to); $result .= $this->addrAppend('To', $this->to);
} elseif (count($this->cc) === 0) { } elseif (count($this->cc) === 0) {
$result .= $this->headerLine('To', 'undisclosed-recipients:;'); $result .= $this->headerLine('To', 'undisclosed-recipients:;');
} }
} }
$result .= $this->addrAppend('From', [[trim($this->From), $this->FromNam e]]); $result .= $this->addrAppend('From', [[trim($this->From), $this->FromNam e]]);
// sendmail and mail() extract Cc from the header before sending //sendmail and mail() extract Cc from the header before sending
if (count($this->cc) > 0) { if (count($this->cc) > 0) {
$result .= $this->addrAppend('Cc', $this->cc); $result .= $this->addrAppend('Cc', $this->cc);
} }
// sendmail and mail() extract Bcc from the header before sending //sendmail and mail() extract Bcc from the header before sending
if ( if (
( (
'sendmail' === $this->Mailer || 'qmail' === $this->Mailer || 'ma il' === $this->Mailer 'sendmail' === $this->Mailer || 'qmail' === $this->Mailer || 'ma il' === $this->Mailer
) )
&& count($this->bcc) > 0 && count($this->bcc) > 0
) { ) {
$result .= $this->addrAppend('Bcc', $this->bcc); $result .= $this->addrAppend('Bcc', $this->bcc);
} }
if (count($this->ReplyTo) > 0) { if (count($this->ReplyTo) > 0) {
$result .= $this->addrAppend('Reply-To', $this->ReplyTo); $result .= $this->addrAppend('Reply-To', $this->ReplyTo);
} }
// mail() sets the subject itself //mail() sets the subject itself
if ('mail' !== $this->Mailer) { if ('mail' !== $this->Mailer) {
$result .= $this->headerLine('Subject', $this->encodeHeader($this->s ecureHeader($this->Subject))); $result .= $this->headerLine('Subject', $this->encodeHeader($this->s ecureHeader($this->Subject)));
} }
// Only allow a custom message ID if it conforms to RFC 5322 section 3.6 //Only allow a custom message ID if it conforms to RFC 5322 section 3.6.
.4 4
// https://tools.ietf.org/html/rfc5322#section-3.6.4 //https://tools.ietf.org/html/rfc5322#section-3.6.4
if ('' !== $this->MessageID && preg_match('/^<.*@.*>$/', $this->MessageI D)) { if ('' !== $this->MessageID && preg_match('/^<.*@.*>$/', $this->MessageI D)) {
$this->lastMessageID = $this->MessageID; $this->lastMessageID = $this->MessageID;
} else { } else {
$this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->se rverHostname()); $this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->se rverHostname());
} }
$result .= $this->headerLine('Message-ID', $this->lastMessageID); $result .= $this->headerLine('Message-ID', $this->lastMessageID);
if (null !== $this->Priority) { if (null !== $this->Priority) {
$result .= $this->headerLine('X-Priority', $this->Priority); $result .= $this->headerLine('X-Priority', $this->Priority);
} }
if ('' === $this->XMailer) { if ('' === $this->XMailer) {
skipping to change at line 2487 skipping to change at line 2542
$myXmailer = trim($this->XMailer); $myXmailer = trim($this->XMailer);
if ($myXmailer) { if ($myXmailer) {
$result .= $this->headerLine('X-Mailer', $myXmailer); $result .= $this->headerLine('X-Mailer', $myXmailer);
} }
} }
if ('' !== $this->ConfirmReadingTo) { if ('' !== $this->ConfirmReadingTo) {
$result .= $this->headerLine('Disposition-Notification-To', '<' . $t his->ConfirmReadingTo . '>'); $result .= $this->headerLine('Disposition-Notification-To', '<' . $t his->ConfirmReadingTo . '>');
} }
// Add custom headers //Add custom headers
foreach ($this->CustomHeader as $header) { foreach ($this->CustomHeader as $header) {
$result .= $this->headerLine( $result .= $this->headerLine(
trim($header[0]), trim($header[0]),
$this->encodeHeader(trim($header[1])) $this->encodeHeader(trim($header[1]))
); );
} }
if (!$this->sign_key_file) { if (!$this->sign_key_file) {
$result .= $this->headerLine('MIME-Version', '1.0'); $result .= $this->headerLine('MIME-Version', '1.0');
$result .= $this->getMailMIME(); $result .= $this->getMailMIME();
} }
skipping to change at line 2529 skipping to change at line 2584
case 'alt_inline_attach': case 'alt_inline_attach':
$result .= $this->headerLine('Content-Type', static::CONTENT_TYP E_MULTIPART_MIXED . ';'); $result .= $this->headerLine('Content-Type', static::CONTENT_TYP E_MULTIPART_MIXED . ';');
$result .= $this->textLine(' boundary="' . $this->boundary[1] . '"'); $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
break; break;
case 'alt': case 'alt':
case 'alt_inline': case 'alt_inline':
$result .= $this->headerLine('Content-Type', static::CONTENT_TYP E_MULTIPART_ALTERNATIVE . ';'); $result .= $this->headerLine('Content-Type', static::CONTENT_TYP E_MULTIPART_ALTERNATIVE . ';');
$result .= $this->textLine(' boundary="' . $this->boundary[1] . '"'); $result .= $this->textLine(' boundary="' . $this->boundary[1] . '"');
break; break;
default: default:
// Catches case 'plain': and case '': //Catches case 'plain': and case '':
$result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet); $result .= $this->textLine('Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet);
$ismultipart = false; $ismultipart = false;
break; break;
} }
// RFC1341 part 5 says 7bit is assumed if not specified //RFC1341 part 5 says 7bit is assumed if not specified
if (static::ENCODING_7BIT !== $this->Encoding) { if (static::ENCODING_7BIT !== $this->Encoding) {
// RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE //RFC 2045 section 6.4 says multipart MIME parts may only use 7bit, 8bit or binary CTE
if ($ismultipart) { if ($ismultipart) {
if (static::ENCODING_8BIT === $this->Encoding) { if (static::ENCODING_8BIT === $this->Encoding) {
$result .= $this->headerLine('Content-Transfer-Encoding', st atic::ENCODING_8BIT); $result .= $this->headerLine('Content-Transfer-Encoding', st atic::ENCODING_8BIT);
} }
// The only remaining alternatives are quoted-printable and base 64, which are both 7bit compatible //The only remaining alternatives are quoted-printable and base6 4, which are both 7bit compatible
} else { } else {
$result .= $this->headerLine('Content-Transfer-Encoding', $this- >Encoding); $result .= $this->headerLine('Content-Transfer-Encoding', $this- >Encoding);
} }
} }
if ('mail' !== $this->Mailer) {
// $result .= static::$LE;
}
return $result; return $result;
} }
/** /**
* Returns the whole MIME message. * Returns the whole MIME message.
* Includes complete headers and body. * Includes complete headers and body.
* Only valid post preSend(). * Only valid post preSend().
* *
* @see PHPMailer::preSend() * @see PHPMailer::preSend()
* *
skipping to change at line 2819 skipping to change at line 2870
); );
$body .= $this->encodeString($this->Body, $bodyEncoding); $body .= $this->encodeString($this->Body, $bodyEncoding);
$body .= static::$LE; $body .= static::$LE;
$body .= $this->attachAll('inline', $this->boundary[3]); $body .= $this->attachAll('inline', $this->boundary[3]);
$body .= static::$LE; $body .= static::$LE;
$body .= $this->endBoundary($this->boundary[2]); $body .= $this->endBoundary($this->boundary[2]);
$body .= static::$LE; $body .= static::$LE;
$body .= $this->attachAll('attachment', $this->boundary[1]); $body .= $this->attachAll('attachment', $this->boundary[1]);
break; break;
default: default:
// Catch case 'plain' and case '', applies to simple `text/plain ` and `text/html` body content types //Catch case 'plain' and case '', applies to simple `text/plain` and `text/html` body content types
//Reset the `Encoding` property in case we changed it for line l ength reasons //Reset the `Encoding` property in case we changed it for line l ength reasons
$this->Encoding = $bodyEncoding; $this->Encoding = $bodyEncoding;
$body .= $this->encodeString($this->Body, $this->Encoding); $body .= $this->encodeString($this->Body, $this->Encoding);
break; break;
} }
if ($this->isError()) { if ($this->isError()) {
$body = ''; $body = '';
if ($this->exceptions) { if ($this->exceptions) {
throw new Exception($this->lang('empty_message'), self::STOP_CRI TICAL); throw new Exception($this->lang('empty_message'), self::STOP_CRI TICAL);
skipping to change at line 2910 skipping to change at line 2961
} }
if ('' === $contentType) { if ('' === $contentType) {
$contentType = $this->ContentType; $contentType = $this->ContentType;
} }
if ('' === $encoding) { if ('' === $encoding) {
$encoding = $this->Encoding; $encoding = $this->Encoding;
} }
$result .= $this->textLine('--' . $boundary); $result .= $this->textLine('--' . $boundary);
$result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSe t); $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSe t);
$result .= static::$LE; $result .= static::$LE;
// RFC1341 part 5 says 7bit is assumed if not specified //RFC1341 part 5 says 7bit is assumed if not specified
if (static::ENCODING_7BIT !== $encoding) { if (static::ENCODING_7BIT !== $encoding) {
$result .= $this->headerLine('Content-Transfer-Encoding', $encoding) ; $result .= $this->headerLine('Content-Transfer-Encoding', $encoding) ;
} }
$result .= static::$LE; $result .= static::$LE;
return $result; return $result;
} }
/** /**
* Return the end of a message boundary. * Return the end of a message boundary.
skipping to change at line 3008 skipping to change at line 3059
$name = '', $name = '',
$encoding = self::ENCODING_BASE64, $encoding = self::ENCODING_BASE64,
$type = '', $type = '',
$disposition = 'attachment' $disposition = 'attachment'
) { ) {
try { try {
if (!static::fileIsAccessible($path)) { if (!static::fileIsAccessible($path)) {
throw new Exception($this->lang('file_access') . $path, self::ST OP_CONTINUE); throw new Exception($this->lang('file_access') . $path, self::ST OP_CONTINUE);
} }
// If a MIME type is not specified, try to work it out from the file name //If a MIME type is not specified, try to work it out from the file name
if ('' === $type) { if ('' === $type) {
$type = static::filenameToType($path); $type = static::filenameToType($path);
} }
$filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME); $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
if ('' === $name) { if ('' === $name) {
$name = $filename; $name = $filename;
} }
if (!$this->validateEncoding($encoding)) { if (!$this->validateEncoding($encoding)) {
throw new Exception($this->lang('encoding') . $encoding); throw new Exception($this->lang('encoding') . $encoding);
} }
$this->attachment[] = [ $this->attachment[] = [
0 => $path, 0 => $path,
1 => $filename, 1 => $filename,
2 => $name, 2 => $name,
3 => $encoding, 3 => $encoding,
4 => $type, 4 => $type,
5 => false, // isStringAttachment 5 => false, //isStringAttachment
6 => $disposition, 6 => $disposition,
7 => $name, 7 => $name,
]; ];
} catch (Exception $exc) { } catch (Exception $exc) {
$this->setError($exc->getMessage()); $this->setError($exc->getMessage());
$this->edebug($exc->getMessage()); $this->edebug($exc->getMessage());
if ($this->exceptions) { if ($this->exceptions) {
throw $exc; throw $exc;
} }
skipping to change at line 3067 skipping to change at line 3118
* *
* @param string $disposition_type * @param string $disposition_type
* @param string $boundary * @param string $boundary
* *
* @throws Exception * @throws Exception
* *
* @return string * @return string
*/ */
protected function attachAll($disposition_type, $boundary) protected function attachAll($disposition_type, $boundary)
{ {
// Return text of body //Return text of body
$mime = []; $mime = [];
$cidUniq = []; $cidUniq = [];
$incl = []; $incl = [];
// Add all attachments //Add all attachments
foreach ($this->attachment as $attachment) { foreach ($this->attachment as $attachment) {
// Check if it is a valid disposition_filter //Check if it is a valid disposition_filter
if ($attachment[6] === $disposition_type) { if ($attachment[6] === $disposition_type) {
// Check for string attachment //Check for string attachment
$string = ''; $string = '';
$path = ''; $path = '';
$bString = $attachment[5]; $bString = $attachment[5];
if ($bString) { if ($bString) {
$string = $attachment[0]; $string = $attachment[0];
} else { } else {
$path = $attachment[0]; $path = $attachment[0];
} }
$inclhash = hash('sha256', serialize($attachment)); $inclhash = hash('sha256', serialize($attachment));
skipping to change at line 3117 skipping to change at line 3168
static::quotedString($this->encodeHeader($this->secureHe ader($name))), static::quotedString($this->encodeHeader($this->secureHe ader($name))),
static::$LE static::$LE
); );
} else { } else {
$mime[] = sprintf( $mime[] = sprintf(
'Content-Type: %s%s', 'Content-Type: %s%s',
$type, $type,
static::$LE static::$LE
); );
} }
// RFC1341 part 5 says 7bit is assumed if not specified //RFC1341 part 5 says 7bit is assumed if not specified
if (static::ENCODING_7BIT !== $encoding) { if (static::ENCODING_7BIT !== $encoding) {
$mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encodi ng, static::$LE); $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encodi ng, static::$LE);
} }
//Only set Content-IDs on inline attachments //Only set Content-IDs on inline attachments
if ((string) $cid !== '' && $disposition === 'inline') { if ((string) $cid !== '' && $disposition === 'inline') {
$mime[] = 'Content-ID: <' . $this->encodeHeader($this->secur eHeader($cid)) . '>' . static::$LE; $mime[] = 'Content-ID: <' . $this->encodeHeader($this->secur eHeader($cid)) . '>' . static::$LE;
} }
// Allow for bypassing the Content-Disposition header //Allow for bypassing the Content-Disposition header
if (!empty($disposition)) { if (!empty($disposition)) {
$encoded_name = $this->encodeHeader($this->secureHeader($nam e)); $encoded_name = $this->encodeHeader($this->secureHeader($nam e));
if (!empty($encoded_name)) { if (!empty($encoded_name)) {
$mime[] = sprintf( $mime[] = sprintf(
'Content-Disposition: %s; filename=%s%s', 'Content-Disposition: %s; filename=%s%s',
$disposition, $disposition,
static::quotedString($encoded_name), static::quotedString($encoded_name),
static::$LE . static::$LE static::$LE . static::$LE
); );
} else { } else {
$mime[] = sprintf( $mime[] = sprintf(
'Content-Disposition: %s%s', 'Content-Disposition: %s%s',
$disposition, $disposition,
static::$LE . static::$LE static::$LE . static::$LE
); );
} }
} else { } else {
$mime[] = static::$LE; $mime[] = static::$LE;
} }
// Encode as string attachment //Encode as string attachment
if ($bString) { if ($bString) {
$mime[] = $this->encodeString($string, $encoding); $mime[] = $this->encodeString($string, $encoding);
} else { } else {
$mime[] = $this->encodeFile($path, $encoding); $mime[] = $this->encodeFile($path, $encoding);
} }
if ($this->isError()) { if ($this->isError()) {
return ''; return '';
} }
$mime[] = static::$LE; $mime[] = static::$LE;
} }
skipping to change at line 3224 skipping to change at line 3275
case static::ENCODING_BASE64: case static::ENCODING_BASE64:
$encoded = chunk_split( $encoded = chunk_split(
base64_encode($str), base64_encode($str),
static::STD_LINE_LENGTH, static::STD_LINE_LENGTH,
static::$LE static::$LE
); );
break; break;
case static::ENCODING_7BIT: case static::ENCODING_7BIT:
case static::ENCODING_8BIT: case static::ENCODING_8BIT:
$encoded = static::normalizeBreaks($str); $encoded = static::normalizeBreaks($str);
// Make sure it ends with a line break //Make sure it ends with a line break
if (substr($encoded, -(strlen(static::$LE))) !== static::$LE) { if (substr($encoded, -(strlen(static::$LE))) !== static::$LE) {
$encoded .= static::$LE; $encoded .= static::$LE;
} }
break; break;
case static::ENCODING_BINARY: case static::ENCODING_BINARY:
$encoded = $str; $encoded = $str;
break; break;
case static::ENCODING_QUOTED_PRINTABLE: case static::ENCODING_QUOTED_PRINTABLE:
$encoded = $this->encodeQP($str); $encoded = $this->encodeQP($str);
break; break;
skipping to change at line 3262 skipping to change at line 3313
* @param string $position What context the string will be used in * @param string $position What context the string will be used in
* *
* @return string * @return string
*/ */
public function encodeHeader($str, $position = 'text') public function encodeHeader($str, $position = 'text')
{ {
$matchcount = 0; $matchcount = 0;
switch (strtolower($position)) { switch (strtolower($position)) {
case 'phrase': case 'phrase':
if (!preg_match('/[\200-\377]/', $str)) { if (!preg_match('/[\200-\377]/', $str)) {
// Can't use addslashes as we don't know the value of magic_ quotes_sybase //Can't use addslashes as we don't know the value of magic_q uotes_sybase
$encoded = addcslashes($str, "\0..\37\177\\\""); $encoded = addcslashes($str, "\0..\37\177\\\"");
if (($str === $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'* +\/=?^_`{|}~ -]/', $str)) { if (($str === $encoded) && !preg_match('/[^A-Za-z0-9!#$%&\'* +\/=?^_`{|}~ -]/', $str)) {
return $encoded; return $encoded;
} }
return "\"$encoded\""; return "\"$encoded\"";
} }
$matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches); $matchcount = preg_match_all('/[^\040\041\043-\133\135-\176]/', $str, $matches);
break; break;
/* @noinspection PhpMissingBreakStatementInspection */ /* @noinspection PhpMissingBreakStatementInspection */
skipping to change at line 3288 skipping to change at line 3339
$matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177- \377]/', $str, $matches); $matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177- \377]/', $str, $matches);
break; break;
} }
if ($this->has8bitChars($str)) { if ($this->has8bitChars($str)) {
$charset = $this->CharSet; $charset = $this->CharSet;
} else { } else {
$charset = static::CHARSET_ASCII; $charset = static::CHARSET_ASCII;
} }
// Q/B encoding adds 8 chars and the charset ("` =?<charset>?[QB]?<conte nt>?=`"). //Q/B encoding adds 8 chars and the charset ("` =?<charset>?[QB]?<conten t>?=`").
$overhead = 8 + strlen($charset); $overhead = 8 + strlen($charset);
if ('mail' === $this->Mailer) { if ('mail' === $this->Mailer) {
$maxlen = static::MAIL_MAX_LINE_LENGTH - $overhead; $maxlen = static::MAIL_MAX_LINE_LENGTH - $overhead;
} else { } else {
$maxlen = static::MAX_LINE_LENGTH - $overhead; $maxlen = static::MAX_LINE_LENGTH - $overhead;
} }
// Select the encoding that produces the shortest output and/or prevents corruption. //Select the encoding that produces the shortest output and/or prevents corruption.
if ($matchcount > strlen($str) / 3) { if ($matchcount > strlen($str) / 3) {
// More than 1/3 of the content needs encoding, use B-encode. //More than 1/3 of the content needs encoding, use B-encode.
$encoding = 'B'; $encoding = 'B';
} elseif ($matchcount > 0) { } elseif ($matchcount > 0) {
// Less than 1/3 of the content needs encoding, use Q-encode. //Less than 1/3 of the content needs encoding, use Q-encode.
$encoding = 'Q'; $encoding = 'Q';
} elseif (strlen($str) > $maxlen) { } elseif (strlen($str) > $maxlen) {
// No encoding needed, but value exceeds max line length, use Q-enco de to prevent corruption. //No encoding needed, but value exceeds max line length, use Q-encod e to prevent corruption.
$encoding = 'Q'; $encoding = 'Q';
} else { } else {
// No reformatting needed //No reformatting needed
$encoding = false; $encoding = false;
} }
switch ($encoding) { switch ($encoding) {
case 'B': case 'B':
if ($this->hasMultiBytes($str)) { if ($this->hasMultiBytes($str)) {
// Use a custom function which correctly encodes and wraps l //Use a custom function which correctly encodes and wraps lo
ong ng
// multibyte strings without breaking lines within a charact //multibyte strings without breaking lines within a characte
er r
$encoded = $this->base64EncodeWrapMB($str, "\n"); $encoded = $this->base64EncodeWrapMB($str, "\n");
} else { } else {
$encoded = base64_encode($str); $encoded = base64_encode($str);
$maxlen -= $maxlen % 4; $maxlen -= $maxlen % 4;
$encoded = trim(chunk_split($encoded, $maxlen, "\n")); $encoded = trim(chunk_split($encoded, $maxlen, "\n"));
} }
$encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encod ing?\\1?=", $encoded); $encoded = preg_replace('/^(.*)$/m', ' =?' . $charset . "?$encod ing?\\1?=", $encoded);
break; break;
case 'Q': case 'Q':
$encoded = $this->encodeQ($str, $position); $encoded = $this->encodeQ($str, $position);
skipping to change at line 3351 skipping to change at line 3402
* @param string $str multi-byte text to wrap encode * @param string $str multi-byte text to wrap encode
* *
* @return bool * @return bool
*/ */
public function hasMultiBytes($str) public function hasMultiBytes($str)
{ {
if (function_exists('mb_strlen')) { if (function_exists('mb_strlen')) {
return strlen($str) > mb_strlen($str, $this->CharSet); return strlen($str) > mb_strlen($str, $this->CharSet);
} }
// Assume no multibytes (we can't handle without mbstring functions anyw ay) //Assume no multibytes (we can't handle without mbstring functions anywa y)
return false; return false;
} }
/** /**
* Does a string contain any 8-bit chars (in any charset)? * Does a string contain any 8-bit chars (in any charset)?
* *
* @param string $text * @param string $text
* *
* @return bool * @return bool
*/ */
skipping to change at line 3389 skipping to change at line 3440
public function base64EncodeWrapMB($str, $linebreak = null) public function base64EncodeWrapMB($str, $linebreak = null)
{ {
$start = '=?' . $this->CharSet . '?B?'; $start = '=?' . $this->CharSet . '?B?';
$end = '?='; $end = '?=';
$encoded = ''; $encoded = '';
if (null === $linebreak) { if (null === $linebreak) {
$linebreak = static::$LE; $linebreak = static::$LE;
} }
$mb_length = mb_strlen($str, $this->CharSet); $mb_length = mb_strlen($str, $this->CharSet);
// Each line must have length <= 75, including $start and $end //Each line must have length <= 75, including $start and $end
$length = 75 - strlen($start) - strlen($end); $length = 75 - strlen($start) - strlen($end);
// Average multi-byte ratio //Average multi-byte ratio
$ratio = $mb_length / strlen($str); $ratio = $mb_length / strlen($str);
// Base64 has a 4:3 ratio //Base64 has a 4:3 ratio
$avgLength = floor($length * $ratio * .75); $avgLength = floor($length * $ratio * .75);
$offset = 0; $offset = 0;
for ($i = 0; $i < $mb_length; $i += $offset) { for ($i = 0; $i < $mb_length; $i += $offset) {
$lookBack = 0; $lookBack = 0;
do { do {
$offset = $avgLength - $lookBack; $offset = $avgLength - $lookBack;
$chunk = mb_substr($str, $i, $offset, $this->CharSet); $chunk = mb_substr($str, $i, $offset, $this->CharSet);
$chunk = base64_encode($chunk); $chunk = base64_encode($chunk);
++$lookBack; ++$lookBack;
} while (strlen($chunk) > $length); } while (strlen($chunk) > $length);
$encoded .= $chunk . $linebreak; $encoded .= $chunk . $linebreak;
} }
// Chomp the last linefeed //Chomp the last linefeed
return substr($encoded, 0, -strlen($linebreak)); return substr($encoded, 0, -strlen($linebreak));
} }
/** /**
* Encode a string in quoted-printable format. * Encode a string in quoted-printable format.
* According to RFC2045 section 6.7. * According to RFC2045 section 6.7.
* *
* @param string $string The text to encode * @param string $string The text to encode
* *
* @return string * @return string
skipping to change at line 3437 skipping to change at line 3488
* *
* @see http://tools.ietf.org/html/rfc2047#section-4.2 * @see http://tools.ietf.org/html/rfc2047#section-4.2
* *
* @param string $str the text to encode * @param string $str the text to encode
* @param string $position Where the text is going to be used, see the RFC f or what that means * @param string $position Where the text is going to be used, see the RFC f or what that means
* *
* @return string * @return string
*/ */
public function encodeQ($str, $position = 'text') public function encodeQ($str, $position = 'text')
{ {
// There should not be any EOL in the string //There should not be any EOL in the string
$pattern = ''; $pattern = '';
$encoded = str_replace(["\r", "\n"], '', $str); $encoded = str_replace(["\r", "\n"], '', $str);
switch (strtolower($position)) { switch (strtolower($position)) {
case 'phrase': case 'phrase':
// RFC 2047 section 5.3 //RFC 2047 section 5.3
$pattern = '^A-Za-z0-9!*+\/ -'; $pattern = '^A-Za-z0-9!*+\/ -';
break; break;
/* /*
* RFC 2047 section 5.2. * RFC 2047 section 5.2.
* Build $pattern without including delimiters and [] * Build $pattern without including delimiters and []
*/ */
/* @noinspection PhpMissingBreakStatementInspection */ /* @noinspection PhpMissingBreakStatementInspection */
case 'comment': case 'comment':
$pattern = '\(\)"'; $pattern = '\(\)"';
/* Intentional fall through */ /* Intentional fall through */
case 'text': case 'text':
default: default:
// RFC 2047 section 5.1 //RFC 2047 section 5.1
// Replace every high ascii, control, =, ? and _ characters //Replace every high ascii, control, =, ? and _ characters
$pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $ pattern; $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $ pattern;
break; break;
} }
$matches = []; $matches = [];
if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) { if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) {
// If the string contains an '=', make sure it's the first thing we //If the string contains an '=', make sure it's the first thing we r
replace eplace
// so as to avoid double-encoding //so as to avoid double-encoding
$eqkey = array_search('=', $matches[0], true); $eqkey = array_search('=', $matches[0], true);
if (false !== $eqkey) { if (false !== $eqkey) {
unset($matches[0][$eqkey]); unset($matches[0][$eqkey]);
array_unshift($matches[0], '='); array_unshift($matches[0], '=');
} }
foreach (array_unique($matches[0]) as $char) { foreach (array_unique($matches[0]) as $char) {
$encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded); $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded);
} }
} }
// Replace spaces with _ (more readable than =20) //Replace spaces with _ (more readable than =20)
// RFC 2047 section 4.2(2) //RFC 2047 section 4.2(2)
return str_replace(' ', '_', $encoded); return str_replace(' ', '_', $encoded);
} }
/** /**
* Add a string or binary attachment (non-filesystem). * Add a string or binary attachment (non-filesystem).
* This method can be used to attach ascii or binary data, * This method can be used to attach ascii or binary data,
* such as a BLOB record from a database. * such as a BLOB record from a database.
* *
* @param string $string String attachment data * @param string $string String attachment data
* @param string $filename Name of the attachment * @param string $filename Name of the attachment
skipping to change at line 3501 skipping to change at line 3552
* @return bool True on successfully adding an attachment * @return bool True on successfully adding an attachment
*/ */
public function addStringAttachment( public function addStringAttachment(
$string, $string,
$filename, $filename,
$encoding = self::ENCODING_BASE64, $encoding = self::ENCODING_BASE64,
$type = '', $type = '',
$disposition = 'attachment' $disposition = 'attachment'
) { ) {
try { try {
// If a MIME type is not specified, try to work it out from the file name //If a MIME type is not specified, try to work it out from the file name
if ('' === $type) { if ('' === $type) {
$type = static::filenameToType($filename); $type = static::filenameToType($filename);
} }
if (!$this->validateEncoding($encoding)) { if (!$this->validateEncoding($encoding)) {
throw new Exception($this->lang('encoding') . $encoding); throw new Exception($this->lang('encoding') . $encoding);
} }
// Append to $attachment array //Append to $attachment array
$this->attachment[] = [ $this->attachment[] = [
0 => $string, 0 => $string,
1 => $filename, 1 => $filename,
2 => static::mb_pathinfo($filename, PATHINFO_BASENAME), 2 => static::mb_pathinfo($filename, PATHINFO_BASENAME),
3 => $encoding, 3 => $encoding,
4 => $type, 4 => $type,
5 => true, // isStringAttachment 5 => true, //isStringAttachment
6 => $disposition, 6 => $disposition,
7 => 0, 7 => 0,
]; ];
} catch (Exception $exc) { } catch (Exception $exc) {
$this->setError($exc->getMessage()); $this->setError($exc->getMessage());
$this->edebug($exc->getMessage()); $this->edebug($exc->getMessage());
if ($this->exceptions) { if ($this->exceptions) {
throw $exc; throw $exc;
} }
skipping to change at line 3568 skipping to change at line 3619
$name = '', $name = '',
$encoding = self::ENCODING_BASE64, $encoding = self::ENCODING_BASE64,
$type = '', $type = '',
$disposition = 'inline' $disposition = 'inline'
) { ) {
try { try {
if (!static::fileIsAccessible($path)) { if (!static::fileIsAccessible($path)) {
throw new Exception($this->lang('file_access') . $path, self::ST OP_CONTINUE); throw new Exception($this->lang('file_access') . $path, self::ST OP_CONTINUE);
} }
// If a MIME type is not specified, try to work it out from the file name //If a MIME type is not specified, try to work it out from the file name
if ('' === $type) { if ('' === $type) {
$type = static::filenameToType($path); $type = static::filenameToType($path);
} }
if (!$this->validateEncoding($encoding)) { if (!$this->validateEncoding($encoding)) {
throw new Exception($this->lang('encoding') . $encoding); throw new Exception($this->lang('encoding') . $encoding);
} }
$filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME); $filename = (string) static::mb_pathinfo($path, PATHINFO_BASENAME);
if ('' === $name) { if ('' === $name) {
$name = $filename; $name = $filename;
} }
// Append to $attachment array //Append to $attachment array
$this->attachment[] = [ $this->attachment[] = [
0 => $path, 0 => $path,
1 => $filename, 1 => $filename,
2 => $name, 2 => $name,
3 => $encoding, 3 => $encoding,
4 => $type, 4 => $type,
5 => false, // isStringAttachment 5 => false, //isStringAttachment
6 => $disposition, 6 => $disposition,
7 => $cid, 7 => $cid,
]; ];
} catch (Exception $exc) { } catch (Exception $exc) {
$this->setError($exc->getMessage()); $this->setError($exc->getMessage());
$this->edebug($exc->getMessage()); $this->edebug($exc->getMessage());
if ($this->exceptions) { if ($this->exceptions) {
throw $exc; throw $exc;
} }
skipping to change at line 3634 skipping to change at line 3685
*/ */
public function addStringEmbeddedImage( public function addStringEmbeddedImage(
$string, $string,
$cid, $cid,
$name = '', $name = '',
$encoding = self::ENCODING_BASE64, $encoding = self::ENCODING_BASE64,
$type = '', $type = '',
$disposition = 'inline' $disposition = 'inline'
) { ) {
try { try {
// If a MIME type is not specified, try to work it out from the name //If a MIME type is not specified, try to work it out from the name
if ('' === $type && !empty($name)) { if ('' === $type && !empty($name)) {
$type = static::filenameToType($name); $type = static::filenameToType($name);
} }
if (!$this->validateEncoding($encoding)) { if (!$this->validateEncoding($encoding)) {
throw new Exception($this->lang('encoding') . $encoding); throw new Exception($this->lang('encoding') . $encoding);
} }
// Append to $attachment array //Append to $attachment array
$this->attachment[] = [ $this->attachment[] = [
0 => $string, 0 => $string,
1 => $name, 1 => $name,
2 => $name, 2 => $name,
3 => $encoding, 3 => $encoding,
4 => $type, 4 => $type,
5 => true, // isStringAttachment 5 => true, //isStringAttachment
6 => $disposition, 6 => $disposition,
7 => $cid, 7 => $cid,
]; ];
} catch (Exception $exc) { } catch (Exception $exc) {
$this->setError($exc->getMessage()); $this->setError($exc->getMessage());
$this->edebug($exc->getMessage()); $this->edebug($exc->getMessage());
if ($this->exceptions) { if ($this->exceptions) {
throw $exc; throw $exc;
} }
skipping to change at line 3870 skipping to change at line 3921
$this->ErrorInfo = $msg; $this->ErrorInfo = $msg;
} }
/** /**
* Return an RFC 822 formatted date. * Return an RFC 822 formatted date.
* *
* @return string * @return string
*/ */
public static function rfcDate() public static function rfcDate()
{ {
// Set the time zone to whatever the default is to avoid 500 errors //Set the time zone to whatever the default is to avoid 500 errors
// Will default to UTC if it's not set properly in php.ini //Will default to UTC if it's not set properly in php.ini
date_default_timezone_set(@date_default_timezone_get()); date_default_timezone_set(@date_default_timezone_get());
return date('D, j M Y H:i:s O'); return date('D, j M Y H:i:s O');
} }
/** /**
* Get the server hostname. * Get the server hostname.
* Returns 'localhost.localdomain' if unknown. * Returns 'localhost.localdomain' if unknown.
* *
* @return string * @return string
skipping to change at line 3949 skipping to change at line 4000
/** /**
* Get an error message in the current language. * Get an error message in the current language.
* *
* @param string $key * @param string $key
* *
* @return string * @return string
*/ */
protected function lang($key) protected function lang($key)
{ {
if (count($this->language) < 1) { if (count($this->language) < 1) {
$this->setLanguage(); // set the default language $this->setLanguage(); //Set the default language
} }
if (array_key_exists($key, $this->language)) { if (array_key_exists($key, $this->language)) {
if ('smtp_connect_failed' === $key) { if ('smtp_connect_failed' === $key) {
//Include a link to troubleshooting docs on SMTP connection fail //Include a link to troubleshooting docs on SMTP connection fail
ure ure.
//this is by far the biggest cause of support questions //This is by far the biggest cause of support questions
//but it's usually not PHPMailer's fault. //but it's usually not PHPMailer's fault.
return $this->language[$key] . ' https://github.com/PHPMailer/PH PMailer/wiki/Troubleshooting'; return $this->language[$key] . ' https://github.com/PHPMailer/PH PMailer/wiki/Troubleshooting';
} }
return $this->language[$key]; return $this->language[$key];
} }
//Return the key as a fallback //Return the key as a fallback
return $key; return $key;
} }
skipping to change at line 3990 skipping to change at line 4041
* both header name and value (name:value). * both header name and value (name:value).
* *
* @param string $name Custom header name * @param string $name Custom header name
* @param string|null $value Header value * @param string|null $value Header value
* *
* @throws Exception * @throws Exception
*/ */
public function addCustomHeader($name, $value = null) public function addCustomHeader($name, $value = null)
{ {
if (null === $value && strpos($name, ':') !== false) { if (null === $value && strpos($name, ':') !== false) {
// Value passed in as name:value //Value passed in as name:value
list($name, $value) = explode(':', $name, 2); list($name, $value) = explode(':', $name, 2);
} }
$name = trim($name); $name = trim($name);
$value = trim($value); $value = trim($value);
//Ensure name is not empty, and that neither name nor value contain line breaks //Ensure name is not empty, and that neither name nor value contain line breaks
if (empty($name) || strpbrk($name . $value, "\r\n") !== false) { if (empty($name) || strpbrk($name . $value, "\r\n") !== false) {
if ($this->exceptions) { if ($this->exceptions) {
throw new Exception('Invalid header name or value'); throw new Exception('Invalid header name or value');
} }
skipping to change at line 4044 skipping to change at line 4095
* *
* @throws Exception * @throws Exception
* *
* @see PHPMailer::html2text() * @see PHPMailer::html2text()
*/ */
public function msgHTML($message, $basedir = '', $advanced = false) public function msgHTML($message, $basedir = '', $advanced = false)
{ {
preg_match_all('/(?<!-)(src|background)=["\'](.*)["\']/Ui', $message, $i mages); preg_match_all('/(?<!-)(src|background)=["\'](.*)["\']/Ui', $message, $i mages);
if (array_key_exists(2, $images)) { if (array_key_exists(2, $images)) {
if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) { if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {
// Ensure $basedir has a trailing / //Ensure $basedir has a trailing /
$basedir .= '/'; $basedir .= '/';
} }
foreach ($images[2] as $imgindex => $url) { foreach ($images[2] as $imgindex => $url) {
// Convert data URIs into embedded images //Convert data URIs into embedded images
//e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAA ABAAEAAAICTAEAOw==" //e.g. "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAA ABAAEAAAICTAEAOw=="
$match = []; $match = [];
if (preg_match('#^data:(image/(?:jpe?g|gif|png));?(base64)?,(.+) #', $url, $match)) { if (preg_match('#^data:(image/(?:jpe?g|gif|png));?(base64)?,(.+) #', $url, $match)) {
if (count($match) === 4 && static::ENCODING_BASE64 === $matc h[2]) { if (count($match) === 4 && static::ENCODING_BASE64 === $matc h[2]) {
$data = base64_decode($match[3]); $data = base64_decode($match[3]);
} elseif ('' === $match[2]) { } elseif ('' === $match[2]) {
$data = rawurldecode($match[3]); $data = rawurldecode($match[3]);
} else { } else {
//Not recognised so leave it alone //Not recognised so leave it alone
continue; continue;
} }
//Hash the decoded data, not the URL, so that the same data- URI image used in multiple places //Hash the decoded data, not the URL, so that the same data- URI image used in multiple places
//will only be embedded once, even if it used a different en coding //will only be embedded once, even if it used a different en coding
$cid = substr(hash('sha256', $data), 0, 32) . '@phpmailer.0' ; // RFC2392 S 2 $cid = substr(hash('sha256', $data), 0, 32) . '@phpmailer.0' ; //RFC2392 S 2
if (!$this->cidExists($cid)) { if (!$this->cidExists($cid)) {
$this->addStringEmbeddedImage( $this->addStringEmbeddedImage(
$data, $data,
$cid, $cid,
'embed' . $imgindex, 'embed' . $imgindex,
static::ENCODING_BASE64, static::ENCODING_BASE64,
$match[1] $match[1]
); );
} }
$message = str_replace( $message = str_replace(
$images[0][$imgindex], $images[0][$imgindex],
$images[1][$imgindex] . '="cid:' . $cid . '"', $images[1][$imgindex] . '="cid:' . $cid . '"',
$message $message
); );
continue; continue;
} }
if ( if (
// Only process relative URLs if a basedir is provided (i.e. no absolute local paths) //Only process relative URLs if a basedir is provided (i.e. no absolute local paths)
!empty($basedir) !empty($basedir)
// Ignore URLs containing parent dir traversal (..) //Ignore URLs containing parent dir traversal (..)
&& (strpos($url, '..') === false) && (strpos($url, '..') === false)
// Do not change urls that are already inline images //Do not change urls that are already inline images
&& 0 !== strpos($url, 'cid:') && 0 !== strpos($url, 'cid:')
// Do not change absolute URLs, including anonymous protocol //Do not change absolute URLs, including anonymous protocol
&& !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url) && !preg_match('#^[a-z][a-z0-9+.-]*:?//#i', $url)
) { ) {
$filename = static::mb_pathinfo($url, PATHINFO_BASENAME); $filename = static::mb_pathinfo($url, PATHINFO_BASENAME);
$directory = dirname($url); $directory = dirname($url);
if ('.' === $directory) { if ('.' === $directory) {
$directory = ''; $directory = '';
} }
// RFC2392 S 2 //RFC2392 S 2
$cid = substr(hash('sha256', $url), 0, 32) . '@phpmailer.0'; $cid = substr(hash('sha256', $url), 0, 32) . '@phpmailer.0';
if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) { if (strlen($basedir) > 1 && '/' !== substr($basedir, -1)) {
$basedir .= '/'; $basedir .= '/';
} }
if (strlen($directory) > 1 && '/' !== substr($directory, -1) ) { if (strlen($directory) > 1 && '/' !== substr($directory, -1) ) {
$directory .= '/'; $directory .= '/';
} }
if ( if (
$this->addEmbeddedImage( $this->addEmbeddedImage(
$basedir . $directory . $filename, $basedir . $directory . $filename,
skipping to change at line 4122 skipping to change at line 4173
$message = preg_replace( $message = preg_replace(
'/' . $images[1][$imgindex] . '=["\']' . preg_quote( $url, '/') . '["\']/Ui', '/' . $images[1][$imgindex] . '=["\']' . preg_quote( $url, '/') . '["\']/Ui',
$images[1][$imgindex] . '="cid:' . $cid . '"', $images[1][$imgindex] . '="cid:' . $cid . '"',
$message $message
); );
} }
} }
} }
} }
$this->isHTML(); $this->isHTML();
// Convert all message body line breaks to LE, makes quoted-printable en coding work much better //Convert all message body line breaks to LE, makes quoted-printable enc oding work much better
$this->Body = static::normalizeBreaks($message); $this->Body = static::normalizeBreaks($message);
$this->AltBody = static::normalizeBreaks($this->html2text($message, $adv anced)); $this->AltBody = static::normalizeBreaks($this->html2text($message, $adv anced));
if (!$this->alternativeExists()) { if (!$this->alternativeExists()) {
$this->AltBody = 'This is an HTML-only message. To view it, activate HTML in your email application.' $this->AltBody = 'This is an HTML-only message. To view it, activate HTML in your email application.'
. static::$LE; . static::$LE;
} }
return $this->Body; return $this->Body;
} }
/** /**
* Convert an HTML string into plain text. * Convert an HTML string into plain text.
* This is used by msgHTML(). * This is used by msgHTML().
* Note - older versions of this function used a bundled advanced converter * Note - older versions of this function used a bundled advanced converter
* which was removed for license reasons in #232. * which was removed for license reasons in #232.
* Example usage: * Example usage:
* *
* ```php * ```php
* // Use default conversion * //Use default conversion
* $plain = $mail->html2text($html); * $plain = $mail->html2text($html);
* // Use your own custom converter * //Use your own custom converter
* $plain = $mail->html2text($html, function($html) { * $plain = $mail->html2text($html, function($html) {
* $converter = new MyHtml2text($html); * $converter = new MyHtml2text($html);
* return $converter->get_text(); * return $converter->get_text();
* }); * });
* ``` * ```
* *
* @param string $html The HTML text to convert * @param string $html The HTML text to convert
* @param bool|callable $advanced Any boolean value to use the internal conv erter, * @param bool|callable $advanced Any boolean value to use the internal conv erter,
* or provide your own callable for custom co nversion * or provide your own callable for custom co nversion
* *
skipping to change at line 4310 skipping to change at line 4361
/** /**
* Map a file name to a MIME type. * Map a file name to a MIME type.
* Defaults to 'application/octet-stream', i.e.. arbitrary binary data. * Defaults to 'application/octet-stream', i.e.. arbitrary binary data.
* *
* @param string $filename A file name or full path, does not need to exist as a file * @param string $filename A file name or full path, does not need to exist as a file
* *
* @return string * @return string
*/ */
public static function filenameToType($filename) public static function filenameToType($filename)
{ {
// In case the path is a URL, strip any query string before getting exte nsion //In case the path is a URL, strip any query string before getting exten sion
$qpos = strpos($filename, '?'); $qpos = strpos($filename, '?');
if (false !== $qpos) { if (false !== $qpos) {
$filename = substr($filename, 0, $qpos); $filename = substr($filename, 0, $qpos);
} }
$ext = static::mb_pathinfo($filename, PATHINFO_EXTENSION); $ext = static::mb_pathinfo($filename, PATHINFO_EXTENSION);
return static::_mime_types($ext); return static::_mime_types($ext);
} }
/** /**
skipping to change at line 4421 skipping to change at line 4472
* @param string $text * @param string $text
* @param string $breaktype What kind of line break to use; defaults to stat ic::$LE * @param string $breaktype What kind of line break to use; defaults to stat ic::$LE
* *
* @return string * @return string
*/ */
public static function normalizeBreaks($text, $breaktype = null) public static function normalizeBreaks($text, $breaktype = null)
{ {
if (null === $breaktype) { if (null === $breaktype) {
$breaktype = static::$LE; $breaktype = static::$LE;
} }
// Normalise to \n //Normalise to \n
$text = str_replace([self::CRLF, "\r"], "\n", $text); $text = str_replace([self::CRLF, "\r"], "\n", $text);
// Now convert LE as needed //Now convert LE as needed
if ("\n" !== $breaktype) { if ("\n" !== $breaktype) {
$text = str_replace("\n", $breaktype, $text); $text = str_replace("\n", $breaktype, $text);
} }
return $text; return $text;
} }
/** /**
* Remove trailing breaks from a string. * Remove trailing breaks from a string.
* *
skipping to change at line 4529 skipping to change at line 4580
} }
$privKeyStr = !empty($this->DKIM_private_string) ? $privKeyStr = !empty($this->DKIM_private_string) ?
$this->DKIM_private_string : $this->DKIM_private_string :
file_get_contents($this->DKIM_private); file_get_contents($this->DKIM_private);
if ('' !== $this->DKIM_passphrase) { if ('' !== $this->DKIM_passphrase) {
$privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphr ase); $privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphr ase);
} else { } else {
$privKey = openssl_pkey_get_private($privKeyStr); $privKey = openssl_pkey_get_private($privKeyStr);
} }
if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryp tion')) { if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryp tion')) {
if (PHP_MAJOR_VERSION < 8) { if (\PHP_MAJOR_VERSION < 8) {
openssl_pkey_free($privKey); openssl_pkey_free($privKey);
} }
return base64_encode($signature); return base64_encode($signature);
} }
if (PHP_MAJOR_VERSION < 8) { if (\PHP_MAJOR_VERSION < 8) {
openssl_pkey_free($privKey); openssl_pkey_free($privKey);
} }
return ''; return '';
} }
/** /**
* Generate a DKIM canonicalization header. * Generate a DKIM canonicalization header.
* Uses the 'relaxed' algorithm from RFC6376 section 3.4.2. * Uses the 'relaxed' algorithm from RFC6376 section 3.4.2.
* Canonicalized headers should *always* use CRLF, regardless of mailer sett ing. * Canonicalized headers should *always* use CRLF, regardless of mailer sett ing.
skipping to change at line 4602 skipping to change at line 4653
* *
* @param string $body Message Body * @param string $body Message Body
* *
* @return string * @return string
*/ */
public function DKIM_BodyC($body) public function DKIM_BodyC($body)
{ {
if (empty($body)) { if (empty($body)) {
return self::CRLF; return self::CRLF;
} }
// Normalize line endings to CRLF //Normalize line endings to CRLF
$body = static::normalizeBreaks($body, self::CRLF); $body = static::normalizeBreaks($body, self::CRLF);
//Reduce multiple trailing line breaks to a single one //Reduce multiple trailing line breaks to a single one
return static::stripTrailingWSP($body) . self::CRLF; return static::stripTrailingWSP($body) . self::CRLF;
} }
/** /**
* Create the DKIM header and body in a new message header. * Create the DKIM header and body in a new message header.
* *
* @param string $headers_line Header lines * @param string $headers_line Header lines
* @param string $subject Subject * @param string $subject Subject
* @param string $body Body * @param string $body Body
* *
* @throws Exception * @throws Exception
* *
* @return string * @return string
*/ */
public function DKIM_Add($headers_line, $subject, $body) public function DKIM_Add($headers_line, $subject, $body)
{ {
$DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms $DKIMsignatureType = 'rsa-sha256'; //Signature & hash algorithms
$DKIMcanonicalization = 'relaxed/simple'; // Canonicalization methods of $DKIMcanonicalization = 'relaxed/simple'; //Canonicalization methods of
header & body header & body
$DKIMquery = 'dns/txt'; // Query method $DKIMquery = 'dns/txt'; //Query method
$DKIMtime = time(); $DKIMtime = time();
//Always sign these headers without being asked //Always sign these headers without being asked
//Recommended list from https://tools.ietf.org/html/rfc6376#section-5.4. 1 //Recommended list from https://tools.ietf.org/html/rfc6376#section-5.4. 1
$autoSignHeaders = [ $autoSignHeaders = [
'from', 'from',
'to', 'to',
'cc', 'cc',
'date', 'date',
'subject', 'subject',
'reply-to', 'reply-to',
skipping to change at line 4725 skipping to change at line 4776
} else { } else {
$copiedHeaderFields .= $copiedHeader; $copiedHeaderFields .= $copiedHeader;
} }
$first = false; $first = false;
} }
$copiedHeaderFields .= ';' . static::$LE; $copiedHeaderFields .= ';' . static::$LE;
} }
$headerKeys = ' h=' . implode(':', $headersToSignKeys) . ';' . static::$ LE; $headerKeys = ' h=' . implode(':', $headersToSignKeys) . ';' . static::$ LE;
$headerValues = implode(static::$LE, $headersToSign); $headerValues = implode(static::$LE, $headersToSign);
$body = $this->DKIM_BodyC($body); $body = $this->DKIM_BodyC($body);
$DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 o //Base64 of packed binary SHA-256 hash of body
f packed binary SHA-256 hash of body $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body)));
$ident = ''; $ident = '';
if ('' !== $this->DKIM_identity) { if ('' !== $this->DKIM_identity) {
$ident = ' i=' . $this->DKIM_identity . ';' . static::$LE; $ident = ' i=' . $this->DKIM_identity . ';' . static::$LE;
} }
//The DKIM-Signature header is included in the signature *except for* th e value of the `b` tag //The DKIM-Signature header is included in the signature *except for* th e value of the `b` tag
//which is appended after calculating the signature //which is appended after calculating the signature
//https://tools.ietf.org/html/rfc6376#section-3.5 //https://tools.ietf.org/html/rfc6376#section-3.5
$dkimSignatureHeader = 'DKIM-Signature: v=1;' . $dkimSignatureHeader = 'DKIM-Signature: v=1;' .
' d=' . $this->DKIM_domain . ';' . ' d=' . $this->DKIM_domain . ';' .
' s=' . $this->DKIM_selector . ';' . static::$LE . ' s=' . $this->DKIM_selector . ';' . static::$LE .
 End of changes. 142 change blocks. 
183 lines changed or deleted 242 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)