From b905c326c2fac53a80836bdd0067662cfee421f2 Mon Sep 17 00:00:00 2001 From: Arcturus Date: Sat, 12 Oct 2024 13:34:19 +0900 Subject: [PATCH] update --- .editorconfig | 12 + .../plugin/PHPMailer/PHPMailerAutoload.php | 16 +- .../plugin/PHPMailer/class.phpmaileroauth.php | 323 +- .../PHPMailer/class.phpmaileroauthgoogle.php | 86 +- AvocadoAmber/plugin/PHPMailer/class.pop3.php | 677 ++-- AvocadoAmber/plugin/PHPMailer/class.smtp.php | 2340 +++++++------- .../plugin/PHPMailer/extras/EasyPeasyICS.php | 202 +- .../plugin/PHPMailer/extras/htmlfilter.php | 1752 +++++------ .../PHPMailer/extras/ntlm_sasl_client.php | 321 +- .../plugin/PHPMailer/get_oauth_token.php | 210 +- AvocadoAmber/plugin/browscap/Browscap.php | 2730 +++++++++-------- AvocadoAmber/plugin/kcaptcha/kcaptcha.lib.php | 470 +-- .../plugin/kcaptcha/kcaptcha_config.php | 102 +- .../plugin/kcaptcha/kcaptcha_image.php | 9 +- AvocadoAmber/plugin/kcaptcha/kcaptcha_mp3.php | 74 +- .../plugin/kcaptcha/kcaptcha_result.php | 16 +- 16 files changed, 4711 insertions(+), 4629 deletions(-) diff --git a/.editorconfig b/.editorconfig index 78c6dde..48d0668 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,3 +6,15 @@ insert_final_newline = true charset = utf-8 indent_style = space indent_size = 2 + +[*.php] +indent_style = space +indent_size = 2 + +[composer.json] +indent_style = space +indent_size = 2 + +[*.yml] +indent_style = space +indent_size = 2 diff --git a/AvocadoAmber/plugin/PHPMailer/PHPMailerAutoload.php b/AvocadoAmber/plugin/PHPMailer/PHPMailerAutoload.php index dc066c2..4247e36 100644 --- a/AvocadoAmber/plugin/PHPMailer/PHPMailerAutoload.php +++ b/AvocadoAmber/plugin/PHPMailer/PHPMailerAutoload.php @@ -23,16 +23,16 @@ */ function PHPMailerAutoload($classname) { - //Can't use __DIR__ as it's only in PHP 5.3+ - $filename = dirname(__FILE__).DIRECTORY_SEPARATOR.'class.'.strtolower($classname).'.php'; - if (is_readable($filename)) { - require $filename; - } + //Can't use __DIR__ as it's only in PHP 5.3+ + $filename = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'class.' . strtolower($classname) . '.php'; + if (is_readable($filename)) { + require $filename; + } } //SPL autoloading was introduced in PHP 5.1.2 if (version_compare(PHP_VERSION, '5.3.0', '>=')) { - spl_autoload_register('PHPMailerAutoload', true, true); + spl_autoload_register('PHPMailerAutoload', true, true); } else { - spl_autoload_register('PHPMailerAutoload'); -} \ No newline at end of file + spl_autoload_register('PHPMailerAutoload'); +} diff --git a/AvocadoAmber/plugin/PHPMailer/class.phpmaileroauth.php b/AvocadoAmber/plugin/PHPMailer/class.phpmaileroauth.php index b1bb09f..7a35204 100644 --- a/AvocadoAmber/plugin/PHPMailer/class.phpmaileroauth.php +++ b/AvocadoAmber/plugin/PHPMailer/class.phpmaileroauth.php @@ -25,173 +25,174 @@ */ class PHPMailerOAuth extends PHPMailer { - /** - * The OAuth user's email address - * @var string - */ - public $oauthUserEmail = ''; + /** + * The OAuth user's email address + * @var string + */ + public $oauthUserEmail = ''; - /** - * The OAuth refresh token - * @var string - */ - public $oauthRefreshToken = ''; + /** + * The OAuth refresh token + * @var string + */ + public $oauthRefreshToken = ''; - /** - * The OAuth client ID - * @var string - */ - public $oauthClientId = ''; + /** + * The OAuth client ID + * @var string + */ + public $oauthClientId = ''; - /** - * The OAuth client secret - * @var string - */ - public $oauthClientSecret = ''; + /** + * The OAuth client secret + * @var string + */ + public $oauthClientSecret = ''; - /** - * An instance of the PHPMailerOAuthGoogle class. - * @var PHPMailerOAuthGoogle - * @access protected - */ - protected $oauth = null; + /** + * An instance of the PHPMailerOAuthGoogle class. + * @var PHPMailerOAuthGoogle + * @access protected + */ + protected $oauth = null; - /** - * Get a PHPMailerOAuthGoogle instance to use. - * @return PHPMailerOAuthGoogle - */ - public function getOAUTHInstance() - { - if (!is_object($this->oauth)) { - $this->oauth = new PHPMailerOAuthGoogle( - $this->oauthUserEmail, - $this->oauthClientSecret, - $this->oauthClientId, - $this->oauthRefreshToken - ); - } - return $this->oauth; + /** + * Get a PHPMailerOAuthGoogle instance to use. + * @return PHPMailerOAuthGoogle + */ + public function getOAUTHInstance() + { + if (!is_object($this->oauth)) { + $this->oauth = new PHPMailerOAuthGoogle( + $this->oauthUserEmail, + $this->oauthClientSecret, + $this->oauthClientId, + $this->oauthRefreshToken + ); + } + return $this->oauth; + } + + /** + * Initiate a connection to an SMTP server. + * Overrides the original smtpConnect method to add support for OAuth. + * @param array $options An array of options compatible with stream_context_create() + * @uses SMTP + * @access public + * @return bool + * @throws phpmailerException + */ + public function smtpConnect($options = array()) + { + if (is_null($this->smtp)) { + $this->smtp = $this->getSMTPInstance(); } - /** - * Initiate a connection to an SMTP server. - * Overrides the original smtpConnect method to add support for OAuth. - * @param array $options An array of options compatible with stream_context_create() - * @uses SMTP - * @access public - * @return bool - * @throws phpmailerException - */ - public function smtpConnect($options = array()) - { - if (is_null($this->smtp)) { - $this->smtp = $this->getSMTPInstance(); - } - - if (is_null($this->oauth)) { - $this->oauth = $this->getOAUTHInstance(); - } - - // Already connected? - if ($this->smtp->connected()) { - return true; - } - - $this->smtp->setTimeout($this->Timeout); - $this->smtp->setDebugLevel($this->SMTPDebug); - $this->smtp->setDebugOutput($this->Debugoutput); - $this->smtp->setVerp($this->do_verp); - $hosts = explode(';', $this->Host); - $lastexception = null; - - 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 - 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 - $prefix = ''; - $secure = $this->SMTPSecure; - $tls = ($this->SMTPSecure == 'tls'); - if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) { - $prefix = 'ssl://'; - $tls = false; // Can't have SSL and TLS at the same time - $secure = 'ssl'; - } elseif ($hostinfo[2] == 'tls') { - $tls = true; - // tls doesn't use a prefix - $secure = 'tls'; - } - //Do we need the OpenSSL extension? - $sslext = defined('OPENSSL_ALGO_SHA1'); - if ('tls' === $secure or 'ssl' === $secure) { - //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled - if (!$sslext) { - throw new phpmailerException($this->lang('extension_missing').'openssl', self::STOP_CRITICAL); - } - } - $host = $hostinfo[3]; - $port = $this->Port; - $tport = (integer)$hostinfo[4]; - if ($tport > 0 and $tport < 65536) { - $port = $tport; - } - if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { - try { - if ($this->Helo) { - $hello = $this->Helo; - } else { - $hello = $this->serverHostname(); - } - $this->smtp->hello($hello); - //Automatically enable TLS encryption if: - // * it's not disabled - // * we have openssl extension - // * we are not already using SSL - // * the server offers STARTTLS - if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) { - $tls = true; - } - if ($tls) { - if (!$this->smtp->startTLS()) { - throw new phpmailerException($this->lang('connect_host')); - } - // We must resend HELO after tls negotiation - $this->smtp->hello($hello); - } - if ($this->SMTPAuth) { - if (!$this->smtp->authenticate( - $this->Username, - $this->Password, - $this->AuthType, - $this->Realm, - $this->Workstation, - $this->oauth - ) - ) { - throw new phpmailerException($this->lang('authenticate')); - } - } - return true; - } catch (phpmailerException $exc) { - $lastexception = $exc; - $this->edebug($exc->getMessage()); - // 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 - $this->smtp->close(); - // As we've caught all exceptions, just report whatever the last one was - if ($this->exceptions and !is_null($lastexception)) { - throw $lastexception; - } - return false; + if (is_null($this->oauth)) { + $this->oauth = $this->getOAUTHInstance(); } + + // Already connected? + if ($this->smtp->connected()) { + return true; + } + + $this->smtp->setTimeout($this->Timeout); + $this->smtp->setDebugLevel($this->SMTPDebug); + $this->smtp->setDebugOutput($this->Debugoutput); + $this->smtp->setVerp($this->do_verp); + $hosts = explode(';', $this->Host); + $lastexception = null; + + 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 + 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 + $prefix = ''; + $secure = $this->SMTPSecure; + $tls = ($this->SMTPSecure == 'tls'); + if ('ssl' == $hostinfo[2] or ('' == $hostinfo[2] and 'ssl' == $this->SMTPSecure)) { + $prefix = 'ssl://'; + $tls = false; // Can't have SSL and TLS at the same time + $secure = 'ssl'; + } elseif ($hostinfo[2] == 'tls') { + $tls = true; + // tls doesn't use a prefix + $secure = 'tls'; + } + //Do we need the OpenSSL extension? + $sslext = defined('OPENSSL_ALGO_SHA1'); + if ('tls' === $secure or 'ssl' === $secure) { + //Check for an OpenSSL constant rather than using extension_loaded, which is sometimes disabled + if (!$sslext) { + throw new phpmailerException($this->lang('extension_missing') . 'openssl', self::STOP_CRITICAL); + } + } + $host = $hostinfo[3]; + $port = $this->Port; + $tport = (integer) $hostinfo[4]; + if ($tport > 0 and $tport < 65536) { + $port = $tport; + } + if ($this->smtp->connect($prefix . $host, $port, $this->Timeout, $options)) { + try { + if ($this->Helo) { + $hello = $this->Helo; + } else { + $hello = $this->serverHostname(); + } + $this->smtp->hello($hello); + //Automatically enable TLS encryption if: + // * it's not disabled + // * we have openssl extension + // * we are not already using SSL + // * the server offers STARTTLS + if ($this->SMTPAutoTLS and $sslext and $secure != 'ssl' and $this->smtp->getServerExt('STARTTLS')) { + $tls = true; + } + if ($tls) { + if (!$this->smtp->startTLS()) { + throw new phpmailerException($this->lang('connect_host')); + } + // We must resend HELO after tls negotiation + $this->smtp->hello($hello); + } + if ($this->SMTPAuth) { + if ( + !$this->smtp->authenticate( + $this->Username, + $this->Password, + $this->AuthType, + $this->Realm, + $this->Workstation, + $this->oauth + ) + ) { + throw new phpmailerException($this->lang('authenticate')); + } + } + return true; + } catch (phpmailerException $exc) { + $lastexception = $exc; + $this->edebug($exc->getMessage()); + // 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 + $this->smtp->close(); + // As we've caught all exceptions, just report whatever the last one was + if ($this->exceptions and !is_null($lastexception)) { + throw $lastexception; + } + return false; + } } diff --git a/AvocadoAmber/plugin/PHPMailer/class.phpmaileroauthgoogle.php b/AvocadoAmber/plugin/PHPMailer/class.phpmaileroauthgoogle.php index 71c9bd3..9bcb9de 100644 --- a/AvocadoAmber/plugin/PHPMailer/class.phpmaileroauthgoogle.php +++ b/AvocadoAmber/plugin/PHPMailer/class.phpmaileroauthgoogle.php @@ -26,52 +26,52 @@ */ class PHPMailerOAuthGoogle { - private $oauthUserEmail = ''; - private $oauthRefreshToken = ''; - private $oauthClientId = ''; - private $oauthClientSecret = ''; + private $oauthUserEmail = ''; + private $oauthRefreshToken = ''; + private $oauthClientId = ''; + private $oauthClientSecret = ''; - /** - * @param string $UserEmail - * @param string $ClientSecret - * @param string $ClientId - * @param string $RefreshToken - */ - public function __construct( - $UserEmail, - $ClientSecret, - $ClientId, - $RefreshToken - ) { - $this->oauthClientId = $ClientId; - $this->oauthClientSecret = $ClientSecret; - $this->oauthRefreshToken = $RefreshToken; - $this->oauthUserEmail = $UserEmail; - } + /** + * @param string $UserEmail + * @param string $ClientSecret + * @param string $ClientId + * @param string $RefreshToken + */ + public function __construct( + $UserEmail, + $ClientSecret, + $ClientId, + $RefreshToken + ) { + $this->oauthClientId = $ClientId; + $this->oauthClientSecret = $ClientSecret; + $this->oauthRefreshToken = $RefreshToken; + $this->oauthUserEmail = $UserEmail; + } - private function getProvider() - { - return new League\OAuth2\Client\Provider\Google([ - 'clientId' => $this->oauthClientId, - 'clientSecret' => $this->oauthClientSecret - ]); - } + private function getProvider() + { + return new League\OAuth2\Client\Provider\Google([ + 'clientId' => $this->oauthClientId, + 'clientSecret' => $this->oauthClientSecret + ]); + } - private function getGrant() - { - return new \League\OAuth2\Client\Grant\RefreshToken(); - } + private function getGrant() + { + return new \League\OAuth2\Client\Grant\RefreshToken(); + } - private function getToken() - { - $provider = $this->getProvider(); - $grant = $this->getGrant(); - return $provider->getAccessToken($grant, ['refresh_token' => $this->oauthRefreshToken]); - } + private function getToken() + { + $provider = $this->getProvider(); + $grant = $this->getGrant(); + return $provider->getAccessToken($grant, ['refresh_token' => $this->oauthRefreshToken]); + } - public function getOauth64() - { - $token = $this->getToken(); - return base64_encode("user=" . $this->oauthUserEmail . "\001auth=Bearer " . $token . "\001\001"); - } + public function getOauth64() + { + $token = $this->getToken(); + return base64_encode("user=" . $this->oauthUserEmail . "\001auth=Bearer " . $token . "\001\001"); + } } diff --git a/AvocadoAmber/plugin/PHPMailer/class.pop3.php b/AvocadoAmber/plugin/PHPMailer/class.pop3.php index e07a569..ee01cd4 100644 --- a/AvocadoAmber/plugin/PHPMailer/class.pop3.php +++ b/AvocadoAmber/plugin/PHPMailer/class.pop3.php @@ -29,379 +29,380 @@ */ class POP3 { - /** - * The POP3 PHPMailer Version number. - * @var string - * @access public - */ - public $Version = '5.2.28'; + /** + * The POP3 PHPMailer Version number. + * @var string + * @access public + */ + public $Version = '5.2.28'; - /** - * Default POP3 port number. - * @var integer - * @access public - */ - public $POP3_PORT = 110; + /** + * Default POP3 port number. + * @var integer + * @access public + */ + public $POP3_PORT = 110; - /** - * Default timeout in seconds. - * @var integer - * @access public - */ - public $POP3_TIMEOUT = 30; + /** + * Default timeout in seconds. + * @var integer + * @access public + */ + public $POP3_TIMEOUT = 30; - /** - * POP3 Carriage Return + Line Feed. - * @var string - * @access public - * @deprecated Use the constant instead - */ - public $CRLF = "\r\n"; + /** + * POP3 Carriage Return + Line Feed. + * @var string + * @access public + * @deprecated Use the constant instead + */ + public $CRLF = "\r\n"; - /** - * Debug display level. - * Options: 0 = no, 1+ = yes - * @var integer - * @access public - */ - public $do_debug = 0; + /** + * Debug display level. + * Options: 0 = no, 1+ = yes + * @var integer + * @access public + */ + public $do_debug = 0; - /** - * POP3 mail server hostname. - * @var string - * @access public - */ - public $host; + /** + * POP3 mail server hostname. + * @var string + * @access public + */ + public $host; - /** - * POP3 port number. - * @var integer - * @access public - */ - public $port; + /** + * POP3 port number. + * @var integer + * @access public + */ + public $port; - /** - * POP3 Timeout Value in seconds. - * @var integer - * @access public - */ - public $tval; + /** + * POP3 Timeout Value in seconds. + * @var integer + * @access public + */ + public $tval; - /** - * POP3 username - * @var string - * @access public - */ - public $username; + /** + * POP3 username + * @var string + * @access public + */ + public $username; - /** - * POP3 password. - * @var string - * @access public - */ - public $password; + /** + * POP3 password. + * @var string + * @access public + */ + public $password; - /** - * Resource handle for the POP3 connection socket. - * @var resource - * @access protected - */ - protected $pop_conn; + /** + * Resource handle for the POP3 connection socket. + * @var resource + * @access protected + */ + protected $pop_conn; - /** - * Are we connected? - * @var boolean - * @access protected - */ - protected $connected = false; + /** + * Are we connected? + * @var boolean + * @access protected + */ + protected $connected = false; - /** - * Error container. - * @var array - * @access protected - */ - protected $errors = array(); + /** + * Error container. + * @var array + * @access protected + */ + protected $errors = array(); - /** - * Line break constant - */ - const CRLF = "\r\n"; + /** + * Line break constant + */ + const CRLF = "\r\n"; - /** - * Simple static wrapper for all-in-one POP before SMTP - * @param $host - * @param integer|boolean $port The port number to connect to - * @param integer|boolean $timeout The timeout value - * @param string $username - * @param string $password - * @param integer $debug_level - * @return boolean - */ - public static function popBeforeSmtp( - $host, - $port = false, - $timeout = false, - $username = '', - $password = '', - $debug_level = 0 - ) { - $pop = new POP3; - return $pop->authorise($host, $port, $timeout, $username, $password, $debug_level); + /** + * Simple static wrapper for all-in-one POP before SMTP + * @param $host + * @param integer|boolean $port The port number to connect to + * @param integer|boolean $timeout The timeout value + * @param string $username + * @param string $password + * @param integer $debug_level + * @return boolean + */ + public static function popBeforeSmtp( + $host, + $port = false, + $timeout = false, + $username = '', + $password = '', + $debug_level = 0 + ) { + $pop = new POP3; + return $pop->authorise($host, $port, $timeout, $username, $password, $debug_level); + } + + /** + * Authenticate with a POP3 server. + * A connect, login, disconnect sequence + * appropriate for POP-before SMTP authorisation. + * @access public + * @param string $host The hostname to connect to + * @param integer|boolean $port The port number to connect to + * @param integer|boolean $timeout The timeout value + * @param string $username + * @param string $password + * @param integer $debug_level + * @return boolean + */ + public function authorise($host, $port = false, $timeout = false, $username = '', $password = '', $debug_level = 0) + { + $this->host = $host; + // If no port value provided, use default + if (false === $port) { + $this->port = $this->POP3_PORT; + } else { + $this->port = (integer) $port; } - - /** - * Authenticate with a POP3 server. - * A connect, login, disconnect sequence - * appropriate for POP-before SMTP authorisation. - * @access public - * @param string $host The hostname to connect to - * @param integer|boolean $port The port number to connect to - * @param integer|boolean $timeout The timeout value - * @param string $username - * @param string $password - * @param integer $debug_level - * @return boolean - */ - public function authorise($host, $port = false, $timeout = false, $username = '', $password = '', $debug_level = 0) - { - $this->host = $host; - // If no port value provided, use default - if (false === $port) { - $this->port = $this->POP3_PORT; - } else { - $this->port = (integer)$port; - } - // If no timeout value provided, use default - if (false === $timeout) { - $this->tval = $this->POP3_TIMEOUT; - } else { - $this->tval = (integer)$timeout; - } - $this->do_debug = $debug_level; - $this->username = $username; - $this->password = $password; - // Reset the error log - $this->errors = array(); - // connect - $result = $this->connect($this->host, $this->port, $this->tval); - if ($result) { - $login_result = $this->login($this->username, $this->password); - if ($login_result) { - $this->disconnect(); - return true; - } - } - // We need to disconnect regardless of whether the login succeeded + // If no timeout value provided, use default + if (false === $timeout) { + $this->tval = $this->POP3_TIMEOUT; + } else { + $this->tval = (integer) $timeout; + } + $this->do_debug = $debug_level; + $this->username = $username; + $this->password = $password; + // Reset the error log + $this->errors = array(); + // connect + $result = $this->connect($this->host, $this->port, $this->tval); + if ($result) { + $login_result = $this->login($this->username, $this->password); + if ($login_result) { $this->disconnect(); - return false; + return true; + } + } + // We need to disconnect regardless of whether the login succeeded + $this->disconnect(); + return false; + } + + /** + * Connect to a POP3 server. + * @access public + * @param string $host + * @param integer|boolean $port + * @param integer $tval + * @return boolean + */ + public function connect($host, $port = false, $tval = 30) + { + // Are we already connected? + if ($this->connected) { + return true; } - /** - * Connect to a POP3 server. - * @access public - * @param string $host - * @param integer|boolean $port - * @param integer $tval - * @return boolean - */ - public function connect($host, $port = false, $tval = 30) - { - // Are we already connected? - if ($this->connected) { - return true; - } + //On Windows this will raise a PHP Warning error if the hostname doesn't exist. + //Rather than suppress it with @fsockopen, capture it cleanly instead + set_error_handler(array($this, 'catchWarning')); - //On Windows this will raise a PHP Warning error if the hostname doesn't exist. - //Rather than suppress it with @fsockopen, capture it cleanly instead - set_error_handler(array($this, 'catchWarning')); - - if (false === $port) { - $port = $this->POP3_PORT; - } - - // connect to the POP3 server - $this->pop_conn = fsockopen( - $host, // POP3 Host - $port, // Port # - $errno, // Error Number - $errstr, // Error Message - $tval - ); // Timeout (seconds) - // Restore the error handler - restore_error_handler(); - - // Did we connect? - if (false === $this->pop_conn) { - // It would appear not... - $this->setError(array( - 'error' => "Failed to connect to server $host on port $port", - 'errno' => $errno, - 'errstr' => $errstr - )); - return false; - } - - // Increase the stream time-out - stream_set_timeout($this->pop_conn, $tval, 0); - - // Get the POP3 server response - $pop3_response = $this->getResponse(); - // Check for the +OK - if ($this->checkResponse($pop3_response)) { - // The connection is established and the POP3 server is talking - $this->connected = true; - return true; - } - return false; + if (false === $port) { + $port = $this->POP3_PORT; } - /** - * Log in to the POP3 server. - * Does not support APOP (RFC 2828, 4949). - * @access public - * @param string $username - * @param string $password - * @return boolean - */ - public function login($username = '', $password = '') - { - if (!$this->connected) { - $this->setError('Not connected to POP3 server'); - } - if (empty($username)) { - $username = $this->username; - } - if (empty($password)) { - $password = $this->password; - } + // connect to the POP3 server + $this->pop_conn = fsockopen( + $host, // POP3 Host + $port, // Port # + $errno, // Error Number + $errstr, // Error Message + $tval + ); // Timeout (seconds) + // Restore the error handler + restore_error_handler(); - // Send the Username - $this->sendString("USER $username" . self::CRLF); - $pop3_response = $this->getResponse(); - if ($this->checkResponse($pop3_response)) { - // Send the Password - $this->sendString("PASS $password" . self::CRLF); - $pop3_response = $this->getResponse(); - if ($this->checkResponse($pop3_response)) { - return true; - } - } - return false; + // Did we connect? + if (false === $this->pop_conn) { + // It would appear not... + $this->setError(array( + 'error' => "Failed to connect to server $host on port $port", + 'errno' => $errno, + 'errstr' => $errstr + )); + return false; } - /** - * Disconnect from the POP3 server. - * @access public - */ - public function disconnect() - { - $this->sendString('QUIT'); - //The QUIT command may cause the daemon to exit, which will kill our connection - //So ignore errors here - try { - @fclose($this->pop_conn); - } catch (Exception $e) { - //Do nothing - }; + // Increase the stream time-out + stream_set_timeout($this->pop_conn, $tval, 0); + + // Get the POP3 server response + $pop3_response = $this->getResponse(); + // Check for the +OK + if ($this->checkResponse($pop3_response)) { + // The connection is established and the POP3 server is talking + $this->connected = true; + return true; + } + return false; + } + + /** + * Log in to the POP3 server. + * Does not support APOP (RFC 2828, 4949). + * @access public + * @param string $username + * @param string $password + * @return boolean + */ + public function login($username = '', $password = '') + { + if (!$this->connected) { + $this->setError('Not connected to POP3 server'); + } + if (empty($username)) { + $username = $this->username; + } + if (empty($password)) { + $password = $this->password; } - /** - * Get a response from the POP3 server. - * $size is the maximum number of bytes to retrieve - * @param integer $size - * @return string - * @access protected - */ - protected function getResponse($size = 128) - { - $response = fgets($this->pop_conn, $size); - if ($this->do_debug >= 1) { - echo "Server -> Client: $response"; - } - return $response; + // Send the Username + $this->sendString("USER $username" . self::CRLF); + $pop3_response = $this->getResponse(); + if ($this->checkResponse($pop3_response)) { + // Send the Password + $this->sendString("PASS $password" . self::CRLF); + $pop3_response = $this->getResponse(); + if ($this->checkResponse($pop3_response)) { + return true; + } } + return false; + } - /** - * Send raw data to the POP3 server. - * @param string $string - * @return integer - * @access protected - */ - protected function sendString($string) - { - if ($this->pop_conn) { - if ($this->do_debug >= 2) { //Show client messages when debug >= 2 - echo "Client -> Server: $string"; - } - return fwrite($this->pop_conn, $string, strlen($string)); - } - return 0; + /** + * Disconnect from the POP3 server. + * @access public + */ + public function disconnect() + { + $this->sendString('QUIT'); + //The QUIT command may cause the daemon to exit, which will kill our connection + //So ignore errors here + try { + @fclose($this->pop_conn); + } catch (Exception $e) { + //Do nothing } + ; + } - /** - * Checks the POP3 server response. - * Looks for for +OK or -ERR. - * @param string $string - * @return boolean - * @access protected - */ - protected function checkResponse($string) - { - if (substr($string, 0, 3) !== '+OK') { - $this->setError(array( - 'error' => "Server reported an error: $string", - 'errno' => 0, - 'errstr' => '' - )); - return false; - } else { - return true; - } + /** + * Get a response from the POP3 server. + * $size is the maximum number of bytes to retrieve + * @param integer $size + * @return string + * @access protected + */ + protected function getResponse($size = 128) + { + $response = fgets($this->pop_conn, $size); + if ($this->do_debug >= 1) { + echo "Server -> Client: $response"; } + return $response; + } - /** - * Add an error to the internal error store. - * Also display debug output if it's enabled. - * @param $error - * @access protected - */ - protected function setError($error) - { - $this->errors[] = $error; - if ($this->do_debug >= 1) { - echo '
';
-            foreach ($this->errors as $error) {
-                print_r($error);
-            }
-            echo '
'; - } + /** + * Send raw data to the POP3 server. + * @param string $string + * @return integer + * @access protected + */ + protected function sendString($string) + { + if ($this->pop_conn) { + if ($this->do_debug >= 2) { //Show client messages when debug >= 2 + echo "Client -> Server: $string"; + } + return fwrite($this->pop_conn, $string, strlen($string)); } + return 0; + } - /** - * Get an array of error messages, if any. - * @return array - */ - public function getErrors() - { - return $this->errors; + /** + * Checks the POP3 server response. + * Looks for for +OK or -ERR. + * @param string $string + * @return boolean + * @access protected + */ + protected function checkResponse($string) + { + if (substr($string, 0, 3) !== '+OK') { + $this->setError(array( + 'error' => "Server reported an error: $string", + 'errno' => 0, + 'errstr' => '' + )); + return false; + } else { + return true; } + } - /** - * POP3 connection error handler. - * @param integer $errno - * @param string $errstr - * @param string $errfile - * @param integer $errline - * @access protected - */ - protected function catchWarning($errno, $errstr, $errfile, $errline) - { - $this->setError(array( - 'error' => "Connecting to the POP3 server raised a PHP warning: ", - 'errno' => $errno, - 'errstr' => $errstr, - 'errfile' => $errfile, - 'errline' => $errline - )); + /** + * Add an error to the internal error store. + * Also display debug output if it's enabled. + * @param $error + * @access protected + */ + protected function setError($error) + { + $this->errors[] = $error; + if ($this->do_debug >= 1) { + echo '
';
+      foreach ($this->errors as $error) {
+        print_r($error);
+      }
+      echo '
'; } + } + + /** + * Get an array of error messages, if any. + * @return array + */ + public function getErrors() + { + return $this->errors; + } + + /** + * POP3 connection error handler. + * @param integer $errno + * @param string $errstr + * @param string $errfile + * @param integer $errline + * @access protected + */ + protected function catchWarning($errno, $errstr, $errfile, $errline) + { + $this->setError(array( + 'error' => "Connecting to the POP3 server raised a PHP warning: ", + 'errno' => $errno, + 'errstr' => $errstr, + 'errfile' => $errfile, + 'errline' => $errline + )); + } } diff --git a/AvocadoAmber/plugin/PHPMailer/class.smtp.php b/AvocadoAmber/plugin/PHPMailer/class.smtp.php index d2b9a0a..d19caea 100644 --- a/AvocadoAmber/plugin/PHPMailer/class.smtp.php +++ b/AvocadoAmber/plugin/PHPMailer/class.smtp.php @@ -26,1251 +26,1253 @@ */ class SMTP { - /** - * The PHPMailer SMTP version number. - * @var string - */ - const VERSION = '5.2.28'; + /** + * The PHPMailer SMTP version number. + * @var string + */ + const VERSION = '5.2.28'; - /** - * SMTP line break constant. - * @var string - */ - const CRLF = "\r\n"; + /** + * SMTP line break constant. + * @var string + */ + const CRLF = "\r\n"; - /** - * The SMTP port to use if one is not specified. - * @var integer - */ - const DEFAULT_SMTP_PORT = 25; + /** + * The SMTP port to use if one is not specified. + * @var integer + */ + const DEFAULT_SMTP_PORT = 25; - /** - * The maximum line length allowed by RFC 2822 section 2.1.1 - * @var integer - */ - const MAX_LINE_LENGTH = 998; + /** + * The maximum line length allowed by RFC 2822 section 2.1.1 + * @var integer + */ + const MAX_LINE_LENGTH = 998; - /** - * Debug level for no output - */ - const DEBUG_OFF = 0; + /** + * 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 messages + */ + const DEBUG_CLIENT = 1; - /** - * Debug level to show client -> server and server -> client messages - */ - const DEBUG_SERVER = 2; + /** + * 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 connection status, client -> server and server -> client messages + */ + const DEBUG_CONNECTION = 3; - /** - * Debug level to show all messages - */ - const DEBUG_LOWLEVEL = 4; + /** + * Debug level to show all messages + */ + const DEBUG_LOWLEVEL = 4; - /** - * The PHPMailer SMTP Version number. - * @var string - * @deprecated Use the `VERSION` constant instead - * @see SMTP::VERSION - */ - public $Version = '5.2.28'; + /** + * The PHPMailer SMTP Version number. + * @var string + * @deprecated Use the `VERSION` constant instead + * @see SMTP::VERSION + */ + public $Version = '5.2.28'; - /** - * SMTP server port number. - * @var 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; + /** + * SMTP server port number. + * @var 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; - /** - * SMTP reply line ending. - * @var string - * @deprecated Use the `CRLF` constant instead - * @see SMTP::CRLF - */ - public $CRLF = "\r\n"; + /** + * SMTP reply line ending. + * @var string + * @deprecated Use the `CRLF` constant instead + * @see SMTP::CRLF + */ + public $CRLF = "\r\n"; - /** - * Debug output level. - * Options: - * * 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 - * @var integer - */ - public $do_debug = self::DEBUG_OFF; + /** + * Debug output level. + * Options: + * * 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 + * @var integer + */ + 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 - * * `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: - * - * $smtp->Debugoutput = function($str, $level) {echo "debug level $level; message: $str";}; - * - * @var string|callable - */ - public $Debugoutput = 'echo'; + /** + * 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 + * + * 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";}; + * + * @var string|callable + */ + public $Debugoutput = 'echo'; - /** - * 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 - * @var boolean - */ - public $do_verp = false; + /** + * 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 + * @var boolean + */ + public $do_verp = false; - /** - * The timeout value for connection, in seconds. - * 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 - * @var integer - */ - public $Timeout = 300; + /** + * The timeout value for connection, in seconds. + * 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 + * @var integer + */ + public $Timeout = 300; - /** - * How long to wait for commands to complete, in seconds. - * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 - * @var integer - */ - public $Timelimit = 300; + /** + * How long to wait for commands to complete, in seconds. + * Default of 5 minutes (300sec) is from RFC2821 section 4.5.3.2 + * @var integer + */ + public $Timelimit = 300; - /** - * @var array Patterns to extract an SMTP transaction id from reply to a DATA command. - * The first capture group in each regex will be used as the ID. - */ - 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 (.*)/' + /** + * @var array Patterns to extract an SMTP transaction id from reply to a DATA command. + * The first capture group in each regex will be used as the ID. + */ + 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 (.*)/' + ); + + /** + * @var string The last transaction ID issued in response to a DATA command, + * if one was detected + */ + protected $last_smtp_transaction_id; + + /** + * The socket for the server connection. + * @var resource + */ + protected $smtp_conn; + + /** + * Error information, if any, for the last SMTP command. + * @var array + */ + protected $error = array( + 'error' => '', + 'detail' => '', + 'smtp_code' => '', + 'smtp_code_ex' => '' + ); + + /** + * The reply the server sent to us for HELO. + * If null, no HELO string has yet been received. + * @var string|null + */ + protected $helo_rply = null; + + /** + * The set of SMTP extensions sent in reply to EHLO command. + * Indexes of the array are extension names. + * Value at index 'HELO' or 'EHLO' (according to command that was sent) + * represents the server name. In case of HELO it is the only element of the array. + * Other values can be boolean TRUE or an array containing extension options. + * If null, no HELO/EHLO string has yet been received. + * @var array|null + */ + protected $server_caps = null; + + /** + * The most recent reply received from the server. + * @var string + */ + protected $last_reply = ''; + + /** + * 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, $level = 0) + { + if ($level > $this->do_debug) { + return; + } + //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, $level); + 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, HTML-safe output + echo gmdate('Y-m-d H:i:s') . ' ' . htmlentities( + 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"; + } + } + + /** + * Connect to an SMTP server. + * @param string $host SMTP server IP or host name + * @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 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->setError(''); + // Make sure we are __not__ connected + if ($this->connected()) { + // Already connected, generate error + $this->setError('Already connected to a server'); + return false; + } + if (empty($port)) { + $port = self::DEFAULT_SMTP_PORT; + } + // Connect to the SMTP server + $this->edebug( + "Connection: opening to $host:$port, timeout=$timeout, options=" . + var_export($options, true), + self::DEBUG_CONNECTION ); + $errno = 0; + $errstr = ''; + if ($streamok) { + $socket_context = stream_context_create($options); + set_error_handler(array($this, 'errorHandler')); + $this->smtp_conn = stream_socket_client( + $host . ":" . $port, + $errno, + $errstr, + $timeout, + 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, + $errno, + $errstr, + $timeout + ); + restore_error_handler(); + } + // Verify we connected properly + if (!is_resource($this->smtp_conn)) { + $this->setError( + 'Failed to connect to server', + $errno, + $errstr + ); + $this->edebug( + 'SMTP ERROR: ' . $this->error['error'] + . ": $errstr ($errno)", + self::DEBUG_CLIENT + ); + return false; + } + $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') { + $max = ini_get('max_execution_time'); + // Don't bother if unlimited + if ($max != 0 && $timeout > $max) { + @set_time_limit($timeout); + } + stream_set_timeout($this->smtp_conn, $timeout, 0); + } + // Get any announcement + $announce = $this->get_lines(); + $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER); + return true; + } - /** - * @var string The last transaction ID issued in response to a DATA command, - * if one was detected - */ - protected $last_smtp_transaction_id; + /** + * Initiate a TLS (encrypted) session. + * @access public + * @return boolean + */ + public function startTLS() + { + if (!$this->sendCommand('STARTTLS', 'STARTTLS', 220)) { + return false; + } - /** - * The socket for the server connection. - * @var resource - */ - protected $smtp_conn; + //Allow the best TLS version(s) we can + $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT; - /** - * Error information, if any, for the last SMTP command. - * @var array - */ - protected $error = array( - 'error' => '', - 'detail' => '', - 'smtp_code' => '', - 'smtp_code_ex' => '' + //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 + set_error_handler(array($this, 'errorHandler')); + $crypto_ok = stream_socket_enable_crypto( + $this->smtp_conn, + true, + $crypto_method ); + restore_error_handler(); + return $crypto_ok; + } - /** - * The reply the server sent to us for HELO. - * If null, no HELO string has yet been received. - * @var string|null - */ - protected $helo_rply = null; - - /** - * The set of SMTP extensions sent in reply to EHLO command. - * Indexes of the array are extension names. - * Value at index 'HELO' or 'EHLO' (according to command that was sent) - * represents the server name. In case of HELO it is the only element of the array. - * Other values can be boolean TRUE or an array containing extension options. - * If null, no HELO/EHLO string has yet been received. - * @var array|null - */ - protected $server_caps = null; - - /** - * The most recent reply received from the server. - * @var string - */ - protected $last_reply = ''; - - /** - * 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, $level = 0) - { - if ($level > $this->do_debug) { - return; - } - //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, $level); - 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, HTML-safe output - echo gmdate('Y-m-d H:i:s') . ' ' . htmlentities( - 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"; - } + /** + * Perform SMTP authentication. + * Must be run after hello(). + * @see hello() + * @param string $username The user name + * @param string $password The password + * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5, XOAUTH2) + * @param string $realm The auth realm for NTLM + * @param string $workstation The auth workstation for NTLM + * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth) + * @return bool True if successfully authenticated.* @access public + */ + public function authenticate( + $username, + $password, + $authtype = null, + $realm = '', + $workstation = '', + $OAuth = null + ) { + if (!$this->server_caps) { + $this->setError('Authentication is not allowed before HELO/EHLO'); + return false; } - /** - * Connect to an SMTP server. - * @param string $host SMTP server IP or host name - * @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 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'); + if (array_key_exists('EHLO', $this->server_caps)) { + // SMTP extensions are available; try to find a proper authentication method + if (!array_key_exists('AUTH', $this->server_caps)) { + $this->setError('Authentication is not allowed at this stage'); + // 'at this stage' means that auth may be allowed after the stage changes + // e.g. after STARTTLS + return false; + } + + self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL); + self::edebug( + 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']), + self::DEBUG_LOWLEVEL + ); + + if (empty($authtype)) { + foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN', 'NTLM', 'XOAUTH2') as $method) { + if (in_array($method, $this->server_caps['AUTH'])) { + $authtype = $method; + break; + } } - // Clear errors to avoid confusion - $this->setError(''); - // Make sure we are __not__ connected - if ($this->connected()) { - // Already connected, generate error - $this->setError('Already connected to a server'); - return false; + if (empty($authtype)) { + $this->setError('No supported authentication methods found'); + return false; } - if (empty($port)) { - $port = self::DEFAULT_SMTP_PORT; + self::edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL); + } + + if (!in_array($authtype, $this->server_caps['AUTH'])) { + $this->setError("The requested authentication method \"$authtype\" is not supported by the server"); + return false; + } + } elseif (empty($authtype)) { + $authtype = 'LOGIN'; + } + switch ($authtype) { + case 'PLAIN': + // Start authentication + if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) { + return false; } - // Connect to the SMTP server - $this->edebug( - "Connection: opening to $host:$port, timeout=$timeout, options=" . - var_export($options, true), - self::DEBUG_CONNECTION + // Send encoded username and password + if ( + !$this->sendCommand( + 'User & Password', + base64_encode("\0" . $username . "\0" . $password), + 235 + ) + ) { + return false; + } + break; + case 'LOGIN': + // Start authentication + if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) { + return false; + } + if (!$this->sendCommand("Username", base64_encode($username), 334)) { + return false; + } + if (!$this->sendCommand("Password", base64_encode($password), 235)) { + 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 ); - $errno = 0; - $errstr = ''; - if ($streamok) { - $socket_context = stream_context_create($options); - set_error_handler(array($this, 'errorHandler')); - $this->smtp_conn = stream_socket_client( - $host . ":" . $port, - $errno, - $errstr, - $timeout, - 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, - $errno, - $errstr, - $timeout - ); - restore_error_handler(); - } - // Verify we connected properly - if (!is_resource($this->smtp_conn)) { - $this->setError( - 'Failed to connect to server', - $errno, - $errstr - ); - $this->edebug( - 'SMTP ERROR: ' . $this->error['error'] - . ": $errstr ($errno)", - self::DEBUG_CLIENT - ); - return false; - } - $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') { - $max = ini_get('max_execution_time'); - // Don't bother if unlimited - if ($max != 0 && $timeout > $max) { - @set_time_limit($timeout); - } - stream_set_timeout($this->smtp_conn, $timeout, 0); - } - // Get any announcement - $announce = $this->get_lines(); - $this->edebug('SERVER -> CLIENT: ' . $announce, self::DEBUG_SERVER); - return true; - } - - /** - * Initiate a TLS (encrypted) session. - * @access public - * @return boolean - */ - public function startTLS() - { - 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 - set_error_handler(array($this, 'errorHandler')); - $crypto_ok = stream_socket_enable_crypto( - $this->smtp_conn, - true, - $crypto_method + //msg3 + $msg3 = $ntlm_client->typeMsg3( + $ntlm_res, + $username, + $realm, + $workstation ); - restore_error_handler(); - return $crypto_ok; - } - - /** - * Perform SMTP authentication. - * Must be run after hello(). - * @see hello() - * @param string $username The user name - * @param string $password The password - * @param string $authtype The auth type (PLAIN, LOGIN, NTLM, CRAM-MD5, XOAUTH2) - * @param string $realm The auth realm for NTLM - * @param string $workstation The auth workstation for NTLM - * @param null|OAuth $OAuth An optional OAuth instance (@see PHPMailerOAuth) - * @return bool True if successfully authenticated.* @access public - */ - public function authenticate( - $username, - $password, - $authtype = null, - $realm = '', - $workstation = '', - $OAuth = null - ) { - if (!$this->server_caps) { - $this->setError('Authentication is not allowed before HELO/EHLO'); - return false; + // 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)) { + return false; } + // Get the challenge + $challenge = base64_decode(substr($this->last_reply, 4)); - if (array_key_exists('EHLO', $this->server_caps)) { - // SMTP extensions are available; try to find a proper authentication method - if (!array_key_exists('AUTH', $this->server_caps)) { - $this->setError('Authentication is not allowed at this stage'); - // 'at this stage' means that auth may be allowed after the stage changes - // e.g. after STARTTLS - return false; - } + // Build the response + $response = $username . ' ' . $this->hmac($challenge, $password); - self::edebug('Auth method requested: ' . ($authtype ? $authtype : 'UNKNOWN'), self::DEBUG_LOWLEVEL); - self::edebug( - 'Auth methods available on the server: ' . implode(',', $this->server_caps['AUTH']), - self::DEBUG_LOWLEVEL - ); - - if (empty($authtype)) { - foreach (array('CRAM-MD5', 'LOGIN', 'PLAIN', 'NTLM', 'XOAUTH2') as $method) { - if (in_array($method, $this->server_caps['AUTH'])) { - $authtype = $method; - break; - } - } - if (empty($authtype)) { - $this->setError('No supported authentication methods found'); - return false; - } - self::edebug('Auth method selected: ' . $authtype, self::DEBUG_LOWLEVEL); - } - - if (!in_array($authtype, $this->server_caps['AUTH'])) { - $this->setError("The requested authentication method \"$authtype\" is not supported by the server"); - return false; - } - } elseif (empty($authtype)) { - $authtype = 'LOGIN'; - } - switch ($authtype) { - case 'PLAIN': - // Start authentication - if (!$this->sendCommand('AUTH', 'AUTH PLAIN', 334)) { - return false; - } - // Send encoded username and password - if (!$this->sendCommand( - 'User & Password', - base64_encode("\0" . $username . "\0" . $password), - 235 - ) - ) { - return false; - } - break; - case 'LOGIN': - // Start authentication - if (!$this->sendCommand('AUTH', 'AUTH LOGIN', 334)) { - return false; - } - if (!$this->sendCommand("Username", base64_encode($username), 334)) { - return false; - } - if (!$this->sendCommand("Password", base64_encode($password), 235)) { - 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)) { - return false; - } - // Get the challenge - $challenge = base64_decode(substr($this->last_reply, 4)); - - // Build the response - $response = $username . ' ' . $this->hmac($challenge, $password); - - // send encoded credentials - return $this->sendCommand('Username', base64_encode($response), 235); - default: - $this->setError("Authentication method \"$authtype\" is not supported"); - return false; - } - return true; - } - - /** - * Calculate an MD5 HMAC hash. - * Works like hash_hmac('md5', $data, $key) - * in case that function is not available - * @param string $data The data to hash - * @param string $key The key to hash with - * @access protected - * @return string - */ - protected function hmac($data, $key) - { - if (function_exists('hash_hmac')) { - return hash_hmac('md5', $data, $key); - } - - // The following borrowed from - // http://php.net/manual/en/function.mhash.php#27225 - - // RFC 2104 HMAC implementation for php. - // Creates an md5 HMAC. - // Eliminates the need to install mhash to compute a HMAC - // by Lance Rushing - - $bytelen = 64; // byte length for md5 - if (strlen($key) > $bytelen) { - $key = pack('H*', md5($key)); - } - $key = str_pad($key, $bytelen, chr(0x00)); - $ipad = str_pad('', $bytelen, chr(0x36)); - $opad = str_pad('', $bytelen, chr(0x5c)); - $k_ipad = $key ^ $ipad; - $k_opad = $key ^ $opad; - - return md5($k_opad . pack('H*', md5($k_ipad . $data))); - } - - /** - * Check connection state. - * @access public - * @return boolean True if connected. - */ - public function connected() - { - 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 - $this->edebug( - 'SMTP NOTICE: EOF caught while checking if connected', - self::DEBUG_CLIENT - ); - $this->close(); - return false; - } - return true; // everything looks good - } + // send encoded credentials + return $this->sendCommand('Username', base64_encode($response), 235); + default: + $this->setError("Authentication method \"$authtype\" is not supported"); return false; } + return true; + } - /** - * Close the socket and clean up the state of the class. - * Don't use this function without first trying to use QUIT. - * @see quit() - * @access public - * @return void - */ - public function close() - { - $this->setError(''); - $this->server_caps = null; - $this->helo_rply = null; - if (is_resource($this->smtp_conn)) { - // close the connection and cleanup - fclose($this->smtp_conn); - $this->smtp_conn = null; //Makes for cleaner serialization - $this->edebug('Connection: closed', self::DEBUG_CONNECTION); - } + /** + * Calculate an MD5 HMAC hash. + * Works like hash_hmac('md5', $data, $key) + * in case that function is not available + * @param string $data The data to hash + * @param string $key The key to hash with + * @access protected + * @return string + */ + protected function hmac($data, $key) + { + if (function_exists('hash_hmac')) { + return hash_hmac('md5', $data, $key); } - /** - * Send an SMTP DATA command. - * Issues a data command and sends the msg_data to the server, - * finializing the mail transaction. $msg_data is the message - * that is to be send with the headers. Each header needs to be - * on a single line followed by a with the message headers - * and the message body being separated by and additional . - * Implements rfc 821: DATA - * @param string $msg_data Message data to send - * @access public - * @return boolean + // The following borrowed from + // http://php.net/manual/en/function.mhash.php#27225 + + // RFC 2104 HMAC implementation for php. + // Creates an md5 HMAC. + // Eliminates the need to install mhash to compute a HMAC + // by Lance Rushing + + $bytelen = 64; // byte length for md5 + if (strlen($key) > $bytelen) { + $key = pack('H*', md5($key)); + } + $key = str_pad($key, $bytelen, chr(0x00)); + $ipad = str_pad('', $bytelen, chr(0x36)); + $opad = str_pad('', $bytelen, chr(0x5c)); + $k_ipad = $key ^ $ipad; + $k_opad = $key ^ $opad; + + return md5($k_opad . pack('H*', md5($k_ipad . $data))); + } + + /** + * Check connection state. + * @access public + * @return boolean True if connected. + */ + public function connected() + { + 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 + $this->edebug( + 'SMTP NOTICE: EOF caught while checking if connected', + self::DEBUG_CLIENT + ); + $this->close(); + return false; + } + return true; // everything looks good + } + return false; + } + + /** + * Close the socket and clean up the state of the class. + * Don't use this function without first trying to use QUIT. + * @see quit() + * @access public + * @return void + */ + public function close() + { + $this->setError(''); + $this->server_caps = null; + $this->helo_rply = null; + if (is_resource($this->smtp_conn)) { + // close the connection and cleanup + fclose($this->smtp_conn); + $this->smtp_conn = null; //Makes for cleaner serialization + $this->edebug('Connection: closed', self::DEBUG_CONNECTION); + } + } + + /** + * Send an SMTP DATA command. + * Issues a data command and sends the msg_data to the server, + * finializing the mail transaction. $msg_data is the message + * that is to be send with the headers. Each header needs to be + * on a single line followed by a with the message headers + * and the message body being separated by and additional . + * Implements rfc 821: DATA + * @param string $msg_data Message data to send + * @access public + * @return boolean + */ + public function data($msg_data) + { + //This will use the standard timelimit + if (!$this->sendCommand('DATA', 'DATA', 354)) { + return false; + } + + /* The server is ready to accept data! + * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF) + * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into + * smaller lines to fit within the limit. + * We will also look for lines that start with a '.' and prepend an additional '.'. + * NOTE: this does not count towards line-length limit. */ - public function data($msg_data) - { - //This will use the standard timelimit - if (!$this->sendCommand('DATA', 'DATA', 354)) { - return false; - } - /* The server is ready to accept data! - * According to rfc821 we should not send more than 1000 characters on a single line (including the CRLF) - * so we will break the data up into lines by \r and/or \n then if needed we will break each of those into - * smaller lines to fit within the limit. - * We will also look for lines that start with a '.' and prepend an additional '.'. - * NOTE: this does not count towards line-length limit. - */ + // Normalize line breaks before exploding + $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data)); - // Normalize line breaks before exploding - $lines = explode("\n", str_replace(array("\r\n", "\r"), "\n", $msg_data)); + /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field + * of the first line (':' separated) does not contain a space then it _should_ be a header and we will + * process all lines before a blank line as headers. + */ - /* To distinguish between a complete RFC822 message and a plain message body, we check if the first field - * of the first line (':' separated) does not contain a space then it _should_ be a header and we will - * process all lines before a blank line as headers. - */ + $field = substr($lines[0], 0, strpos($lines[0], ':')); + $in_headers = false; + if (!empty($field) && strpos($field, ' ') === false) { + $in_headers = true; + } - $field = substr($lines[0], 0, strpos($lines[0], ':')); + foreach ($lines as $line) { + $lines_out = array(); + if ($in_headers and $line == '') { $in_headers = false; - if (!empty($field) && strpos($field, ' ') === false) { - $in_headers = true; - } - - foreach ($lines as $line) { - $lines_out = array(); - if ($in_headers and $line == '') { - $in_headers = false; - } - //Break this line up into several smaller lines if it's too long - //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len), - while (isset($line[self::MAX_LINE_LENGTH])) { - //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on - //so as to avoid breaking in the middle of a word - $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' '); - //Deliberately matches both false and 0 - if (!$pos) { - //No nice break found, add a hard break - $pos = self::MAX_LINE_LENGTH - 1; - $lines_out[] = substr($line, 0, $pos); - $line = substr($line, $pos); - } else { - //Break at the found point - $lines_out[] = substr($line, 0, $pos); - //Move along by the amount we dealt with - $line = substr($line, $pos + 1); - } - //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1 - if ($in_headers) { - $line = "\t" . $line; - } - } - $lines_out[] = $line; - - //Send the lines to the server - foreach ($lines_out as $line_out) { - //RFC2821 section 4.5.2 - if (!empty($line_out) and $line_out[0] == '.') { - $line_out = '.' . $line_out; - } - $this->client_send($line_out . self::CRLF); - } - } - - //Message data has been sent, complete the command - //Increase timelimit for end of DATA command - $savetimelimit = $this->Timelimit; - $this->Timelimit = $this->Timelimit * 2; - $result = $this->sendCommand('DATA END', '.', 250); - $this->recordLastTransactionID(); - //Restore timelimit - $this->Timelimit = $savetimelimit; - return $result; - } - - /** - * Send an SMTP HELO or EHLO command. - * Used to identify the sending server to the receiving server. - * This makes sure that client and server are in a known state. - * Implements RFC 821: HELO - * and RFC 2821 EHLO. - * @param string $host The host name or IP to connect to - * @access public - * @return boolean - */ - public function hello($host = '') - { - //Try extended hello first (RFC 2821) - return (boolean)($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host)); - } - - /** - * Send an SMTP HELO or EHLO command. - * Low-level implementation used by hello() - * @see hello() - * @param string $hello The HELO string - * @param string $host The hostname to say we are - * @access protected - * @return boolean - */ - protected function sendHello($hello, $host) - { - $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250); - $this->helo_rply = $this->last_reply; - if ($noerror) { - $this->parseHelloFields($hello); + } + //Break this line up into several smaller lines if it's too long + //Micro-optimisation: isset($str[$len]) is faster than (strlen($str) > $len), + while (isset($line[self::MAX_LINE_LENGTH])) { + //Working backwards, try to find a space within the last MAX_LINE_LENGTH chars of the line to break on + //so as to avoid breaking in the middle of a word + $pos = strrpos(substr($line, 0, self::MAX_LINE_LENGTH), ' '); + //Deliberately matches both false and 0 + if (!$pos) { + //No nice break found, add a hard break + $pos = self::MAX_LINE_LENGTH - 1; + $lines_out[] = substr($line, 0, $pos); + $line = substr($line, $pos); } else { - $this->server_caps = null; + //Break at the found point + $lines_out[] = substr($line, 0, $pos); + //Move along by the amount we dealt with + $line = substr($line, $pos + 1); } - return $noerror; - } - - /** - * Parse a reply to HELO/EHLO command to discover server extensions. - * In case of HELO, the only parameter that can be discovered is a server name. - * @access protected - * @param string $type - 'HELO' or 'EHLO' - */ - protected function parseHelloFields($type) - { - $this->server_caps = array(); - $lines = explode("\n", $this->helo_rply); - - foreach ($lines as $n => $s) { - //First 4 chars contain response code followed by - or space - $s = trim(substr($s, 4)); - if (empty($s)) { - continue; - } - $fields = explode(' ', $s); - if (!empty($fields)) { - if (!$n) { - $name = $type; - $fields = $fields[0]; - } else { - $name = array_shift($fields); - switch ($name) { - case 'SIZE': - $fields = ($fields ? $fields[0] : 0); - break; - case 'AUTH': - if (!is_array($fields)) { - $fields = array(); - } - break; - default: - $fields = true; - } - } - $this->server_caps[$name] = $fields; - } + //If processing headers add a LWSP-char to the front of new line RFC822 section 3.1.1 + if ($in_headers) { + $line = "\t" . $line; } - } + } + $lines_out[] = $line; - /** - * Send an SMTP MAIL command. - * Starts a mail transaction from the email address specified in - * $from. Returns true if successful or false otherwise. If True - * the mail transaction is started and then one or more recipient - * commands may be called followed by a data command. - * Implements rfc 821: MAIL FROM: - * @param string $from Source address of this message - * @access public - * @return boolean - */ - public function mail($from) - { - $useVerp = ($this->do_verp ? ' XVERP' : ''); - return $this->sendCommand( - 'MAIL FROM', - 'MAIL FROM:<' . $from . '>' . $useVerp, - 250 - ); - } - - /** - * 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 boolean $close_on_error Should the connection close if an error occurs? - * @access public - * @return boolean - */ - public function quit($close_on_error = true) - { - $noerror = $this->sendCommand('QUIT', 'QUIT', 221); - $err = $this->error; //Save any error - if ($noerror or $close_on_error) { - $this->close(); - $this->error = $err; //Restore any error from the quit command + //Send the lines to the server + foreach ($lines_out as $line_out) { + //RFC2821 section 4.5.2 + if (!empty($line_out) and $line_out[0] == '.') { + $line_out = '.' . $line_out; } - return $noerror; + $this->client_send($line_out . self::CRLF); + } } - /** - * Send an SMTP RCPT command. - * Sets the TO argument to $toaddr. - * Returns true if the recipient was accepted false if it was rejected. - * Implements from rfc 821: RCPT TO: - * @param string $address The address the message is being sent to - * @access public - * @return boolean - */ - public function recipient($address) - { - return $this->sendCommand( - 'RCPT TO', - 'RCPT TO:<' . $address . '>', - array(250, 251) - ); + //Message data has been sent, complete the command + //Increase timelimit for end of DATA command + $savetimelimit = $this->Timelimit; + $this->Timelimit = $this->Timelimit * 2; + $result = $this->sendCommand('DATA END', '.', 250); + $this->recordLastTransactionID(); + //Restore timelimit + $this->Timelimit = $savetimelimit; + return $result; + } + + /** + * Send an SMTP HELO or EHLO command. + * Used to identify the sending server to the receiving server. + * This makes sure that client and server are in a known state. + * Implements RFC 821: HELO + * and RFC 2821 EHLO. + * @param string $host The host name or IP to connect to + * @access public + * @return boolean + */ + public function hello($host = '') + { + //Try extended hello first (RFC 2821) + return (boolean) ($this->sendHello('EHLO', $host) or $this->sendHello('HELO', $host)); + } + + /** + * Send an SMTP HELO or EHLO command. + * Low-level implementation used by hello() + * @see hello() + * @param string $hello The HELO string + * @param string $host The hostname to say we are + * @access protected + * @return boolean + */ + protected function sendHello($hello, $host) + { + $noerror = $this->sendCommand($hello, $hello . ' ' . $host, 250); + $this->helo_rply = $this->last_reply; + if ($noerror) { + $this->parseHelloFields($hello); + } else { + $this->server_caps = null; } + return $noerror; + } - /** - * Send an SMTP RSET command. - * Abort any transaction that is currently in progress. - * Implements rfc 821: RSET - * @access public - * @return boolean True on success. - */ - public function reset() - { - return $this->sendCommand('RSET', 'RSET', 250); - } + /** + * Parse a reply to HELO/EHLO command to discover server extensions. + * In case of HELO, the only parameter that can be discovered is a server name. + * @access protected + * @param string $type - 'HELO' or 'EHLO' + */ + protected function parseHelloFields($type) + { + $this->server_caps = array(); + $lines = explode("\n", $this->helo_rply); - /** - * 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 integer|array $expect One or more expected integer success codes - * @access protected - * @return boolean True on success. - */ - protected function sendCommand($command, $commandstring, $expect) - { - if (!$this->connected()) { - $this->setError("Called $command without being connected"); - return false; - } - //Reject line breaks in all commands - if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) { - $this->setError("Command '$command' contained line breaks"); - return false; - } - $this->client_send($commandstring . self::CRLF); - - $this->last_reply = $this->get_lines(); - // Fetch SMTP code and possible error code explanation - $matches = array(); - if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) { - $code = $matches[1]; - $code_ex = (count($matches) > 2 ? $matches[2] : null); - // Cut off error code from each response line - $detail = preg_replace( - "/{$code}[ -]" . - ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . "/m", - '', - $this->last_reply - ); + foreach ($lines as $n => $s) { + //First 4 chars contain response code followed by - or space + $s = trim(substr($s, 4)); + if (empty($s)) { + continue; + } + $fields = explode(' ', $s); + if (!empty($fields)) { + if (!$n) { + $name = $type; + $fields = $fields[0]; } else { - // Fall back to simple parsing if regex fails - $code = substr($this->last_reply, 0, 3); - $code_ex = null; - $detail = substr($this->last_reply, 4); + $name = array_shift($fields); + switch ($name) { + case 'SIZE': + $fields = ($fields ? $fields[0] : 0); + break; + case 'AUTH': + if (!is_array($fields)) { + $fields = array(); + } + break; + default: + $fields = true; + } } + $this->server_caps[$name] = $fields; + } + } + } - $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); + /** + * Send an SMTP MAIL command. + * Starts a mail transaction from the email address specified in + * $from. Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more recipient + * commands may be called followed by a data command. + * Implements rfc 821: MAIL FROM: + * @param string $from Source address of this message + * @access public + * @return boolean + */ + public function mail($from) + { + $useVerp = ($this->do_verp ? ' XVERP' : ''); + return $this->sendCommand( + 'MAIL FROM', + 'MAIL FROM:<' . $from . '>' . $useVerp, + 250 + ); + } - if (!in_array($code, (array)$expect)) { - $this->setError( - "$command command failed", - $detail, - $code, - $code_ex - ); - $this->edebug( - 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply, - self::DEBUG_CLIENT - ); - return false; - } + /** + * 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 boolean $close_on_error Should the connection close if an error occurs? + * @access public + * @return boolean + */ + public function quit($close_on_error = true) + { + $noerror = $this->sendCommand('QUIT', 'QUIT', 221); + $err = $this->error; //Save any error + if ($noerror or $close_on_error) { + $this->close(); + $this->error = $err; //Restore any error from the quit command + } + return $noerror; + } - $this->setError(''); - return true; + /** + * Send an SMTP RCPT command. + * Sets the TO argument to $toaddr. + * Returns true if the recipient was accepted false if it was rejected. + * Implements from rfc 821: RCPT TO: + * @param string $address The address the message is being sent to + * @access public + * @return boolean + */ + public function recipient($address) + { + return $this->sendCommand( + 'RCPT TO', + 'RCPT TO:<' . $address . '>', + array(250, 251) + ); + } + + /** + * Send an SMTP RSET command. + * Abort any transaction that is currently in progress. + * Implements rfc 821: RSET + * @access public + * @return boolean True on success. + */ + public function reset() + { + return $this->sendCommand('RSET', 'RSET', 250); + } + + /** + * 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 integer|array $expect One or more expected integer success codes + * @access protected + * @return boolean True on success. + */ + protected function sendCommand($command, $commandstring, $expect) + { + if (!$this->connected()) { + $this->setError("Called $command without being connected"); + return false; + } + //Reject line breaks in all commands + if (strpos($commandstring, "\n") !== false or strpos($commandstring, "\r") !== false) { + $this->setError("Command '$command' contained line breaks"); + return false; + } + $this->client_send($commandstring . self::CRLF); + + $this->last_reply = $this->get_lines(); + // Fetch SMTP code and possible error code explanation + $matches = array(); + if (preg_match("/^([0-9]{3})[ -](?:([0-9]\\.[0-9]\\.[0-9]) )?/", $this->last_reply, $matches)) { + $code = $matches[1]; + $code_ex = (count($matches) > 2 ? $matches[2] : null); + // Cut off error code from each response line + $detail = preg_replace( + "/{$code}[ -]" . + ($code_ex ? str_replace('.', '\\.', $code_ex) . ' ' : '') . "/m", + '', + $this->last_reply + ); + } else { + // Fall back to simple parsing if regex fails + $code = substr($this->last_reply, 0, 3); + $code_ex = null; + $detail = substr($this->last_reply, 4); } - /** - * Send an SMTP SAML command. - * Starts a mail transaction from the email address specified in $from. - * Returns true if successful or false otherwise. If True - * the mail transaction is started and then one or more recipient - * commands may be called followed by a data command. This command - * will send the message to the users terminal if they are logged - * in and send them an email. - * Implements rfc 821: SAML FROM: - * @param string $from The address the message is from - * @access public - * @return boolean - */ - public function sendAndMail($from) - { - return $this->sendCommand('SAML', "SAML FROM:$from", 250); + $this->edebug('SERVER -> CLIENT: ' . $this->last_reply, self::DEBUG_SERVER); + + if (!in_array($code, (array) $expect)) { + $this->setError( + "$command command failed", + $detail, + $code, + $code_ex + ); + $this->edebug( + 'SMTP ERROR: ' . $this->error['error'] . ': ' . $this->last_reply, + self::DEBUG_CLIENT + ); + return false; } - /** - * Send an SMTP VRFY command. - * @param string $name The name to verify - * @access public - * @return boolean - */ - public function verify($name) - { - return $this->sendCommand('VRFY', "VRFY $name", array(250, 251)); + $this->setError(''); + return true; + } + + /** + * Send an SMTP SAML command. + * Starts a mail transaction from the email address specified in $from. + * Returns true if successful or false otherwise. If True + * the mail transaction is started and then one or more recipient + * commands may be called followed by a data command. This command + * will send the message to the users terminal if they are logged + * in and send them an email. + * Implements rfc 821: SAML FROM: + * @param string $from The address the message is from + * @access public + * @return boolean + */ + public function sendAndMail($from) + { + return $this->sendCommand('SAML', "SAML FROM:$from", 250); + } + + /** + * Send an SMTP VRFY command. + * @param string $name The name to verify + * @access public + * @return boolean + */ + public function verify($name) + { + return $this->sendCommand('VRFY', "VRFY $name", array(250, 251)); + } + + /** + * Send an SMTP NOOP command. + * Used to keep keep-alives alive, doesn't actually do anything + * @access public + * @return boolean + */ + public function noop() + { + return $this->sendCommand('NOOP', 'NOOP', 250); + } + + /** + * Send an SMTP TURN command. + * This is an optional command for SMTP that this class does not support. + * This method is here to make the RFC821 Definition complete for this class + * and _may_ be implemented in future + * Implements from rfc 821: TURN + * @access public + * @return boolean + */ + public function turn() + { + $this->setError('The SMTP TURN command is not implemented'); + $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); + return false; + } + + /** + * Send raw data to the server. + * @param string $data The data to send + * @access public + * @return integer|boolean The number of bytes sent to the server or false on error + */ + public function client_send($data) + { + $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT); + set_error_handler(array($this, 'errorHandler')); + $result = fwrite($this->smtp_conn, $data); + restore_error_handler(); + return $result; + } + + /** + * Get the latest error. + * @access public + * @return array + */ + public function getError() + { + return $this->error; + } + + /** + * Get SMTP extensions available on the server + * @access public + * @return array|null + */ + public function getServerExtList() + { + return $this->server_caps; + } + + /** + * A multipurpose method + * The method works in three ways, dependent on argument value and current state + * 1. HELO/EHLO was not sent - returns null and set up $this->error + * 2. HELO was sent + * $name = 'HELO': returns server name + * $name = 'EHLO': returns boolean false + * $name = any string: returns null and set up $this->error + * 3. EHLO was sent + * $name = 'HELO'|'EHLO': returns server name + * $name = any string: if extension $name exists, returns boolean True + * or its options. Otherwise returns boolean False + * In other words, one can use this method to detect 3 conditions: + * - null returned: handshake was not or we don't know about ext (refer to $this->error) + * - false returned: the requested feature exactly not exists + * - positive value returned: the requested feature exists + * @param string $name Name of SMTP extension or 'HELO'|'EHLO' + * @return mixed + */ + public function getServerExt($name) + { + if (!$this->server_caps) { + $this->setError('No HELO/EHLO was sent'); + return null; } - /** - * Send an SMTP NOOP command. - * Used to keep keep-alives alive, doesn't actually do anything - * @access public - * @return boolean - */ - public function noop() - { - return $this->sendCommand('NOOP', 'NOOP', 250); - } - - /** - * Send an SMTP TURN command. - * This is an optional command for SMTP that this class does not support. - * This method is here to make the RFC821 Definition complete for this class - * and _may_ be implemented in future - * Implements from rfc 821: TURN - * @access public - * @return boolean - */ - public function turn() - { - $this->setError('The SMTP TURN command is not implemented'); - $this->edebug('SMTP NOTICE: ' . $this->error['error'], self::DEBUG_CLIENT); + // the tight logic knot ;) + if (!array_key_exists($name, $this->server_caps)) { + if ($name == 'HELO') { + return $this->server_caps['EHLO']; + } + if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) { return false; + } + $this->setError('HELO handshake was used. Client knows nothing about server extensions'); + return null; } - /** - * Send raw data to the server. - * @param string $data The data to send - * @access public - * @return integer|boolean The number of bytes sent to the server or false on error - */ - public function client_send($data) - { - $this->edebug("CLIENT -> SERVER: $data", self::DEBUG_CLIENT); - set_error_handler(array($this, 'errorHandler')); - $result = fwrite($this->smtp_conn, $data); - restore_error_handler(); - return $result; + return $this->server_caps[$name]; + } + + /** + * Get the last reply from the server. + * @access public + * @return string + */ + public function getLastReply() + { + return $this->last_reply; + } + + /** + * Read the SMTP server's response. + * Either before eof or socket timeout occurs on the operation. + * With SMTP we can tell if we have more lines to read if the + * 4th character is '-' symbol. If it is a space then we don't + * need to read anything else. + * @access protected + * @return string + */ + protected function get_lines() + { + // If the connection is bad, give up straight away + if (!is_resource($this->smtp_conn)) { + return ''; } - - /** - * Get the latest error. - * @access public - * @return array - */ - public function getError() - { - return $this->error; + $data = ''; + $endtime = 0; + stream_set_timeout($this->smtp_conn, $this->Timeout); + if ($this->Timelimit > 0) { + $endtime = time() + $this->Timelimit; } - - /** - * Get SMTP extensions available on the server - * @access public - * @return array|null - */ - public function getServerExtList() - { - return $this->server_caps; - } - - /** - * A multipurpose method - * The method works in three ways, dependent on argument value and current state - * 1. HELO/EHLO was not sent - returns null and set up $this->error - * 2. HELO was sent - * $name = 'HELO': returns server name - * $name = 'EHLO': returns boolean false - * $name = any string: returns null and set up $this->error - * 3. EHLO was sent - * $name = 'HELO'|'EHLO': returns server name - * $name = any string: if extension $name exists, returns boolean True - * or its options. Otherwise returns boolean False - * In other words, one can use this method to detect 3 conditions: - * - null returned: handshake was not or we don't know about ext (refer to $this->error) - * - false returned: the requested feature exactly not exists - * - positive value returned: the requested feature exists - * @param string $name Name of SMTP extension or 'HELO'|'EHLO' - * @return mixed - */ - public function getServerExt($name) - { - if (!$this->server_caps) { - $this->setError('No HELO/EHLO was sent'); - return null; - } - - // the tight logic knot ;) - if (!array_key_exists($name, $this->server_caps)) { - if ($name == 'HELO') { - return $this->server_caps['EHLO']; - } - if ($name == 'EHLO' || array_key_exists('EHLO', $this->server_caps)) { - return false; - } - $this->setError('HELO handshake was used. Client knows nothing about server extensions'); - return null; - } - - return $this->server_caps[$name]; - } - - /** - * Get the last reply from the server. - * @access public - * @return string - */ - public function getLastReply() - { - return $this->last_reply; - } - - /** - * Read the SMTP server's response. - * Either before eof or socket timeout occurs on the operation. - * With SMTP we can tell if we have more lines to read if the - * 4th character is '-' symbol. If it is a space then we don't - * need to read anything else. - * @access protected - * @return string - */ - protected function get_lines() - { - // If the connection is bad, give up straight away - if (!is_resource($this->smtp_conn)) { - return ''; - } - $data = ''; - $endtime = 0; - stream_set_timeout($this->smtp_conn, $this->Timeout); - if ($this->Timelimit > 0) { - $endtime = time() + $this->Timelimit; - } - while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { - $str = @fgets($this->smtp_conn, 515); - $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL); - $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL); - $data .= $str; - // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled), - // or 4th character is a space, we are done reading, break the loop, - // string array access is a micro-optimisation over strlen - if (!isset($str[3]) or (isset($str[3]) and $str[3] == ' ')) { - break; - } - // Timed-out? Log and break - $info = stream_get_meta_data($this->smtp_conn); - if ($info['timed_out']) { - $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) { - $this->edebug( - 'SMTP -> get_lines(): timelimit reached (' . - $this->Timelimit . ' sec)', - self::DEBUG_LOWLEVEL - ); - break; - } - } - return $data; - } - - /** - * Enable or disable VERP address generation. - * @param boolean $enabled - */ - public function setVerp($enabled = false) - { - $this->do_verp = $enabled; - } - - /** - * Get VERP address generation mode. - * @return boolean - */ - public function getVerp() - { - return $this->do_verp; - } - - /** - * Set error messages and codes. - * @param string $message The error message - * @param string $detail Further detail on the error - * @param string $smtp_code An associated SMTP error code - * @param string $smtp_code_ex Extended SMTP code - */ - protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '') - { - $this->error = array( - 'error' => $message, - 'detail' => $detail, - 'smtp_code' => $smtp_code, - 'smtp_code_ex' => $smtp_code_ex - ); - } - - /** - * Set debug output method. - * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it. - */ - public function setDebugOutput($method = 'echo') - { - $this->Debugoutput = $method; - } - - /** - * Get debug output method. - * @return string - */ - public function getDebugOutput() - { - return $this->Debugoutput; - } - - /** - * Set debug output level. - * @param integer $level - */ - public function setDebugLevel($level = 0) - { - $this->do_debug = $level; - } - - /** - * Get debug output level. - * @return integer - */ - public function getDebugLevel() - { - return $this->do_debug; - } - - /** - * Set SMTP timeout. - * @param integer $timeout - */ - public function setTimeout($timeout = 0) - { - $this->Timeout = $timeout; - } - - /** - * Get SMTP timeout. - * @return integer - */ - public function getTimeout() - { - 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. - * @param string $errfile The file the error occurred in - * @param integer $errline The line number the error occurred on - */ - protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0) - { - $notice = 'Connection failed.'; - $this->setError( - $notice, - $errno, - $errmsg - ); + while (is_resource($this->smtp_conn) && !feof($this->smtp_conn)) { + $str = @fgets($this->smtp_conn, 515); + $this->edebug("SMTP -> get_lines(): \$data is \"$data\"", self::DEBUG_LOWLEVEL); + $this->edebug("SMTP -> get_lines(): \$str is \"$str\"", self::DEBUG_LOWLEVEL); + $data .= $str; + // If response is only 3 chars (not valid, but RFC5321 S4.2 says it must be handled), + // or 4th character is a space, we are done reading, break the loop, + // string array access is a micro-optimisation over strlen + if (!isset($str[3]) or (isset($str[3]) and $str[3] == ' ')) { + break; + } + // Timed-out? Log and break + $info = stream_get_meta_data($this->smtp_conn); + if ($info['timed_out']) { $this->edebug( - $notice . ' Error #' . $errno . ': ' . $errmsg . " [$errfile line $errline]", - self::DEBUG_CONNECTION + 'SMTP -> get_lines(): timed-out (' . $this->Timeout . ' sec)', + self::DEBUG_LOWLEVEL ); + break; + } + // Now check if reads took too long + if ($endtime and time() > $endtime) { + $this->edebug( + 'SMTP -> get_lines(): timelimit reached (' . + $this->Timelimit . ' sec)', + self::DEBUG_LOWLEVEL + ); + break; + } } + return $data; + } - /** - * Extract and return the ID of the last SMTP transaction based on - * a list of patterns provided in SMTP::$smtp_transaction_id_patterns. - * Relies on the host providing the ID in response to a DATA command. - * If no reply has been received yet, it will return null. - * If no pattern was matched, it will return false. - * @return bool|null|string - */ - protected function recordLastTransactionID() - { - $reply = $this->getLastReply(); + /** + * Enable or disable VERP address generation. + * @param boolean $enabled + */ + public function setVerp($enabled = false) + { + $this->do_verp = $enabled; + } - if (empty($reply)) { - $this->last_smtp_transaction_id = null; - } else { - $this->last_smtp_transaction_id = false; - foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) { - if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) { - $this->last_smtp_transaction_id = $matches[1]; - } - } + /** + * Get VERP address generation mode. + * @return boolean + */ + public function getVerp() + { + return $this->do_verp; + } + + /** + * Set error messages and codes. + * @param string $message The error message + * @param string $detail Further detail on the error + * @param string $smtp_code An associated SMTP error code + * @param string $smtp_code_ex Extended SMTP code + */ + protected function setError($message, $detail = '', $smtp_code = '', $smtp_code_ex = '') + { + $this->error = array( + 'error' => $message, + 'detail' => $detail, + 'smtp_code' => $smtp_code, + 'smtp_code_ex' => $smtp_code_ex + ); + } + + /** + * Set debug output method. + * @param string|callable $method The name of the mechanism to use for debugging output, or a callable to handle it. + */ + public function setDebugOutput($method = 'echo') + { + $this->Debugoutput = $method; + } + + /** + * Get debug output method. + * @return string + */ + public function getDebugOutput() + { + return $this->Debugoutput; + } + + /** + * Set debug output level. + * @param integer $level + */ + public function setDebugLevel($level = 0) + { + $this->do_debug = $level; + } + + /** + * Get debug output level. + * @return integer + */ + public function getDebugLevel() + { + return $this->do_debug; + } + + /** + * Set SMTP timeout. + * @param integer $timeout + */ + public function setTimeout($timeout = 0) + { + $this->Timeout = $timeout; + } + + /** + * Get SMTP timeout. + * @return integer + */ + public function getTimeout() + { + 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. + * @param string $errfile The file the error occurred in + * @param integer $errline The line number the error occurred on + */ + protected function errorHandler($errno, $errmsg, $errfile = '', $errline = 0) + { + $notice = 'Connection failed.'; + $this->setError( + $notice, + $errno, + $errmsg + ); + $this->edebug( + $notice . ' Error #' . $errno . ': ' . $errmsg . " [$errfile line $errline]", + self::DEBUG_CONNECTION + ); + } + + /** + * Extract and return the ID of the last SMTP transaction based on + * a list of patterns provided in SMTP::$smtp_transaction_id_patterns. + * Relies on the host providing the ID in response to a DATA command. + * If no reply has been received yet, it will return null. + * If no pattern was matched, it will return false. + * @return bool|null|string + */ + protected function recordLastTransactionID() + { + $reply = $this->getLastReply(); + + if (empty($reply)) { + $this->last_smtp_transaction_id = null; + } else { + $this->last_smtp_transaction_id = false; + foreach ($this->smtp_transaction_id_patterns as $smtp_transaction_id_pattern) { + if (preg_match($smtp_transaction_id_pattern, $reply, $matches)) { + $this->last_smtp_transaction_id = $matches[1]; } - - return $this->last_smtp_transaction_id; + } } - /** - * Get the queue/transaction ID of the last SMTP transaction - * If no reply has been received yet, it will return null. - * If no pattern was matched, it will return false. - * @return bool|null|string - * @see recordLastTransactionID() - */ - public function getLastTransactionID() - { - return $this->last_smtp_transaction_id; - } + return $this->last_smtp_transaction_id; + } + + /** + * Get the queue/transaction ID of the last SMTP transaction + * If no reply has been received yet, it will return null. + * If no pattern was matched, it will return false. + * @return bool|null|string + * @see recordLastTransactionID() + */ + public function getLastTransactionID() + { + return $this->last_smtp_transaction_id; + } } diff --git a/AvocadoAmber/plugin/PHPMailer/extras/EasyPeasyICS.php b/AvocadoAmber/plugin/PHPMailer/extras/EasyPeasyICS.php index d8bfcfa..d5e2af7 100644 --- a/AvocadoAmber/plugin/PHPMailer/extras/EasyPeasyICS.php +++ b/AvocadoAmber/plugin/PHPMailer/extras/EasyPeasyICS.php @@ -19,104 +19,104 @@ */ class EasyPeasyICS { - /** - * The name of the calendar - * @var string - */ - protected $calendarName; - /** - * The array of events to add to this calendar - * @var array - */ - protected $events = array(); + /** + * The name of the calendar + * @var string + */ + protected $calendarName; + /** + * The array of events to add to this calendar + * @var array + */ + protected $events = array(); - /** - * Constructor - * @param string $calendarName - */ - public function __construct($calendarName = "") - { - $this->calendarName = $calendarName; + /** + * Constructor + * @param string $calendarName + */ + public function __construct($calendarName = "") + { + $this->calendarName = $calendarName; + } + + /** + * Add an event to this calendar. + * @param string $start The start date and time as a unix timestamp + * @param string $end The end date and time as a unix timestamp + * @param string $summary A summary or title for the event + * @param string $description A description of the event + * @param string $url A URL for the event + * @param string $uid A unique identifier for the event - generated automatically if not provided + * @return array An array of event details, including any generated UID + */ + public function addEvent($start, $end, $summary = '', $description = '', $url = '', $uid = '') + { + if (empty($uid)) { + $uid = md5(uniqid(mt_rand(), true)) . '@EasyPeasyICS'; } + $event = array( + 'start' => gmdate('Ymd', $start) . 'T' . gmdate('His', $start) . 'Z', + 'end' => gmdate('Ymd', $end) . 'T' . gmdate('His', $end) . 'Z', + 'summary' => $summary, + 'description' => $description, + 'url' => $url, + 'uid' => $uid + ); + $this->events[] = $event; + return $event; + } - /** - * Add an event to this calendar. - * @param string $start The start date and time as a unix timestamp - * @param string $end The end date and time as a unix timestamp - * @param string $summary A summary or title for the event - * @param string $description A description of the event - * @param string $url A URL for the event - * @param string $uid A unique identifier for the event - generated automatically if not provided - * @return array An array of event details, including any generated UID - */ - public function addEvent($start, $end, $summary = '', $description = '', $url = '', $uid = '') - { - if (empty($uid)) { - $uid = md5(uniqid(mt_rand(), true)) . '@EasyPeasyICS'; - } - $event = array( - 'start' => gmdate('Ymd', $start) . 'T' . gmdate('His', $start) . 'Z', - 'end' => gmdate('Ymd', $end) . 'T' . gmdate('His', $end) . 'Z', - 'summary' => $summary, - 'description' => $description, - 'url' => $url, - 'uid' => $uid - ); - $this->events[] = $event; - return $event; - } + /** + * @return array Get the array of events. + */ + public function getEvents() + { + return $this->events; + } - /** - * @return array Get the array of events. - */ - public function getEvents() - { - return $this->events; - } + /** + * Clear all events. + */ + public function clearEvents() + { + $this->events = array(); + } - /** - * Clear all events. - */ - public function clearEvents() - { - $this->events = array(); - } + /** + * Get the name of the calendar. + * @return string + */ + public function getName() + { + return $this->calendarName; + } - /** - * Get the name of the calendar. - * @return string - */ - public function getName() - { - return $this->calendarName; - } + /** + * Set the name of the calendar. + * @param $name + */ + public function setName($name) + { + $this->calendarName = $name; + } - /** - * Set the name of the calendar. - * @param $name - */ - public function setName($name) - { - $this->calendarName = $name; - } - - /** - * Render and optionally output a vcal string. - * @param bool $output Whether to output the calendar data directly (the default). - * @return string The complete rendered vlal - */ - public function render($output = true) - { - //Add header - $ics = 'BEGIN:VCALENDAR + /** + * Render and optionally output a vcal string. + * @param bool $output Whether to output the calendar data directly (the default). + * @return string The complete rendered vlal + */ + public function render($output = true) + { + //Add header + $ics = 'BEGIN:VCALENDAR METHOD:PUBLISH VERSION:2.0 X-WR-CALNAME:' . $this->calendarName . ' PRODID:-//hacksw/handcal//NONSGML v1.0//EN'; - //Add events - foreach ($this->events as $event) { - $ics .= ' + //Add events + foreach ($this->events as $event) { + $ics .= ' BEGIN:VEVENT UID:' . $event['uid'] . ' DTSTAMP:' . gmdate('Ymd') . 'T' . gmdate('His') . 'Z @@ -126,23 +126,23 @@ SUMMARY:' . str_replace("\n", "\\n", $event['summary']) . ' DESCRIPTION:' . str_replace("\n", "\\n", $event['description']) . ' URL;VALUE=URI:' . $event['url'] . ' END:VEVENT'; - } + } - //Add footer - $ics .= ' + //Add footer + $ics .= ' END:VCALENDAR'; - if ($output) { - //Output - $filename = $this->calendarName; - //Filename needs quoting if it contains spaces - if (strpos($filename, ' ') !== false) { - $filename = '"'.$filename.'"'; - } - header('Content-type: text/calendar; charset=utf-8'); - header('Content-Disposition: inline; filename=' . $filename . '.ics'); - echo $ics; - } - return $ics; + if ($output) { + //Output + $filename = $this->calendarName; + //Filename needs quoting if it contains spaces + if (strpos($filename, ' ') !== false) { + $filename = '"' . $filename . '"'; + } + header('Content-type: text/calendar; charset=utf-8'); + header('Content-Disposition: inline; filename=' . $filename . '.ics'); + echo $ics; } + return $ics; + } } diff --git a/AvocadoAmber/plugin/PHPMailer/extras/htmlfilter.php b/AvocadoAmber/plugin/PHPMailer/extras/htmlfilter.php index 1581c41..81e5fd8 100644 --- a/AvocadoAmber/plugin/PHPMailer/extras/htmlfilter.php +++ b/AvocadoAmber/plugin/PHPMailer/extras/htmlfilter.php @@ -40,23 +40,23 @@ */ function tln_tagprint($tagname, $attary, $tagtype) { - if ($tagtype == 2) { - $fulltag = ''; - } else { - $fulltag = '<' . $tagname; - if (is_array($attary) && count($attary)) { - $atts = array(); - foreach($attary as $attname => $attvalue) { - array_push($atts, "$attname=$attvalue"); - } - $fulltag .= ' ' . join(' ', $atts); - } - if ($tagtype == 3) { - $fulltag .= ' /'; - } - $fulltag .= '>'; + if ($tagtype == 2) { + $fulltag = ''; + } else { + $fulltag = '<' . $tagname; + if (is_array($attary) && count($attary)) { + $atts = array(); + foreach ($attary as $attname => $attvalue) { + array_push($atts, "$attname=$attvalue"); + } + $fulltag .= ' ' . join(' ', $atts); } - return $fulltag; + if ($tagtype == 3) { + $fulltag .= ' /'; + } + $fulltag .= '>'; + } + return $fulltag; } /** @@ -68,7 +68,7 @@ function tln_tagprint($tagname, $attary, $tagtype) */ function tln_casenormalize(&$val) { - $val = strtolower($val); + $val = strtolower($val); } /** @@ -83,12 +83,12 @@ function tln_casenormalize(&$val) */ function tln_skipspace($body, $offset) { - preg_match('/^(\s*)/s', substr($body, $offset), $matches); - if (count($matches[1])) { - $count = strlen($matches[1]); - $offset += $count; - } - return $offset; + preg_match('/^(\s*)/s', substr($body, $offset), $matches); + if (count($matches[1])) { + $count = strlen($matches[1]); + $offset += $count; + } + return $offset; } /** @@ -104,11 +104,11 @@ function tln_skipspace($body, $offset) */ function tln_findnxstr($body, $offset, $needle) { - $pos = strpos($body, $needle, $offset); - if ($pos === false) { - $pos = strlen($body); - } - return $pos; + $pos = strpos($body, $needle, $offset); + if ($pos === false) { + $pos = strlen($body); + } + return $pos; } /** @@ -126,18 +126,18 @@ function tln_findnxstr($body, $offset, $needle) */ function tln_findnxreg($body, $offset, $reg) { - $matches = array(); - $retarr = array(); - $preg_rule = '%^(.*?)(' . $reg . ')%s'; - preg_match($preg_rule, substr($body, $offset), $matches); - if (!isset($matches[0]) || !$matches[0]) { - $retarr = false; - } else { - $retarr[0] = $offset + strlen($matches[1]); - $retarr[1] = $matches[1]; - $retarr[2] = $matches[2]; - } - return $retarr; + $matches = array(); + $retarr = array(); + $preg_rule = '%^(.*?)(' . $reg . ')%s'; + preg_match($preg_rule, substr($body, $offset), $matches); + if (!isset($matches[0]) || !$matches[0]) { + $retarr = false; + } else { + $retarr[0] = $offset + strlen($matches[1]); + $retarr[1] = $matches[1]; + $retarr[2] = $matches[2]; + } + return $retarr; } /** @@ -156,276 +156,276 @@ function tln_findnxreg($body, $offset, $reg) */ function tln_getnxtag($body, $offset) { - if ($offset > strlen($body)) { - return false; - } - $lt = tln_findnxstr($body, $offset, '<'); - if ($lt == strlen($body)) { - return false; - } - /** - * We are here: - * blah blah - * \---------^ - */ - $pos = tln_skipspace($body, $lt + 1); - if ($pos >= strlen($body)) { - return array(false, false, false, $lt, strlen($body)); - } - /** - * There are 3 kinds of tags: - * 1. Opening tag, e.g.: - * - * 2. Closing tag, e.g.: - * - * 3. XHTML-style content-less tag, e.g.: - * - */ - switch (substr($body, $pos, 1)) { + if ($offset > strlen($body)) { + return false; + } + $lt = tln_findnxstr($body, $offset, '<'); + if ($lt == strlen($body)) { + return false; + } + /** + * We are here: + * blah blah + * \---------^ + */ + $pos = tln_skipspace($body, $lt + 1); + if ($pos >= strlen($body)) { + return array(false, false, false, $lt, strlen($body)); + } + /** + * There are 3 kinds of tags: + * 1. Opening tag, e.g.: + * + * 2. Closing tag, e.g.: + * + * 3. XHTML-style content-less tag, e.g.: + * + */ + switch (substr($body, $pos, 1)) { case '/': - $tagtype = 2; - $pos++; - break; + $tagtype = 2; + $pos++; + break; case '!': - /** - * A comment or an SGML declaration. - */ - if (substr($body, $pos + 1, 2) == '--') { - $gt = strpos($body, '-->', $pos); - if ($gt === false) { - $gt = strlen($body); - } else { - $gt += 2; - } - return array(false, false, false, $lt, $gt); + /** + * A comment or an SGML declaration. + */ + if (substr($body, $pos + 1, 2) == '--') { + $gt = strpos($body, '-->', $pos); + if ($gt === false) { + $gt = strlen($body); } else { - $gt = tln_findnxstr($body, $pos, '>'); - return array(false, false, false, $lt, $gt); + $gt += 2; } - break; + return array(false, false, false, $lt, $gt); + } else { + $gt = tln_findnxstr($body, $pos, '>'); + return array(false, false, false, $lt, $gt); + } + break; default: + /** + * Assume tagtype 1 for now. If it's type 3, we'll switch values + * later. + */ + $tagtype = 1; + break; + } + + /** + * Look for next [\W-_], which will indicate the end of the tag name. + */ + $regary = tln_findnxreg($body, $pos, '[^\w\-_]'); + if ($regary == false) { + return array(false, false, false, $lt, strlen($body)); + } + list($pos, $tagname, $match) = $regary; + $tagname = strtolower($tagname); + + /** + * $match can be either of these: + * '>' indicating the end of the tag entirely. + * '\s' indicating the end of the tag name. + * '/' indicating that this is type-3 xhtml tag. + * + * Whatever else we find there indicates an invalid tag. + */ + switch ($match) { + case '/': + /** + * This is an xhtml-style tag with a closing / at the + * end, like so: . Check if it's followed + * by the closing bracket. If not, then this tag is invalid + */ + if (substr($body, $pos, 2) == '/>') { + $pos++; + $tagtype = 3; + } else { + $gt = tln_findnxstr($body, $pos, '>'); + $retary = array(false, false, false, $lt, $gt); + return $retary; + } + //intentional fall-through + case '>': + return array($tagname, false, $tagtype, $lt, $pos); + break; + default: + /** + * Check if it's whitespace + */ + if (!preg_match('/\s/', $match)) { /** - * Assume tagtype 1 for now. If it's type 3, we'll switch values - * later. + * This is an invalid tag! Look for the next closing ">". */ - $tagtype = 1; - break; + $gt = tln_findnxstr($body, $lt, '>'); + return array(false, false, false, $lt, $gt); + } + break; + } + + /** + * At this point we're here: + * + * \-------^ + * + * At this point we loop in order to find all attributes. + */ + $attary = array(); + + while ($pos <= strlen($body)) { + $pos = tln_skipspace($body, $pos); + if ($pos == strlen($body)) { + /** + * Non-closed tag. + */ + return array(false, false, false, $lt, $pos); + } + /** + * See if we arrived at a ">" or "/>", which means that we reached + * the end of the tag. + */ + $matches = array(); + if (preg_match('%^(\s*)(>|/>)%s', substr($body, $pos), $matches)) { + /** + * Yep. So we did. + */ + $pos += strlen($matches[1]); + if ($matches[2] == '/>') { + $tagtype = 3; + $pos++; + } + return array($tagname, $attary, $tagtype, $lt, $pos); } /** - * Look for next [\W-_], which will indicate the end of the tag name. + * There are several types of attributes, with optional + * [:space:] between members. + * Type 1: + * attrname[:space:]=[:space:]'CDATA' + * Type 2: + * attrname[:space:]=[:space:]"CDATA" + * Type 3: + * attr[:space:]=[:space:]CDATA + * Type 4: + * attrname + * + * We leave types 1 and 2 the same, type 3 we check for + * '"' and convert to """ if needed, then wrap in + * double quotes. Type 4 we convert into: + * attrname="yes". */ $regary = tln_findnxreg($body, $pos, '[^\w\-_]'); if ($regary == false) { - return array(false, false, false, $lt, strlen($body)); + /** + * Looks like body ended before the end of tag. + */ + return array(false, false, false, $lt, strlen($body)); } - list($pos, $tagname, $match) = $regary; - $tagname = strtolower($tagname); - + list($pos, $attname, $match) = $regary; + $attname = strtolower($attname); /** - * $match can be either of these: - * '>' indicating the end of the tag entirely. - * '\s' indicating the end of the tag name. - * '/' indicating that this is type-3 xhtml tag. - * - * Whatever else we find there indicates an invalid tag. + * We arrived at the end of attribute name. Several things possible + * here: + * '>' means the end of the tag and this is attribute type 4 + * '/' if followed by '>' means the same thing as above + * '\s' means a lot of things -- look what it's followed by. + * anything else means the attribute is invalid. */ switch ($match) { - case '/': + case '/': /** * This is an xhtml-style tag with a closing / at the * end, like so: . Check if it's followed * by the closing bracket. If not, then this tag is invalid */ if (substr($body, $pos, 2) == '/>') { - $pos++; - $tagtype = 3; + $pos++; + $tagtype = 3; } else { - $gt = tln_findnxstr($body, $pos, '>'); - $retary = array(false, false, false, $lt, $gt); - return $retary; + $gt = tln_findnxstr($body, $pos, '>'); + $retary = array(false, false, false, $lt, $gt); + return $retary; } - //intentional fall-through - case '>': - return array($tagname, false, $tagtype, $lt, $pos); + //intentional fall-through + case '>': + $attary[$attname] = '"yes"'; + return array($tagname, $attary, $tagtype, $lt, $pos); break; - default: + default: /** - * Check if it's whitespace + * Skip whitespace and see what we arrive at. */ - if (!preg_match('/\s/', $match)) { - /** - * This is an invalid tag! Look for the next closing ">". - */ - $gt = tln_findnxstr($body, $lt, '>'); - return array(false, false, false, $lt, $gt); - } - break; - } - - /** - * At this point we're here: - * - * \-------^ - * - * At this point we loop in order to find all attributes. - */ - $attary = array(); - - while ($pos <= strlen($body)) { $pos = tln_skipspace($body, $pos); - if ($pos == strlen($body)) { - /** - * Non-closed tag. - */ - return array(false, false, false, $lt, $pos); - } + $char = substr($body, $pos, 1); /** - * See if we arrived at a ">" or "/>", which means that we reached - * the end of the tag. + * Two things are valid here: + * '=' means this is attribute type 1 2 or 3. + * \w means this was attribute type 4. + * anything else we ignore and re-loop. End of tag and + * invalid stuff will be caught by our checks at the beginning + * of the loop. */ - $matches = array(); - if (preg_match('%^(\s*)(>|/>)%s', substr($body, $pos), $matches)) { - /** - * Yep. So we did. - */ - $pos += strlen($matches[1]); - if ($matches[2] == '/>') { - $tagtype = 3; - $pos++; + if ($char == '=') { + $pos++; + $pos = tln_skipspace($body, $pos); + /** + * Here are 3 possibilities: + * "'" attribute type 1 + * '"' attribute type 2 + * everything else is the content of tag type 3 + */ + $quot = substr($body, $pos, 1); + if ($quot == '\'') { + $regary = tln_findnxreg($body, $pos + 1, '\''); + if ($regary == false) { + return array(false, false, false, $lt, strlen($body)); } - return array($tagname, $attary, $tagtype, $lt, $pos); - } - - /** - * There are several types of attributes, with optional - * [:space:] between members. - * Type 1: - * attrname[:space:]=[:space:]'CDATA' - * Type 2: - * attrname[:space:]=[:space:]"CDATA" - * Type 3: - * attr[:space:]=[:space:]CDATA - * Type 4: - * attrname - * - * We leave types 1 and 2 the same, type 3 we check for - * '"' and convert to """ if needed, then wrap in - * double quotes. Type 4 we convert into: - * attrname="yes". - */ - $regary = tln_findnxreg($body, $pos, '[^\w\-_]'); - if ($regary == false) { - /** - * Looks like body ended before the end of tag. - */ - return array(false, false, false, $lt, strlen($body)); - } - list($pos, $attname, $match) = $regary; - $attname = strtolower($attname); - /** - * We arrived at the end of attribute name. Several things possible - * here: - * '>' means the end of the tag and this is attribute type 4 - * '/' if followed by '>' means the same thing as above - * '\s' means a lot of things -- look what it's followed by. - * anything else means the attribute is invalid. - */ - switch ($match) { - case '/': - /** - * This is an xhtml-style tag with a closing / at the - * end, like so: . Check if it's followed - * by the closing bracket. If not, then this tag is invalid - */ - if (substr($body, $pos, 2) == '/>') { - $pos++; - $tagtype = 3; - } else { - $gt = tln_findnxstr($body, $pos, '>'); - $retary = array(false, false, false, $lt, $gt); - return $retary; + list($pos, $attval, $match) = $regary; + $pos++; + $attary[$attname] = '\'' . $attval . '\''; + } elseif ($quot == '"') { + $regary = tln_findnxreg($body, $pos + 1, '\"'); + if ($regary == false) { + return array(false, false, false, $lt, strlen($body)); } - //intentional fall-through - case '>': - $attary[$attname] = '"yes"'; - return array($tagname, $attary, $tagtype, $lt, $pos); - break; - default: + list($pos, $attval, $match) = $regary; + $pos++; + $attary[$attname] = '"' . $attval . '"'; + } else { /** - * Skip whitespace and see what we arrive at. + * These are hateful. Look for \s, or >. */ - $pos = tln_skipspace($body, $pos); - $char = substr($body, $pos, 1); - /** - * Two things are valid here: - * '=' means this is attribute type 1 2 or 3. - * \w means this was attribute type 4. - * anything else we ignore and re-loop. End of tag and - * invalid stuff will be caught by our checks at the beginning - * of the loop. - */ - if ($char == '=') { - $pos++; - $pos = tln_skipspace($body, $pos); - /** - * Here are 3 possibilities: - * "'" attribute type 1 - * '"' attribute type 2 - * everything else is the content of tag type 3 - */ - $quot = substr($body, $pos, 1); - if ($quot == '\'') { - $regary = tln_findnxreg($body, $pos + 1, '\''); - if ($regary == false) { - return array(false, false, false, $lt, strlen($body)); - } - list($pos, $attval, $match) = $regary; - $pos++; - $attary[$attname] = '\'' . $attval . '\''; - } elseif ($quot == '"') { - $regary = tln_findnxreg($body, $pos + 1, '\"'); - if ($regary == false) { - return array(false, false, false, $lt, strlen($body)); - } - list($pos, $attval, $match) = $regary; - $pos++; - $attary[$attname] = '"' . $attval . '"'; - } else { - /** - * These are hateful. Look for \s, or >. - */ - $regary = tln_findnxreg($body, $pos, '[\s>]'); - if ($regary == false) { - return array(false, false, false, $lt, strlen($body)); - } - list($pos, $attval, $match) = $regary; - /** - * If it's ">" it will be caught at the top. - */ - $attval = preg_replace('/\"/s', '"', $attval); - $attary[$attname] = '"' . $attval . '"'; - } - } elseif (preg_match('|[\w/>]|', $char)) { - /** - * That was attribute type 4. - */ - $attary[$attname] = '"yes"'; - } else { - /** - * An illegal character. Find next '>' and return. - */ - $gt = tln_findnxstr($body, $pos, '>'); - return array(false, false, false, $lt, $gt); + $regary = tln_findnxreg($body, $pos, '[\s>]'); + if ($regary == false) { + return array(false, false, false, $lt, strlen($body)); } - break; + list($pos, $attval, $match) = $regary; + /** + * If it's ">" it will be caught at the top. + */ + $attval = preg_replace('/\"/s', '"', $attval); + $attary[$attname] = '"' . $attval . '"'; + } + } elseif (preg_match('|[\w/>]|', $char)) { + /** + * That was attribute type 4. + */ + $attary[$attname] = '"yes"'; + } else { + /** + * An illegal character. Find next '>' and return. + */ + $gt = tln_findnxstr($body, $pos, '>'); + return array(false, false, false, $lt, $gt); } + break; } - /** - * The fact that we got here indicates that the tag end was never - * found. Return invalid tag indication so it gets stripped. - */ - return array(false, false, false, $lt, strlen($body)); + } + /** + * The fact that we got here indicates that the tag end was never + * found. Return invalid tag indication so it gets stripped. + */ + return array(false, false, false, $lt, strlen($body)); } /** @@ -438,21 +438,21 @@ function tln_getnxtag($body, $offset) */ function tln_deent(&$attvalue, $regex, $hex = false) { - preg_match_all($regex, $attvalue, $matches); - if (is_array($matches) && count($matches[0]) > 0) { - $repl = array(); - for ($i = 0; $i < count($matches[0]); $i++) { - $numval = $matches[1][$i]; - if ($hex) { - $numval = hexdec($numval); - } - $repl[$matches[0][$i]] = chr($numval); - } - $attvalue = strtr($attvalue, $repl); - return true; - } else { - return false; + preg_match_all($regex, $attvalue, $matches); + if (is_array($matches) && count($matches[0]) > 0) { + $repl = array(); + for ($i = 0; $i < count($matches[0]); $i++) { + $numval = $matches[1][$i]; + if ($hex) { + $numval = hexdec($numval); + } + $repl[$matches[0][$i]] = chr($numval); } + $attvalue = strtr($attvalue, $repl); + return true; + } else { + return false; + } } /** @@ -464,21 +464,22 @@ function tln_deent(&$attvalue, $regex, $hex = false) */ function tln_defang(&$attvalue) { - /** - * Skip this if there aren't ampersands or backslashes. - */ - if (strpos($attvalue, '&') === false - && strpos($attvalue, '\\') === false - ) { - return; - } - do { - $m = false; - $m = $m || tln_deent($attvalue, '/\�*(\d+);*/s'); - $m = $m || tln_deent($attvalue, '/\�*((\d|[a-f])+);*/si', true); - $m = $m || tln_deent($attvalue, '/\\\\(\d+)/s', true); - } while ($m == true); - $attvalue = stripslashes($attvalue); + /** + * Skip this if there aren't ampersands or backslashes. + */ + if ( + strpos($attvalue, '&') === false + && strpos($attvalue, '\\') === false + ) { + return; + } + do { + $m = false; + $m = $m || tln_deent($attvalue, '/\�*(\d+);*/s'); + $m = $m || tln_deent($attvalue, '/\�*((\d|[a-f])+);*/si', true); + $m = $m || tln_deent($attvalue, '/\\\\(\d+)/s', true); + } while ($m == true); + $attvalue = stripslashes($attvalue); } /** @@ -490,13 +491,13 @@ function tln_defang(&$attvalue) */ function tln_unspace(&$attvalue) { - if (strcspn($attvalue, "\t\r\n\0 ") != strlen($attvalue)) { - $attvalue = str_replace( - array("\t", "\r", "\n", "\0", " "), - array('', '', '', '', ''), - $attvalue - ); - } + if (strcspn($attvalue, "\t\r\n\0 ") != strlen($attvalue)) { + $attvalue = str_replace( + array("\t", "\r", "\n", "\0", " "), + array('', '', '', '', ''), + $attvalue + ); + } } /** @@ -512,316 +513,318 @@ function tln_unspace(&$attvalue) * @return array with modified attributes. */ function tln_fixatts( - $tagname, - $attary, - $rm_attnames, - $bad_attvals, - $add_attr_to_tag, - $trans_image_path, - $block_external_images + $tagname, + $attary, + $rm_attnames, + $bad_attvals, + $add_attr_to_tag, + $trans_image_path, + $block_external_images ) { - foreach($attary as $attname => $attvalue) { - /** - * See if this attribute should be removed. - */ - foreach ($rm_attnames as $matchtag => $matchattrs) { - if (preg_match($matchtag, $tagname)) { - foreach ($matchattrs as $matchattr) { - if (preg_match($matchattr, $attname)) { - unset($attary[$attname]); - continue; - } - } - } - } - /** - * Remove any backslashes, entities, or extraneous whitespace. - */ - $oldattvalue = $attvalue; - tln_defang($attvalue); - if ($attname == 'style' && $attvalue !== $oldattvalue) { - $attvalue = "idiocy"; - $attary[$attname] = $attvalue; - } - tln_unspace($attvalue); - - /** - * Now let's run checks on the attvalues. - * I don't expect anyone to comprehend this. If you do, - * get in touch with me so I can drive to where you live and - * shake your hand personally. :) - */ - foreach ($bad_attvals as $matchtag => $matchattrs) { - if (preg_match($matchtag, $tagname)) { - foreach ($matchattrs as $matchattr => $valary) { - if (preg_match($matchattr, $attname)) { - /** - * There are two arrays in valary. - * First is matches. - * Second one is replacements - */ - list($valmatch, $valrepl) = $valary; - $newvalue = preg_replace($valmatch, $valrepl, $attvalue); - if ($newvalue != $attvalue) { - $attary[$attname] = $newvalue; - $attvalue = $newvalue; - } - } - } - } - } - if ($attname == 'style') { - if (preg_match('/[\0-\37\200-\377]+/', $attvalue)) { - $attary[$attname] = '"disallowed character"'; - } - preg_match_all("/url\s*\((.+)\)/si", $attvalue, $aMatch); - if (count($aMatch)) { - foreach($aMatch[1] as $sMatch) { - $urlvalue = $sMatch; - tln_fixurl($attname, $urlvalue, $trans_image_path, $block_external_images); - $attary[$attname] = str_replace($sMatch, $urlvalue, $attvalue); - } - } - } - } + foreach ($attary as $attname => $attvalue) { /** - * See if we need to append any attributes to this tag. + * See if this attribute should be removed. */ - foreach ($add_attr_to_tag as $matchtag => $addattary) { - if (preg_match($matchtag, $tagname)) { - $attary = array_merge($attary, $addattary); + foreach ($rm_attnames as $matchtag => $matchattrs) { + if (preg_match($matchtag, $tagname)) { + foreach ($matchattrs as $matchattr) { + if (preg_match($matchattr, $attname)) { + unset($attary[$attname]); + continue; + } } + } } - return $attary; + /** + * Remove any backslashes, entities, or extraneous whitespace. + */ + $oldattvalue = $attvalue; + tln_defang($attvalue); + if ($attname == 'style' && $attvalue !== $oldattvalue) { + $attvalue = "idiocy"; + $attary[$attname] = $attvalue; + } + tln_unspace($attvalue); + + /** + * Now let's run checks on the attvalues. + * I don't expect anyone to comprehend this. If you do, + * get in touch with me so I can drive to where you live and + * shake your hand personally. :) + */ + foreach ($bad_attvals as $matchtag => $matchattrs) { + if (preg_match($matchtag, $tagname)) { + foreach ($matchattrs as $matchattr => $valary) { + if (preg_match($matchattr, $attname)) { + /** + * There are two arrays in valary. + * First is matches. + * Second one is replacements + */ + list($valmatch, $valrepl) = $valary; + $newvalue = preg_replace($valmatch, $valrepl, $attvalue); + if ($newvalue != $attvalue) { + $attary[$attname] = $newvalue; + $attvalue = $newvalue; + } + } + } + } + } + if ($attname == 'style') { + if (preg_match('/[\0-\37\200-\377]+/', $attvalue)) { + $attary[$attname] = '"disallowed character"'; + } + preg_match_all("/url\s*\((.+)\)/si", $attvalue, $aMatch); + if (count($aMatch)) { + foreach ($aMatch[1] as $sMatch) { + $urlvalue = $sMatch; + tln_fixurl($attname, $urlvalue, $trans_image_path, $block_external_images); + $attary[$attname] = str_replace($sMatch, $urlvalue, $attvalue); + } + } + } + } + /** + * See if we need to append any attributes to this tag. + */ + foreach ($add_attr_to_tag as $matchtag => $addattary) { + if (preg_match($matchtag, $tagname)) { + $attary = array_merge($attary, $addattary); + } + } + return $attary; } function tln_fixurl($attname, &$attvalue, $trans_image_path, $block_external_images) { - $sQuote = '"'; - $attvalue = trim($attvalue); - if ($attvalue && ($attvalue[0] =='"'|| $attvalue[0] == "'")) { - // remove the double quotes - $sQuote = $attvalue[0]; - $attvalue = trim(substr($attvalue,1,-1)); - } + $sQuote = '"'; + $attvalue = trim($attvalue); + if ($attvalue && ($attvalue[0] == '"' || $attvalue[0] == "'")) { + // remove the double quotes + $sQuote = $attvalue[0]; + $attvalue = trim(substr($attvalue, 1, -1)); + } - /** - * Replace empty src tags with the blank image. src is only used - * for frames, images, and image inputs. Doing a replace should - * not affect them working as should be, however it will stop - * IE from being kicked off when src for img tags are not set - */ - if ($attvalue == '') { - $attvalue = $sQuote . $trans_image_path . $sQuote; + /** + * Replace empty src tags with the blank image. src is only used + * for frames, images, and image inputs. Doing a replace should + * not affect them working as should be, however it will stop + * IE from being kicked off when src for img tags are not set + */ + if ($attvalue == '') { + $attvalue = $sQuote . $trans_image_path . $sQuote; + } else { + // first, disallow 8 bit characters and control characters + if (preg_match('/[\0-\37\200-\377]+/', $attvalue)) { + switch ($attname) { + case 'href': + $attvalue = $sQuote . 'http://invalid-stuff-detected.example.com' . $sQuote; + break; + default: + $attvalue = $sQuote . $trans_image_path . $sQuote; + break; + } } else { - // first, disallow 8 bit characters and control characters - if (preg_match('/[\0-\37\200-\377]+/',$attvalue)) { - switch ($attname) { - case 'href': - $attvalue = $sQuote . 'http://invalid-stuff-detected.example.com' . $sQuote; - break; - default: - $attvalue = $sQuote . $trans_image_path . $sQuote; - break; - } - } else { - $aUrl = parse_url($attvalue); - if (isset($aUrl['scheme'])) { - switch(strtolower($aUrl['scheme'])) { - case 'mailto': - case 'http': - case 'https': - case 'ftp': - if ($attname != 'href') { - if ($block_external_images == true) { - $attvalue = $sQuote . $trans_image_path . $sQuote; - } else { - if (!isset($aUrl['path'])) { - $attvalue = $sQuote . $trans_image_path . $sQuote; - } - } - } else { - $attvalue = $sQuote . $attvalue . $sQuote; - } - break; - case 'outbind': - $attvalue = $sQuote . $attvalue . $sQuote; - break; - case 'cid': - $attvalue = $sQuote . $attvalue . $sQuote; - break; - default: - $attvalue = $sQuote . $trans_image_path . $sQuote; - break; + $aUrl = parse_url($attvalue); + if (isset($aUrl['scheme'])) { + switch (strtolower($aUrl['scheme'])) { + case 'mailto': + case 'http': + case 'https': + case 'ftp': + if ($attname != 'href') { + if ($block_external_images == true) { + $attvalue = $sQuote . $trans_image_path . $sQuote; + } else { + if (!isset($aUrl['path'])) { + $attvalue = $sQuote . $trans_image_path . $sQuote; } + } } else { - if (!isset($aUrl['path']) || $aUrl['path'] != $trans_image_path) { - $$attvalue = $sQuote . $trans_image_path . $sQuote; - } + $attvalue = $sQuote . $attvalue . $sQuote; } + break; + case 'outbind': + $attvalue = $sQuote . $attvalue . $sQuote; + break; + case 'cid': + $attvalue = $sQuote . $attvalue . $sQuote; + break; + default: + $attvalue = $sQuote . $trans_image_path . $sQuote; + break; } + } else { + if (!isset($aUrl['path']) || $aUrl['path'] != $trans_image_path) { + $$attvalue = $sQuote . $trans_image_path . $sQuote; + } + } } + } } function tln_fixstyle($body, $pos, $trans_image_path, $block_external_images) { - // workaround for in between comments - $content = ''; - $sToken = ''; - $bSucces = false; - $bEndTag = false; - for ($i=$pos,$iCount=strlen($body);$i<$iCount;++$i) { - $char = $body[$i]; - switch ($char) { - case '<': - $sToken = $char; - break; - case '/': - if ($sToken == '<') { - $sToken .= $char; - $bEndTag = true; - } else { - $content .= $char; - } - break; - case '>': - if ($bEndTag) { - $sToken .= $char; - if (preg_match('/\<\/\s*style\s*\>/i',$sToken,$aMatch)) { - $newpos = $i + 1; - $bSucces = true; - break 2; - } else { - $content .= $sToken; - } - $bEndTag = false; - } else { - $content .= $char; - } - break; - case '!': - if ($sToken == '<') { - // possible comment - if (isset($body[$i+2]) && substr($body,$i,3) == '!--') { - $i = strpos($body,'-->',$i+3); - if ($i === false) { // no end comment - $i = strlen($body); - } - $sToken = ''; - } - } else { - $content .= $char; - } - break; - default: - if ($bEndTag) { - $sToken .= $char; - } else { - $content .= $char; - } - break; + // workaround for in between comments + $content = ''; + $sToken = ''; + $bSucces = false; + $bEndTag = false; + for ($i = $pos, $iCount = strlen($body); $i < $iCount; ++$i) { + $char = $body[$i]; + switch ($char) { + case '<': + $sToken = $char; + break; + case '/': + if ($sToken == '<') { + $sToken .= $char; + $bEndTag = true; + } else { + $content .= $char; } - } - if ($bSucces == FALSE){ - return array(FALSE, strlen($body)); - } - - - - /** - * First look for general BODY style declaration, which would be - * like so: - * body {background: blah-blah} - * and change it to .bodyclass so we can just assign it to a
- */ - $content = preg_replace("|body(\s*\{.*?\})|si", ".bodyclass\\1", $content); - - /** - * Fix url('blah') declarations. - */ - // $content = preg_replace("|url\s*\(\s*([\'\"])\s*\S+script\s*:.*?([\'\"])\s*\)|si", - // "url(\\1$trans_image_path\\2)", $content); - - // first check for 8bit sequences and disallowed control characters - if (preg_match('/[\16-\37\200-\377]+/',$content)) { - $content = ''; - return array($content, $newpos); - } - - // remove @import line - $content = preg_replace("/^\s*(@import.*)$/mi","\n\n",$content); - - $content = preg_replace("/(\\\\)?u(\\\\)?r(\\\\)?l(\\\\)?/i", 'url', $content); - preg_match_all("/url\s*\((.+)\)/si",$content,$aMatch); - if (count($aMatch)) { - $aValue = $aReplace = array(); - foreach($aMatch[1] as $sMatch) { - // url value - $urlvalue = $sMatch; - tln_fixurl('style',$urlvalue, $trans_image_path, $block_external_images); - $aValue[] = $sMatch; - $aReplace[] = $urlvalue; + break; + case '>': + if ($bEndTag) { + $sToken .= $char; + if (preg_match('/\<\/\s*style\s*\>/i', $sToken, $aMatch)) { + $newpos = $i + 1; + $bSucces = true; + break 2; + } else { + $content .= $sToken; + } + $bEndTag = false; + } else { + $content .= $char; } - $content = str_replace($aValue,$aReplace,$content); + break; + case '!': + if ($sToken == '<') { + // possible comment + if (isset($body[$i + 2]) && substr($body, $i, 3) == '!--') { + $i = strpos($body, '-->', $i + 3); + if ($i === false) { // no end comment + $i = strlen($body); + } + $sToken = ''; + } + } else { + $content .= $char; + } + break; + default: + if ($bEndTag) { + $sToken .= $char; + } else { + $content .= $char; + } + break; } + } + if ($bSucces == FALSE) { + return array(FALSE, strlen($body)); + } - /** - * Remove any backslashes, entities, and extraneous whitespace. - */ - $contentTemp = $content; - tln_defang($contentTemp); - tln_unspace($contentTemp); - $match = array('/\/\*.*\*\//', - '/expression/i', - '/behaviou*r/i', - '/binding/i', - '/include-source/i', - '/javascript/i', - '/script/i', - '/position/i'); - $replace = array('','idiocy', 'idiocy', 'idiocy', 'idiocy', 'idiocy', 'idiocy', ''); - $contentNew = preg_replace($match, $replace, $contentTemp); - if ($contentNew !== $contentTemp) { - $content = $contentNew; - } + + /** + * First look for general BODY style declaration, which would be + * like so: + * body {background: blah-blah} + * and change it to .bodyclass so we can just assign it to a
+ */ + $content = preg_replace("|body(\s*\{.*?\})|si", ".bodyclass\\1", $content); + + /** + * Fix url('blah') declarations. + */ + // $content = preg_replace("|url\s*\(\s*([\'\"])\s*\S+script\s*:.*?([\'\"])\s*\)|si", + // "url(\\1$trans_image_path\\2)", $content); + + // first check for 8bit sequences and disallowed control characters + if (preg_match('/[\16-\37\200-\377]+/', $content)) { + $content = ''; return array($content, $newpos); + } + + // remove @import line + $content = preg_replace("/^\s*(@import.*)$/mi", "\n\n", $content); + + $content = preg_replace("/(\\\\)?u(\\\\)?r(\\\\)?l(\\\\)?/i", 'url', $content); + preg_match_all("/url\s*\((.+)\)/si", $content, $aMatch); + if (count($aMatch)) { + $aValue = $aReplace = array(); + foreach ($aMatch[1] as $sMatch) { + // url value + $urlvalue = $sMatch; + tln_fixurl('style', $urlvalue, $trans_image_path, $block_external_images); + $aValue[] = $sMatch; + $aReplace[] = $urlvalue; + } + $content = str_replace($aValue, $aReplace, $content); + } + + /** + * Remove any backslashes, entities, and extraneous whitespace. + */ + $contentTemp = $content; + tln_defang($contentTemp); + tln_unspace($contentTemp); + + $match = array( + '/\/\*.*\*\//', + '/expression/i', + '/behaviou*r/i', + '/binding/i', + '/include-source/i', + '/javascript/i', + '/script/i', + '/position/i' + ); + $replace = array('', 'idiocy', 'idiocy', 'idiocy', 'idiocy', 'idiocy', 'idiocy', ''); + $contentNew = preg_replace($match, $replace, $contentTemp); + if ($contentNew !== $contentTemp) { + $content = $contentNew; + } + return array($content, $newpos); } function tln_body2div($attary, $trans_image_path) { - $divattary = array('class' => "'bodyclass'"); - $text = '#000000'; - $has_bgc_stl = $has_txt_stl = false; - $styledef = ''; - if (is_array($attary) && count($attary) > 0){ - foreach ($attary as $attname=>$attvalue){ - $quotchar = substr($attvalue, 0, 1); - $attvalue = str_replace($quotchar, "", $attvalue); - switch ($attname){ - case 'background': - $styledef .= "background-image: url('$trans_image_path'); "; - break; - case 'bgcolor': - $has_bgc_stl = true; - $styledef .= "background-color: $attvalue; "; - break; - case 'text': - $has_txt_stl = true; - $styledef .= "color: $attvalue; "; - break; - } - } - // Outlook defines a white bgcolor and no text color. This can lead to - // white text on a white bg with certain themes. - if ($has_bgc_stl && !$has_txt_stl) { - $styledef .= "color: $text; "; - } - if (strlen($styledef) > 0){ - $divattary["style"] = "\"$styledef\""; - } + $divattary = array('class' => "'bodyclass'"); + $text = '#000000'; + $has_bgc_stl = $has_txt_stl = false; + $styledef = ''; + if (is_array($attary) && count($attary) > 0) { + foreach ($attary as $attname => $attvalue) { + $quotchar = substr($attvalue, 0, 1); + $attvalue = str_replace($quotchar, "", $attvalue); + switch ($attname) { + case 'background': + $styledef .= "background-image: url('$trans_image_path'); "; + break; + case 'bgcolor': + $has_bgc_stl = true; + $styledef .= "background-color: $attvalue; "; + break; + case 'text': + $has_txt_stl = true; + $styledef .= "color: $attvalue; "; + break; + } } - return $divattary; + // Outlook defines a white bgcolor and no text color. This can lead to + // white text on a white bg with certain themes. + if ($has_bgc_stl && !$has_txt_stl) { + $styledef .= "color: $text; "; + } + if (strlen($styledef) > 0) { + $divattary["style"] = "\"$styledef\""; + } + } + return $divattary; } /** @@ -840,169 +843,174 @@ function tln_body2div($attary, $trans_image_path) * @return string Sanitized html safe to show on your pages. */ function tln_sanitize( - $body, - $tag_list, - $rm_tags_with_content, - $self_closing_tags, - $force_tag_closing, - $rm_attnames, - $bad_attvals, - $add_attr_to_tag, - $trans_image_path, - $block_external_images + $body, + $tag_list, + $rm_tags_with_content, + $self_closing_tags, + $force_tag_closing, + $rm_attnames, + $bad_attvals, + $add_attr_to_tag, + $trans_image_path, + $block_external_images ) { + /** + * Normalize rm_tags and rm_tags_with_content. + */ + $rm_tags = array_shift($tag_list); + @array_walk($tag_list, 'tln_casenormalize'); + @array_walk($rm_tags_with_content, 'tln_casenormalize'); + @array_walk($self_closing_tags, 'tln_casenormalize'); + /** + * See if tag_list is of tags to remove or tags to allow. + * false means remove these tags + * true means allow these tags + */ + $curpos = 0; + $open_tags = array(); + $trusted = "\n"; + $skip_content = false; + /** + * Take care of netscape's stupid javascript entities like + * &{alert('boo')}; + */ + $body = preg_replace('/&(\{.*?\};)/si', '&\\1', $body); + while (($curtag = tln_getnxtag($body, $curpos)) != false) { + list($tagname, $attary, $tagtype, $lt, $gt) = $curtag; + $free_content = substr($body, $curpos, $lt - $curpos); /** - * Normalize rm_tags and rm_tags_with_content. + * Take care of