From 3412e50b54e3daac8745234e21ab6e72be0ed165 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Wed, 04 Jun 2014 11:20:33 -0400
Subject: [PATCH] Fix attachment menu structure and aria-attributes
---
program/lib/Net/SMTP.php | 208 ++++++++++++++++++++++++++++++++++++---------------
1 files changed, 147 insertions(+), 61 deletions(-)
diff --git a/program/lib/Net/SMTP.php b/program/lib/Net/SMTP.php
index fef8076..2c1ef5c 100644
--- a/program/lib/Net/SMTP.php
+++ b/program/lib/Net/SMTP.php
@@ -17,8 +17,6 @@
// | Jon Parise <jon@php.net> |
// | Damian Alejandro Fernandez Sosa <damlists@cnba.uba.ar> |
// +----------------------------------------------------------------------+
-//
-// $Id$
require_once 'PEAR.php';
require_once 'Net/Socket.php';
@@ -62,7 +60,7 @@
* @var array
* @access public
*/
- var $auth_methods = array('DIGEST-MD5', 'CRAM-MD5', 'LOGIN', 'PLAIN');
+ var $auth_methods = array();
/**
* Use SMTP command pipelining (specified in RFC 2920) if the SMTP
@@ -104,6 +102,14 @@
* @access private
*/
var $_socket = null;
+
+ /**
+ * Array of socket options that will be passed to Net_Socket::connect().
+ * @see stream_context_create()
+ * @var array
+ * @access private
+ */
+ var $_socket_options = null;
/**
* The socket I/O timeout value in seconds.
@@ -156,12 +162,13 @@
* @param string $localhost The value to give when sending EHLO or HELO.
* @param boolean $pipeling Use SMTP command pipelining
* @param integer $timeout Socket I/O timeout in seconds.
+ * @param array $socket_options Socket stream_context_create() options.
*
* @access public
* @since 1.0
*/
function Net_SMTP($host = null, $port = null, $localhost = null,
- $pipelining = false, $timeout = 0)
+ $pipelining = false, $timeout = 0, $socket_options = null)
{
if (isset($host)) {
$this->host = $host;
@@ -175,17 +182,19 @@
$this->pipelining = $pipelining;
$this->_socket = new Net_Socket();
+ $this->_socket_options = $socket_options;
$this->_timeout = $timeout;
- /* Include the Auth_SASL package. If the package is not
- * available, we disable the authentication methods that
- * depend upon it. */
- if ((@include_once 'Auth/SASL.php') === false) {
- $pos = array_search('DIGEST-MD5', $this->auth_methods);
- unset($this->auth_methods[$pos]);
- $pos = array_search('CRAM-MD5', $this->auth_methods);
- unset($this->auth_methods[$pos]);
+ /* Include the Auth_SASL package. If the package is available, we
+ * enable the authentication methods that depend upon it. */
+ if (@include_once 'Auth/SASL.php') {
+ $this->setAuthMethod('CRAM-MD5', array($this, '_authCram_MD5'));
+ $this->setAuthMethod('DIGEST-MD5', array($this, '_authDigest_MD5'));
}
+
+ /* These standard authentication methods are always available. */
+ $this->setAuthMethod('LOGIN', array($this, '_authLogin'), false);
+ $this->setAuthMethod('PLAIN', array($this, '_authPlain'), false);
}
/**
@@ -240,7 +249,8 @@
*
* @param string $data The string of data to send.
*
- * @return mixed True on success or a PEAR_Error object on failure.
+ * @return mixed The number of bytes that were actually written,
+ * or a PEAR_Error object on failure.
*
* @access private
* @since 1.1.0
@@ -249,13 +259,14 @@
{
$this->_debug("Send: $data");
- $error = $this->_socket->write($data);
- if ($error === false || PEAR::isError($error)) {
- $msg = ($error) ? $error->getMessage() : "unknown error";
- return PEAR::raiseError("Failed to write to socket: $msg");
+ $result = $this->_socket->write($data);
+ if (!$result || PEAR::isError($result)) {
+ $msg = ($result) ? $result->getMessage() : "unknown error";
+ return PEAR::raiseError("Failed to write to socket: $msg",
+ null, PEAR_ERROR_RETURN);
}
- return true;
+ return $result;
}
/**
@@ -282,7 +293,8 @@
}
if (strcspn($command, "\r\n") !== strlen($command)) {
- return PEAR::raiseError('Commands cannot contain newlines');
+ return PEAR::raiseError('Commands cannot contain newlines',
+ null, PEAR_ERROR_RETURN);
}
return $this->_send($command . "\r\n");
@@ -321,10 +333,11 @@
while ($line = $this->_socket->readLine()) {
$this->_debug("Recv: $line");
- /* If we receive an empty line, the connection has been closed. */
+ /* If we receive an empty line, the connection was closed. */
if (empty($line)) {
$this->disconnect();
- return PEAR::raiseError('Connection was unexpectedly closed');
+ return PEAR::raiseError('Connection was closed',
+ null, PEAR_ERROR_RETURN);
}
/* Read the code and store the rest in the arguments array. */
@@ -356,7 +369,32 @@
}
return PEAR::raiseError('Invalid response code received from server',
- $this->_code);
+ $this->_code, PEAR_ERROR_RETURN);
+ }
+
+ /**
+ * Issue an SMTP command and verify its response.
+ *
+ * @param string $command The SMTP command string or data.
+ * @param mixed $valid The set of valid response codes. These
+ * may be specified as an array of integer
+ * values or as a single integer value.
+ *
+ * @return mixed True on success or a PEAR_Error object on failure.
+ *
+ * @access public
+ * @since 1.6.0
+ */
+ function command($command, $valid)
+ {
+ if (PEAR::isError($error = $this->_put($command))) {
+ return $error;
+ }
+ if (PEAR::isError($error = $this->_parseResponse($valid))) {
+ return $error;
+ }
+
+ return true;
}
/**
@@ -405,7 +443,8 @@
{
$this->_greeting = null;
$result = $this->_socket->connect($this->host, $this->port,
- $persistent, $timeout);
+ $persistent, $timeout,
+ $this->_socket_options);
if (PEAR::isError($result)) {
return PEAR::raiseError('Failed to connect socket: ' .
$result->getMessage());
@@ -417,8 +456,10 @@
* timeout values for the initial connection (our $timeout parameter)
* and all other socket operations.
*/
- if (PEAR::isError($error = $this->setTimeout($this->_timeout))) {
- return $error;
+ if ($this->_timeout > 0) {
+ if (PEAR::isError($error = $this->setTimeout($this->_timeout))) {
+ return $error;
+ }
}
if (PEAR::isError($error = $this->_parseResponse(220))) {
@@ -486,7 +527,8 @@
return $error;
}
if (PEAR::isError($this->_parseResponse(250))) {
- return PEAR::raiseError('HELO was not accepted: ', $this->_code);
+ return PEAR::raiseError('HELO was not accepted: ', $this->_code,
+ PEAR_ERROR_RETURN);
}
return true;
@@ -520,13 +562,14 @@
{
$available_methods = explode(' ', $this->_esmtp['AUTH']);
- foreach ($this->auth_methods as $method) {
+ foreach ($this->auth_methods as $method => $callback) {
if (in_array($method, $available_methods)) {
return $method;
}
}
- return PEAR::raiseError('No supported authentication methods');
+ return PEAR::raiseError('No supported authentication methods',
+ null, PEAR_ERROR_RETURN);
}
/**
@@ -586,36 +629,70 @@
}
} else {
$method = strtoupper($method);
- if (!in_array($method, $this->auth_methods)) {
+ if (!array_key_exists($method, $this->auth_methods)) {
return PEAR::raiseError("$method is not a supported authentication method");
}
}
- switch ($method) {
- case 'DIGEST-MD5':
- $result = $this->_authDigest_MD5($uid, $pwd, $authz);
- break;
-
- case 'CRAM-MD5':
- $result = $this->_authCRAM_MD5($uid, $pwd);
- break;
-
- case 'LOGIN':
- $result = $this->_authLogin($uid, $pwd);
- break;
-
- case 'PLAIN':
- $result = $this->_authPlain($uid, $pwd, $authz);
- break;
-
- default:
- $result = PEAR::raiseError("$method is not a supported authentication method");
- break;
+ if (!isset($this->auth_methods[$method])) {
+ return PEAR::raiseError("$method is not a supported authentication method");
}
+
+ if (!is_callable($this->auth_methods[$method], false)) {
+ return PEAR::raiseError("$method authentication method cannot be called");
+ }
+
+ if (is_array($this->auth_methods[$method])) {
+ list($object, $method) = $this->auth_methods[$method];
+ $result = $object->{$method}($uid, $pwd, $authz, $this);
+ } else {
+ $func = $this->auth_methods[$method];
+ $result = $func($uid, $pwd, $authz, $this);
+ }
/* If an error was encountered, return the PEAR_Error object. */
if (PEAR::isError($result)) {
return $result;
+ }
+
+ return true;
+ }
+
+ /**
+ * Add a new authentication method.
+ *
+ * @param string The authentication method name (e.g. 'PLAIN')
+ * @param mixed The authentication callback (given as the name of a
+ * function or as an (object, method name) array).
+ * @param bool Should the new method be prepended to the list of
+ * available methods? This is the default behavior,
+ * giving the new method the highest priority.
+ *
+ * @return mixed True on success or a PEAR_Error object on failure.
+ *
+ * @access public
+ * @since 1.6.0
+ */
+ function setAuthMethod($name, $callback, $prepend = true)
+ {
+ if (!is_string($name)) {
+ return PEAR::raiseError('Method name is not a string');
+ }
+
+ if (!is_string($callback) && !is_array($callback)) {
+ return PEAR::raiseError('Method callback must be string or array');
+ }
+
+ if (is_array($callback)) {
+ if (!is_object($callback[0]) || !is_string($callback[1]))
+ return PEAR::raiseError('Bad mMethod callback array');
+ }
+
+ if ($prepend) {
+ $this->auth_methods = array_merge(array($name => $callback),
+ $this->auth_methods);
+ } else {
+ $this->auth_methods[$name] = $callback;
}
return true;
@@ -648,7 +725,7 @@
}
$challenge = base64_decode($this->_arguments[0]);
- $digest = &Auth_SASL::factory('digestmd5');
+ $digest = &Auth_SASL::factory('digest-md5');
$auth_str = base64_encode($digest->getResponse($uid, $pwd, $challenge,
$this->host, "smtp",
$authz));
@@ -678,13 +755,14 @@
*
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
+ * @param string The optional authorization proxy identifier.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access private
* @since 1.1.0
*/
- function _authCRAM_MD5($uid, $pwd)
+ function _authCRAM_MD5($uid, $pwd, $authz = '')
{
if (PEAR::isError($error = $this->_put('AUTH', 'CRAM-MD5'))) {
return $error;
@@ -699,7 +777,7 @@
}
$challenge = base64_decode($this->_arguments[0]);
- $cram = &Auth_SASL::factory('crammd5');
+ $cram = &Auth_SASL::factory('cram-md5');
$auth_str = base64_encode($cram->getResponse($uid, $pwd, $challenge));
if (PEAR::isError($error = $this->_put($auth_str))) {
@@ -717,13 +795,14 @@
*
* @param string The userid to authenticate as.
* @param string The password to authenticate with.
+ * @param string The optional authorization proxy identifier.
*
* @return mixed Returns a PEAR_Error with an error message on any
* kind of failure, or true on success.
* @access private
* @since 1.1.0
*/
- function _authLogin($uid, $pwd)
+ function _authLogin($uid, $pwd, $authz = '')
{
if (PEAR::isError($error = $this->_put('AUTH', 'LOGIN'))) {
return $error;
@@ -923,14 +1002,12 @@
*/
function quotedata(&$data)
{
- /* Change Unix (\n) and Mac (\r) linefeeds into
- * Internet-standard CRLF (\r\n) linefeeds. */
- $data = preg_replace(array('/(?<!\r)\n/','/\r(?!\n)/'), "\r\n", $data);
-
/* Because a single leading period (.) signifies an end to the
- * data, legitimate leading periods need to be "doubled"
- * (e.g. '..'). */
- $data = str_replace("\n.", "\n..", $data);
+ * data, legitimate leading periods need to be "doubled" ('..'). */
+ $data = preg_replace('/^\./m', '..', $data);
+
+ /* Change Unix (\n) and Mac (\r) linefeeds into CRLF's (\r\n). */
+ $data = preg_replace('/(?:\r\n|\n|\r(?!\n))/', "\r\n", $data);
}
/**
@@ -999,7 +1076,16 @@
/* Stream the contents of the file resource out over our socket
* connection, line by line. Each line must be run through the
* quoting routine. */
- while ($line = fgets($data, 1024)) {
+ while (strlen($line = fread($data, 8192)) > 0) {
+ /* If the last character is an newline, we need to grab the
+ * next character to check to see if it is a period. */
+ while (!feof($data)) {
+ $char = fread($data, 1);
+ $line .= $char;
+ if ($char != "\n") {
+ break;
+ }
+ }
$this->quotedata($line);
if (PEAR::isError($result = $this->_send($line))) {
return $result;
--
Gitblit v1.9.1