From bcd49186fb1cd1ead943ab37ea6b5563b69a5150 Mon Sep 17 00:00:00 2001 From: "Michael Kaufmann (d00p)" Date: Wed, 7 Jan 2015 18:12:27 +0100 Subject: [PATCH] update phpMailer to 5.2.9 Signed-off-by: Michael Kaufmann (d00p) --- lib/classes/phpmailer/class.PHPMailer.php | 474 ++++++++++++---------- lib/classes/phpmailer/class.SMTP.php | 337 ++++++++------- 2 files changed, 440 insertions(+), 371 deletions(-) diff --git a/lib/classes/phpmailer/class.PHPMailer.php b/lib/classes/phpmailer/class.PHPMailer.php index 65d4c9d4..cd102a28 100644 --- a/lib/classes/phpmailer/class.PHPMailer.php +++ b/lib/classes/phpmailer/class.PHPMailer.php @@ -31,12 +31,12 @@ class PHPMailer * The PHPMailer Version number. * @type string */ - public $Version = '5.2.8'; + public $Version = '5.2.9'; /** * Email priority. * Options: 1 = High, 3 = Normal, 5 = low. - * @type int + * @type integer */ public $Priority = 3; @@ -149,7 +149,7 @@ class PHPMailer /** * Word-wrap the message body to this number of chars. - * @type int + * @type integer */ public $WordWrap = 0; @@ -169,7 +169,7 @@ class PHPMailer /** * Whether mail() uses a fully sendmail-compatible MTA. * One which supports sendmail's "-oi -f" options. - * @type bool + * @type boolean */ public $UseSendmailOptions = true; @@ -216,6 +216,8 @@ class PHPMailer * You can also specify a different port * for each host by using this format: [hostname:port] * (e.g. "smtp1.example.com:25;smtp2.example.com"). + * You can also specify encryption type, for example: + * (e.g. "tls://smtp1.example.com:587;ssl://smtp2.example.com:465"). * Hosts will be tried in order. * @type string */ @@ -223,8 +225,8 @@ class PHPMailer /** * The default SMTP server port. - * @type int - * @Todo Why is this needed when the SMTP class takes care of it? + * @type integer + * @TODO Why is this needed when the SMTP class takes care of it? */ public $Port = 25; @@ -246,7 +248,7 @@ class PHPMailer /** * Whether to use SMTP authentication. * Uses the Username and Password properties. - * @type bool + * @type boolean * @see PHPMailer::$Username * @see PHPMailer::$Password */ @@ -287,19 +289,20 @@ class PHPMailer /** * The SMTP server timeout in seconds. - * @type int + * @type integer */ public $Timeout = 10; /** * SMTP class debug output mode. + * Debug output level. * Options: - * 0: no output - * 1: commands - * 2: data and commands - * 3: as 2 plus connection status - * 4: low level data output - * @type int + * * `0` No output + * * `1` Commands + * * `2` Data and commands + * * `3` As 2 plus connection status + * * `4` Low-level data output + * @type integer * @see SMTP::$do_debug */ public $SMTPDebug = 0; @@ -307,10 +310,15 @@ class PHPMailer /** * How to handle debug output. * Options: - * 'echo': Output plain-text as-is, appropriate for CLI - * 'html': Output escaped, line breaks converted to
, appropriate for browser output - * 'error_log': Output to error log as configured in php.ini - * @type string + * * `echo` Output plain-text as-is, appropriate for CLI + * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output + * * `error_log` Output to error log as configured in php.ini + * + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * $mail->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * + * @type string|callable * @see SMTP::$Debugoutput */ public $Debugoutput = 'echo'; @@ -319,21 +327,21 @@ class PHPMailer * Whether to keep SMTP connection open after each message. * If this is set to true then to close the connection * requires an explicit call to smtpClose(). - * @type bool + * @type boolean */ public $SMTPKeepAlive = false; /** * Whether to split multiple to addresses into multiple messages * or send them all in one message. - * @type bool + * @type boolean */ public $SingleTo = false; /** * Storage for addresses when SingleTo is enabled. * @type array - * @todo This should really not be public + * @TODO This should really not be public */ public $SingleToArray = array(); @@ -342,13 +350,13 @@ class PHPMailer * Only applicable when sending via SMTP. * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path * @link http://www.postfix.org/VERP_README.html Postfix VERP info - * @type bool + * @type boolean */ public $do_verp = false; /** * Whether to allow sending messages with an empty body. - * @type bool + * @type boolean */ public $AllowEmpty = false; @@ -402,7 +410,7 @@ class PHPMailer * Value can be any php callable: http://www.php.net/is_callable * * Parameters: - * bool $result result of the send action + * boolean $result result of the send action * string $to email address of the recipient * string $cc cc email addresses * string $bcc bcc email addresses @@ -536,45 +544,38 @@ class PHPMailer /** * Whether to throw exceptions for errors. - * @type bool + * @type boolean * @access protected */ protected $exceptions = false; /** - * Error severity: message only, continue processing + * Error severity: message only, continue processing. */ const STOP_MESSAGE = 0; /** - * Error severity: message, likely ok to continue processing + * Error severity: message, likely ok to continue processing. */ const STOP_CONTINUE = 1; /** - * Error severity: message, plus full stop, critical error reached + * Error severity: message, plus full stop, critical error reached. */ const STOP_CRITICAL = 2; /** - * SMTP RFC standard line ending + * SMTP RFC standard line ending. */ const CRLF = "\r\n"; /** - * Constructor - * @param bool $exceptions Should we throw external exceptions? + * Constructor. + * @param boolean $exceptions Should we throw external exceptions? */ public function __construct($exceptions = false) { $this->exceptions = ($exceptions == true); - //Make sure our autoloader is loaded - if (version_compare(PHP_VERSION, '5.1.2', '>=')) { - $autoload = spl_autoload_functions(); - if ($autoload === false or !in_array('PHPMailerAutoload', $autoload)) { - require 'PHPMailerAutoload.php'; - } - } } /** @@ -598,7 +599,7 @@ class PHPMailer * @param string $header Additional Header(s) * @param string $params Params * @access private - * @return bool + * @return boolean */ private function mailPassthru($to, $subject, $body, $header, $params) { @@ -618,38 +619,54 @@ class PHPMailer /** * Output debugging info via user-defined method. - * Only if debug output is enabled. + * Only generates output if SMTP debug output is enabled (@see SMTP::$do_debug). * @see PHPMailer::$Debugoutput * @see PHPMailer::$SMTPDebug * @param string $str */ protected function edebug($str) { - if (!$this->SMTPDebug) { + if ($this->SMTPDebug <= 0) { + return; + } + if (is_callable($this->Debugoutput)) { + call_user_func($this->Debugoutput, $str, $this->SMTPDebug); return; } switch ($this->Debugoutput) { case 'error_log': + //Don't output, just log error_log($str); break; case 'html': - //Cleans up output a bit for a better looking display that's HTML-safe - echo htmlentities(preg_replace('/[\r\n]+/', '', $str), ENT_QUOTES, $this->CharSet) . "
\n"; + //Cleans up output a bit for a better looking, HTML-safe output + echo htmlentities( + preg_replace('/[\r\n]+/', '', $str), + ENT_QUOTES, + 'UTF-8' + ) + . "
\n"; break; case 'echo': default: - echo $str."\n"; + //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"; } } /** * Sets message type to HTML or plain. - * @param bool $ishtml True for HTML mode. + * @param boolean $isHtml True for HTML mode. * @return void */ - public function isHTML($ishtml = true) + public function isHTML($isHtml = true) { - if ($ishtml) { + if ($isHtml) { $this->ContentType = 'text/html'; } else { $this->ContentType = 'text/plain'; @@ -681,7 +698,7 @@ class PHPMailer public function isSendmail() { $ini_sendmail_path = ini_get('sendmail_path'); - + if (!stristr($ini_sendmail_path, 'sendmail')) { $this->Sendmail = '/usr/sbin/sendmail'; } else { @@ -697,7 +714,7 @@ class PHPMailer public function isQmail() { $ini_sendmail_path = ini_get('sendmail_path'); - + if (!stristr($ini_sendmail_path, 'qmail')) { $this->Sendmail = '/var/qmail/bin/qmail-inject'; } else { @@ -710,7 +727,7 @@ class PHPMailer * Add a "To" address. * @param string $address * @param string $name - * @return bool true on success, false if address already used + * @return boolean true on success, false if address already used */ public function addAddress($address, $name = '') { @@ -722,7 +739,7 @@ class PHPMailer * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. * @param string $address * @param string $name - * @return bool true on success, false if address already used + * @return boolean true on success, false if address already used */ public function addCC($address, $name = '') { @@ -734,7 +751,7 @@ class PHPMailer * @note: This function works with the SMTP mailer on win32, not with the "mail" mailer. * @param string $address * @param string $name - * @return bool true on success, false if address already used + * @return boolean true on success, false if address already used */ public function addBCC($address, $name = '') { @@ -745,7 +762,7 @@ class PHPMailer * Add a "Reply-to" address. * @param string $address * @param string $name - * @return bool + * @return boolean */ public function addReplyTo($address, $name = '') { @@ -759,7 +776,7 @@ class PHPMailer * @param string $address The email address to send to * @param string $name * @throws phpmailerException - * @return bool true on success, false if address already used or invalid in some way + * @return boolean true on success, false if address already used or invalid in some way * @access protected */ protected function addAnAddress($kind, $address, $name = '') @@ -801,9 +818,9 @@ class PHPMailer * Set the From and FromName properties. * @param string $address * @param string $name - * @param bool $auto Whether to also set the Sender address, defaults to true + * @param boolean $auto Whether to also set the Sender address, defaults to true * @throws phpmailerException - * @return bool + * @return boolean */ public function setFrom($address, $name = '', $auto = true) { @@ -849,19 +866,25 @@ class PHPMailer * * `php` Use PHP built-in FILTER_VALIDATE_EMAIL; same as pcre8 but does not allow 'dotless' domains; * * `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. - * @return bool + * @return boolean * @static * @access public */ public static function validateAddress($address, $patternselect = 'auto') { if (!$patternselect or $patternselect == 'auto') { - if (defined('PCRE_VERSION')) { //Check this constant so it works when extension_loaded() is disabled - if (version_compare(PCRE_VERSION, '8.0') >= 0) { + //Check this constant first so it works when extension_loaded() is disabled by safe mode + //Constant was added in PHP 5.2.4 + if (defined('PCRE_VERSION')) { + //This pattern can get stuck in a recursive loop in PCRE <= 8.0.2 + if (version_compare(PCRE_VERSION, '8.0.3') >= 0) { $patternselect = 'pcre8'; } else { $patternselect = 'pcre'; } + } elseif (function_exists('extension_loaded') and extension_loaded('pcre')) { + //Fall back to older PCRE + $patternselect = 'pcre'; } else { //Filter_var appeared in PHP 5.2.0 and does not require the PCRE extension if (version_compare(PHP_VERSION, '5.2.0') >= 0) { @@ -879,7 +902,7 @@ class PHPMailer * @copyright 2009-2010 Michael Rushton * Feel free to use and redistribute this code. But please keep this copyright notice. */ - return (bool)preg_match( + return (boolean)preg_match( '/^(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){255,})(?!(?>(?1)"?(?>\\\[ -~]|[^"])"?(?1)){65,}@)' . '((?>(?>(?>((?>(?>(?>\x0D\x0A)?[\t ])+|(?>[\t ]*\x0D\x0A)?[\t ]+)?)(\((?>(?2)' . '(?>[\x01-\x08\x0B\x0C\x0E-\'*-\[\]-\x7F]|\\\[\x00-\x7F]|(?3)))*(?2)\)))+(?2))|(?2))?)' . @@ -891,10 +914,9 @@ class PHPMailer '|[1-9]?[0-9])(?>\.(?9)){3}))\])(?1)$/isD', $address ); - break; case 'pcre': //An older regex that doesn't need a recent PCRE - return (bool)preg_match( + return (boolean)preg_match( '/^(?!(?>"?(?>\\\[ -~]|[^"])"?){255,})(?!(?>"?(?>\\\[ -~]|[^"])"?){65,}@)(?>' . '[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*")' . '(?>\.(?>[!#-\'*+\/-9=?^-~-]+|"(?>(?>[\x01-\x08\x0B\x0C\x0E-!#-\[\]-\x7F]|\\\[\x00-\xFF]))*"))*' . @@ -907,26 +929,25 @@ class PHPMailer '|[1-9]?[0-9])(?>\.(?>25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}))\])$/isD', $address ); - break; case 'html5': /** * This is the pattern used in the HTML5 spec for validation of 'email' type form input elements. * @link http://www.whatwg.org/specs/web-apps/current-work/#e-mail-state-(type=email) */ - return (bool)preg_match('/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . - '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', $address); - break; - case 'php': - default: - return (bool)filter_var($address, FILTER_VALIDATE_EMAIL); - break; + return (boolean)preg_match( + '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}' . + '[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/sD', + $address + ); case 'noregex': //No PCRE! Do something _very_ approximate! //Check the address is 3 chars or longer and contains an @ that's not the first or last char return (strlen($address) >= 3 and strpos($address, '@') >= 1 and strpos($address, '@') != strlen($address) - 1); - break; + case 'php': + default: + return (boolean)filter_var($address, FILTER_VALIDATE_EMAIL); } } @@ -934,7 +955,7 @@ class PHPMailer * Create a message and send it. * Uses the sending method specified by $Mailer. * @throws phpmailerException - * @return bool false on error - See the ErrorInfo property for details of the error. + * @return boolean false on error - See the ErrorInfo property for details of the error. */ public function send() { @@ -956,7 +977,7 @@ class PHPMailer /** * Prepare a message for sending. * @throws phpmailerException - * @return bool + * @return boolean */ public function preSend() { @@ -1024,7 +1045,7 @@ class PHPMailer * Actually send a message. * Send the email via the selected mechanism * @throws phpmailerException - * @return bool + * @return boolean */ public function postSend() { @@ -1043,7 +1064,7 @@ class PHPMailer if (method_exists($this, $sendMethod)) { return $this->$sendMethod($this->MIMEHeader, $this->MIMEBody); } - + return $this->mailSend($this->MIMEHeader, $this->MIMEBody); } } catch (phpmailerException $exc) { @@ -1063,7 +1084,7 @@ class PHPMailer * @see PHPMailer::$Sendmail * @throws phpmailerException * @access protected - * @return bool + * @return boolean */ protected function sendmailSend($header, $body) { @@ -1081,17 +1102,23 @@ class PHPMailer } } if ($this->SingleTo === true) { - foreach ($this->SingleToArray as $val) { + foreach ($this->SingleToArray as $toAddr) { if (!@$mail = popen($sendmail, 'w')) { throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); } - fputs($mail, 'To: ' . $val . "\n"); + fputs($mail, 'To: ' . $toAddr . "\n"); fputs($mail, $header); fputs($mail, $body); $result = pclose($mail); - // implement call back function if it exists - $isSent = ($result == 0) ? 1 : 0; - $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body, $this->From); + $this->doCallback( + ($result == 0), + array($toAddr), + $this->cc, + $this->bcc, + $this->Subject, + $body, + $this->From + ); if ($result != 0) { throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); } @@ -1103,9 +1130,7 @@ class PHPMailer fputs($mail, $header); fputs($mail, $body); $result = pclose($mail); - // implement call back function if it exists - $isSent = ($result == 0) ? 1 : 0; - $this->doCallback($isSent, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From); + $this->doCallback(($result == 0), $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From); if ($result != 0) { throw new phpmailerException($this->lang('execute') . $this->Sendmail, self::STOP_CRITICAL); } @@ -1120,7 +1145,7 @@ class PHPMailer * @link http://www.php.net/manual/en/book.mail.php * @throws phpmailerException * @access protected - * @return bool + * @return boolean */ protected function mailSend($header, $body) { @@ -1141,17 +1166,13 @@ class PHPMailer } $result = false; if ($this->SingleTo === true && count($toArr) > 1) { - foreach ($toArr as $val) { - $result = $this->mailPassthru($val, $this->Subject, $body, $header, $params); - // implement call back function if it exists - $isSent = ($result == 1) ? 1 : 0; - $this->doCallback($isSent, $val, $this->cc, $this->bcc, $this->Subject, $body, $this->From); + 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); } } else { $result = $this->mailPassthru($to, $this->Subject, $body, $header, $params); - // implement call back function if it exists - $isSent = ($result == 1) ? 1 : 0; - $this->doCallback($isSent, $to, $this->cc, $this->bcc, $this->Subject, $body, $this->From); + $this->doCallback($result, $this->to, $this->cc, $this->bcc, $this->Subject, $body, $this->From); } if (isset($old_from)) { ini_set('sendmail_from', $old_from); @@ -1185,7 +1206,7 @@ class PHPMailer * @throws phpmailerException * @uses SMTP * @access protected - * @return bool + * @return boolean */ protected function smtpSend($header, $body) { @@ -1204,32 +1225,32 @@ class PHPMailer foreach ($this->to as $to) { if (!$this->smtp->recipient($to[0])) { $bad_rcpt[] = $to[0]; - $isSent = 0; + $isSent = false; } else { - $isSent = 1; + $isSent = true; } - $this->doCallback($isSent, $to[0], '', '', $this->Subject, $body, $this->From); + $this->doCallback($isSent, array($to[0]), array(), array(), $this->Subject, $body, $this->From); } foreach ($this->cc as $cc) { if (!$this->smtp->recipient($cc[0])) { $bad_rcpt[] = $cc[0]; - $isSent = 0; + $isSent = false; } else { - $isSent = 1; + $isSent = true; } - $this->doCallback($isSent, '', $cc[0], '', $this->Subject, $body, $this->From); + $this->doCallback($isSent, array(), array($cc[0]), array(), $this->Subject, $body, $this->From); } foreach ($this->bcc as $bcc) { if (!$this->smtp->recipient($bcc[0])) { $bad_rcpt[] = $bcc[0]; - $isSent = 0; + $isSent = false; } else { - $isSent = 1; + $isSent = true; } - $this->doCallback($isSent, '', '', $bcc[0], $this->Subject, $body, $this->From); + $this->doCallback($isSent, array(), array(), array($bcc[0]), $this->Subject, $body, $this->From); } - //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)) and !$this->smtp->data($header . $body)) { throw new phpmailerException($this->lang('data_not_accepted'), self::STOP_CRITICAL); } @@ -1239,7 +1260,7 @@ class PHPMailer $this->smtp->quit(); $this->smtp->close(); } - if (count($bad_rcpt) > 0) { //Create error message for any bad addresses + if (count($bad_rcpt) > 0) { // Create error message for any bad addresses throw new phpmailerException( $this->lang('recipients_failed') . implode(', ', $bad_rcpt), self::STOP_CONTINUE @@ -1255,7 +1276,7 @@ class PHPMailer * @uses SMTP * @access public * @throws phpmailerException - * @return bool + * @return boolean */ public function smtpConnect($options = array()) { @@ -1263,7 +1284,7 @@ class PHPMailer $this->smtp = $this->getSMTPInstance(); } - //Already connected? + // Already connected? if ($this->smtp->connected()) { return true; } @@ -1278,22 +1299,22 @@ class PHPMailer foreach ($hosts as $hostentry) { $hostinfo = array(); if (!preg_match('/^((ssl|tls):\/\/)*([a-zA-Z0-9\.-]*):?([0-9]*)$/', trim($hostentry), $hostinfo)) { - //Not a valid host entry + // Not a valid host entry continue; } - //$hostinfo[2]: optional ssl or tls prefix - //$hostinfo[3]: the hostname - //$hostinfo[4]: optional port number - //The host string prefix can temporarily override the current setting for SMTPSecure - //If it's not specified, the default value is used + // $hostinfo[2]: optional ssl or tls prefix + // $hostinfo[3]: the hostname + // $hostinfo[4]: optional port number + // The host string prefix can temporarily override the current setting for SMTPSecure + // If it's not specified, the default value is used $prefix = ''; $tls = ($this->SMTPSecure == 'tls'); if ($hostinfo[2] == 'ssl' or ($hostinfo[2] == '' and $this->SMTPSecure == 'ssl')) { $prefix = 'ssl://'; - $tls = false; //Can't have SSL and TLS at once + $tls = false; // Can't have SSL and TLS at once } elseif ($hostinfo[2] == 'tls') { $tls = true; - //tls doesn't use a prefix + // tls doesn't use a prefix } $host = $hostinfo[3]; $port = $this->Port; @@ -1314,7 +1335,7 @@ class PHPMailer if (!$this->smtp->startTLS()) { throw new phpmailerException($this->lang('connect_host')); } - //We must resend HELO after tls negotiation + // We must resend HELO after tls negotiation $this->smtp->hello($hello); } if ($this->SMTPAuth) { @@ -1332,14 +1353,14 @@ class PHPMailer return true; } catch (phpmailerException $exc) { $lastexception = $exc; - //We must have connected, but then failed TLS or Auth, so close connection nicely + // We must have connected, but then failed TLS or Auth, so close connection nicely $this->smtp->quit(); } } } - //If we get here, all connection attempts have failed, so close connection hard + // If we get here, all connection attempts have failed, so close connection hard $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 and !is_null($lastexception)) { throw $lastexception; } @@ -1366,12 +1387,12 @@ class PHPMailer * The default language is English. * @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 trailing separator (slash) - * @return bool + * @return boolean * @access public */ public function setLanguage($langcode = 'en', $lang_path = '') { - //Define full set of translatable strings in English + // Define full set of translatable strings in English $PHPMAILER_LANG = array( 'authenticate' => 'SMTP Error: Could not authenticate.', 'connect_host' => 'SMTP Error: Could not connect to SMTP host.', @@ -1393,23 +1414,23 @@ class PHPMailer 'variable_set' => 'Cannot set or reset variable: ' ); 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(__FILE__). DIRECTORY_SEPARATOR . 'language'. DIRECTORY_SEPARATOR; } $foundlang = true; $lang_file = $lang_path . 'phpmailer.lang-' . $langcode . '.php'; - if ($langcode != 'en') { //There is no English translation file - //Make sure language file path is readable + if ($langcode != 'en') { // There is no English translation file + // Make sure language file path is readable if (!is_readable($lang_file)) { $foundlang = false; } else { - //Overwrite language-specific strings. - //This way we'll never have missing translations. + // Overwrite language-specific strings. + // This way we'll never have missing translations. $foundlang = include $lang_file; } } $this->language = $PHPMAILER_LANG; - return ($foundlang == true); //Returns false if language not found + return ($foundlang == true); // Returns false if language not found } /** @@ -1465,7 +1486,7 @@ class PHPMailer * Original written by philippe. * @param string $message The message to wrap * @param integer $length The line length to wrap to - * @param bool $qp_mode Whether to run in Quoted-Printable mode + * @param boolean $qp_mode Whether to run in Quoted-Printable mode * @access public * @return string */ @@ -1554,8 +1575,8 @@ class PHPMailer * Original written by Colin Brown. * @access public * @param string $encodedText utf-8 QP text - * @param int $maxLength find last character boundary prior to this length - * @return int + * @param integer $maxLength find last character boundary prior to this length + * @return integer */ public function utf8CharBoundary($encodedText, $maxLength) { @@ -1747,14 +1768,14 @@ class PHPMailer $ismultipart = false; break; } - //RFC1341 part 5 says 7bit is assumed if not specified + // RFC1341 part 5 says 7bit is assumed if not specified if ($this->Encoding != '7bit') { - //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 ($this->Encoding == '8bit') { $result .= $this->headerLine('Content-Transfer-Encoding', '8bit'); } - //The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible + // The only remaining alternatives are quoted-printable and base64, which are both 7bit compatible } else { $result .= $this->headerLine('Content-Transfer-Encoding', $this->Encoding); } @@ -1913,9 +1934,9 @@ class PHPMailer if (!defined('PKCS7_TEXT')) { throw new phpmailerException($this->lang('signing') . ' OpenSSL extension missing.'); } - //TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1 + // @TODO would be nice to use php://temp streams here, but need to wrap for PHP < 5.1 $file = tempnam(sys_get_temp_dir(), 'mail'); - file_put_contents($file, $body); //TODO check this worked + file_put_contents($file, $body); // @TODO check this worked $signed = tempnam(sys_get_temp_dir(), 'signed'); if (@openssl_pkcs7_sign( $file, @@ -1967,7 +1988,7 @@ class PHPMailer $result .= $this->textLine('--' . $boundary); $result .= sprintf('Content-Type: %s; charset=%s', $contentType, $charSet); $result .= $this->LE; - //RFC1341 part 5 says 7bit is assumed if not specified + // RFC1341 part 5 says 7bit is assumed if not specified if ($encoding != '7bit') { $result .= $this->headerLine('Content-Transfer-Encoding', $encoding); } @@ -1996,17 +2017,17 @@ class PHPMailer */ protected function setMessageType() { - $this->message_type = array(); + $type = array(); if ($this->alternativeExists()) { - $this->message_type[] = 'alt'; + $type[] = 'alt'; } if ($this->inlineImageExists()) { - $this->message_type[] = 'inline'; + $type[] = 'inline'; } if ($this->attachmentExists()) { - $this->message_type[] = 'attach'; + $type[] = 'attach'; } - $this->message_type = implode('_', $this->message_type); + $this->message_type = implode('_', $type); if ($this->message_type == '') { $this->message_type = 'plain'; } @@ -2044,7 +2065,7 @@ class PHPMailer * @param string $type File extension (MIME) type. * @param string $disposition Disposition to use * @throws phpmailerException - * @return bool + * @return boolean */ public function addAttachment($path, $name = '', $encoding = 'base64', $type = '', $disposition = 'attachment') { @@ -2053,7 +2074,7 @@ class PHPMailer throw new phpmailerException($this->lang('file_access') . $path, self::STOP_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 == '') { $type = self::filenameToType($path); } @@ -2145,7 +2166,7 @@ class PHPMailer $this->encodeHeader($this->secureHeader($name)), $this->LE ); - //RFC1341 part 5 says 7bit is assumed if not specified + // RFC1341 part 5 says 7bit is assumed if not specified if ($encoding != '7bit') { $mime[] = sprintf('Content-Transfer-Encoding: %s%s', $encoding, $this->LE); } @@ -2159,18 +2180,19 @@ class PHPMailer // Fixes a warning in IETF's msglint MIME checker // Allow for bypassing the Content-Disposition header totally if (!(empty($disposition))) { - if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $name)) { + $encoded_name = $this->encodeHeader($this->secureHeader($name)); + if (preg_match('/[ \(\)<>@,;:\\"\/\[\]\?=]/', $encoded_name)) { $mime[] = sprintf( 'Content-Disposition: %s; filename="%s"%s', $disposition, - $this->encodeHeader($this->secureHeader($name)), + $encoded_name, $this->LE . $this->LE ); } else { $mime[] = sprintf( 'Content-Disposition: %s; filename=%s%s', $disposition, - $this->encodeHeader($this->secureHeader($name)), + $encoded_name, $this->LE . $this->LE ); } @@ -2219,8 +2241,11 @@ class PHPMailer $magic_quotes = get_magic_quotes_runtime(); if ($magic_quotes) { if (version_compare(PHP_VERSION, '5.3.0', '<')) { - set_magic_quotes_runtime(0); + set_magic_quotes_runtime(false); } else { + //Doesn't exist in PHP 5.4, but we don't need to check because + //get_magic_quotes_runtime always returns false in 5.4+ + //so it will never get here ini_set('magic_quotes_runtime', 0); } } @@ -2230,7 +2255,7 @@ class PHPMailer if (version_compare(PHP_VERSION, '5.3.0', '<')) { set_magic_quotes_runtime($magic_quotes); } else { - ini_set('magic_quotes_runtime', $magic_quotes); + ini_set('magic_quotes_runtime', ($magic_quotes?'1':'0')); } } return $file_buffer; @@ -2258,7 +2283,7 @@ class PHPMailer case '7bit': case '8bit': $encoded = $this->fixEOL($str); - //Make sure it ends with a line break + // Make sure it ends with a line break if (substr($encoded, -(strlen($this->LE))) != $this->LE) { $encoded .= $this->LE; } @@ -2310,14 +2335,14 @@ class PHPMailer break; } - if ($matchcount == 0) { //There are no chars that need encoding + if ($matchcount == 0) { // There are no chars that need encoding return ($str); } $maxlen = 75 - 7 - strlen($this->CharSet); // Try to select the encoding which should produce the shortest output if ($matchcount > strlen($str) / 3) { - //More than a third of the content will need encoding, so B encoding will be most efficient + // More than a third of the content will need encoding, so B encoding will be most efficient $encoding = 'B'; if (function_exists('mb_strlen') && $this->hasMultiBytes($str)) { // Use a custom function which correctly encodes and wraps long @@ -2345,7 +2370,7 @@ class PHPMailer * Check if a string contains multi-byte characters. * @access public * @param string $str multi-byte text to wrap encode - * @return bool + * @return boolean */ public function hasMultiBytes($str) { @@ -2359,11 +2384,11 @@ class PHPMailer /** * Does a string contain any 8-bit chars (in any charset)? * @param string $text - * @return bool + * @return boolean */ public function has8bitChars($text) { - return (bool)preg_match('/[\x80-\xFF]/', $text); + return (boolean)preg_match('/[\x80-\xFF]/', $text); } /** @@ -2420,10 +2445,10 @@ class PHPMailer */ public function encodeQP($string, $line_max = 76) { - if (function_exists('quoted_printable_encode')) { //Use native function if it's available (>= PHP5.3) + if (function_exists('quoted_printable_encode')) { // Use native function if it's available (>= PHP5.3) return $this->fixEOL(quoted_printable_encode($string)); } - //Fall back to a pure PHP implementation + // Fall back to a pure PHP implementation $string = str_replace( array('%20', '%0D%0A.', '%0D%0A', '%'), array(' ', "\r\n=2E", "\r\n", '='), @@ -2439,7 +2464,7 @@ class PHPMailer * @access public * @param string $string * @param integer $line_max - * @param bool $space_conv + * @param boolean $space_conv * @return string * @deprecated Use encodeQP instead. */ @@ -2461,31 +2486,31 @@ class PHPMailer */ 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 = ''; $encoded = str_replace(array("\r", "\n"), '', $str); switch (strtolower($position)) { case 'phrase': - //RFC 2047 section 5.3 + // RFC 2047 section 5.3 $pattern = '^A-Za-z0-9!*+\/ -'; break; /** @noinspection PhpMissingBreakStatementInspection */ case 'comment': - //RFC 2047 section 5.2 + // 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 - //Replace every high ascii, control, =, ? and _ characters + // RFC 2047 section 5.1 + // Replace every high ascii, control, =, ? and _ characters $pattern = '\000-\011\013\014\016-\037\075\077\137\177-\377' . $pattern; break; } $matches = array(); if (preg_match_all("/[{$pattern}]/", $encoded, $matches)) { - //If the string contains an '=', make sure it's the first thing we replace - //so as to avoid double-encoding + // If the string contains an '=', make sure it's the first thing we replace + // so as to avoid double-encoding $eqkey = array_search('=', $matches[0]); if ($eqkey !== false) { unset($matches[0][$eqkey]); @@ -2495,7 +2520,7 @@ class PHPMailer $encoded = str_replace($char, '=' . sprintf('%02X', ord($char)), $encoded); } } - //Replace every spaces to _ (more readable than =20) + // Replace every spaces to _ (more readable than =20) return str_replace(' ', '_', $encoded); } @@ -2518,7 +2543,7 @@ class PHPMailer $type = '', $disposition = 'attachment' ) { - //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 == '') { $type = self::filenameToType($filename); } @@ -2549,7 +2574,7 @@ class PHPMailer * @param string $encoding File encoding (see $Encoding). * @param string $type File MIME type. * @param string $disposition Disposition to use - * @return bool True on successfully adding an attachment + * @return boolean True on successfully adding an attachment */ public function addEmbeddedImage($path, $cid, $name = '', $encoding = 'base64', $type = '', $disposition = 'inline') { @@ -2558,7 +2583,7 @@ class PHPMailer return false; } - //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 == '') { $type = self::filenameToType($path); } @@ -2594,7 +2619,7 @@ class PHPMailer * @param string $encoding File encoding (see $Encoding). * @param string $type MIME type. * @param string $disposition Disposition to use - * @return bool True on successfully adding an attachment + * @return boolean True on successfully adding an attachment */ public function addStringEmbeddedImage( $string, @@ -2604,7 +2629,7 @@ class PHPMailer $type = '', $disposition = 'inline' ) { - //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 == '') { $type = self::filenameToType($name); } @@ -2626,7 +2651,7 @@ class PHPMailer /** * Check if an inline attachment is present. * @access public - * @return bool + * @return boolean */ public function inlineImageExists() { @@ -2640,7 +2665,7 @@ class PHPMailer /** * Check if an attachment (non-inline) is present. - * @return bool + * @return boolean */ public function attachmentExists() { @@ -2654,7 +2679,7 @@ class PHPMailer /** * Check if this message has an alternative body set. - * @return bool + * @return boolean */ public function alternativeExists() { @@ -2762,8 +2787,8 @@ class PHPMailer */ public static function rfcDate() { - //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 + // 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 date_default_timezone_set(@date_default_timezone_get()); return date('D, j M Y H:i:s O'); } @@ -2811,7 +2836,7 @@ class PHPMailer /** * Check if an error occurred. * @access public - * @return bool True if an error did occur. + * @return boolean True if an error did occur. */ public function isError() { @@ -2863,7 +2888,7 @@ class PHPMailer * @access public * @param string $message HTML message string * @param string $basedir baseline directory for path - * @param bool $advanced Whether to use the advanced HTML to text converter + * @param boolean $advanced Whether to use the advanced HTML to text converter * @return string $message */ public function msgHTML($message, $basedir = '', $advanced = false) @@ -2871,14 +2896,30 @@ class PHPMailer preg_match_all('/(src|background)=["\'](.*)["\']/Ui', $message, $images); if (isset($images[2])) { foreach ($images[2] as $imgindex => $url) { - // do not change urls for absolute images (thanks to corvuscorax) - if (!preg_match('#^[A-z]+://#', $url)) { + // Convert data URIs into embedded images + if (preg_match('#^data:(image[^;,]*)(;base64)?,#', $url, $match)) { + $data = substr($url, strpos($url, ',')); + if ($match[2]) { + $data = base64_decode($data); + } else { + $data = rawurldecode($data); + } + $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2 + if ($this->addStringEmbeddedImage($data, $cid, '', 'base64', $match[1])) { + $message = preg_replace( + '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui', + $images[1][$imgindex] . '="cid:' . $cid . '"', + $message + ); + } + } elseif (!preg_match('#^[A-z]+://#', $url)) { + // Do not change urls for absolute images (thanks to corvuscorax) $filename = basename($url); $directory = dirname($url); if ($directory == '.') { $directory = ''; } - $cid = md5($url) . '@phpmailer.0'; //RFC2392 S 2 + $cid = md5($url) . '@phpmailer.0'; // RFC2392 S 2 if (strlen($basedir) > 1 && substr($basedir, -1) != '/') { $basedir .= '/'; } @@ -2903,7 +2944,7 @@ class PHPMailer } } $this->isHTML(true); - //Convert all message body line breaks to CRLF, makes quoted-printable encoding work much better + // 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)) { @@ -2916,7 +2957,7 @@ class PHPMailer /** * Convert an HTML string into plain text. * @param string $html The HTML text to convert - * @param bool $advanced Should this use the more complex html2text converter or just a simple one? + * @param boolean $advanced Should this use the more complex html2text converter or just a simple one? * @return string */ public function html2text($html, $advanced = false) @@ -3044,7 +3085,7 @@ class PHPMailer */ public static function filenameToType($filename) { - //In case the path is a URL, strip any query string before getting extension + // In case the path is a URL, strip any query string before getting extension $qpos = strpos($filename, '?'); if ($qpos !== false) { $filename = substr($filename, 0, $qpos); @@ -3068,36 +3109,33 @@ class PHPMailer { $ret = array('dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''); $pathinfo = array(); - preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo); - if (array_key_exists(1, $pathinfo)) { - $ret['dirname'] = $pathinfo[1]; - } - if (array_key_exists(2, $pathinfo)) { - $ret['basename'] = $pathinfo[2]; - } - if (array_key_exists(5, $pathinfo)) { - $ret['extension'] = $pathinfo[5]; - } - if (array_key_exists(3, $pathinfo)) { - $ret['filename'] = $pathinfo[3]; + if (preg_match('%^(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$%im', $path, $pathinfo)) { + if (array_key_exists(1, $pathinfo)) { + $ret['dirname'] = $pathinfo[1]; + } + if (array_key_exists(2, $pathinfo)) { + $ret['basename'] = $pathinfo[2]; + } + if (array_key_exists(5, $pathinfo)) { + $ret['extension'] = $pathinfo[5]; + } + if (array_key_exists(3, $pathinfo)) { + $ret['filename'] = $pathinfo[3]; + } } switch ($options) { case PATHINFO_DIRNAME: case 'dirname': return $ret['dirname']; - break; case PATHINFO_BASENAME: case 'basename': return $ret['basename']; - break; case PATHINFO_EXTENSION: case 'extension': return $ret['extension']; - break; case PATHINFO_FILENAME: case 'filename': return $ret['filename']; - break; default: return $ret; } @@ -3114,8 +3152,8 @@ class PHPMailer * @param mixed $value * NOTE: will not work with arrays, there are no arrays to set/reset * @throws phpmailerException - * @return bool - * @todo Should this not be using __set() magic function? + * @return boolean + * @TODO Should this not be using __set() magic function? */ public function set($name, $value = '') { @@ -3198,11 +3236,11 @@ class PHPMailer /** * Generate a DKIM signature. * @access public - * @param string $signheader Header + * @param string $signHeader * @throws phpmailerException * @return string */ - public function DKIM_Sign($signheader) + public function DKIM_Sign($signHeader) { if (!defined('PKCS7_TEXT')) { if ($this->exceptions) { @@ -3216,7 +3254,7 @@ class PHPMailer } else { $privKey = $privKeyStr; } - if (openssl_sign($signheader, $signature, $privKey)) { + if (openssl_sign($signHeader, $signature, $privKey)) { return base64_encode($signature); } return ''; @@ -3225,21 +3263,21 @@ class PHPMailer /** * Generate a DKIM canonicalization header. * @access public - * @param string $signheader Header + * @param string $signHeader Header * @return string */ - public function DKIM_HeaderC($signheader) + public function DKIM_HeaderC($signHeader) { - $signheader = preg_replace('/\r\n\s+/', ' ', $signheader); - $lines = explode("\r\n", $signheader); + $signHeader = preg_replace('/\r\n\s+/', ' ', $signHeader); + $lines = explode("\r\n", $signHeader); foreach ($lines as $key => $line) { list($heading, $value) = explode(':', $line, 2); $heading = strtolower($heading); $value = preg_replace('/\s+/', ' ', $value); // Compress useless spaces $lines[$key] = $heading . ':' . trim($value); // Don't forget to remove WSP around the value } - $signheader = implode("\r\n", $lines); - return $signheader; + $signHeader = implode("\r\n", $lines); + return $signHeader; } /** @@ -3381,15 +3419,15 @@ class PHPMailer /** * Perform a callback. - * @param bool $isSent - * @param string $to - * @param string $cc - * @param string $bcc + * @param boolean $isSent + * @param array $to + * @param array $cc + * @param array $bcc * @param string $subject * @param string $body * @param string $from */ - protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from = null) + protected function doCallback($isSent, $to, $cc, $bcc, $subject, $body, $from) { if (!empty($this->action_function) && is_callable($this->action_function)) { $params = array($isSent, $to, $cc, $bcc, $subject, $body, $from); diff --git a/lib/classes/phpmailer/class.SMTP.php b/lib/classes/phpmailer/class.SMTP.php index ec75ca54..9d1f2283 100644 --- a/lib/classes/phpmailer/class.SMTP.php +++ b/lib/classes/phpmailer/class.SMTP.php @@ -30,7 +30,7 @@ class SMTP * The PHPMailer SMTP version number. * @type string */ - const VERSION = '5.2.8'; + const VERSION = '5.2.9'; /** * SMTP line break constant. @@ -40,28 +40,53 @@ class SMTP /** * The SMTP port to use if one is not specified. - * @type int + * @type integer */ const DEFAULT_SMTP_PORT = 25; /** * The maximum line length allowed by RFC 2822 section 2.1.1 - * @type int + * @type integer */ const MAX_LINE_LENGTH = 998; + /** + * Debug level for no output + */ + const DEBUG_OFF = 0; + + /** + * Debug level to show client -> server messages + */ + const DEBUG_CLIENT = 1; + + /** + * Debug level to show client -> server and server -> client messages + */ + const DEBUG_SERVER = 2; + + /** + * Debug level to show connection status, client -> server and server -> client messages + */ + const DEBUG_CONNECTION = 3; + + /** + * Debug level to show all messages + */ + const DEBUG_LOWLEVEL = 4; + /** * The PHPMailer SMTP Version number. * @type string - * @deprecated Use the constant instead + * @deprecated Use the `VERSION` constant instead * @see SMTP::VERSION */ - public $Version = '5.2.8'; + public $Version = '5.2.9'; /** * SMTP server port number. - * @type int - * @deprecated This is only ever used as a default value, so use the constant instead + * @type integer + * @deprecated This is only ever used as a default value, so use the `DEFAULT_SMTP_PORT` constant instead * @see SMTP::DEFAULT_SMTP_PORT */ public $SMTP_PORT = 25; @@ -69,7 +94,7 @@ class SMTP /** * SMTP reply line ending. * @type string - * @deprecated Use the constant instead + * @deprecated Use the `CRLF` constant instead * @see SMTP::CRLF */ public $CRLF = "\r\n"; @@ -77,22 +102,27 @@ class SMTP /** * Debug output level. * Options: - * * `0` No output - * * `1` Commands - * * `2` Data and commands - * * `3` As 2 plus connection status - * * `4` Low-level data output - * @type int + * * self::DEBUG_OFF (`0`) No debug output, default + * * self::DEBUG_CLIENT (`1`) Client commands + * * self::DEBUG_SERVER (`2`) Client commands and server responses + * * self::DEBUG_CONNECTION (`3`) As DEBUG_SERVER plus connection status + * * self::DEBUG_LOWLEVEL (`4`) Low-level data output, all messages + * @type integer */ - public $do_debug = 0; + public $do_debug = self::DEBUG_OFF; /** * How to handle debug output. * Options: * * `echo` Output plain-text as-is, appropriate for CLI - * * `html` Output escaped, line breaks converted to
, appropriate for browser output + * * `html` Output escaped, line breaks converted to `
`, appropriate for browser output * * `error_log` Output to error log as configured in php.ini - * @type string + * + * Alternatively, you can provide a callable expecting two params: a message string and the debug level: + * + * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; + * + * @type string|callable */ public $Debugoutput = 'echo'; @@ -100,7 +130,7 @@ class SMTP * Whether to use VERP. * @link http://en.wikipedia.org/wiki/Variable_envelope_return_path * @link http://www.postfix.org/VERP_README.html Info on VERP - * @type bool + * @type boolean */ public $do_verp = false; @@ -109,13 +139,13 @@ class SMTP * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 * This needs to be quite high to function correctly with hosts using greetdelay as an anti-spam measure. * @link http://tools.ietf.org/html/rfc2821#section-4.5.3.2 - * @type int + * @type integer */ public $Timeout = 300; /** * The SMTP timelimit value for reads, in seconds. - * @type int + * @type integer */ public $Timelimit = 30; @@ -127,15 +157,16 @@ class SMTP /** * Error message, if any, for the last call. - * @type string + * @type array */ - protected $error = ''; + protected $error = array(); /** * The reply the server sent to us for HELO. - * @type string + * If null, no HELO string has yet been received. + * @type string|null */ - protected $helo_rply = ''; + protected $helo_rply = null; /** * The most recent reply received from the server. @@ -143,25 +174,23 @@ class SMTP */ protected $last_reply = ''; - /** - * Constructor. - * @access public - */ - public function __construct() - { - $this->smtp_conn = 0; - $this->error = null; - $this->helo_rply = null; - $this->do_debug = 0; - } - /** * Output debugging info via a user-selected method. + * @see SMTP::$Debugoutput + * @see SMTP::$do_debug * @param string $str Debug string to output + * @param integer $level The debug level of this message; see DEBUG_* constants * @return void */ - protected function edebug($str) + protected function edebug($str, $level = 0) { + if ($level > $this->do_debug) { + return; + } + if (is_callable($this->Debugoutput)) { + call_user_func($this->Debugoutput, $str, $this->do_debug); + return; + } switch ($this->Debugoutput) { case 'error_log': //Don't output, just log @@ -178,23 +207,35 @@ class SMTP break; case 'echo': default: - echo gmdate('Y-m-d H:i:s')."\t".trim($str)."\n"; + //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"; } } /** * Connect to an SMTP server. * @param string $host SMTP server IP or host name - * @param int $port The port number to connect to - * @param int $timeout How long to wait for the connection to open + * @param integer $port The port number to connect to + * @param integer $timeout How long to wait for the connection to open * @param array $options An array of options for stream_context_create() * @access public - * @return bool + * @return boolean */ public function connect($host, $port = null, $timeout = 30, $options = array()) { + static $streamok; + //This is enabled by default since 5.0.0 but some providers disable it + //Check this once and cache the result + if (is_null($streamok)) { + $streamok = function_exists('stream_socket_client'); + } // Clear errors to avoid confusion - $this->error = null; + $this->error = array(); // Make sure we are __not__ connected if ($this->connected()) { // Already connected, generate error @@ -205,39 +246,52 @@ class SMTP $port = self::DEFAULT_SMTP_PORT; } // Connect to the SMTP server - if ($this->do_debug >= 3) { - $this->edebug('Connection: opening'); - } + $this->edebug( + "Connection: opening to $host:$port, t=$timeout, opt=".var_export($options, true), + self::DEBUG_CONNECTION + ); $errno = 0; $errstr = ''; - $socket_context = stream_context_create($options); - //Suppress errors; connection failures are handled at a higher level - $this->smtp_conn = @stream_socket_client( - $host . ":" . $port, - $errno, - $errstr, - $timeout, - STREAM_CLIENT_CONNECT, - $socket_context - ); + if ($streamok) { + $socket_context = stream_context_create($options); + //Suppress errors; connection failures are handled at a higher level + $this->smtp_conn = @stream_socket_client( + $host . ":" . $port, + $errno, + $errstr, + $timeout, + STREAM_CLIENT_CONNECT, + $socket_context + ); + } 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 + ); + $this->smtp_conn = fsockopen( + $host, + $port, + $errno, + $errstr, + $timeout + ); + } // Verify we connected properly - if (empty($this->smtp_conn)) { + if (!is_resource($this->smtp_conn)) { $this->error = array( 'error' => 'Failed to connect to server', 'errno' => $errno, 'errstr' => $errstr ); - if ($this->do_debug >= 1) { - $this->edebug( - 'SMTP ERROR: ' . $this->error['error'] - . ": $errstr ($errno)" - ); - } + $this->edebug( + 'SMTP ERROR: ' . $this->error['error'] + . ": $errstr ($errno)", + self::DEBUG_CLIENT + ); return false; } - if ($this->do_debug >= 3) { - $this->edebug('Connection: opened'); - } + $this->edebug('Connection: opened', self::DEBUG_CONNECTION); // SMTP server can take longer to respond, give longer timeout for first read // Windows does not have support for this timeout function if (substr(PHP_OS, 0, 3) != 'WIN') { @@ -249,16 +303,14 @@ class SMTP } // Get any announcement $announce = $this->get_lines(); - if ($this->do_debug >= 2) { - $this->edebug('SERVER -> CLIENT: ' . $announce); - } + $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER); return true; } /** * Initiate a TLS (encrypted) session. * @access public - * @return bool + * @return boolean */ public function startTLS() { @@ -286,7 +338,7 @@ class SMTP * @param string $realm The auth realm for NTLM * @param string $workstation The auth workstation for NTLM * @access public - * @return bool True if successfully authenticated. + * @return boolean True if successfully authenticated. */ public function authenticate( $username, @@ -341,12 +393,11 @@ class SMTP //Check that functions are available if (!$ntlm_client->Initialize($temp)) { $this->error = array('error' => $temp->error); - if ($this->do_debug >= 1) { - $this->edebug( - 'You need to enable some modules in your php.ini file: ' - . $this->error['error'] - ); - } + $this->edebug( + 'You need to enable some modules in your php.ini file: ' + . $this->error['error'], + self::DEBUG_CLIENT + ); return false; } //msg1 @@ -377,7 +428,6 @@ class SMTP ); // send encoded username return $this->sendCommand('Username', base64_encode($msg3), 235); - break; case 'CRAM-MD5': // Start authentication if (!$this->sendCommand('AUTH CRAM-MD5', 'AUTH CRAM-MD5', 334)) { @@ -391,7 +441,6 @@ class SMTP // send encoded credentials return $this->sendCommand('Username', base64_encode($response), 235); - break; } return true; } @@ -417,7 +466,7 @@ class SMTP // RFC 2104 HMAC implementation for php. // Creates an md5 HMAC. // Eliminates the need to install mhash to compute a HMAC - // Hacked by Lance Rushing + // by Lance Rushing $bytelen = 64; // byte length for md5 if (strlen($key) > $bytelen) { @@ -435,19 +484,18 @@ class SMTP /** * Check connection state. * @access public - * @return bool True if connected. + * @return boolean True if connected. */ public function connected() { - if (!empty($this->smtp_conn)) { + if (is_resource($this->smtp_conn)) { $sock_status = stream_get_meta_data($this->smtp_conn); if ($sock_status['eof']) { - // the socket is valid but we are not connected - if ($this->do_debug >= 1) { - $this->edebug( - 'SMTP NOTICE: EOF caught while checking if connected' - ); - } + // The socket is valid but we are not connected + $this->edebug( + 'SMTP NOTICE: EOF caught while checking if connected', + self::DEBUG_CLIENT + ); $this->close(); return false; } @@ -465,15 +513,13 @@ class SMTP */ public function close() { - $this->error = null; // so there is no confusion + $this->error = array(); $this->helo_rply = null; - if (!empty($this->smtp_conn)) { + if (is_resource($this->smtp_conn)) { // close the connection and cleanup fclose($this->smtp_conn); - if ($this->do_debug >= 3) { - $this->edebug('Connection: closed'); - } - $this->smtp_conn = 0; + $this->smtp_conn = null; //Makes for cleaner serialization + $this->edebug('Connection: closed', self::DEBUG_CONNECTION); } } @@ -487,7 +533,7 @@ class SMTP * Implements rfc 821: DATA * @param string $msg_data Message data to send * @access public - * @return bool + * @return boolean */ public function data($msg_data) { @@ -569,12 +615,12 @@ class SMTP * and RFC 2821 EHLO. * @param string $host The host name or IP to connect to * @access public - * @return bool + * @return boolean */ public function hello($host = '') { // Try extended hello first (RFC 2821) - return (bool)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host)); + return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host)); } /** @@ -584,7 +630,7 @@ class SMTP * @param string $hello The HELO string * @param string $host The hostname to say we are * @access protected - * @return bool + * @return boolean */ protected function sendHello($hello, $host) { @@ -602,7 +648,7 @@ class SMTP * Implements rfc 821: MAIL FROM: * @param string $from Source address of this message * @access public - * @return bool + * @return boolean */ public function mail($from) { @@ -618,9 +664,9 @@ class SMTP * Send an SMTP QUIT command. * Closes the socket if there is no error or the $close_on_error argument is true. * Implements from rfc 821: QUIT - * @param bool $close_on_error Should the connection close if an error occurs? + * @param boolean $close_on_error Should the connection close if an error occurs? * @access public - * @return bool + * @return boolean */ public function quit($close_on_error = true) { @@ -640,7 +686,7 @@ class SMTP * Implements from rfc 821: RCPT TO: * @param string $toaddr The address the message is being sent to * @access public - * @return bool + * @return boolean */ public function recipient($toaddr) { @@ -656,7 +702,7 @@ class SMTP * Abort any transaction that is currently in progress. * Implements rfc 821: RSET * @access public - * @return bool True on success. + * @return boolean True on success. */ public function reset() { @@ -667,9 +713,9 @@ class SMTP * Send a command to an SMTP server and check its return code. * @param string $command The command name - not sent to the server * @param string $commandstring The actual command to send - * @param int|array $expect One or more expected integer success codes + * @param integer|array $expect One or more expected integer success codes * @access protected - * @return bool True on success. + * @return boolean True on success. */ protected function sendCommand($command, $commandstring, $expect) { @@ -681,30 +727,25 @@ class SMTP } $this->client_send($commandstring . self::CRLF); - $reply = $this->get_lines(); - $code = substr($reply, 0, 3); + $this->last_reply = $this->get_lines(); + $code = substr($this->last_reply, 0, 3); - if ($this->do_debug >= 2) { - $this->edebug('SERVER -> CLIENT: ' . $reply); - } + $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); if (!in_array($code, (array)$expect)) { - $this->last_reply = null; $this->error = array( 'error' => "$command command failed", 'smtp_code' => $code, - 'detail' => substr($reply, 4) + 'detail' => substr($this->last_reply, 4) + ); + $this->edebug( + 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply, + self::DEBUG_CLIENT ); - if ($this->do_debug >= 1) { - $this->edebug( - 'SMTP ERROR: ' . $this->error['error'] . ': ' . $reply - ); - } return false; } - $this->last_reply = $reply; - $this->error = null; + $this->error = array(); return true; } @@ -719,7 +760,7 @@ class SMTP * Implements rfc 821: SAML FROM: * @param string $from The address the message is from * @access public - * @return bool + * @return boolean */ public function sendAndMail($from) { @@ -730,7 +771,7 @@ class SMTP * Send an SMTP VRFY command. * @param string $name The name to verify * @access public - * @return bool + * @return boolean */ public function verify($name) { @@ -741,7 +782,7 @@ class SMTP * Send an SMTP NOOP command. * Used to keep keep-alives alive, doesn't actually do anything * @access public - * @return bool + * @return boolean */ public function noop() { @@ -755,16 +796,14 @@ class SMTP * and _may_ be implemented in future * Implements from rfc 821: TURN * @access public - * @return bool + * @return boolean */ public function turn() { $this->error = array( 'error' => 'The SMTP TURN command is not implemented' ); - if ($this->do_debug >= 1) { - $this->edebug('SMTP NOTICE: ' . $this->error['error']); - } + $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); return false; } @@ -772,13 +811,11 @@ class SMTP * Send raw data to the server. * @param string $data The data to send * @access public - * @return int|bool The number of bytes sent to the server or false on error + * @return integer|boolean The number of bytes sent to the server or false on error */ public function client_send($data) { - if ($this->do_debug >= 1) { - $this->edebug("CLIENT -> SERVER: $data"); - } + $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT); return fwrite($this->smtp_conn, $data); } @@ -825,14 +862,10 @@ class SMTP } while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { $str = @fgets($this->smtp_conn, 515); - if ($this->do_debug >= 4) { - $this->edebug("SMTP -> get_lines(): \$data was \"$data\""); - $this->edebug("SMTP -> get_lines(): \$str is \"$str\""); - } + $this->edebug("SMTP -> get_lines(): \$data was \"$data\"", self::DEBUG_LOWLEVEL); + $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL); $data .= $str; - if ($this->do_debug >= 4) { - $this->edebug("SMTP -> get_lines(): \$data is \"$data\""); - } + $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL); // If 4th character is a space, we are done reading, break the loop, micro-optimisation over strlen if ((isset($str[3]) and $str[3] == ' ')) { break; @@ -840,21 +873,19 @@ class SMTP // Timed-out? Log and break $info = stream_get_meta_data($this->smtp_conn); if ($info['timed_out']) { - if ($this->do_debug >= 4) { - $this->edebug( - 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)' - ); - } + $this->edebug( + 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)', + self::DEBUG_LOWLEVEL + ); break; } // Now check if reads took too long if ($endtime and time() > $endtime) { - if ($this->do_debug >= 4) { - $this->edebug( - 'SMTP -> get_lines(): timelimit reached ('. - $this->Timelimit . ' sec)' - ); - } + $this->edebug( + 'SMTP -> get_lines(): timelimit reached ('. + $this->Timelimit . ' sec)', + self::DEBUG_LOWLEVEL + ); break; } } @@ -863,7 +894,7 @@ class SMTP /** * Enable or disable VERP address generation. - * @param bool $enabled + * @param boolean $enabled */ public function setVerp($enabled = false) { @@ -872,7 +903,7 @@ class SMTP /** * Get VERP address generation mode. - * @return bool + * @return boolean */ public function getVerp() { @@ -899,7 +930,7 @@ class SMTP /** * Set debug output level. - * @param int $level + * @param integer $level */ public function setDebugLevel($level = 0) { @@ -908,7 +939,7 @@ class SMTP /** * Get debug output level. - * @return int + * @return integer */ public function getDebugLevel() { @@ -917,7 +948,7 @@ class SMTP /** * Set SMTP timeout. - * @param int $timeout + * @param integer $timeout */ public function setTimeout($timeout = 0) { @@ -926,7 +957,7 @@ class SMTP /** * Get SMTP timeout. - * @return int + * @return integer */ public function getTimeout() {