Index: wp-includes/class-smtp.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- wp-includes/class-smtp.php (revision a3ea7ea2e977864e2402776508b92f521ad53260)
+++ wp-includes/class-smtp.php (revision )
@@ -30,7 +30,7 @@
* The PHPMailer SMTP version number.
* @var string
*/
- const VERSION = '5.2.14';
+ const VERSION = '5.2.19';
/**
* SMTP line break constant.
@@ -81,7 +81,7 @@
* @deprecated Use the `VERSION` constant instead
* @see SMTP::VERSION
*/
- public $Version = '5.2.14';
+ public $Version = '5.2.19';
/**
* SMTP server port number.
@@ -150,6 +150,17 @@
*/
public $Timelimit = 300;
+ /**
+ * @var array patterns to extract smtp transaction id from smtp reply
+ * Only first capture group will be use, use non-capturing group to deal with it
+ * Extend this class to override this property to fulfil your needs.
+ */
+ protected $smtp_transaction_id_patterns = array(
+ 'exim' => '/[0-9]{3} OK id=(.*)/',
+ 'sendmail' => '/[0-9]{3} 2.0.0 (.*) Message/',
+ 'postfix' => '/[0-9]{3} 2.0.0 Ok: queued as (.*)/'
+ );
+
/**
* The socket for the server connection.
* @var resource
@@ -206,7 +217,7 @@
}
//Avoid clash with built-in function names
if (!in_array($this->Debugoutput, array('error_log', 'html', 'echo')) and is_callable($this->Debugoutput)) {
- call_user_func($this->Debugoutput, $str, $this->do_debug);
+ call_user_func($this->Debugoutput, $str, $level);
return;
}
switch ($this->Debugoutput) {
@@ -217,21 +228,21 @@
case 'html':
//Cleans up output a bit for a better looking, HTML-safe output
echo htmlentities(
- preg_replace('/[\r\n]+/', '', $str),
- ENT_QUOTES,
- 'UTF-8'
- )
- . "
\n";
+ preg_replace('/[\r\n]+/', '', $str),
+ ENT_QUOTES,
+ 'UTF-8'
+ )
+ . "
\n";
break;
case 'echo':
default:
//Normalize line breaks
$str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
- "\n",
- "\n \t ",
- trim($str)
- )."\n";
+ "\n",
+ "\n \t ",
+ trim($str)
+ )."\n";
}
}
@@ -272,8 +283,8 @@
$errstr = '';
if ($streamok) {
$socket_context = stream_context_create($options);
- //Suppress errors; connection failures are handled at a higher level
- $this->smtp_conn = @stream_socket_client(
+ set_error_handler(array($this, 'errorHandler'));
+ $this->smtp_conn = stream_socket_client(
$host . ":" . $port,
$errno,
$errstr,
@@ -281,12 +292,14 @@
STREAM_CLIENT_CONNECT,
$socket_context
);
+ restore_error_handler();
} else {
//Fall back to fsockopen which should work in more places, but is missing some features
$this->edebug(
"Connection: stream_socket_client not available, falling back to fsockopen",
self::DEBUG_CONNECTION
);
+ set_error_handler(array($this, 'errorHandler'));
$this->smtp_conn = fsockopen(
$host,
$port,
@@ -294,6 +307,7 @@
$errstr,
$timeout
);
+ restore_error_handler();
}
// Verify we connected properly
if (!is_resource($this->smtp_conn)) {
@@ -336,11 +350,22 @@
if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) {
return false;
}
+
+ //Allow the best TLS version(s) we can
+ $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT;
+
+ //PHP 5.6.7 dropped inclusion of TLS 1.1 and 1.2 in STREAM_CRYPTO_METHOD_TLS_CLIENT
+ //so add them back in manually if we can
+ if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) {
+ $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
+ $crypto_method |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT;
+ }
+
// Begin encrypted connection
if (!stream_socket_enable_crypto(
$this->smtp_conn,
true,
- STREAM_CRYPTO_METHOD_TLS_CLIENT
+ $crypto_method
)) {
return false;
}
@@ -373,7 +398,7 @@
}
if (array_key_exists('EHLO', $this->server_caps)) {
- // SMTP extensions are available. Let's try to find a proper authentication method
+ // SMTP extensions are available. Let's try to find a proper authentication method
if (!array_key_exists('AUTH', $this->server_caps)) {
$this->setError('Authentication is not allowed at this stage');
@@ -389,7 +414,7 @@
);
if (empty($authtype)) {
- foreach (array('LOGIN', 'CRAM-MD5', 'PLAIN') as $method) {
+ foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN', 'NTLM', 'XOAUTH2') as $method) {
if (in_array($method, $this->server_caps['AUTH'])) {
$authtype = $method;
break;
@@ -437,6 +462,69 @@
return false;
}
break;
+ case 'XOAUTH2':
+ //If the OAuth Instance is not set. Can be a case when PHPMailer is used
+ //instead of PHPMailerOAuth
+ if (is_null($OAuth)) {
+ return false;
+ }
+ $oauth = $OAuth->getOauth64();
+
+ // Start authentication
+ if (!$this->sendCommand('AUTH', 'AUTH XOAUTH2 ' . $oauth, 235)) {
+ return false;
+ }
+ break;
+ case 'NTLM':
+ /*
+ * ntlm_sasl_client.php
+ * Bundled with Permission
+ *
+ * How to telnet in windows:
+ * http://technet.microsoft.com/en-us/library/aa995718%28EXCHG.65%29.aspx
+ * PROTOCOL Docs http://curl.haxx.se/rfc/ntlm.html#ntlmSmtpAuthentication
+ */
+ require_once 'extras/ntlm_sasl_client.php';
+ $temp = new stdClass;
+ $ntlm_client = new ntlm_sasl_client_class;
+ //Check that functions are available
+ if (!$ntlm_client->initialize($temp)) {
+ $this->setError($temp->error);
+ $this->edebug(
+ 'You need to enable some modules in your php.ini file: '
+ . $this->error['error'],
+ self::DEBUG_CLIENT
+ );
+ return false;
+ }
+ //msg1
+ $msg1 = $ntlm_client->typeMsg1($realm, $workstation); //msg1
+
+ if (!$this->sendCommand(
+ 'AUTH NTLM',
+ 'AUTH NTLM ' . base64_encode($msg1),
+ 334
+ )
+ ) {
+ return false;
+ }
+ //Though 0 based, there is a white space after the 3 digit number
+ //msg2
+ $challenge = substr($this->last_reply, 3);
+ $challenge = base64_decode($challenge);
+ $ntlm_res = $ntlm_client->NTLMResponse(
+ substr($challenge, 24, 8),
+ $password
+ );
+ //msg3
+ $msg3 = $ntlm_client->typeMsg3(
+ $ntlm_res,
+ $username,
+ $realm,
+ $workstation
+ );
+ // send encoded username
+ return $this->sendCommand('Username', base64_encode($msg3), 235);
case 'CRAM-MD5':
// Start authentication
if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) {
@@ -673,7 +761,7 @@
protected function parseHelloFields($type)
{
$this->server_caps = array();
- $lines = explode("\n", $this->last_reply);
+ $lines = explode("\n", $this->helo_rply);
foreach ($lines as $n => $s) {
//First 4 chars contain response code followed by - or space
@@ -1115,4 +1203,47 @@
{
return $this->Timeout;
}
-}
+
+ /**
+ * Reports an error number and string.
+ * @param integer $errno The error number returned by PHP.
+ * @param string $errmsg The error message returned by PHP.
+ */
+ protected function errorHandler($errno, $errmsg)
+ {
+ $notice = 'Connection: Failed to connect to server.';
+ $this->setError(
+ $notice,
+ $errno,
+ $errmsg
+ );
+ $this->edebug(
+ $notice . ' Error number ' . $errno . '. "Error notice: ' . $errmsg,
+ self::DEBUG_CONNECTION
+ );
+ }
+
+ /**
+ * Will return the ID of the last smtp transaction based on a list of patterns provided
+ * in SMTP::$smtp_transaction_id_patterns.
+ * If no reply has been received yet, it will return null.
+ * If no pattern has been matched, it will return false.
+ * @return bool|null|string
+ */
+ public function getLastTransactionID()
+ {
+ $reply = $this->getLastReply();
+
+ if (empty($reply)) {
+ return null;
+ }
+
+ foreach($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) {
+ if(preg_match($smtp_transaction_id_pattern, $reply, $matches)) {
+ return $matches[1];
+ }
+ }
+
+ return false;
+ }
+}
\ No newline at end of file
Index: wp-includes/class-phpmailer.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- wp-includes/class-phpmailer.php (revision a3ea7ea2e977864e2402776508b92f521ad53260)
+++ wp-includes/class-phpmailer.php (revision )
@@ -31,7 +31,7 @@
* The PHPMailer Version number.
* @var string
*/
- public $Version = '5.2.14';
+ public $Version = '5.2.19';
/**
* Email priority.
@@ -201,6 +201,9 @@
/**
* An ID to be used in the Message-ID header.
* If empty, a unique id will be generated.
+ * You can set your own, but it must be in the format "",
+ * as defined in RFC5322 section 3.6.4 or it will be ignored.
+ * @see https://tools.ietf.org/html/rfc5322#section-3.6.4
* @var string
*/
public $MessageID = '';
@@ -285,7 +288,7 @@
/**
* SMTP auth type.
- * Options are LOGIN (default), PLAIN, NTLM, CRAM-MD5
+ * Options are CRAM-MD5, LOGIN, PLAIN, NTLM, XOAUTH2, attempted in that order if not specified
* @var string
*/
public $AuthType = '';
@@ -352,6 +355,7 @@
/**
* Whether to split multiple to addresses into multiple messages
* or send them all in one message.
+ * Only supported in `mail` and `sendmail` transports, not in SMTP.
* @var boolean
*/
public $SingleTo = false;
@@ -394,7 +398,7 @@
/**
* DKIM Identity.
- * Usually the email address used as the source of the email
+ * Usually the email address used as the source of the email.
* @var string
*/
public $DKIM_identity = '';
@@ -419,6 +423,13 @@
*/
public $DKIM_private = '';
+ /**
+ * DKIM private key string.
+ * If set, takes precedence over `$DKIM_private`.
+ * @var string
+ */
+ public $DKIM_private_string = '';
+
/**
* Callback Action function name.
*
@@ -446,6 +457,15 @@
*/
public $XMailer = '';
+ /**
+ * Which validator to use by default when validating email addresses.
+ * May be a callable to inject your own validator, but there are several built-in validators.
+ * @see PHPMailer::validateAddress()
+ * @var string|callable
+ * @static
+ */
+ public static $validator = 'auto';
+
/**
* An instance of the SMTP sender class.
* @var SMTP
@@ -634,9 +654,11 @@
* Constructor.
* @param boolean $exceptions Should we throw external exceptions?
*/
- public function __construct($exceptions = false)
+ public function __construct($exceptions = null)
{
- $this->exceptions = (boolean)$exceptions;
+ if ($exceptions !== null) {
+ $this->exceptions = (boolean)$exceptions;
+ }
}
/**
@@ -645,9 +667,7 @@
public function __destruct()
{
//Close any open SMTP connection nicely
- if ($this->Mailer == 'smtp') {
- $this->smtpClose();
- }
+ $this->smtpClose();
}
/**
@@ -671,14 +691,16 @@
} else {
$subject = $this->encodeHeader($this->secureHeader($subject));
}
- if (ini_get('safe_mode') || !($this->UseSendmailOptions)) {
+
+ //Can't use additional_parameters in safe_mode, calling mail() with null params breaks
+ //@link http://php.net/manual/en/function.mail.php
+ if (ini_get('safe_mode') or !$this->UseSendmailOptions or is_null($params)) {
$result = @mail($to, $subject, $body, $header);
} else {
$result = @mail($to, $subject, $body, $header, $params);
}
return $result;
}
-
/**
* Output debugging info via user-defined method.
* Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug).
@@ -704,21 +726,21 @@
case 'html':
//Cleans up output a bit for a better looking, HTML-safe output
echo htmlentities(
- preg_replace('/[\r\n]+/', '', $str),
- ENT_QUOTES,
- 'UTF-8'
- )
- . "
\n";
+ preg_replace('/[\r\n]+/', '', $str),
+ ENT_QUOTES,
+ 'UTF-8'
+ )
+ . "
\n";
break;
case 'echo':
default:
//Normalize line breaks
- $str = preg_replace('/(\r\n|\r|\n)/ms', "\n", $str);
+ $str = preg_replace('/\r\n?/ms', "\n", $str);
echo gmdate('Y-m-d H:i:s') . "\t" . str_replace(
- "\n",
- "\n \t ",
- trim($str)
- ) . "\n";
+ "\n",
+ "\n \t ",
+ trim($str)
+ ) . "\n";
}
}
@@ -850,7 +872,7 @@
$name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim
if (($pos = strrpos($address, '@')) === false) {
// At-sign is misssing.
- $error_message = $this->lang('invalid_address') . $address;
+ $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
@@ -900,7 +922,7 @@
return false;
}
if (!$this->validateAddress($address)) {
- $error_message = $this->lang('invalid_address') . $address;
+ $error_message = $this->lang('invalid_address') . " (addAnAddress $kind): $address";
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
@@ -923,6 +945,61 @@
return false;
}
+ /**
+ * Parse and validate a string containing one or more RFC822-style comma-separated email addresses
+ * of the form "display name " into an array of name/address pairs.
+ * Uses the imap_rfc822_parse_adrlist function if the IMAP extension is available.
+ * Note that quotes in the name part are removed.
+ * @param string $addrstr The address list string
+ * @param bool $useimap Whether to use the IMAP extension to parse the list
+ * @return array
+ * @link http://www.andrew.cmu.edu/user/agreen1/testing/mrbs/web/Mail/RFC822.php A more careful implementation
+ */
+ public function parseAddresses($addrstr, $useimap = true)
+ {
+ $addresses = array();
+ if ($useimap and function_exists('imap_rfc822_parse_adrlist')) {
+ //Use this built-in parser if it's available
+ $list = imap_rfc822_parse_adrlist($addrstr, '');
+ foreach ($list as $address) {
+ if ($address->host != '.SYNTAX-ERROR.') {
+ if ($this->validateAddress($address->mailbox . '@' . $address->host)) {
+ $addresses[] = array(
+ 'name' => (property_exists($address, 'personal') ? $address->personal : ''),
+ 'address' => $address->mailbox . '@' . $address->host
+ );
+ }
+ }
+ }
+ } else {
+ //Use this simpler parser
+ $list = explode(',', $addrstr);
+ foreach ($list as $address) {
+ $address = trim($address);
+ //Is there a separate name part?
+ if (strpos($address, '<') === false) {
+ //No separate name, just use the whole thing
+ if ($this->validateAddress($address)) {
+ $addresses[] = array(
+ 'name' => '',
+ 'address' => $address
+ );
+ }
+ } else {
+ list($name, $email) = explode('<', $address);
+ $email = trim(str_replace('>', '', $email));
+ if ($this->validateAddress($email)) {
+ $addresses[] = array(
+ 'name' => trim(str_replace(array('"', "'"), '', $name)),
+ 'address' => $email
+ );
+ }
+ }
+ }
+ }
+ return $addresses;
+ }
+
/**
* Set the From and FromName properties.
* @param string $address
@@ -939,7 +1016,7 @@
if (($pos = strrpos($address, '@')) === false or
(!$this->has8bitChars(substr($address, ++$pos)) or !$this->idnSupported()) and
!$this->validateAddress($address)) {
- $error_message = $this->lang('invalid_address') . $address;
+ $error_message = $this->lang('invalid_address') . " (setFrom) $address";
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
@@ -972,19 +1049,30 @@
/**
* Check that a string looks like an email address.
* @param string $address The email address to check
- * @param string $patternselect A selector for the validation pattern to use :
+ * @param string|callable $patternselect A selector for the validation pattern to use :
* * `auto` Pick best pattern automatically;
* * `pcre8` Use the squiloople.com pattern, requires PCRE > 8.0, PHP >= 5.3.2, 5.2.14;
* * `pcre` Use old PCRE implementation;
* * `php` Use PHP built-in FILTER_VALIDATE_EMAIL;
* * `html5` Use the pattern given by the HTML5 spec for 'email' type form input elements.
* * `noregex` Don't use a regex: super fast, really dumb.
+ * Alternatively you may pass in a callable to inject your own validator, for example:
+ * PHPMailer::validateAddress('user@example.com', function($address) {
+ * return (strpos($address, '@') !== false);
+ * });
+ * You can also set the PHPMailer::$validator static to a callable, allowing built-in methods to use your validator.
* @return boolean
* @static
* @access public
*/
- public static function validateAddress($address, $patternselect = 'auto')
+ public static function validateAddress($address, $patternselect = null)
{
+ if (is_null($patternselect)) {
+ $patternselect = self::$validator;
+ }
+ if (is_callable($patternselect)) {
+ return call_user_func($patternselect, $address);
+ }
//Reject line breaks in addresses; it's valid RFC5322, but not RFC5321
if (strpos($address, "\n") !== false or strpos($address, "\r") !== false) {
return false;
@@ -1101,8 +1189,8 @@
if ($this->has8bitChars($domain) and @mb_check_encoding($domain, $this->CharSet)) {
$domain = mb_convert_encoding($domain, 'UTF-8', $this->CharSet);
if (($punycode = defined('INTL_IDNA_VARIANT_UTS46') ?
- idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) :
- idn_to_ascii($domain)) !== false) {
+ idn_to_ascii($domain, 0, INTL_IDNA_VARIANT_UTS46) :
+ idn_to_ascii($domain)) !== false) {
return substr($address, 0, $pos) . $punycode;
}
}
@@ -1161,7 +1249,7 @@
}
$this->$address_kind = $this->punyencodeAddress($this->$address_kind);
if (!$this->validateAddress($this->$address_kind)) {
- $error_message = $this->lang('invalid_address') . $this->$address_kind;
+ $error_message = $this->lang('invalid_address') . ' (punyEncode) ' . $this->$address_kind;
$this->setError($error_message);
$this->edebug($error_message);
if ($this->exceptions) {
@@ -1172,7 +1260,7 @@
}
// Set whether the message is multipart/alternative
- if (!empty($this->AltBody)) {
+ if ($this->alternativeExists()) {
$this->ContentType = 'multipart/alternative';
}
@@ -1206,9 +1294,11 @@
// Sign with DKIM if enabled
if (!empty($this->DKIM_domain)
- && !empty($this->DKIM_private)
&& !empty($this->DKIM_selector)
- && file_exists($this->DKIM_private)) {
+ && (!empty($this->DKIM_private_string)
+ || (!empty($this->DKIM_private) && file_exists($this->DKIM_private))
+ )
+ ) {
$header_dkim = $this->DKIM_Add(
$this->MIMEHeader . $this->mailHeader,
$this->encodeHeader($this->secureHeader($this->Subject)),
@@ -1274,7 +1364,7 @@
*/
protected function sendmailSend($header, $body)
{
- if ($this->Sender != '') {
+ if (!empty($this->Sender)) {
if ($this->Mailer == 'qmail') {
$sendmail = sprintf('%s -f%s', escapeshellcmd($this->Sendmail), escapeshellarg($this->Sender));
} else {
@@ -1349,17 +1439,17 @@
}
$to = implode(', ', $toArr);
- if (empty($this->Sender)) {
- $params = ' ';
- } else {
- $params = sprintf('-f%s', $this->Sender);
+ $params = null;
+ //This sets the SMTP envelope sender which gets turned into a return-path header by the receiver
+ if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
+ $params = sprintf('-f%s', escapeshellarg($this->Sender));
}
- if ($this->Sender != '' and !ini_get('safe_mode')) {
+ if (!empty($this->Sender) and !ini_get('safe_mode') and $this->validateAddress($this->Sender)) {
$old_from = ini_get('sendmail_from');
ini_set('sendmail_from', $this->Sender);
}
$result = false;
- if ($this->SingleTo && count($toArr) > 1) {
+ if ($this->SingleTo and count($toArr) > 1) {
foreach ($toArr as $toAddr) {
$result = $this->mailPassthru($toAddr, $this->Subject, $body, $header, $params);
$this->doCallback($result, array($toAddr), $this->cc, $this->bcc, $this->Subject, $body, $this->From);
@@ -1385,7 +1475,6 @@
public function getSMTPInstance()
{
if (!is_object($this->smtp)) {
- require_once( 'class-smtp.php' );
$this->smtp = new SMTP;
}
return $this->smtp;
@@ -1409,10 +1498,10 @@
if (!$this->smtpConnect($this->SMTPOptions)) {
throw new phpmailerException($this->lang('smtp_connect_failed'), self::STOP_CRITICAL);
}
- if ('' == $this->Sender) {
- $smtp_from = $this->From;
- } else {
+ if (!empty($this->Sender) and $this->validateAddress($this->Sender)) {
$smtp_from = $this->Sender;
+ } else {
+ $smtp_from = $this->From;
}
if (!$this->smtp->mail($smtp_from)) {
$this->setError($this->lang('from_failed') . $smtp_from . ' : ' . implode(',', $this->smtp->getError()));
@@ -1466,12 +1555,17 @@
* @throws phpmailerException
* @return boolean
*/
- public function smtpConnect($options = array())
+ public function smtpConnect($options = null)
{
if (is_null($this->smtp)) {
$this->smtp = $this->getSMTPInstance();
}
+ //If no options are provided, use whatever is set in the instance
+ if (is_null($options)) {
+ $options = $this->SMTPOptions;
+ }
+
// Already connected?
if ($this->smtp->connected()) {
return true;
@@ -1541,7 +1635,7 @@
if (!$this->smtp->startTLS()) {
throw new phpmailerException($this->lang('connect_host'));
}
- // We must resend HELO after tls negotiation
+ // We must resend EHLO after TLS negotiation
$this->smtp->hello($hello);
}
if ($this->SMTPAuth) {
@@ -1580,7 +1674,7 @@
*/
public function smtpClose()
{
- if ($this->smtp !== null) {
+ if (is_a($this->smtp, 'SMTP')) {
if ($this->smtp->connected()) {
$this->smtp->quit();
$this->smtp->close();
@@ -1599,6 +1693,19 @@
*/
public function setLanguage($langcode = 'en', $lang_path = '')
{
+ // Backwards compatibility for renamed language codes
+ $renamed_langcodes = array(
+ 'br' => 'pt_br',
+ 'cz' => 'cs',
+ 'dk' => 'da',
+ 'no' => 'nb',
+ 'se' => 'sv',
+ );
+
+ if (isset($renamed_langcodes[$langcode])) {
+ $langcode = $renamed_langcodes[$langcode];
+ }
+
// Define full set of translatable strings in English
$PHPMAILER_LANG = array(
'authenticate' => 'SMTP Error: Could not authenticate.',
@@ -1625,6 +1732,10 @@
// Calculate an absolute path so it can work if CWD is not here
$lang_path = dirname(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR;
}
+ //Validate $langcode
+ if (!preg_match('/^[a-z]{2}(?:_[a-zA-Z]{2})?$/', $langcode)) {
+ $langcode = 'en';
+ }
$foundlang = true;
$lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php';
// There is no English translation file
@@ -1683,8 +1794,8 @@
return $this->secureHeader($addr[0]);
} else {
return $this->encodeHeader($this->secureHeader($addr[1]), 'phrase') . ' <' . $this->secureHeader(
- $addr[0]
- ) . '>';
+ $addr[0]
+ ) . '>';
}
}
@@ -1918,7 +2029,9 @@
$result .= $this->headerLine('Subject', $this->encodeHeader($this->secureHeader($this->Subject)));
}
- if ($this->MessageID != '') {
+ // Only allow a custom message ID if it conforms to RFC 5322 section 3.6.4
+ // https://tools.ietf.org/html/rfc5322#section-3.6.4
+ if ('' != $this->MessageID and preg_match('/^<.*@.*>$/', $this->MessageID)) {
$this->lastMessageID = $this->MessageID;
} else {
$this->lastMessageID = sprintf('<%s@%s>', $this->uniqueid, $this->serverHostname());
@@ -2020,7 +2133,15 @@
*/
public function getSentMIMEMessage()
{
- return $this->MIMEHeader . $this->mailHeader . self::CRLF . $this->MIMEBody;
+ return rtrim($this->MIMEHeader . $this->mailHeader, "\n\r") . self::CRLF . self::CRLF . $this->MIMEBody;
+ }
+
+ /**
+ * Create unique ID
+ * @return string
+ */
+ protected function generateId() {
+ return md5(uniqid(time()));
}
/**
@@ -2034,7 +2155,7 @@
{
$body = '';
//Create unique IDs and preset boundaries
- $this->uniqueid = md5(uniqid(time()));
+ $this->uniqueid = $this->generateId();
$this->boundary[1] = 'b1_' . $this->uniqueid;
$this->boundary[2] = 'b2_' . $this->uniqueid;
$this->boundary[3] = 'b3_' . $this->uniqueid;
@@ -2050,11 +2171,12 @@
//Can we do a 7-bit downgrade?
if ($bodyEncoding == '8bit' and !$this->has8bitChars($this->Body)) {
$bodyEncoding = '7bit';
+ //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
$bodyCharSet = 'us-ascii';
}
- //If lines are too long, change to quoted-printable transfer encoding
- if (self::hasLineLongerThanMax($this->Body)) {
- $this->Encoding = 'quoted-printable';
+ //If lines are too long, and we're not already using an encoding that will shorten them,
+ //change to quoted-printable transfer encoding for the body part only
+ if ('base64' != $this->Encoding and self::hasLineLongerThanMax($this->Body)) {
$bodyEncoding = 'quoted-printable';
}
@@ -2063,10 +2185,12 @@
//Can we do a 7-bit downgrade?
if ($altBodyEncoding == '8bit' and !$this->has8bitChars($this->AltBody)) {
$altBodyEncoding = '7bit';
+ //All ISO 8859, Windows codepage and UTF-8 charsets are ascii compatible up to 7-bit
$altBodyCharSet = 'us-ascii';
}
- //If lines are too long, change to quoted-printable transfer encoding
- if (self::hasLineLongerThanMax($this->AltBody)) {
+ //If lines are too long, and we're not already using an encoding that will shorten them,
+ //change to quoted-printable transfer encoding for the alt body part only
+ if ('base64' != $altBodyEncoding and self::hasLineLongerThanMax($this->AltBody)) {
$altBodyEncoding = 'quoted-printable';
}
//Use this as a preamble in all multipart message types
@@ -2169,8 +2293,10 @@
$body .= $this->attachAll('attachment', $this->boundary[1]);
break;
default:
- // catch case 'plain' and case ''
- $body .= $this->encodeString($this->Body, $bodyEncoding);
+ // 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 length reasons
+ $this->Encoding = $bodyEncoding;
+ $body .= $this->encodeString($this->Body, $this->Encoding);
break;
}
@@ -2276,8 +2402,7 @@
/**
* Set the message type.
- * PHPMailer only supports some preset message types,
- * not arbitrary MIME structures.
+ * PHPMailer only supports some preset message types, not arbitrary MIME structures.
* @access protected
* @return void
*/
@@ -2295,6 +2420,7 @@
}
$this->message_type = implode('_', $type);
if ($this->message_type == '') {
+ //The 'plain' message_type refers to the message having a single body element, not that it is plain-text
$this->message_type = 'plain';
}
}
@@ -2610,7 +2736,7 @@
/** @noinspection PhpMissingBreakStatementInspection */
case 'comment':
$matchcount = preg_match_all('/[()"]/', $str, $matches);
- // Intentional fall-through
+ // Intentional fall-through
case 'text':
default:
$matchcount += preg_match_all('/[\000-\010\013\014\016-\037\177-\377]/', $str, $matches);
@@ -2781,8 +2907,8 @@
case 'comment':
// RFC 2047 section 5.2
$pattern = '\(\)"';
- // intentional fall-through
- // for this reason we build the $pattern without including delimiters and []
+ // intentional fall-through
+ // for this reason we build the $pattern without including delimiters and []
case 'text':
default:
// RFC 2047 section 5.1
@@ -3209,16 +3335,18 @@
}
/**
- * Create a message from an HTML string.
- * Automatically makes modifications for inline images and backgrounds
- * and creates a plain-text version by converting the HTML.
- * Overwrites any existing values in $this->Body and $this->AltBody
+ * Create a message body from an HTML string.
+ * Automatically inlines images and creates a plain-text version by converting the HTML,
+ * overwriting any existing values in Body and AltBody.
+ * $basedir is used when handling relative image paths, e.g.
+ * will look for an image file in $basedir/images/a.png and convert it to inline.
+ * If you don't want to apply these transformations to your HTML, just set Body and AltBody yourself.
* @access public
* @param string $message HTML message string
- * @param string $basedir baseline directory for path
+ * @param string $basedir base directory for relative paths to images
* @param boolean|callable $advanced Whether to use the internal HTML to text converter
* or your own custom converter @see PHPMailer::html2text()
- * @return string $message
+ * @return string $message The transformed message Body
*/
public function msgHTML($message, $basedir = '', $advanced = false)
{
@@ -3241,7 +3369,7 @@
$message
);
}
- } elseif (substr($url, 0, 4) !== 'cid:' && !preg_match('#^[A-z]+://#', $url)) {
+ } elseif (substr($url, 0, 4) !== 'cid:' && !preg_match('#^[a-z][a-z0-9+.-]*://#i', $url)) {
// Do not change urls for absolute images (thanks to corvuscorax)
// Do not change urls that are already inline images
$filename = basename($url);
@@ -3277,7 +3405,7 @@
// Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better
$this->Body = $this->normalizeBreaks($message);
$this->AltBody = $this->normalizeBreaks($this->html2text($message, $advanced));
- if (empty($this->AltBody)) {
+ if (!$this->alternativeExists()) {
$this->AltBody = 'To view this email message, open it in a program that understands HTML!' .
self::CRLF . self::CRLF;
}
@@ -3288,7 +3416,7 @@
* Convert an HTML string into plain text.
* This is used by msgHTML().
* Note - older versions of this function used a bundled advanced converter
- * which was been removed for license reasons in #232
+ * which was been removed for license reasons in #232.
* Example usage:
*
* // Use default conversion
@@ -3588,7 +3716,7 @@
* @access public
* @param string $signHeader
* @throws phpmailerException
- * @return string
+ * @return string The DKIM signature value
*/
public function DKIM_Sign($signHeader)
{
@@ -3598,15 +3726,35 @@
}
return '';
}
- $privKeyStr = file_get_contents($this->DKIM_private);
- if ($this->DKIM_passphrase != '') {
+ $privKeyStr = !empty($this->DKIM_private_string) ? $this->DKIM_private_string : file_get_contents($this->DKIM_private);
+ if ('' != $this->DKIM_passphrase) {
$privKey = openssl_pkey_get_private($privKeyStr, $this->DKIM_passphrase);
} else {
- $privKey = $privKeyStr;
+ $privKey = openssl_pkey_get_private($privKeyStr);
}
- if (openssl_sign($signHeader, $signature, $privKey)) {
- return base64_encode($signature);
+ //Workaround for missing digest algorithms in old PHP & OpenSSL versions
+ //@link http://stackoverflow.com/a/11117338/333340
+ if (version_compare(PHP_VERSION, '5.3.0') >= 0 and
+ in_array('sha256WithRSAEncryption', openssl_get_md_methods(true))) {
+ if (openssl_sign($signHeader, $signature, $privKey, 'sha256WithRSAEncryption')) {
+ openssl_pkey_free($privKey);
+ return base64_encode($signature);
+ }
+ } else {
+ $pinfo = openssl_pkey_get_details($privKey);
+ $hash = hash('sha256', $signHeader);
+ //'Magic' constant for SHA256 from RFC3447
+ //@link https://tools.ietf.org/html/rfc3447#page-43
+ $t = '3031300d060960864801650304020105000420' . $hash;
+ $pslen = $pinfo['bits'] / 8 - (strlen($t) / 2 + 3);
+ $eb = pack('H*', '0001' . str_repeat('FF', $pslen) . '00' . $t);
+
+ if (openssl_private_encrypt($eb, $signature, $privKey, OPENSSL_NO_PADDING)) {
+ openssl_pkey_free($privKey);
+ return base64_encode($signature);
+ }
}
+ openssl_pkey_free($privKey);
return '';
}
@@ -3623,7 +3771,7 @@
foreach ($lines as $key => $line) {
list($heading, $value) = explode(':', $line, 2);
$heading = strtolower($heading);
- $value = preg_replace('/\s+/', ' ', $value); // Compress useless spaces
+ $value = preg_replace('/\s{2,}/', ' ', $value); // Compress useless spaces
$lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value
}
$signHeader = implode("\r\n", $lines);
@@ -3661,7 +3809,7 @@
*/
public function DKIM_Add($headers_line, $subject, $body)
{
- $DKIMsignatureType = 'rsa-sha1'; // Signature & hash algorithms
+ $DKIMsignatureType = 'rsa-sha256'; // Signature & hash algorithms
$DKIMcanonicalization = 'relaxed/simple'; // Canonicalization of header/body
$DKIMquery = 'dns/txt'; // Query method
$DKIMtime = time(); // Signature Timestamp = seconds since 00:00:00 - Jan 1, 1970 (UTC time zone)
@@ -3669,6 +3817,7 @@
$headers = explode($this->LE, $headers_line);
$from_header = '';
$to_header = '';
+ $date_header = '';
$current = '';
foreach ($headers as $header) {
if (strpos($header, 'From:') === 0) {
@@ -3677,6 +3826,9 @@
} elseif (strpos($header, 'To:') === 0) {
$to_header = $header;
$current = 'to_header';
+ } elseif (strpos($header, 'Date:') === 0) {
+ $date_header = $header;
+ $current = 'date_header';
} else {
if (!empty($$current) && strpos($header, ' =?') === 0) {
$$current .= $header;
@@ -3687,6 +3839,7 @@
}
$from = str_replace('|', '=7C', $this->DKIM_QP($from_header));
$to = str_replace('|', '=7C', $this->DKIM_QP($to_header));
+ $date = str_replace('|', '=7C', $this->DKIM_QP($date_header));
$subject = str_replace(
'|',
'=7C',
@@ -3694,7 +3847,7 @@
); // Copied header fields (dkim-quoted-printable)
$body = $this->DKIM_BodyC($body);
$DKIMlen = strlen($body); // Length of body
- $DKIMb64 = base64_encode(pack('H*', sha1($body))); // Base64 of packed binary SHA-1 hash of body
+ $DKIMb64 = base64_encode(pack('H*', hash('sha256', $body))); // Base64 of packed binary SHA-256 hash of body
if ('' == $this->DKIM_identity) {
$ident = '';
} else {
@@ -3707,16 +3860,18 @@
$this->DKIM_selector .
";\r\n" .
"\tt=" . $DKIMtime . '; c=' . $DKIMcanonicalization . ";\r\n" .
- "\th=From:To:Subject;\r\n" .
+ "\th=From:To:Date:Subject;\r\n" .
"\td=" . $this->DKIM_domain . ';' . $ident . "\r\n" .
"\tz=$from\r\n" .
"\t|$to\r\n" .
+ "\t|$date\r\n" .
"\t|$subject;\r\n" .
"\tbh=" . $DKIMb64 . ";\r\n" .
"\tb=";
$toSign = $this->DKIM_HeaderC(
$from_header . "\r\n" .
$to_header . "\r\n" .
+ $date_header . "\r\n" .
$subject_header . "\r\n" .
$dkimhdrs
);
@@ -3825,4 +3980,4 @@
$errorMsg = '' . $this->getMessage() . "
\n";
return $errorMsg;
}
-}
+}
\ No newline at end of file