package.xml0000644000175000007640000004550712267540554013214 0ustar slusarzslusarz Horde_Mail pear.horde.org Horde Mail Library Provides interfaces for sending e-mail messages and parsing e-mail addresses. Michael Slusarz slusarz slusarz@horde.org yes 2014-01-21 2.1.4 2.1.0 stable stable BSD-2-Clause * [mms] More thorough job trying to parse addresses that contain an @ but no domain information. 5.3.0 1.7.0 Horde_Exception pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Mime pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Stream_Filter pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Smtp pear.horde.org 1.0.0 2.0.0alpha1 2.0.0alpha1 Horde_Support pear.horde.org 2.0.0 3.0.0alpha1 3.0.0alpha1 Horde_Test pear.horde.org 2.1.0 3.0.0alpha1 3.0.0alpha1 Net_DNS2 pear.php.net Net_SMTP pear.php.net 1.6.0 1.0.0alpha1 1.0.0 alpha alpha 2011-03-08 BSD-2-Clause * First alpha release for Horde 4. 1.0.0beta1 1.0.0 beta beta 2011-03-16 BSD-2-Clause * First beta release for Horde 4. 1.0.0RC1 1.0.0 beta beta 2011-03-22 BSD-2-Clause * First release candidate for Horde 4. * [jan] Fix passing arguments to sendmail binary (Gonçalo Queirós, Bug #9693). 1.0.0RC2 1.0.0 beta beta 2011-03-29 BSD-2-Clause * Second release candidate for Horde 4. 1.0.0 1.0.0 stable stable 2011-04-06 BSD-2-Clause * First stable release for Horde 4. 1.0.1 1.0.0 stable stable 2011-09-20 BSD-2-Clause * [mms] Fix parseAddressList() for empty address strings (Bug #10534). * [mms] Fix SMTP error codes/messages (Bug #10498). 1.0.2 1.0.0 stable stable 2011-11-18 BSD-2-Clause * [mms] Always use canonical line endings in sendmail driver (Bug #10696). 1.0.3 1.0.0 stable stable 2012-01-17 BSD-2-Clause * [mms] Fix line-endings for transport drivers that send messages using local tools/programs. 1.1.0 1.1.0 stable stable 2012-02-22 BSD-2-Clause * [mms] Created Horde_Mail_Rfc822_Address and Horde_Mail_Rfc822_Group objects to represent e-mail addresses. * [mms] Improved parser for e-mail addresses (Request #10949). 1.2.0 1.2.0 stable stable 2012-04-10 BSD-2-Clause * [mms] Added Horde_Mail_Rfc822_Address#encoded property. * [mms] Better support for IDN hosts in non-validate address parsing mode (Bug #11021). * [mms] Added Horde_Mail_Rfc822#trimAddress(). * [mms] Add optional arguments to the Horde_Mail_Rfc822_Group constructor. * [mms] Horde_Mail_Rfc822#parseAddressList() now accepts Horde_Mail_Rfc822_Objects within the first argument. * [mms] The address and group objects now extend the base Horde_Mail_Rfc822_Object class. * [mms] Add optional arguments to the Horde_Mail_Rfc822_Group constructor. * [mms] First argument to Horde_Mail_Rfc822#parseAddressList() can now be an array of addresses. * [mms] Add Horde_Mail_Rfc822#encode(). * [mms] Workaround unexpected return from PHP_EOL in Mail transport drivers. 1.2.1 1.2.0 stable stable 2012-04-10 BSD-2-Clause * 2012-07-05 2.0.0alpha1 2.0.0alpha1 alpha alpha BSD-2-Clause * First alpha release for Horde 5. * [mms] Implement new 2.0 API. See UPGRADING for full details of changes. 2.0.0beta1 2.0.0beta1 beta beta 2012-07-19 BSD-2-Clause * First beta release for Horde 5. 2.0.0beta2 2.0.0beta1 beta beta 2012-08-29 BSD-2-Clause * [mms] Add Horde_Mail_Rfc822_Address#matchDomain(). * [mms] Add Horde_Mail_Rfc822_Address#matchInsensitive(). * [mms] Fix parsing Return-Path header when using sendmail driver (Bug #11361). 2.0.0 2.0.0 stable stable 2012-10-30 BSD-2-Clause * First stable release for Horde 5. 2.0.1 2.0.0 stable stable 2012-11-16 BSD-2-Clause * [mms] Ensure bare address is output to SMTP MAIL command. 2.0.2 2.0.0 stable stable 2012-12-03 BSD-2-Clause * [mms] Fix default_domain parameter to Horde_Mail_Rfc822 (Bug #11804). 2.0.3 2.0.0 stable stable 2012-12-26 BSD-2-Clause * [mms] Fix behavior of 'persist' option in SMTP transport driver. 2.0.4 2.0.0 stable stable 2013-03-05 BSD-2-Clause * [mms] Improve unit tests. 2.0.5 2.0.0 stable stable 2013-04-08 BSD-2-Clause * [mms] Correctly encode IDN domains when sending mail (Bug #12116). 2.0.6 2.0.0 stable stable 2013-07-16 BSD-2-Clause * [mms] Honor 'pipelining' configuration option in SMTP transport driver. 2.1.0 2.1.0 stable stable 2013-08-22 BSD-2-Clause * [mms] Added the Horde_Mail_Transport_Smtphorde driver. * [mms] Added the 'bare_addresses_idn' property to Horde_Mail_Rfc822_List. * [mms] Added the 'bare_address_idn' property to Horde_Mail_Rfc822_Address. 2.1.1 2.1.0 stable stable 2013-08-26 BSD-2-Clause * [mms] Workaround broken Net_SMTP handling regarding the end of message data (Bug #12614). 2.1.2 2.1.0 stable stable 2013-10-15 BSD-2-Clause * [mms] Discard personal information when it is identical to e-mail address. 2.1.3 2.1.0 stable stable 2014-01-17 BSD-2-Clause * [mms] Correctly identify e-mail addresses without domain information when validating. 2.1.4 2.1.0 stable stable 2014-01-21 BSD-2-Clause * [mms] More thorough job trying to parse addresses that contain an @ but no domain information. Horde_Mail-2.1.4/doc/Horde/Mail/COPYING0000644000175000007640000000243012267540554017251 0ustar slusarzslusarz Copyright 1999-2013 Horde LLC. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE HORDE PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Horde_Mail-2.1.4/doc/Horde/Mail/UPGRADING0000644000175000007640000000664212267540554017472 0ustar slusarzslusarz====================== Upgrading Horde_Mail ====================== :Contact: dev@lists.horde.org .. contents:: Contents .. section-numbering:: This lists the API changes between releases of the package. Upgrading to 2.1 ================ - Horde_Mail_Rfc822_Address Added the 'bare_address_idn' property. - Horde_Mail_Rfc822_List Added the 'bare_addresses_idn' property. - Horde_Mail_Transport_Smtphorde This Transport driver has been added. Upgrading to 2.0 ================ - Horde_Mail This class has been removed (no more Horde_Mail#factory()). Directly instantiate a transport driver instead. - Horde_Mail_Rfc822 The 'num_groups' property has been removed. The validateMailbox() method has been removed. parseAddressList() now returns a Horde_Mail_Rfc822_List object. The 'group' parameter to parseAddressList() has been added. The 'nest_groups' parameter to parseAddressList() has been removed. parseAddressList() does not validate by default. - Horde_Mail_Rfc822_Address The object can no longer be accessed as an array. Removed the 'adl', 'route', and 'personal_decoded' properties. The 'personal' property now always returns the MIME decoded personal part. The 'host' property now always returns the IDN decoded host. The 'encode' and 'idn' parameters to writeAddress() have changed behavior. Added the 'host_idn' and 'valid' properties. Renamed the 'full_address' property to 'bare_address'. - Horde_Mail_Rfc822_Group The object can no longer be accessed as an array. Removed the 'groupname_decoded' property. The 'groupname' property now always returns the MIME decoded groupname. The 'encode' and 'idn' parameters to writeAddress() have changed behavior. Added the 'valid' property. - Horde_Mail_Rfc822_Object Added a match() method. Passing boolean true to writeAddress() now defaults to full encoding of the address. Upgrading To 1.2 ================ Method API additions -------------------- - Horde_Mail_Rfc822#parseAddressList() The first argument can now be a Horde_Mail_Rfc822_Object or an array of address strings and/or Horde_Mail_Rfc822_Objects. - Horde_Mail_Rfc822_Address#__construct() The constructor now takes 1 optional argument: address. - Horde_Mail_Rfc822_Group#__construct() The constructor now takes 2 optional arguments: groupname and group addresses. New Methods ----------- - Horde_Mail_Rfc822#encode() - Horde_Mail_Rfc822#trimAddress() New Objects ----------- - Horde_Mail_Rfc822_Object Horde_Mail_Rfc822_Address and Horde_Mail_Rfc822_Group now extend this class. Allows for easier determination if an object contains RFC 822 element information. Upgrading To 1.1 ================ New Objects ----------- Horde_Mail_Rfc822::parseAddressList() now returns an array of Horde_Mail_Rfc822_Address objects (and Horde_Mail_Rfc822_Group objects, if nest_groups is true). These objects are backward compatible with the former array representation. They also include additional functionality. - Horde_Mail_Rfc822_Address Anything other than accessing these properties is a new feature available only since 1.1.0: - adl - comment - host - mailbox - personal - Horde_Mail_Rfc822_Group Anything other than accessing these properties is a new feature available only since 1.1.0: - addresses - groupname Horde_Mail-2.1.4/lib/Horde/Mail/Rfc822/Address.php0000644000175000007640000001474512267540554021277 0ustar slusarzslusarz * @category Horde * @copyright 2012-2013 Horde LLC * @license http://www.horde.org/licenses/bsd New BSD License * @package Mail * * @property-read string $bare_address The bare mailbox@host address. * @property-read string $bare_address_idn The bare mailbox@host address (IDN * encoded). (@since 2.1.0) * @property-read string $encoded The full MIME/IDN encoded address (UTF-8). * @property string $host Returns the host part (UTF-8). * @property-read string $host_idn Returns the IDN encoded host part. * @property-read string $label The shorthand label for this address. * @property string $personal The personal part (UTF-8). * @property-read string $personal_encoded The MIME encoded personal part * (UTF-8). * @property-read boolean $valid Returns true if there is enough information * in object to create a valid address. */ class Horde_Mail_Rfc822_Address extends Horde_Mail_Rfc822_Object { /** * Comments associated with the personal phrase. * * @var array */ public $comment = array(); /** * Local-part of the address. * * @var string */ public $mailbox = null; /** * Hostname of the address. * * @var string */ protected $_host = null; /** * Personal part of the address. * * @var string */ protected $_personal = null; /** * Constructor. * * @param string $address If set, address is parsed and used as the * object address. Address is not validated; * first e-mail address parsed is used. */ public function __construct($address = null) { if (!is_null($address)) { $rfc822 = new Horde_Mail_Rfc822(); $addr = $rfc822->parseAddressList($address); if (count($addr)) { foreach ($addr[0] as $key => $val) { $this->$key = $val; } } } } /** */ public function __set($name, $value) { switch ($name) { case 'host': $value = ltrim($value, '@'); $this->_host = function_exists('idn_to_utf8') ? strtolower(idn_to_utf8($value)) : strtolower($value); break; case 'personal': $this->_personal = strlen($value) ? Horde_Mime::decode($value) : null; break; } } /** */ public function __get($name) { switch ($name) { case 'bare_address': return is_null($this->host) ? $this->mailbox : $this->mailbox . '@' . $this->host; case 'bare_address_idn': $personal = $this->_personal; $this->_personal = null; $res = $this->encoded; $this->_personal = $personal; return $res; case 'encoded': return $this->writeAddress(true); case 'host': return $this->_host; case 'host_idn': return function_exists('idn_to_ascii') ? idn_to_ascii($this->_host) : $this->host; case 'label': return is_null($this->personal) ? $this->bare_address : $this->_personal; case 'personal': return (strcasecmp($this->_personal, $this->bare_address) === 0) ? null : $this->_personal; case 'personal_encoded': return Horde_Mime::encode($this->personal); case 'valid': return (bool)strlen($this->mailbox); default: return null; } } /** */ protected function _writeAddress($opts) { $rfc822 = new Horde_Mail_Rfc822(); $address = $rfc822->encode($this->mailbox, 'address'); $host = empty($opts['idn']) ? $this->host : $this->host_idn; if (strlen($host)) { $address .= '@' . $host; } $personal = $this->personal; if (strlen($personal)) { if (!empty($opts['encode'])) { $personal = Horde_Mime::encode($this->personal, $opts['encode']); } $personal = $rfc822->encode($personal, 'personal'); } return (strlen($personal) && ($personal != $address)) ? $personal . ' <' . $address . '>' : $address; } /** */ public function match($ob) { if (!($ob instanceof Horde_Mail_Rfc822_Address)) { $ob = new Horde_Mail_Rfc822_Address($ob); } return ($this->bare_address == $ob->bare_address); } /** * Do a case-insensitive match on the address. Per RFC 822/2822/5322, * although the host portion of an address is case-insensitive, the * mailbox portion is platform dependent. * * @param mixed $ob Address data. * * @return boolean True if the data reflects the same case-insensitive * address. */ public function matchInsensitive($ob) { if (!($ob instanceof Horde_Mail_Rfc822_Address)) { $ob = new Horde_Mail_Rfc822_Address($ob); } return (Horde_String::lower($this->bare_address) == Horde_String::lower($ob->bare_address)); } /** * Do a case-insensitive match on the address for a given domain. * Matches as many parts of the subdomain in the address as is given in * the input. * * @param string $domain Domain to match. * * @return boolean True if the address matches the given domain. */ public function matchDomain($domain) { $host = $this->host; if (is_null($host)) { return false; } $match_domain = explode('.', $domain); $match_host = array_slice(explode('.', $host), count($match_domain) * -1); return (strcasecmp($domain, implode('.', $match_host)) === 0); } } Horde_Mail-2.1.4/lib/Horde/Mail/Rfc822/Group.php0000644000175000007640000000710512267540554020776 0ustar slusarzslusarz * @category Horde * @copyright 2012-2013 Horde LLC * @license http://www.horde.org/licenses/bsd New BSD License * @package Mail * * @property string $groupname Groupname (UTF-8). * @property-read string $groupname_encoded MIME encoded groupname (UTF-8). * @property-read string $label The shorthand label for this group. * @property-read boolean $valid Returns true if there is enough information * in object to create a valid address. */ class Horde_Mail_Rfc822_Group extends Horde_Mail_Rfc822_Object implements Countable { /** * List of group e-mail address objects. * * @var Horde_Mail_Rfc822_GroupList */ public $addresses; /** * Group name (MIME decoded). * * @var string */ protected $_groupname = 'Group'; /** * Constructor. * * @param string $groupname If set, used as the group name. * @param mixed $addresses If a GroupList object, used as the address * list. Any other non-null value is parsed and * used as the address list (addresses not * verified; sub-groups are ignored). */ public function __construct($groupname = null, $addresses = null) { if (!is_null($groupname)) { $this->groupname = $groupname; } if (is_null($addresses)) { $this->addresses = new Horde_Mail_Rfc822_GroupList(); } elseif ($addresses instanceof Horde_Mail_Rfc822_GroupList) { $this->addresses = clone $addresses; } else { $rfc822 = new Horde_Mail_Rfc822(); $this->addresses = $rfc822->parseAddressList($addresses, array( 'group' => true )); } } /** */ public function __set($name, $value) { switch ($name) { case 'groupname': $this->_groupname = Horde_Mime::decode($value); break; } } /** */ public function __get($name) { switch ($name) { case 'groupname': case 'label': return $this->_groupname; case 'groupname_encoded': return Horde_Mime::encode($this->_groupname); case 'valid': return (bool)strlen($this->_groupname); default: return null; } } /** */ protected function _writeAddress($opts) { $addr = $this->addresses->writeAddress($opts); $groupname = $this->groupname; if (!empty($opts['encode'])) { $groupname = Horde_Mime::encode($groupname, $opts['encode']); } $rfc822 = new Horde_Mail_Rfc822(); return $rfc822->encode($groupname, 'personal') . ':' . (strlen($addr) ? (' ' . $addr) : '') . ';'; } /** */ public function match($ob) { return $this->addresses->match($ob); } /* Countable methods. */ /** * Address count. * * @return integer The number of addresses. */ public function count() { return count($this->addresses); } } Horde_Mail-2.1.4/lib/Horde/Mail/Rfc822/GroupList.php0000644000175000007640000000251212267540554021627 0ustar slusarzslusarz * @category Horde * @copyright 2012-2013 Horde LLC * @license http://www.horde.org/licenses/bsd New BSD License * @package Mail */ class Horde_Mail_Rfc822_GroupList extends Horde_Mail_Rfc822_List { /** * Add objects to the container. * * @param mixed $obs A RFC 822 object (or list of objects) to store in * this object. */ public function add($obs) { if ($obs instanceof Horde_Mail_Rfc822_Object) { $obs = array($obs); } foreach ($obs as $val) { /* Only allow addresses. */ if ($val instanceof Horde_Mail_Rfc822_Address) { parent::add($val); } } } /** * Group count. * * @return integer The number of groups in the list. */ public function groupCount() { return 0; } } Horde_Mail-2.1.4/lib/Horde/Mail/Rfc822/List.php0000644000175000007640000003035212267540554020615 0ustar slusarzslusarz * @category Horde * @copyright 2012-2013 Horde LLC * @license http://www.horde.org/licenses/bsd New BSD License * @package Mail * * @property-read array $addresses The list of all addresses (address * w/personal parts). * @property-read array $bare_addresses The list of all addresses (mail@host). * @property-read array $bare_addresses_idn The list of all addresses * (mail@host; IDN encoded). * (@since 2.1.0) * @property-read array $base_addresses The list of ONLY base addresses * (Address objects). * @property-read array $raw_addresses The list of all addresses (Address * objects). */ class Horde_Mail_Rfc822_List extends Horde_Mail_Rfc822_Object implements ArrayAccess, Countable, SeekableIterator, Serializable { /** Filter masks. */ const HIDE_GROUPS = 1; const BASE_ELEMENTS = 2; /** * List data. * * @var array */ protected $_data = array(); /** * Current Iterator filter. * * @var array */ protected $_filter = array(); /** * Current Iterator pointer. * * @var array */ protected $_ptr; /** * Constructor. * * @param mixed $obs Address data to store in this object. */ public function __construct($obs = null) { if (!is_null($obs)) { $this->add($obs); } } /** */ public function __get($name) { switch ($name) { case 'addresses': case 'bare_addresses': case 'bare_addresses_idn': case 'base_addresses': case 'raw_addresses': $old = $this->_filter; $mask = ($name == 'base_addresses') ? self::BASE_ELEMENTS : self::HIDE_GROUPS; $this->setIteratorFilter($mask, empty($old['filter']) ? null : $old['filter']); $out = array(); foreach ($this as $val) { switch ($name) { case 'addresses': $out[] = strval($val); break; case 'bare_addresses': $out[] = $val->bare_address; break; case 'bare_addresses_idn': $out[] = $val->bare_address_idn; break; case 'base_addresses': case 'raw_addresses': $out[] = clone $val; break; } } $this->_filter = $old; return $out; } } /** * Add objects to the container. * * @param mixed $obs Address data to store in this object. */ public function add($obs) { foreach ($this->_normalize($obs) as $val) { $this->_data[] = $val; } } /** * Remove addresses from the container. This method ignores Group objects. * * @param mixed $obs Addresses to remove. */ public function remove($obs) { $old = $this->_filter; $this->setIteratorFilter(self::HIDE_GROUPS | self::BASE_ELEMENTS); foreach ($this->_normalize($obs) as $val) { $remove = array(); foreach ($this as $key => $val2) { if ($val2->match($val)) { $remove[] = $key; } } foreach (array_reverse($remove) as $key) { unset($this[$key]); } } $this->_filter = $old; } /** * Removes duplicate addresses from list. This method ignores Group * objects. */ public function unique() { $exist = $remove = array(); $old = $this->_filter; $this->setIteratorFilter(self::HIDE_GROUPS | self::BASE_ELEMENTS); // For duplicates, we use the first address that contains personal // information. foreach ($this as $key => $val) { $bare = $val->bare_address; if (isset($exist[$bare])) { if (($exist[$bare] == -1) || is_null($val->personal)) { $remove[] = $key; } else { $remove[] = $exist[$bare]; $exist[$bare] = -1; } } else { $exist[$bare] = is_null($val->personal) ? $key : -1; } } foreach (array_reverse($remove) as $key) { unset($this[$key]); } $this->_filter = $old; } /** * Group count. * * @return integer The number of groups in the list. */ public function groupCount() { $ret = 0; foreach ($this->_data as $val) { if ($val instanceof Horde_Mail_Rfc822_Group) { ++$ret; } } return $ret; } /** * Set the Iterator filter. * * @param integer $mask Filter masks. * @param mixed $filter An e-mail, or as list of e-mails, to filter by. */ public function setIteratorFilter($mask = 0, $filter = null) { $this->_filter = array(); if ($mask) { $this->_filter['mask'] = $mask; } if (!is_null($filter)) { $rfc822 = new Horde_Mail_Rfc822(); $this->_filter['filter'] = $rfc822->parseAddressList($filter); } } /** */ protected function _writeAddress($opts) { $out = array(); foreach ($this->_data as $val) { $out[] = $val->writeAddress($opts); } return implode(', ', $out); } /** */ public function match($ob) { if (!($ob instanceof Horde_Mail_Rfc822_List)) { $ob = new Horde_Mail_Rfc822_List($ob); } $a = $this->bare_addresses; sort($a); $b = $ob->bare_addresses; sort($b); return ($a == $b); } /** * Does this list contain the given e-mail address? * * @param mixed $address An e-mail address. * * @return boolean True of the e-mail address is contained in the list. */ public function contains($address) { $ob = new Horde_Mail_Rfc822_Address($address); foreach ($this->raw_addresses as $val) { if ($val->match($ob)) { return true; } } return false; } /** * Normalize objects to add to list. * * @param mixed $obs Address data to store in this object. * * @return array Entries to add. */ protected function _normalize($obs) { $add = array(); if (!($obs instanceof Horde_Mail_Rfc822_List) && !is_array($obs)) { $obs = array($obs); } foreach ($obs as $val) { if (is_string($val)) { $rfc822 = new Horde_Mail_Rfc822(); $val = $rfc822->parseAddressList($val); } if ($val instanceof Horde_Mail_Rfc822_List) { $val->setIteratorFilter(self::BASE_ELEMENTS); foreach ($val as $val2) { $add[] = $val2; } } elseif ($val instanceof Horde_Mail_Rfc822_Object) { $add[] = $val; } } return $add; } /* ArrayAccess methods. */ /** */ public function offsetExists($offset) { return !is_null($this[$offset]); } /** */ public function offsetGet($offset) { try { $this->seek($offset); return $this->current(); } catch (OutOfBoundsException $e) { return null; } } /** */ public function offsetSet($offset, $value) { if ($ob = $this[$offset]) { if (is_null($this->_ptr['subidx'])) { $tmp = $this->_normalize($value); if (isset($tmp[0])) { $this->_data[$this->_ptr['idx']] = $tmp[0]; } } else { $ob[$offset] = $value; } $this->_ptr = null; } } /** */ public function offsetUnset($offset) { if ($ob = $this[$offset]) { if (is_null($this->_ptr['subidx'])) { unset($this->_data[$this->_ptr['idx']]); $this->_data = array_values($this->_data); } else { unset($ob->addresses[$this->_ptr['subidx']]); } $this->_ptr = null; } } /* Countable methods. */ /** * Address count. * * @return integer The number of addresses. */ public function count() { return count($this->addresses); } /* Iterator methods. */ public function current() { if (!$this->valid()) { return null; } $ob = $this->_data[$this->_ptr['idx']]; return is_null($this->_ptr['subidx']) ? $ob : $ob->addresses[$this->_ptr['subidx']]; } public function key() { return $this->_ptr['key']; } public function next() { if (is_null($this->_ptr['subidx'])) { $curr = $this->current(); if (($curr instanceof Horde_Mail_Rfc822_Group) && count($curr)) { $this->_ptr['subidx'] = 0; } else { ++$this->_ptr['idx']; } $curr = $this->current(); } elseif (!($curr = $this->_data[$this->_ptr['idx']]->addresses[++$this->_ptr['subidx']])) { $this->_ptr['subidx'] = null; ++$this->_ptr['idx']; $curr = $this->current(); } if (!is_null($curr)) { if (!empty($this->_filter) && $this->_iteratorFilter($curr)) { $this->next(); } else { ++$this->_ptr['key']; } } } public function rewind() { $this->_ptr = array( 'idx' => 0, 'key' => 0, 'subidx' => null ); if ($this->valid() && !empty($this->_filter) && $this->_iteratorFilter($this->current())) { $this->next(); $this->_ptr['key'] = 0; } } public function valid() { return (!empty($this->_ptr) && isset($this->_data[$this->_ptr['idx']])); } public function seek($position) { if (!$this->valid() || ($position < $this->_ptr['key'])) { $this->rewind(); } for ($i = $this->_ptr['key']; ; ++$i) { if ($i == $position) { return; } $this->next(); if (!$this->valid()) { throw new OutOfBoundsException('Position not found.'); } } } protected function _iteratorFilter($ob) { if (!empty($this->_filter['mask'])) { if (($this->_filter['mask'] & self::HIDE_GROUPS) && ($ob instanceof Horde_Mail_Rfc822_Group)) { return true; } if (($this->_filter['mask'] & self::BASE_ELEMENTS) && !is_null($this->_ptr['subidx'])) { return true; } } if (!empty($this->_filter['filter']) && ($ob instanceof Horde_Mail_Rfc822_Address)) { foreach ($this->_filter['filter'] as $val) { if ($ob->match($val)) { return true; } } } return false; } /* Serializable methods. */ public function serialize() { return serialize($this->_data); } public function unserialize($data) { $this->_data = unserialize($data); } } Horde_Mail-2.1.4/lib/Horde/Mail/Rfc822/Object.php0000644000175000007640000000466112267540554021114 0ustar slusarzslusarz * @category Horde * @copyright 2012-2013 Horde LLC * @license http://www.horde.org/licenses/bsd New BSD License * @package Mail */ abstract class Horde_Mail_Rfc822_Object { /** * String representation of object. * * @return string Returns the full e-mail address. */ public function __toString() { return $this->writeAddress(); } /** * Write an address given information in this part. * * @param mixed $opts If boolean true, is equivalent to passing true for * both 'encode' and 'idn'. If an array, these * keys are supported: * - encode: (mixed) MIME encode the personal/groupname parts? * If boolean true, encodes in 'UTF-8'. * If a string, encodes using this charset. * DEFAULT: false * - idn: (boolean) If true, encodes IDN domain names * (Punycode/RFC 3490). * Requires the idn or intl PHP module. * DEFAULT: false * * @return string The correctly escaped/quoted address. */ public function writeAddress($opts = array()) { if ($opts === true) { $opts = array( 'encode' => 'UTF-8', 'idn' => true ); } elseif (!empty($opts['encode']) && ($opts['encode'] === true)) { $opts['encode'] = 'UTF-8'; } return $this->_writeAddress($opts); } /** * Class-specific implementation of writeAddress(). * * @see writeAddress() * * @param array $opts See writeAddress(). * * @return string The correctly escaped/quoted address. */ abstract protected function _writeAddress($opts); /** * Compare this object against other data. * * @param mixed $ob Address data. * * @return boolean True if the data reflects the same canonical address. */ abstract public function match($ob); } Horde_Mail-2.1.4/lib/Horde/Mail/Transport/Mail.php0000644000175000007640000001070312267540554021610 0ustar slusarzslusarz * @author Michael Slusarz * @category Horde * @copyright 2010-2013 Horde LLC * @license http://www.horde.org/licenses/bsd New BSD License * @package Mail */ class Horde_Mail_Transport_Mail extends Horde_Mail_Transport { /** * @param array $params Additional parameters: * - args: (string) Extra arguments for the mail() function. */ public function __construct(array $params = array()) { $this->_params = array_merge($this->_params, $params); } /** */ public function send($recipients, array $headers, $body) { $headers = $this->_sanitizeHeaders($headers); $recipients = $this->parseRecipients($recipients); $subject = ''; foreach (array_keys($headers) as $hdr) { if (strcasecmp($hdr, 'Subject') === 0) { // Get the Subject out of the headers array so that we can // pass it as a separate argument to mail(). $subject = $headers[$hdr]; unset($headers[$hdr]); } elseif (strcasecmp($hdr, 'To') === 0) { // Remove the To: header. The mail() function will add its // own To: header based on the contents of $recipients. unset($headers[$hdr]); } } // Flatten the headers out. list(, $text_headers) = $this->prepareHeaders($headers); // mail() requires a string for $body. If resource, need to convert // to a string. if (is_resource($body)) { $body_str = ''; stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol'); stream_filter_append($body, 'horde_eol', STREAM_FILTER_READ, array('eol' => $this->sep)); rewind($body); while (!feof($body)) { $body_str .= fread($body, 8192); } $body = $body_str; } else { // Convert EOL characters in body. $body = $this->_normalizeEOL($body); } // We only use mail()'s optional fifth parameter if the additional // parameters have been provided and we're not running in safe mode. if (empty($this->_params) || ini_get('safe_mode')) { $result = mail($recipients, $subject, $body, $text_headers); } else { $result = mail($recipients, $subject, $body, $text_headers, isset($this->_params['args']) ? $this->_params['args'] : ''); } // If the mail() function returned failure, we need to create an // Exception and return it instead of the boolean result. if ($result === false) { throw new Horde_Mail_Exception('mail() returned failure.'); } } } Horde_Mail-2.1.4/lib/Horde/Mail/Transport/Mock.php0000644000175000007640000001035612267540554021623 0ustar slusarzslusarz * @author Michael Slusarz * @category Horde * @copyright 2010-2013 Horde LLC * @license http://www.horde.org/licenses/bsd New BSD License * @package Mail */ class Horde_Mail_Transport_Mock extends Horde_Mail_Transport { /** * Array of messages that have been sent with the mock. * * @var array */ public $sentMessages = array(); /** * Callback before sending mail. * * @var callback */ protected $_preSendCallback; /** * Callback after sending mai. * * @var callback */ protected $_postSendCallback; /** * @param array Optional parameters: * - postSendCallback: (callback) Called after an email would have been * sent. * - preSendCallback: (callback) Called before an email would be sent. */ public function __construct(array $params = array()) { if (isset($params['preSendCallback']) && is_callable($params['preSendCallback'])) { $this->_preSendCallback = $params['preSendCallback']; } if (isset($params['postSendCallback']) && is_callable($params['postSendCallback'])) { $this->_postSendCallback = $params['postSendCallback']; } } /** */ public function send($recipients, array $headers, $body) { if ($this->_preSendCallback) { call_user_func_array($this->_preSendCallback, array($this, $recipients, $headers, $body)); } $headers = $this->_sanitizeHeaders($headers); list($from, $text_headers) = $this->prepareHeaders($headers); if (is_resource($body)) { stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol'); stream_filter_append($body, 'horde_eol', STREAM_FILTER_READ, array('eol' => $this->sep)); rewind($body); $body_txt = stream_get_contents($body); } else { $body_txt = $this->_normalizeEOL($body); } $from = $this->_getFrom($from, $headers); $recipients = $this->parseRecipients($recipients); $this->sentMessages[] = array( 'body' => $body_txt, 'from' => $from, 'headers' => $headers, 'header_text' => $text_headers, 'recipients' => $recipients ); if ($this->_postSendCallback) { call_user_func_array($this->_postSendCallback, array($this, $recipients, $headers, $body_txt)); } } } Horde_Mail-2.1.4/lib/Horde/Mail/Transport/Null.php0000644000175000007640000000431312267540554021640 0ustar slusarzslusarz * @author Michael Slusarz * @category Horde * @copyright 2010-2013 Horde LLC * @copyright 2010 Phil Kernick * @license http://www.horde.org/licenses/bsd New BSD License * @package Mail */ class Horde_Mail_Transport_Null extends Horde_Mail_Transport { /** */ public function send($recipients, array $headers, $body) { } } Horde_Mail-2.1.4/lib/Horde/Mail/Transport/Sendmail.php0000644000175000007640000001414012267540554022461 0ustar slusarzslusarz * @author Michael Slusarz * @category Horde * @copyright 2010-2013 Horde LLC * @license http://www.horde.org/licenses/bsd New BSD License * @package Mail */ class Horde_Mail_Transport_Sendmail extends Horde_Mail_Transport { /** * Any extra command-line parameters to pass to the sendmail or * sendmail wrapper binary. * * @var string */ protected $_sendmailArgs = '-i'; /** * The location of the sendmail or sendmail wrapper binary on the * filesystem. * * @var string */ protected $_sendmailPath = '/usr/sbin/sendmail'; /** * Constructor. * * @param array $params Additional parameters: * - sendmail_args: (string) Any extra parameters to pass to the sendmail * or sendmail wrapper binary. * DEFAULT: -i * - sendmail_path: (string) The location of the sendmail binary on the * filesystem. * DEFAULT: /usr/sbin/sendmail */ public function __construct(array $params = array()) { if (isset($params['sendmail_args'])) { $this->_sendmailArgs = $params['sendmail_args']; } if (isset($params['sendmail_path'])) { $this->_sendmailPath = $params['sendmail_path']; } } /** */ public function send($recipients, array $headers, $body) { $recipients = implode(' ', array_map('escapeshellarg', $this->parseRecipients($recipients))); $headers = $this->_sanitizeHeaders($headers); list($from, $text_headers) = $this->prepareHeaders($headers); $from = $this->_getFrom($from, $headers); $mail = @popen($this->_sendmailPath . (empty($this->_sendmailArgs) ? '' : ' ' . $this->_sendmailArgs) . ' -f ' . escapeshellarg($from) . ' -- ' . $recipients, 'w'); if (!$mail) { throw new Horde_Mail_Exception('Failed to open sendmail [' . $this->_sendmailPath . '] for execution.'); } // Write the headers following by two newlines: one to end the headers // section and a second to separate the headers block from the body. fputs($mail, $text_headers . $this->sep . $this->sep); if (is_resource($body)) { stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol'); stream_filter_append($body, 'horde_eol', STREAM_FILTER_READ, array('eol' => $this->sep)); rewind($body); while (!feof($body)) { fputs($mail, fread($body, 8192)); } } else { fputs($mail, $this->_normalizeEOL($body)); } $result = pclose($mail); if (!$result) { return; } switch ($result) { case 64: // EX_USAGE $msg = 'command line usage error'; break; case 65: // EX_DATAERR $msg = 'data format error'; break; case 66: // EX_NOINPUT $msg = 'cannot open input'; break; case 67: // EX_NOUSER $msg = 'addressee unknown'; break; case 68: // EX_NOHOST $msg = 'host name unknown'; break; case 69: // EX_UNAVAILABLE $msg = 'service unavailable'; break; case 70: // EX_SOFTWARE $msg = 'internal software error'; break; case 71: // EX_OSERR $msg = 'system error'; break; case 72: // EX_OSFILE $msg = 'critical system file missing'; break; case 73: // EX_CANTCREAT $msg = 'cannot create output file'; break; case 74: // EX_IOERR $msg = 'input/output error'; case 75: // EX_TEMPFAIL $msg = 'temporary failure'; break; case 76: // EX_PROTOCOL $msg = 'remote error in protocol'; break; case 77: // EX_NOPERM $msg = 'permission denied'; break; case 78: // EX_CONFIG $msg = 'configuration error'; break; case 79: // EX_NOTFOUND $msg = 'entry not found'; break; default: $msg = 'unknown error'; break; } throw new Horde_Mail_Exception('sendmail: ' . $msg . ' (' . $result . ')', $result); } } Horde_Mail-2.1.4/lib/Horde/Mail/Transport/Smtp.php0000644000175000007640000002716312267540554021661 0ustar slusarzslusarz * @author Jon Parise * @author Michael Slusarz * @category Horde * @copyright 2010-2013 Horde LLC * @license http://www.horde.org/licenses/bsd New BSD License * @package Mail */ class Horde_Mail_Transport_Smtp extends Horde_Mail_Transport { /* Error: Failed to create a Net_SMTP object */ const ERROR_CREATE = 10000; /* Error: Failed to connect to SMTP server */ const ERROR_CONNECT = 10001; /* Error: SMTP authentication failure */ const ERROR_AUTH = 10002; /* Error: No From: address has been provided */ const ERROR_FROM = 10003; /* Error: Failed to set sender */ const ERROR_SENDER = 10004; /* Error: Failed to add recipient */ const ERROR_RECIPIENT = 10005; /* Error: Failed to send data */ const ERROR_DATA = 10006; /** * The SMTP greeting. * * @var string */ public $greeting = null; /** * The SMTP queued response. * * @var string */ public $queuedAs = null; /** * SMTP connection object. * * @var Net_SMTP */ protected $_smtp = null; /** * The list of service extension parameters to pass to the Net_SMTP * mailFrom() command. * * @var array */ protected $_extparams = array(); /** * Constructor. * * @param array $params Additional parameters: * - auth: (mixed) SMTP authentication. * This value may be set to true, false or the name of a * specific authentication method. If the value is set to true, * the Net_SMTP package will attempt to use the best * authentication method advertised by the remote SMTP server. * DEFAULT: false. * - debug: (boolean) Activate SMTP debug mode? * DEFAULT: false * - host: (string) The server to connect to. * DEFAULT: localhost * - localhost: (string) Hostname or domain that will be sent to the * remote SMTP server in the HELO / EHLO message. * DEFAULT: localhost * - password: (string) The password to use for SMTP auth. * DEFAULT: NONE * - persist: (boolean) Should the SMTP connection persist? * DEFAULT: false * - pipelining: (boolean) Use SMTP command pipelining. * Use SMTP command pipelining (specified in RFC 2920) if * the SMTP server supports it. This speeds up delivery * over high-latency connections. * DEFAULT: false (use default value from Net_SMTP) * - port: (integer) The port to connect to. * DEFAULT: 25 * - timeout: (integer) The SMTP connection timeout. * DEFAULT: NONE * - username: (string) The username to use for SMTP auth. * DEFAULT: NONE */ public function __construct(array $params = array()) { $this->_params = array_merge(array( 'auth' => false, 'debug' => false, 'host' => 'localhost', 'localhost' => 'localhost', 'password' => '', 'persist' => false, 'pipelining' => false, 'port' => 25, 'timeout' => null, 'username' => '' ), $params); /* Destructor implementation to ensure that we disconnect from any * potentially-alive persistent SMTP connections. */ register_shutdown_function(array($this, 'disconnect')); /* SMTP requires CRLF line endings. */ $this->sep = "\r\n"; } /** */ public function send($recipients, array $headers, $body) { /* If we don't already have an SMTP object, create one. */ $this->getSMTPObject(); $headers = $this->_sanitizeHeaders($headers); /* Make sure the message has a trailing newline. */ if (is_resource($body)) { fseek($body, -1, SEEK_END); switch (fgetc($body)) { case "\r": if (fgetc($body) != "\n") { fputs($body, "\n"); } break; default: fputs($body, "\r\n"); break; } rewind($body); } elseif (substr($body, -2, 0) != "\r\n") { $body .= "\r\n"; } try { list($from, $textHeaders) = $this->prepareHeaders($headers); } catch (Horde_Mail_Exception $e) { $this->_smtp->rset(); throw $e; } try { $from = $this->_getFrom($from, $headers); } catch (Horde_Mail_Exception $e) { $this->_smtp->rset(); throw new Horde_Mail_Exception('No From: address has been provided', self::ERROR_FROM); } $params = ''; foreach ($this->_extparams as $key => $val) { $params .= ' ' . $key . (is_null($val) ? '' : '=' . $val); } $res = $this->_smtp->mailFrom($from, ltrim($params)); if ($res instanceof PEAR_Error) { $this->_error(sprintf("Failed to set sender: %s", $from), $res, self::ERROR_SENDER); } try { $recipients = $this->parseRecipients($recipients); } catch (Horde_Mail_Exception $e) { $this->_smtp->rset(); throw $e; } foreach ($recipients as $recipient) { $res = $this->_smtp->rcptTo($recipient); if ($res instanceof PEAR_Error) { $this->_error("Failed to add recipient: $recipient", $res, self::ERROR_RECIPIENT); } } /* Send the message's headers and the body as SMTP data. Net_SMTP does * the necessary EOL conversions. */ $res = $this->_smtp->data($body, $textHeaders); list(,$args) = $this->_smtp->getResponse(); if (preg_match("/Ok: queued as (.*)/", $args, $queued)) { $this->queuedAs = $queued[1]; } /* We need the greeting; from it we can extract the authorative name * of the mail server we've really connected to. Ideal if we're * connecting to a round-robin of relay servers and need to track * which exact one took the email */ $this->greeting = $this->_smtp->getGreeting(); if ($res instanceof PEAR_Error) { $this->_error('Failed to send data', $res, self::ERROR_DATA); } /* If persistent connections are disabled, destroy our SMTP object. */ if (!$this->_params['persist']) { $this->disconnect(); } } /** * Connect to the SMTP server by instantiating a Net_SMTP object. * * @return Net_SMTP The SMTP object. * @throws Horde_Mail_Exception */ public function getSMTPObject() { if ($this->_smtp) { return $this->_smtp; } $this->_smtp = new Net_SMTP( $this->_params['host'], $this->_params['port'], $this->_params['localhost'] ); /* Set pipelining. */ if ($this->_params['pipelining']) { $this->_smtp->pipelining = true; } /* If we still don't have an SMTP object at this point, fail. */ if (!($this->_smtp instanceof Net_SMTP)) { throw new Horde_Mail_Exception('Failed to create a Net_SMTP object', self::ERROR_CREATE); } /* Configure the SMTP connection. */ if ($this->_params['debug']) { $this->_smtp->setDebug(true); } /* Attempt to connect to the configured SMTP server. */ $res = $this->_smtp->connect($this->_params['timeout']); if ($res instanceof PEAR_Error) { $this->_error('Failed to connect to ' . $this->_params['host'] . ':' . $this->_params['port'], $res, self::ERROR_CONNECT); } /* Attempt to authenticate if authentication has been enabled. */ if ($this->_params['auth']) { $method = is_string($this->_params['auth']) ? $this->_params['auth'] : ''; $res = $this->_smtp->auth($this->_params['username'], $this->_params['password'], $method); if ($res instanceof PEAR_Error) { $this->_error("$method authentication failure", $res, self::ERROR_AUTH); } } return $this->_smtp; } /** * Add parameter associated with a SMTP service extension. * * @param string $keyword Extension keyword. * @param string $value Any value the keyword needs. */ public function addServiceExtensionParameter($keyword, $value = null) { $this->_extparams[$keyword] = $value; } /** * Disconnect and destroy the current SMTP connection. * * @return boolean True if the SMTP connection no longer exists. */ public function disconnect() { /* If we have an SMTP object, disconnect and destroy it. */ if (is_object($this->_smtp) && $this->_smtp->disconnect()) { $this->_smtp = null; } /* We are disconnected if we no longer have an SMTP object. */ return ($this->_smtp === null); } /** * Build a standardized string describing the current SMTP error. * * @param string $text Custom string describing the error context. * @param PEAR_Error $error PEAR_Error object. * @param integer $e_code Error code. * * @throws Horde_Mail_Exception */ protected function _error($text, $error, $e_code) { /* Split the SMTP response into a code and a response string. */ list($code, $response) = $this->_smtp->getResponse(); /* Abort current SMTP transaction. */ $this->_smtp->rset(); /* Build our standardized error string. */ throw new Horde_Mail_Exception($text . ' [SMTP: ' . $error->getMessage() . " (code: $code, response: $response)]", $e_code); } } Horde_Mail-2.1.4/lib/Horde/Mail/Transport/Smtphorde.php0000644000175000007640000001155312267540554022677 0ustar slusarzslusarz * @category Horde * @copyright 2013 Horde LLC * @license http://www.horde.org/licenses/bsd New BSD License * @package Mail */ class Horde_Mail_Transport_Smtphorde extends Horde_Mail_Transport { /** * Send the message as 8bit? * * @var boolean */ public $send8bit = false; /** * SMTP object. * * @var Horde_Smtp */ protected $_smtp = null; /** * Constructor. * * @param array $params Additional parameters: *
    *
  • * debug: (string) If set, will output debug information to the stream * provided. The value can be any PHP supported wrapper that * can be opened via fopen(). * DEFAULT: No debug output *
  • *
  • * host: (string) The SMTP server. * DEFAULT: localhost *
  • *
  • * password: (string) The SMTP password. * DEFAULT: NONE *
  • *
  • * port: (string) The SMTP port. * DEFAULT: 587 *
  • *
  • * secure: (string) Use SSL or TLS to connect. * DEFAULT: No encryption *
      *
    • false (No encryption)
    • *
    • 'ssl' (Auto-detect SSL version)
    • *
    • 'sslv2' (Force SSL version 3)
    • *
    • 'sslv3' (Force SSL version 2)
    • *
    • 'tls' (TLS)
    • *
    *
  • *
  • * timeout: (integer) Connection timeout, in seconds. * DEFAULT: 30 seconds *
  • *
  • * username: (string) The SMTP username. * DEFAULT: NONE *
  • *
*/ public function __construct(array $params = array()) { $this->_params = $params; /* SMTP requires CRLF line endings. */ $this->sep = "\r\n"; } /** */ public function send($recipients, array $headers, $body) { /* If we don't already have an SMTP object, create one. */ $this->getSMTPObject(); $headers = $this->_sanitizeHeaders($headers); list($from, $textHeaders) = $this->prepareHeaders($headers); $from = $this->_getFrom($from, $headers); $swrapper = new Horde_Support_CombineStream(array( rtrim($textHeaders, $this->sep), $this->sep . $this->sep, $body )); try { $this->_smtp->send($from, $recipients, $swrapper->fopen(), array( '8bit' => $this->send8bit )); } catch (Horde_Smtp_Exception $e) { throw new Horde_Mail_Exception($e); } } /** * Connect to the SMTP server by instantiating a Horde_Smtp object. * * @return Horde_Smtp The SMTP object. * @throws Horde_Mail_Exception */ public function getSMTPObject() { if (!$this->_smtp) { $this->_smtp = new Horde_Smtp($this->_params); try { $this->_smtp->login(); } catch (Horde_Smtp_Exception $e) { throw new Horde_Mail_Exception($e); } } return $this->_smtp; } } Horde_Mail-2.1.4/lib/Horde/Mail/Transport/Smtpmx.php0000644000175000007640000002624212267540554022223 0ustar slusarzslusarz * @author Michael Slusarz * @category Horde * @copyright 2010-2013 Horde LLC * @copyright 2010 Gerd Schaufelberger * @license http://www.horde.org/licenses/bsd New BSD License * @package Mail */ class Horde_Mail_Transport_Smtpmx extends Horde_Mail_Transport { /** * SMTP connection object. * * @var Net_SMTP */ protected $_smtp = null; /** * Net_DNS2_Resolver object. * * @var Net_DNS2_Resolver */ protected $_resolver; /** * Internal error codes. * Translate internal error identifier to human readable messages. * * @var array */ protected $_errorCode = array( 'not_connected' => array( 'code' => 1, 'msg' => 'Could not connect to any mail server ({HOST}) at port {PORT} to send mail to {RCPT}.' ), 'failed_vrfy_rcpt' => array( 'code' => 2, 'msg' => 'Recipient "{RCPT}" could not be veryfied.' ), 'failed_set_from' => array( 'code' => 3, 'msg' => 'Failed to set sender: {FROM}.' ), 'failed_set_rcpt' => array( 'code' => 4, 'msg' => 'Failed to set recipient: {RCPT}.' ), 'failed_send_data' => array( 'code' => 5, 'msg' => 'Failed to send mail to: {RCPT}.' ), 'no_from' => array( 'code' => 5, 'msg' => 'No from address has be provided.' ), 'send_data' => array( 'code' => 7, 'msg' => 'Failed to create Net_SMTP object.' ), 'no_mx' => array( 'code' => 8, 'msg' => 'No MX-record for {RCPT} found.' ), 'no_resolver' => array( 'code' => 9, 'msg' => 'Could not start resolver! Install PEAR:Net_DNS2 or switch off "netdns"' ), 'failed_rset' => array( 'code' => 10, 'msg' => 'RSET command failed, SMTP-connection corrupt.' ) ); /** * @param array $params Additional options: * - debug: (boolean) Activate SMTP debug mode? * DEFAULT: false * - mailname: (string) The name of the local mail system (a valid * hostname which matches the reverse lookup) * DEFAULT: Auto-determined * - netdns: (boolean) Use PEAR:Net_DNS2 (true) or the PHP builtin * getmxrr(). * DEFAULT: true * - port: (integer) Port. * DEFAULT: Auto-determined * - test: (boolean) Activate test mode? * DEFAULT: false * - timeout: (integer) The SMTP connection timeout (in seconds). * DEFAULT: 10 * - verp: (boolean) Whether to use VERP. * If not a boolean, the string value will be used as the VERP * separators. * DEFAULT: false * - vrfy: (boolean) Whether to use VRFY. * DEFAULT: false */ public function __construct(array $params = array()) { /* Try to find a valid mailname. */ if (!isset($params['mailname']) && function_exists('posix_uname')) { $uname = posix_uname(); $params['mailname'] = $uname['nodename']; } if (!isset($params['port'])) { $params['port'] = getservbyname('smtp', 'tcp'); } $this->_params = array_merge(array( 'debug' => false, 'mailname' => 'localhost', 'netdns' => true, 'port' => 25, 'test' => false, 'timeout' => 10, 'verp' => false, 'vrfy' => false ), $params); /* SMTP requires CRLF line endings. */ $this->sep = "\r\n"; } /** * Destructor implementation to ensure that we disconnect from any * potentially-alive persistent SMTP connections. */ public function __destruct() { if (is_object($this->_smtp)) { $this->_smtp->disconnect(); $this->_smtp = null; } } /** */ public function send($recipients, array $headers, $body) { $headers = $this->_sanitizeHeaders($headers); // Prepare headers list($from, $textHeaders) = $this->prepareHeaders($headers); try { $from = $this->_getFrom($from, $headers); } catch (Horde_Mail_Exception $e) { $this->_error('no_from'); } // Prepare recipients foreach ($this->parseRecipients($recipients) as $rcpt) { list(,$host) = explode('@', $rcpt); $mx = $this->_getMx($host); if (!$mx) { $this->_error('no_mx', array('rcpt' => $rcpt)); } $connected = false; foreach (array_keys($mx) as $mserver) { $this->_smtp = new Net_SMTP($mserver, $this->_params['port'], $this->_params['mailname']); // configure the SMTP connection. if ($this->_params['debug']) { $this->_smtp->setDebug(true); } // attempt to connect to the configured SMTP server. $res = $this->_smtp->connect($this->_params['timeout']); if ($res instanceof PEAR_Error) { $this->_smtp = null; continue; } // connection established if ($res) { $connected = true; break; } } if (!$connected) { $this->_error('not_connected', array( 'host' => implode(', ', array_keys($mx)), 'port' => $this->_params['port'], 'rcpt' => $rcpt )); } // Verify recipient if ($this->_params['vrfy']) { $res = $this->_smtp->vrfy($rcpt); if ($res instanceof PEAR_Error) { $this->_error('failed_vrfy_rcpt', array('rcpt' => $rcpt)); } } // mail from: $args['verp'] = $this->_params['verp']; $res = $this->_smtp->mailFrom($from, $args); if ($res instanceof PEAR_Error) { $this->_error('failed_set_from', array('from' => $from)); } // rcpt to: $res = $this->_smtp->rcptTo($rcpt); if ($res instanceof PEAR_Error) { $this->_error('failed_set_rcpt', array('rcpt' => $rcpt)); } // Don't send anything in test mode if ($this->_params['test']) { $res = $this->_smtp->rset(); if ($res instanceof PEAR_Error) { $this->_error('failed_rset'); } $this->_smtp->disconnect(); $this->_smtp = null; return; } // Send data. Net_SMTP does necessary EOL conversions. $res = $this->_smtp->data($body, $textHeaders); if ($res instanceof PEAR_Error) { $this->_error('failed_send_data', array('rcpt' => $rcpt)); } $this->_smtp->disconnect(); $this->_smtp = null; } } /** * Recieve MX records for a host. * * @param string $host Mail host. * * @return mixed Sorted MX list or false on error. */ protected function _getMx($host) { $mx = array(); if ($this->params['netdns']) { $this->_loadNetDns(); try { $response = $this->_resolver->query($host, 'MX'); if (!$response) { return false; } } catch (Exception $e) { throw new Horde_Mail_Exception($e); } foreach ($response->answer as $rr) { if ($rr->type == 'MX') { $mx[$rr->exchange] = $rr->preference; } } } else { $mxHost = $mxWeight = array(); if (!getmxrr($host, $mxHost, $mxWeight)) { return false; } for ($i = 0; $i < count($mxHost); ++$i) { $mx[$mxHost[$i]] = $mxWeight[$i]; } } asort($mx); return $mx; } /** * Initialize Net_DNS2_Resolver. */ protected function _loadNetDns() { if (!$this->_resolver) { if (!class_exists('Net_DNS2_Resolver')) { $this->_error('no_resolver'); } $this->_resolver = new Net_DNS2_Resolver(); } } /** * Format error message. * * @param string $id Maps error ids to codes and message. * @param array $info Optional information in associative array. * * @throws Horde_Mail_Exception */ protected function _error($id, $info = array()) { $msg = $this->_errorCode[$id]['msg']; // include info to messages if (!empty($info)) { $replace = $search = array(); foreach ($info as $key => $value) { $search[] = '{' . strtoupper($key) . '}'; $replace[] = $value; } $msg = str_replace($search, $replace, $msg); } throw new Horde_Mail_Exception($msg, $this->_errorCode[$id]['code']); } } Horde_Mail-2.1.4/lib/Horde/Mail/Exception.php0000644000175000007640000000122712267540554020671 0ustar slusarzslusarz * @category Horde * @copyright 2010-2013 Horde LLC * @license http://www.horde.org/licenses/bsd New BSD License * @package Mail */ class Horde_Mail_Exception extends Horde_Exception_Wrapped { } Horde_Mail-2.1.4/lib/Horde/Mail/Rfc822.php0000644000175000007640000005623712267540554017714 0ustar slusarzslusarz * * @category Horde * @copyright 2001-2010 Richard Heyes * @copyright 2002-2011 Timo Sirainen * @copyright 2011-2013 Horde LLC * @license http://www.horde.org/licenses/bsd New BSD License * @package Mail */ /** * RFC 822/2822/3490/5322 Email parser/validator. * * @author Richard Heyes * @author Chuck Hagenbuch * @author Michael Slusarz * @author Timo Sirainen * @category Horde * @copyright 2001-2010 Richard Heyes * @copyright 2002-2011 Timo Sirainen * @copyright 2011-2013 Horde LLC * @license http://www.horde.org/licenses/bsd New BSD License * @package Mail */ class Horde_Mail_Rfc822 { /** * Valid atext characters. * * @since 2.0.3 */ const ATEXT = '!#$%&\'*+-./0123456789=?ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz{|}~'; /** * Excluded (in ASCII): 0-8, 10-31, 34, 40-41, 44, 58-60, 62, 64, * 91-93, 127 * * @since 2.0.3 */ const ENCODE_FILTER = "\0\1\2\3\4\5\6\7\10\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\"(),:;<>@[\\]\177"; /** * The address string to parse. * * @var string */ protected $_data; /** * Length of the address string. * * @var integer */ protected $_datalen; /** * Comment cache. * * @var string */ protected $_comments = array(); /** * List object to return in parseAddressList(). * * @var Horde_Mail_Rfc822_List */ protected $_listob; /** * Configuration parameters. * * @var array */ protected $_params = array(); /** * Data pointer. * * @var integer */ protected $_ptr; /** * Starts the whole process. * * @param mixed $address The address(es) to validate. Either a string, * a Horde_Mail_Rfc822_Object, or an array of * strings and/or Horde_Mail_Rfc822_Objects. * @param array $params Optional parameters: * - default_domain: (string) Default domain/host. * DEFAULT: None * - group: (boolean) Return a GroupList object instead of a List object? * DEFAULT: false * - limit: (integer) Stop processing after this many addresses. * DEFAULT: No limit (0) * - validate: (boolean) Strict validation of personal part data? If * true, throws an Exception on error. If false, attempts * to allow non-ASCII characters and non-quoted strings in * the personal data, and will silently abort if an * unparseable address is found. * DEFAULT: false * * @return Horde_Mail_Rfc822_List A list object. * * @throws Horde_Mail_Exception */ public function parseAddressList($address, array $params = array()) { if ($address instanceof Horde_Mail_Rfc822_List) { return $address; } if (empty($params['limit'])) { $params['limit'] = null; } $this->_params = array_merge(array( 'default_domain' => null, 'validate' => false ), $params); $this->_listob = empty($this->_params['group']) ? new Horde_Mail_Rfc822_List() : new Horde_Mail_Rfc822_GroupList(); if (!is_array($address)) { $address = array($address); } $tmp = array(); foreach ($address as $val) { if ($val instanceof Horde_Mail_Rfc822_Object) { $this->_listob->add($val); } else { $tmp[] = rtrim(trim($val), ','); } } if (!empty($tmp)) { $this->_data = implode(',', $tmp); $this->_datalen = strlen($this->_data); $this->_ptr = 0; $this->_parseAddressList(); } return $this->_listob; } /** * Quotes and escapes the given string if necessary using rules contained * in RFC 2822 [3.2.5]. * * @param string $str The string to be quoted and escaped. * @param string $type Either 'address', or 'personal'. * * @return string The correctly quoted and escaped string. */ public function encode($str, $type = 'address') { switch ($type) { case 'personal': // RFC 2822 [3.4]: Period not allowed in display name $filter = '.'; break; case 'address': default: // RFC 2822 [3.4.1]: (HTAB, SPACE) not allowed in address $filter = "\11\40"; break; } // Strip double quotes if they are around the string already. // If quoted, we know that the contents are already escaped, so // unescape now. $str = trim($str); if ($str && ($str[0] == '"') && (substr($str, -1) == '"')) { $str = stripslashes(substr($str, 1, -1)); } return (strcspn($str, self::ENCODE_FILTER . $filter) != strlen($str)) ? '"' . addcslashes($str, '\\"') . '"' : $str; } /** * If an email address has no personal information, get rid of any angle * brackets (<>) around it. * * @param string $address The address to trim. * * @return string The trimmed address. */ public function trimAddress($address) { $address = trim($address); return (($address[0] == '<') && (substr($address, -1) == '>')) ? substr($address, 1, -1) : $address; } /* RFC 822 parsing methods. */ /** * address-list = (address *("," address)) / obs-addr-list */ protected function _parseAddressList() { $limit = $this->_params['limit']; while (($this->_curr() !== false) && (is_null($limit) || ($limit > 0))) { try { $this->_parseAddress(); } catch (Horde_Mail_Exception $e) { if ($this->_params['validate']) { throw $e; } ++$this->_ptr; } switch ($this->_curr()) { case ',': $this->_rfc822SkipLwsp(true); break; case false: // No-op break; default: if ($this->_params['validate']) { throw new Horde_Mail_Exception('Error when parsing address list.'); } break; } $limit--; } } /** * address = mailbox / group */ protected function _parseAddress() { $start = $this->_ptr; if (!$this->_parseGroup()) { $this->_ptr = $start; if ($mbox = $this->_parseMailbox()) { $this->_listob->add($mbox); } } } /** * group = display-name ":" [mailbox-list / CFWS] ";" [CFWS] * display-name = phrase * * @return boolean True if a group was parsed. * * @throws Horde_Mail_Exception */ protected function _parseGroup() { $this->_rfc822ParsePhrase($groupname); if ($this->_curr(true) != ':') { return false; } $addresses = new Horde_Mail_Rfc822_GroupList(); $this->_rfc822SkipLwsp(); while (($chr = $this->_curr()) !== false) { if ($chr == ';') { ++$this->_ptr; if (count($addresses)) { $this->_listob->add(new Horde_Mail_Rfc822_Group($groupname, $addresses)); } return true; } /* mailbox-list = (mailbox *("," mailbox)) / obs-mbox-list */ $addresses->add($this->_parseMailbox()); switch ($this->_curr()) { case ',': $this->_rfc822SkipLwsp(true); break; case ';': // No-op break; default: break 2; } } throw new Horde_Mail_Exception('Error when parsing group.'); } /** * mailbox = name-addr / addr-spec * * @return mixed Mailbox object if mailbox was parsed, or false. */ protected function _parseMailbox() { $this->_comments = array(); $start = $this->_ptr; if (!($ob = $this->_parseNameAddr())) { $this->_comments = array(); $this->_ptr = $start; $ob = $this->_parseAddrSpec(); } if ($ob) { $ob->comment = $this->_comments; } return $ob; } /** * name-addr = [display-name] angle-addr * display-name = phrase * * @return mixed Mailbox object, or false. */ protected function _parseNameAddr() { $this->_rfc822ParsePhrase($personal); if ($ob = $this->_parseAngleAddr()) { $ob->personal = $personal; return $ob; } return false; } /** * addr-spec = local-part "@" domain * * @return mixed Mailbox object. * * @throws Horde_Mail_Exception */ protected function _parseAddrSpec() { $ob = new Horde_Mail_Rfc822_Address(); $ob->mailbox = $this->_parseLocalPart(); if ($this->_curr() == '@') { try { $this->_rfc822ParseDomain($host); if (strlen($host)) { $ob->host = $host; } } catch (Horde_Mail_Exception $e) { if (!empty($this->_params['validate'])) { throw $e; } } } if (is_null($ob->host)) { if (!is_null($this->_params['default_domain'])) { $ob->host = $this->_params['default_domain']; } elseif (!empty($this->_params['validate'])) { throw new Horde_Mail_Exception('Address is missing domain.'); } } return $ob; } /** * local-part = dot-atom / quoted-string / obs-local-part * obs-local-part = word *("." word) * * @return string The local part. * * @throws Horde_Mail_Exception */ protected function _parseLocalPart() { if (($curr = $this->_curr()) === false) { throw new Horde_Mail_Exception('Error when parsing local part.'); } if ($curr == '"') { $this->_rfc822ParseQuotedString($str); } else { $this->_rfc822ParseDotAtom($str, ',;@'); } return $str; } /** * "<" [ "@" route ":" ] local-part "@" domain ">" * * @return mixed Mailbox object, or false. * * @throws Horde_Mail_Exception */ protected function _parseAngleAddr() { if ($this->_curr() != '<') { return false; } $this->_rfc822SkipLwsp(true); if ($this->_curr() == '@') { // Route information is ignored. $this->_parseDomainList(); if ($this->_curr() != ':') { throw new Horde_Mail_Exception('Invalid route.'); } $this->_rfc822SkipLwsp(true); } $ob = $this->_parseAddrSpec(); if ($this->_curr() != '>') { throw new Horde_Mail_Exception('Error when parsing angle address.'); } $this->_rfc822SkipLwsp(true); return $ob; } /** * obs-domain-list = "@" domain *(*(CFWS / "," ) [CFWS] "@" domain) * * @return array Routes. * * @throws Horde_Mail_Exception */ protected function _parseDomainList() { $route = array(); while ($this->_curr() !== false) { $this->_rfc822ParseDomain($str); $route[] = '@' . $str; $this->_rfc822SkipLwsp(); if ($this->_curr() != ',') { return $route; } ++$this->_ptr; } throw new Horde_Mail_Exception('Invalid domain list.'); } /* RFC 822 parsing methods. */ /** * phrase = 1*word / obs-phrase * word = atom / quoted-string * obs-phrase = word *(word / "." / CFWS) * * @param string &$phrase The phrase data. * * @throws Horde_Mail_Exception */ protected function _rfc822ParsePhrase(&$phrase) { $curr = $this->_curr(); if (($curr === false) || ($curr == '.')) { throw new Horde_Mail_Exception('Error when parsing a group.'); } while (($curr = $this->_curr()) !== false) { if ($curr == '"') { $this->_rfc822ParseQuotedString($phrase); } else { $this->_rfc822ParseAtomOrDot($phrase); } $chr = $this->_curr(); if (!$this->_rfc822IsAtext($chr) && ($chr != '"') && ($chr != '.')) { break; } $phrase .= ' '; } $this->_rfc822SkipLwsp(); } /** * @param string &$phrase The quoted string data. * * @throws Horde_Mail_Exception */ protected function _rfc822ParseQuotedString(&$str) { if ($this->_curr(true) != '"') { throw new Horde_Mail_Exception('Error when parsing a quoted string.'); } while (($chr = $this->_curr(true)) !== false) { switch ($chr) { case '"': $this->_rfc822SkipLwsp(); return; case "\n": /* Folding whitespace, remove the (CR)LF. */ if ($str[strlen($str) - 1] == "\r") { $str = substr($str, 0, -1); } continue; case '\\': if (($chr = $this->_curr(true)) === false) { break 2; } break; } $str .= $chr; } /* Missing trailing '"', or partial quoted character. */ throw new Horde_Mail_Exception('Error when parsing a quoted string.'); } /** * dot-atom = [CFWS] dot-atom-text [CFWS] * dot-atom-text = 1*atext *("." 1*atext) * * atext = ; Any character except controls, SP, and specials. * * For RFC-822 compatibility allow LWSP around '.' * * @param string &$str The atom/dot data. * @param string $validate Use these characters as delimiter. * * @throws Horde_Mail_Exception */ protected function _rfc822ParseDotAtom(&$str, $validate = null) { $curr = $this->_curr(); if (($curr === false) || !$this->_rfc822IsAtext($curr, $validate)) { throw new Horde_Mail_Exception('Error when parsing dot-atom.'); } while (($chr = $this->_curr()) !== false) { if ($this->_rfc822IsAtext($chr, $validate)) { $str .= $chr; ++$this->_ptr; } else { $this->_rfc822SkipLwsp(); if ($this->_curr() != '.') { return; } $str .= '.'; $this->_rfc822SkipLwsp(true); } } } /** * atom = [CFWS] 1*atext [CFWS] * atext = ; Any character except controls, SP, and specials. * * This method doesn't just silently skip over WS. * * @param string &$str The atom/dot data. * * @throws Horde_Mail_Exception */ protected function _rfc822ParseAtomOrDot(&$str) { while (($chr = $this->_curr()) !== false) { if (($chr != '.') && !$this->_rfc822IsAtext($chr, ',<:')) { $this->_rfc822SkipLwsp(); if (!$this->_params['validate']) { $str = trim($str); } return; } $str .= $chr; ++$this->_ptr; } } /** * domain = dot-atom / domain-literal / obs-domain * domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS] * obs-domain = atom *("." atom) * * @param string &$str The domain string. * * @throws Horde_Mail_Exception */ protected function _rfc822ParseDomain(&$str) { if ($this->_curr(true) != '@') { throw new Horde_Mail_Exception('Error when parsing domain.'); } $this->_rfc822SkipLwsp(); if ($this->_curr() == '[') { $this->_rfc822ParseDomainLiteral($str); } else { $this->_rfc822ParseDotAtom($str, ';,> '); } } /** * domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS] * dcontent = dtext / quoted-pair * dtext = NO-WS-CTL / ; Non white space controls * %d33-90 / ; The rest of the US-ASCII * %d94-126 ; characters not including "[", * ; "]", or "\" * * @param string &$str The domain string. * * @throws Horde_Mail_Exception */ protected function _rfc822ParseDomainLiteral(&$str) { if ($this->_curr(true) != '[') { throw new Horde_Mail_Exception('Error parsing domain literal.'); } while (($chr = $this->_curr(true)) !== false) { switch ($chr) { case '\\': if (($chr = $this->_curr(true)) === false) { break 2; } break; case ']': $this->_rfc822SkipLwsp(); return; } $str .= $chr; } throw new Horde_Mail_Exception('Error parsing domain literal.'); } /** * @param boolean $advance Advance cursor? * * @throws Horde_Mail_Exception */ protected function _rfc822SkipLwsp($advance = false) { if ($advance) { ++$this->_ptr; } while (($chr = $this->_curr()) !== false) { switch ($chr) { case ' ': case "\n": case "\r": case "\t": ++$this->_ptr; continue; case '(': $this->_rfc822SkipComment(); break; default: return; } } } /** * @throws Horde_Mail_Exception */ protected function _rfc822SkipComment() { if ($this->_curr(true) != '(') { throw new Horde_Mail_Exception('Error when parsing a comment.'); } $comment = ''; $level = 1; while (($chr = $this->_curr(true)) !== false) { switch ($chr) { case '(': ++$level; continue; case ')': if (--$level == 0) { $this->_comments[] = $comment; return; } break; case '\\': if (($chr = $this->_curr(true)) === false) { break 2; } break; } $comment .= $chr; } throw new Horde_Mail_Exception('Error when parsing a comment.'); } /** * Check if data is an atom. * * @param string $chr The character to check. * @param string $validate If in non-validate mode, use these characters * as the non-atom delimiters. * * @return boolean True if an atom. */ protected function _rfc822IsAtext($chr, $validate = null) { if (is_null($chr)) { return false; } return ($this->_params['validate'] || is_null($validate)) ? !strcspn($chr, self::ATEXT) : strcspn($chr, $validate); } /* Helper methods. */ /** * Return current character. * * @param boolean $advance If true, advance the cursor. * * @return string The current character (false if EOF reached). */ protected function _curr($advance = false) { return ($this->_ptr >= $this->_datalen) ? false : $this->_data[$advance ? $this->_ptr++ : $this->_ptr]; } /* Other public methods. */ /** * Returns an approximate count of how many addresses are in the string. * This is APPROXIMATE as it only splits based on a comma which has no * preceding backslash. * * @param string $data Addresses to count. * * @return integer Approximate count. */ public function approximateCount($data) { return count(preg_split('/(?@. This can be sufficient for most people. * * Optional stricter mode can be utilized which restricts mailbox * characters allowed to: alphanumeric, full stop, hyphen, and underscore. * * @param string $data Address to check. * @param boolean $strict Strict check? * * @return mixed False if it fails, an indexed array username/domain if * it matches. */ public function isValidInetAddress($data, $strict = false) { $regex = $strict ? '/^([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i' : '/^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i'; return preg_match($regex, trim($data), $matches) ? array($matches[1], $matches[2]) : false; } } Horde_Mail-2.1.4/lib/Horde/Mail/Transport.php0000644000175000007640000002251312267540554020730 0ustar slusarzslusarz * @author Richard Heyes * @author Michael Slusarz * @category Horde * @copyright 1997-2013 Horde LLC (http://www.horde.org/) * @copyright 2002-2007 Richard Heyes * @license http://www.horde.org/licenses/bsd New BSD License * @package Mail */ abstract class Horde_Mail_Transport { /** * Line terminator used for separating header lines. * * @var string */ public $sep = PHP_EOL; /** * Configuration parameters. * * @var array */ protected $_params = array(); /** * Send a message. * * @param mixed $recipients Either a comma-seperated list of recipients * (RFC822 compliant), or an array of * recipients, each RFC822 valid. This may * contain recipients not specified in the * headers, for Bcc:, resending messages, etc. * @param array $headers The headers to send with the mail, in an * associative array, where the array key is the * header name (ie, 'Subject'), and the array * value is the header value (ie, 'test'). The * header produced from those values would be * 'Subject: test'. * If the '_raw' key exists, the value of this * key will be used as the exact text for * sending the message. * @param mixed $body The full text of the message body, including * any Mime parts, etc. Either a string or a * stream resource. * * @throws Horde_Mail_Exception */ abstract public function send($recipients, array $headers, $body); /** * Take an array of mail headers and return a string containing text * usable in sending a message. * * @param array $headers The array of headers to prepare, in an * associative array, where the array key is the * header name (ie, 'Subject'), and the array value * is the header value (ie, 'test'). The header * produced from those values would be 'Subject: * test'. * If the '_raw' key exists, the value of this key * will be used as the exact text for sending the * message. * * @return mixed Returns false if it encounters a bad address; otherwise * returns an array containing two elements: Any From: * address found in the headers, and the plain text version * of the headers. * @throws Horde_Mail_Exception */ public function prepareHeaders(array $headers) { $from = null; $lines = array(); $raw = isset($headers['_raw']) ? $headers['_raw'] : null; foreach ($headers as $key => $value) { if (strcasecmp($key, 'From') === 0) { $parser = new Horde_Mail_Rfc822(); $addresses = $parser->parseAddressList($value, array( 'validate' => true )); $from = $addresses[0]->bare_address; // Reject envelope From: addresses with spaces. if (strstr($from, ' ')) { return false; } $lines[] = $key . ': ' . $this->_normalizeEOL($value); } elseif (!$raw && (strcasecmp($key, 'Received') === 0)) { $received = array(); if (!is_array($value)) { $value = array($value); } foreach ($value as $line) { $received[] = $key . ': ' . $this->_normalizeEOL($line); } // Put Received: headers at the top. Spam detectors often // flag messages with Received: headers after the Subject: // as spam. $lines = array_merge($received, $lines); } elseif (!$raw) { // If $value is an array (i.e., a list of addresses), convert // it to a comma-delimited string of its elements (addresses). if (is_array($value)) { $value = implode(', ', $value); } $lines[] = $key . ': ' . $this->_normalizeEOL($value); } } return array($from, $raw ? $raw : implode($this->sep, $lines)); } /** * Take a set of recipients and parse them, returning an array of bare * addresses (forward paths) that can be passed to sendmail or an SMTP * server with the 'RCPT TO:' command. * * @param mixed $recipients Either a comma-separated list of recipients * (RFC822 compliant), or an array of * recipients, each RFC822 valid. * * @return array Forward paths (bare addresses, IDN encoded). * @throws Horde_Mail_Exception */ public function parseRecipients($recipients) { // Parse recipients, leaving out all personal info. This is // for smtp recipients, etc. All relevant personal information // should already be in the headers. $rfc822 = new Horde_Mail_Rfc822(); return $rfc822->parseAddressList($recipients, array( 'validate' => true ))->bare_addresses_idn; } /** * Sanitize an array of mail headers by removing any additional header * strings present in a legitimate header's value. The goal of this * filter is to prevent mail injection attacks. * * Raw headers are sent as-is. * * @param array $headers The associative array of headers to sanitize. * * @return array The sanitized headers. */ protected function _sanitizeHeaders($headers) { foreach (array_diff(array_keys($headers), array('_raw')) as $key) { $headers[$key] = preg_replace('=((||0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i', null, $headers[$key]); } return $headers; } /** * Normalizes EOLs in string data. * * @param string $data Data. * * @return string Normalized data. */ protected function _normalizeEOL($data) { return strtr($data, array( "\r\n" => $this->sep, "\r" => $this->sep, "\n" => $this->sep )); } /** * Get the from address. * * @param string $from From address. * @param array $headers Headers array. * * @return string Address object. * @throws Horde_Mail_Exception */ protected function _getFrom($from, $headers) { /* Since few MTAs are going to allow this header to be forged unless * it's in the MAIL FROM: exchange, we'll use Return-Path instead of * From: if it's set. */ foreach (array_keys($headers) as $hdr) { if (strcasecmp($hdr, 'Return-Path') === 0) { $from = $headers[$hdr]; break; } } if (!strlen($from)) { throw new Horde_Mail_Exception('No from address provided.'); } $from = new Horde_Mail_Rfc822_Address($from); return $from->bare_address_idn; } } Horde_Mail-2.1.4/test/Horde/Mail/AddressTest.php0000644000175000007640000000332512267540554021372 0ustar slusarzslusarz * @category Horde * @license http://www.horde.org/licenses/bsd BSD * @package Mail * @subpackage UnitTests */ class Horde_Mail_AddressTest extends PHPUnit_Framework_TestCase { public function testDomainMatch() { $address = new Horde_Mail_Rfc822_Address('Test '); $this->assertTrue($address->matchDomain('example.com')); $this->assertFalse($address->matchDomain('foo.example.com')); $address2 = new Horde_Mail_Rfc822_Address('Test '); $this->assertTrue($address2->matchDomain('example.com')); $this->assertTrue($address2->matchDomain('foo.example.com')); $address3 = new Horde_Mail_Rfc822_Address('Test '); $this->assertTrue($address3->matchDomain('example.co.uk')); $this->assertFalse($address3->matchDomain('foo.example.co.uk')); $this->assertTrue($address3->matchDomain('co.uk')); $address4 = new Horde_Mail_Rfc822_Address('Test '); $this->assertTrue($address4->matchDomain('example.co.uk')); $this->assertTrue($address4->matchDomain('foo.example.co.uk')); $this->assertTrue($address4->matchDomain('co.uk')); } public function testPersonalIsSameAsEmail() { $address = new Horde_Mail_Rfc822_Address('"test@example.com" '); $this->assertEquals( 'test@example.com', strval($address) ); $address = new Horde_Mail_Rfc822_Address('"TEST@EXAMPLE.COM" '); $this->assertEquals( 'test@example.com', strval($address) ); } } Horde_Mail-2.1.4/test/Horde/Mail/AllTests.php0000644000175000007640000000013212267540554020671 0ustar slusarzslusarzrun(); Horde_Mail-2.1.4/test/Horde/Mail/bootstrap.php0000644000175000007640000000014312267540554021155 0ustar slusarzslusarz * @category Horde * @license http://www.horde.org/licenses/bsd BSD * @package Mail * @subpackage UnitTests */ class Horde_Mail_GroupTest extends PHPUnit_Framework_TestCase { public function testWriteAddress() { $addresses = array( 'Test ', 'foo@example.com' ); $groupname = 'Testing'; $group_ob = new Horde_Mail_Rfc822_Group($groupname, $addresses); $this->assertEquals( 'Testing: Test , foo@example.com;', $group_ob->writeAddress() ); } public function testWriteAddressEncode() { $addresses = array( 'Fooã ', 'foo@example.com' ); $groupname = 'Testing'; $group_ob = new Horde_Mail_Rfc822_Group($groupname, $addresses); $this->assertEquals( 'Testing: =?utf-8?b?Rm9vw6M=?= , foo@example.com;', $group_ob->writeAddress(array('encode' => true)) ); } public function testValid() { $group_ob = new Horde_Mail_Rfc822_Group(); $this->assertTrue($group_ob->valid); $group_ob->groupname = ''; $this->assertFalse($group_ob->valid); } public function testEmptyGroupCount() { $group_ob = new Horde_Mail_Rfc822_Group('Group'); $this->assertEquals( 0, count($group_ob) ); } public function testEncodingGroupname() { $group_ob = new Horde_Mail_Rfc822_Group('Group "Foo"'); $this->assertEquals( '"Group \"Foo\"":;', $group_ob->writeAddress(true) ); } } Horde_Mail-2.1.4/test/Horde/Mail/ListTest.php0000644000175000007640000002645412267540554020730 0ustar slusarzslusarz * @category Horde * @license http://www.horde.org/licenses/bsd BSD * @package Mail * @subpackage UnitTests */ class Horde_Mail_ListTest extends PHPUnit_Framework_TestCase { private $rfc822; public function setUp() { $this->rfc822 = new Horde_Mail_Rfc822(); } public function tearDown() { unset($this->rfc822); } public function testSingleAddress() { $email = 'Test '; $res = $this->rfc822->parseAddressList($email); $this->assertEquals( 1, count($res) ); $expected = array($email); foreach ($res as $key => $val) { $this->assertEquals( $expected[$key], strval($val) ); } $this->assertEquals( $expected, $res->addresses ); $expected = array( 'test@example.com' ); $this->assertEquals( $expected, $res->bare_addresses ); $this->assertEquals( 0, $res->groupCount() ); } public function testSingleAddressWithFilter() { $email = 'Test '; $res = $this->rfc822->parseAddressList($email); $res->setIteratorFilter(0, array('test@example.com')); $this->assertEquals( 0, count($res) ); foreach ($res as $val) { $this->fail('Results should be empty.'); } $this->assertEquals( array(), $res->addresses ); $this->assertEquals( array(), $res->bare_addresses ); $this->assertEquals( 0, $res->groupCount() ); } public function testSimpleAddressList() { $email = 'Test , Test2 '; $res = $this->rfc822->parseAddressList($email); $this->assertEquals( 2, count($res) ); $expected = array( 'Test ', 'Test2 ' ); foreach ($res as $key => $val) { $this->assertEquals( $expected[$key], strval($val) ); } $this->assertEquals( $expected, $res->addresses ); $expected = array( 'test@example.com', 'test2@example.com' ); $this->assertEquals( $expected, $res->bare_addresses ); $this->assertEquals( 0, $res->groupCount() ); } public function testSeekingInList() { $email = 'Test , Test2 '; $res = $this->rfc822->parseAddressList($email); try { $res->seek(1); } catch (OutOfBoundsException $e) { $this->fail('Unexpected Exception.'); } $this->assertEquals( 'test2', $res->current()->mailbox ); // Seek to current pointer value. try { $res->seek(1); } catch (OutOfBoundsException $e) { $this->fail('Unexpected Exception.'); } $this->assertEquals( 'test2', $res->current()->mailbox ); try { $res->seek(0); } catch (OutOfBoundsException $e) { $this->fail('Unexpected Exception.'); } $this->assertEquals( 'test', $res->current()->mailbox ); try { $res->seek(2); $this->fail('Expected Exception.'); } catch (OutOfBoundsException $e) {} } public function testArraySet() { $email = 'Test '; $res = $this->rfc822->parseAddressList($email); $res[0] = 'Test2 '; $this->assertEquals( 1, count($res) ); $this->assertEquals( 'test2', $res[0]->mailbox ); } public function testArrayUnset() { $email = 'Test , Test2 '; $res = $this->rfc822->parseAddressList($email); unset($res[0]); $this->assertEquals( 1, count($res) ); $this->assertEquals( 'test2', $res[0]->mailbox ); } public function testSimpleAddressListWithFilter() { $email = 'Test , Test2 '; $res = $this->rfc822->parseAddressList($email); $res->setIteratorFilter(0, array('test@example.com')); $this->assertEquals( 1, count($res) ); $expected = array( 'Test2 ' ); foreach ($res as $key => $val) { $this->assertEquals( $expected[$key], strval($val) ); } $this->assertEquals( $expected, $res->addresses ); $expected = array( 'test2@example.com' ); $this->assertEquals( $expected, $res->bare_addresses ); $this->assertEquals( 0, $res->groupCount() ); } public function testAddressListWithGroup() { $email = 'Test , Group: foo@example.com, Foo 2 ;, Test2 '; $res = $this->rfc822->parseAddressList($email); $this->assertEquals( 4, count($res) ); $expected = array( 'Test ', 'Group: foo@example.com, Foo 2 ;', 'foo@example.com', 'Foo 2 ', 'Test2 ' ); foreach ($res as $key => $val) { $this->assertEquals( $expected[$key], strval($val) ); } unset($expected[1]); $this->assertEquals( array_values($expected), $res->addresses ); $expected = array( 'test@example.com', 'foo@example.com', 'foo2@example.com', 'test2@example.com' ); $this->assertEquals( $expected, $res->bare_addresses ); $this->assertEquals( 1, $res->groupCount() ); } public function testAddressListWithGroupWithFilter() { $email = 'Test , Group: foo@example.com, Foo 2 ;, Test2 '; $res = $this->rfc822->parseAddressList($email); $res->setIteratorFilter(0, array('foo@example.com')); $this->assertEquals( 3, count($res) ); $expected = array( 'Test ', 'Group: foo@example.com, Foo 2 ;', 'Foo 2 ', 'Test2 ' ); foreach ($res as $key => $val) { $this->assertEquals( $expected[$key], strval($val) ); } unset($expected[1]); $this->assertEquals( array_values($expected), $res->addresses ); $expected = array( 'test@example.com', 'foo2@example.com', 'test2@example.com' ); $this->assertEquals( $expected, $res->bare_addresses ); $res->setIteratorFilter(Horde_Mail_Rfc822_List::HIDE_GROUPS); $expected = array( 'Test ', 'foo@example.com', 'Foo 2 ', 'Test2 ' ); foreach ($res as $key => $val) { $this->assertEquals( $expected[$key], strval($val) ); } } public function testRemove() { $email = 'Test , Group: foo@example.com, Foo 2 ;, Test2 '; $res = $this->rfc822->parseAddressList($email); $res_clone = clone $res; $res_clone->remove('test2@example.com'); $this->assertEquals( 3, count($res_clone) ); $res_clone = clone $res; $res_clone->remove('foo@example.com'); $this->assertEquals( 4, count($res_clone) ); } public function testUnique() { $email = 'Test , test@example.com'; $res = $this->rfc822->parseAddressList($email); $res->unique(); $this->assertEquals( 1, count($res) ); $this->assertEquals( 'Test', $res[0]->personal ); $email = 'test@example.com, Test , test2@example.com'; $res = $this->rfc822->parseAddressList($email); $res->unique(); $this->assertEquals( 2, count($res) ); $this->assertEquals( 'Test', $res[0]->personal ); } public function testAddressesProperties() { $email = 'Test , Group: foo@example.com, Foo 2 ;, Test2 '; $res = $this->rfc822->parseAddressList($email); $ob = $res->addresses; $this->assertEquals( 4, count($ob) ); $this->assertInternalType('string', $ob[0]); $ob = $res->bare_addresses; $this->assertEquals( 4, count($ob) ); $this->assertInternalType('string', $ob[0]); $ob = $res->base_addresses; $this->assertEquals( 3, count($ob) ); $this->assertTrue($ob[0] instanceof Horde_Mail_Rfc822_Address); $this->assertTrue($ob[1] instanceof Horde_Mail_Rfc822_Group); $ob = $res->raw_addresses; $this->assertEquals( 4, count($ob) ); $this->assertTrue($ob[0] instanceof Horde_Mail_Rfc822_Address); $this->assertTrue($ob[1] instanceof Horde_Mail_Rfc822_Address); } public function testIterateEmptyArray() { $ob = new Horde_Mail_Rfc822_List(); foreach ($ob as $val) { $this->fail('Nothing to iterate.'); } $ob = new Horde_Mail_Rfc822_List( new Horde_Mail_Rfc822_Group() ); $ob->setIteratorFilter(Horde_Mail_Rfc822_List::HIDE_GROUPS); foreach ($ob as $val) { $this->fail('Nothing to iterate.'); } } public function testContains() { $email = 'Test , Group: foo@example.com, Foo 2 ;, Test2 '; $res = $this->rfc822->parseAddressList($email); $this->assertTrue($res->contains('test@example.com')); $this->assertTrue($res->contains('foo2@example.com')); $this->assertFalse($res->contains('foo4@example.com')); } } Horde_Mail-2.1.4/test/Horde/Mail/MatchTest.php0000644000175000007640000000170212267540554021036 0ustar slusarzslusarz * @category Horde * @license http://www.horde.org/licenses/bsd BSD * @package Mail * @subpackage UnitTests */ class Horde_Mail_MatchTest extends PHPUnit_Framework_TestCase { public function testMatch() { $address = new Horde_Mail_Rfc822_Address('Test '); $this->assertTrue($address->match('Foo ')); $this->assertTrue($address->match('Foo ')); $this->assertFalse($address->match('Foo ')); } public function testInsensitiveMatch() { $address = new Horde_Mail_Rfc822_Address('Test '); $this->assertTrue($address->matchInsensitive('Foo ')); $this->assertTrue($address->matchInsensitive('Foo ')); $this->assertTrue($address->matchInsensitive('Foo ')); } } Horde_Mail-2.1.4/test/Horde/Mail/ObjectTest.php0000644000175000007640000000741112267540554021213 0ustar slusarzslusarz * @category Horde * @license http://www.horde.org/licenses/bsd BSD * @package Mail * @subpackage UnitTests */ class Horde_Mail_ObjectTest extends PHPUnit_Framework_TestCase { public function testWriteAddress() { $address = 'Test '; $parser = new Horde_Mail_Rfc822(); $result = $parser->parseAddressList($address); $this->assertEquals( $address, $result[0]->writeAddress() ); } public function testEncoding() { $address = 'Fooã '; $parser = new Horde_Mail_Rfc822(); $result = $parser->parseAddressList($address); $this->assertEquals( $address, $result[0]->writeAddress() ); $this->assertEquals( '=?utf-8?b?Rm9vw6M=?= ', $result[0]->writeAddress(array('encode' => true)) ); $this->assertEquals( '=?iso-8859-1?b?Rm9v4w==?= ', $result[0]->writeAddress(array('encode' => 'iso-8859-1')) ); $email = 'ß '; $result = $parser->parseAddressList($email); $this->assertEquals( '=?utf-8?b?w58=?= ', $result[0]->writeAddress(array('encode' => true)) ); $email2 = 'ß X '; $result = $parser->parseAddressList($email2); $this->assertEquals( '=?utf-8?b?w58=?= X ', $result[0]->writeAddress(array('encode' => true)) ); $email3 = '"ß X" '; $result = $parser->parseAddressList($email3); $this->assertEquals( '=?utf-8?b?w58=?= X ', $result[0]->writeAddress(array('encode' => true)) ); } public function testAddressConstructor() { $address = 'Test '; $addr_ob = new Horde_Mail_Rfc822_Address($address); $this->assertEquals( 'Test', $addr_ob->personal ); $this->assertEquals( 'test', $addr_ob->mailbox ); $this->assertEquals( 'example.com', $addr_ob->host ); } public function testEncodedAddressWithIDNHost() { if (!extension_loaded('intl')) { $this->markTestSkipped('Intl module is not available.'); } $ob = new Horde_Mail_Rfc822_Address(); $ob->personal = 'Aäb'; $ob->mailbox = 'test'; $ob->host = 'üexample.com'; $this->assertEquals( '=?utf-8?b?QcOkYg==?= ', $ob->encoded ); } public function testDecodedAddressWithIDNHost() { $ob = new Horde_Mail_Rfc822_Address(); $ob->personal = '=?utf-8?b?QcOkYg==?='; $ob->mailbox = 'test'; $ob->host = 'xn--example-m2a.com'; $this->assertEquals( 'Aäb ', strval($ob) ); } public function testBug4834() { // Bug #4834: Wrong encoding of email lists with groups. $addr = '"John Doe" , Group: peter@example.com, jane@example.com;'; $parser = new Horde_Mail_Rfc822(); $result = $parser->parseAddressList($addr); $this->assertEquals( 'John Doe , Group: peter@example.com, jane@example.com;', strval($result) ); } public function testValid() { $ob = new Horde_Mail_Rfc822_Address(); $this->assertFalse($ob->valid); $ob->mailbox = 'test'; $this->assertTrue($ob->valid); } } Horde_Mail-2.1.4/test/Horde/Mail/ParseTest.php0000644000175000007640000004052612267540554021063 0ustar slusarzslusarz * @license http://www.horde.org/licenses/bsd BSD * @category Horde * @package Mail * @subpackage UnitTests */ class Horde_Mail_ParseTest extends PHPUnit_Framework_TestCase { private $rfc822; public function setUp() { $this->rfc822 = new Horde_Mail_Rfc822(); } public function tearDown() { unset($this->rfc822); } /* Test case for PEAR Mail:: bug #13659 */ public function testParseBug13659() { $address = '"Test Student" (test)'; $result = $this->rfc822->parseAddressList($address, array( 'default_domain' => 'anydomain.com' )); $this->assertTrue($result instanceof Horde_Mail_Rfc822_List); $ob = $result[0]; $this->assertTrue($ob instanceof Horde_Mail_Rfc822_Address); $this->assertEquals( 'Test Student', $ob->personal ); $this->assertEquals( 'test', $ob->mailbox ); $this->assertEquals( 'mydomain.com', $ob->host ); $this->assertInternalType( 'array', $ob->comment ); $this->assertEquals( 1, count($ob->comment) ); $this->assertEquals( 'test', $ob->comment[0] ); } /* Test case for PEAR Mail:: bug #9137 */ public function testParseBug9137() { $addresses = array( array('name' => 'John Doe', 'email' => 'test@example.com'), array('name' => 'John Doe\\', 'email' => 'test@example.com'), array('name' => 'John "Doe', 'email' => 'test@example.com'), array('name' => 'John "Doe\\', 'email' => 'test@example.com'), ); foreach ($addresses as $val) { $address = '"' . addslashes($val['name']) . '" <' . $val['email'] . '>'; /* Throws Exception on error. */ $this->rfc822->parseAddressList($address); } } /* Test case for PEAR Mail:: bug #9137, take 2 */ public function testParseBug9137Take2() { $addresses = array( array( 'raw' => '"John Doe" ' ), array( 'raw' => '"John Doe' . chr(92) . '" ', 'fail' => true ), array( 'raw' => '"John Doe' . chr(92) . chr(92) . '" ' ), array( 'raw' => '"John Doe' . chr(92) . chr(92) . chr(92) . '" ', 'fail' => true ), array( 'raw' => '"John Doe' . chr(92) . chr(92) . chr(92) . chr(92) . '" ' ), array( 'raw' => '"John Doe ', 'fail' => true ) ); foreach ($addresses as $val) { try { $this->rfc822->parseAddressList($val['raw'], array( 'validate' => true )); if (!empty($val['fail'])) { $this->fail('An expected exception was not raised.'); } } catch (Horde_Mail_Exception $e) { if (empty($val['fail'])) { $this->fail('An unexpected exception was raised.'); } } } } public function testGeneralParsing() { /* A simple, bare address. */ $address = 'user@example.com'; $result = $this->rfc822->parseAddressList($address, array( 'default_domain' => null )); $this->assertTrue($result instanceof Horde_Mail_Rfc822_List); $this->assertTrue($result[0] instanceof Horde_Mail_Rfc822_Address); $this->assertEquals($result[0]->personal, ''); $this->assertInternalType('array', $result[0]->comment); $this->assertEquals($result[0]->comment, array()); $this->assertEquals($result[0]->mailbox, 'user'); $this->assertEquals($result[0]->host, 'example.com'); /* Address groups. */ $address = 'My Group: "Richard" (A comment), ted@example.com (Ted Bloggs), Barney;'; $result = $this->rfc822->parseAddressList($address, array( 'default_domain' => null )); $this->assertTrue($result instanceof Horde_Mail_Rfc822_List); $this->assertTrue($result[0] instanceof Horde_Mail_Rfc822_Group); $this->assertEquals($result[0]->groupname, 'My Group'); $this->assertTrue($result[0]->addresses instanceof Horde_Mail_Rfc822_GroupList); $this->assertInternalType('object', $result[0]->addresses[0]); $this->assertEquals($result[0]->addresses[0]->personal, 'Richard'); $this->assertInternalType('array', $result[0]->addresses[0]->comment); $this->assertEquals($result[0]->addresses[0]->comment[0], 'A comment'); $this->assertEquals($result[0]->addresses[0]->mailbox, 'richard'); $this->assertEquals($result[0]->addresses[0]->host, 'localhost'); $this->assertInternalType('object', $result[0]->addresses[1]); $this->assertEquals($result[0]->addresses[1]->personal, ''); $this->assertInternalType('array', $result[0]->addresses[1]->comment); $this->assertEquals($result[0]->addresses[1]->comment[0], 'Ted Bloggs'); $this->assertEquals($result[0]->addresses[1]->mailbox, 'ted'); $this->assertEquals($result[0]->addresses[1]->host, 'example.com'); $this->assertInternalType('object', $result[0]->addresses[2]); $this->assertEquals($result[0]->addresses[2]->personal, ''); $this->assertInternalType('array', $result[0]->addresses[2]->comment); $this->assertEquals($result[0]->addresses[2]->comment, array()); $this->assertEquals($result[0]->addresses[2]->mailbox, 'Barney'); $this->assertEmpty($result[0]->addresses[2]->host); /* A valid address with spaces in the local part. */ $address = '<"Jon Parise"@php.net>'; $result = $this->rfc822->parseAddressList($address, array( 'default_domain' => null )); $this->assertTrue($result instanceof Horde_Mail_Rfc822_List); $this->assertTrue($result[0] instanceof Horde_Mail_Rfc822_Address); $this->assertEquals($result[0]->personal, ''); $this->assertInternalType('array', $result[0]->comment); $this->assertEquals($result[0]->comment, array()); $this->assertEquals($result[0]->mailbox, 'Jon Parise'); $this->assertEquals($result[0]->host, 'php.net'); /* An invalid address with spaces in the local part. */ $address = ''; try { $this->rfc822->parseAddressList($address, array( 'validate' => true )); $this->fail('An expected exception was not raised.'); } catch (Horde_Mail_Exception $e) {} /* A valid address with an uncommon TLD. */ $address = 'jon@host.longtld'; try { $this->rfc822->parseAddressList($address, array( 'validate' => true )); } catch (Horde_Mail_Exception $e) { $this->fail('An unexpected exception was raised.'); } } public function testValidateQuotedString() { $address_string = '"Joe Doe \(from Somewhere\)" , postmaster@example.com, root'; $res = $this->rfc822->parseAddressList($address_string, array( 'default_domain' => 'example.com' )); $this->assertTrue($res instanceof Horde_Mail_Rfc822_List); $this->assertEquals(count($res), 3); } public function testBug9525() { try { $ob = $this->rfc822->parseAddressList( 'ß ', array( 'default_domain' => 'example.com', 'validate' => true ) ); $this->fail('Expecting Exception.'); } catch (Horde_Mail_Exception $e) {} /* This technically shouldn't validate, but the parser is very liberal * about accepting characters within quotes. */ $ob = $this->rfc822->parseAddressList( '"ß" ', array( 'default_domain' => 'example.com' ) ); } public function testBug10534() { $ob = $this->rfc822->parseAddressList(''); $this->assertEquals( 0, count($ob) ); } public function testNoValidation() { $ob = $this->rfc822->parseAddressList( '"ß" ', array( 'default_domain' => 'example.com' ) ); $this->assertEquals( 'ß', $ob[0]->personal ); $ob = $this->rfc822->parseAddressList( 'ß ß ', array( 'default_domain' => 'example.com' ) ); $this->assertEquals( 'ß ß', $ob[0]->personal ); } public function testLimit() { $email = array_fill(0, 10, 'A '); $ob = $this->rfc822->parseAddressList( implode(', ', $email), array( 'limit' => 5 ) ); $this->assertEquals( 5, count($ob) ); } public function testMissingMailboxInNonValidateMode() { $email = 'A '; $ob = $this->rfc822->parseAddressList($email); /* This can't work even in non-validate mode; since there is no hope * that something like encoding will fix in the future. */ $this->assertEquals( 0, count($ob) ); } public function testMissingAddressWhenParsingGroupInNonValidateMode() { $email = 'Group: foo@example.com, A;'; $ob = $this->rfc822->parseAddressList($email); $this->assertEquals( 2, count($ob[0]->addresses) ); } public function testParseGroupWhenNotValidating() { $email = 'Group: foo@example.com, foo2@example.com;'; $ob = $this->rfc822->parseAddressList($email); $this->assertEquals( 2, count($ob[0]->addresses) ); } public function testLargeParse() { $email = array_fill(0, 1000, 'A , "A B" , foo@example.com, Group: A ;, Group2: "A B" ;'); $ob = $this->rfc822->parseAddressList(implode(', ', $email)); $this->assertEquals( 5000, count($ob) ); } public function testArrayAccess() { $ob = $this->rfc822->parseAddressList( 'A ', array( 'default_domain' => 'example.com' ) ); $this->assertEquals( 'A', $ob[0]->personal ); $this->assertEquals( 'example.com', $ob[0]->host ); $this->assertTrue( isset($ob[0]->mailbox) ); $this->assertFalse( isset($ob[0]->bar) ); } public function testEmailInDisplayPart() { $ob = $this->rfc822->parseAddressList( 'Foo Bar , "bad_email@example.com, Baz" , "Qux" ' ); $this->assertEquals( 3, count($ob) ); } public function testValidation() { $ob = $this->rfc822->parseAddressList( '"Tek-Diária - Newsletter" ' ); $this->assertEquals( 1, count($ob) ); } public function testBadCharactersInEmail() { $address = 'fooççç@example.com'; $ob = $this->rfc822->parseAddressList($address); $this->assertEquals( 1, count($ob) ); try { $this->rfc822->parseAddressList($address, array( 'validate' => true )); $this->fail('Expected Exception.'); } catch (Horde_Mail_Exception $e) {} } public function testParsingNonValidateAddressWithBareAddressAtFront() { $address = 'test@example.com, Foo '; $ob = $this->rfc822->parseAddressList($address); $this->assertEquals( 2, count($ob) ); $this->assertEquals( 'example.com', $ob[0]->host ); } public function testParsingIDNHost() { if (!extension_loaded('intl')) { $this->markTestSkipped('Intl module is not available.'); } $email = 'Aäb '; $ob = $this->rfc822->parseAddressList($email); $this->assertEquals( 1, count($ob) ); $this->assertEquals( 'üexample.com', $ob[0]->host ); try { $this->rfc822->parseAddressList($email, array( 'validate' => true )); $this->fail('Expected Exception'); } catch (Exception $e) {} } public function testParsingSimpleString() { $email = 'Test'; $ob = $this->rfc822->parseAddressList($email); $this->assertEquals( 1, count($ob) ); $this->assertEquals( $email, $ob[0]->mailbox ); $this->assertEquals( $email, (string)$ob[0] ); } public function testParsingPersonalPartWithQuotes() { $email = '"Test \\"F-oo\\" Bar" '; $ob = new Horde_Mail_Rfc822_Address($email); $this->assertEquals( '"Test \"F-oo\" Bar" ', $ob->writeAddress() ); $this->assertEquals( $email, $ob->writeAddress(true) ); } public function testParsingPersonalPartWithCommas() { $email = "\"Foo, Bar\" "; $ob = $this->rfc822->parseAddressList($email); $this->assertEquals( $email, $ob->writeAddress(true) ); $ob = $this->rfc822->parseAddressList($email, array( 'validate' => true )); $this->assertEquals( $email, $ob->writeAddress(true) ); } public function testParseOfGroupObject() { $email = 'Test: foo@example.com, bar@example.com;'; $ob = $this->rfc822->parseAddressList($email); $ob2 = $this->rfc822->parseAddressList($ob); $this->assertEquals( 2, count($ob2) ); } public function testDefaultDomain() { $address = 'foo@example2.com'; $result = $this->rfc822->parseAddressList($address, array( 'default_domain' => 'example.com' )); $this->assertEquals( 'foo@example2.com', strval($result) ); $address = 'foo'; $result = $this->rfc822->parseAddressList($address, array( 'default_domain' => 'example.com' )); $this->assertEquals( 'foo@example.com', strval($result) ); } public function testBareMailboxWithoutDefaultDomainWithoutValidating() { $addresses = array('foo', 'foo@'); foreach ($addresses as $val) { $res = $this->rfc822->parseAddressList($val, array( 'default_domain' => null )); $this->assertEquals( 'foo', $res[0]->mailbox ); $this->assertNull($res[0]->host); } } public function testBareMailboxWithoutDefaultDomainWhenValidating() { $addresses = array('foo', 'foo@'); foreach ($addresses as $val) { try { $this->rfc822->parseAddressList($val, array( 'default_domain' => null, 'validate' => true )); $this->fail('An expected exception was not raised.'); } catch (Horde_Mail_Exception $e) {} } } } Horde_Mail-2.1.4/test/Horde/Mail/phpunit.xml0000644000175000007640000000005612267540554020643 0ustar slusarzslusarz Horde_Mail-2.1.4/test/Horde/Mail/SendTest.php0000644000175000007640000000452712267540554020703 0ustar slusarzslusarz * @category Horde * @license http://www.horde.org/licenses/bsd BSD * @package Mail * @subpackage UnitTests */ class Horde_Mail_SendTest extends PHPUnit_Framework_TestCase { /* Test case for mixed EOLs. */ public function testMixedEOLs() { $ob = new Horde_Mail_Transport_Mock(); $ob->sep = "\n"; $recipients = 'Test '; $body = "Foo\r\nBar\nBaz\rTest"; $headers = array( 'To' => '', 'From' => '', 'Subject' => 'Test', 'X-Test' => 'Line 1\r\n\tLine 2\n\tLine 3\r\tLine 4', 'X-Truncated-Header' => $body ); $ob->send($recipients, $headers, $body); if (preg_match("/(?<=\r)\n/", $ob->sentMessages[0]['header_text'])) { $this->fail("Unexpected EOL in headers."); } if (preg_match("/(?<=\r)\n/", $ob->sentMessages[0]['body'])) { $this->fail("Unexpected EOL in body."); } $ob->sep = "\r\n"; $ob->send($recipients, $headers, $body); if (preg_match("/(?sentMessages[1]['header_text'])) { $this->fail("Unexpected EOL in headers."); } if (preg_match("/(?sentMessages[1]['body'])) { $this->fail("Unexpected EOL in body."); } } public function testBug12116() { $addr = new Horde_Mail_Rfc822_Address(); $addr->personal = 'Aäb'; $addr->mailbox = 'test'; $addr->host = 'üexample.com'; $ob = new Horde_Mail_Transport_Mock(); $ob->send( array($addr), array( 'Return-Path' => $addr ), 'Foo' ); $this->assertEquals( array('test@xn--example-m2a.com'), $ob->sentMessages[0]['recipients'] ); $this->assertEquals( 'test@xn--example-m2a.com', $ob->sentMessages[0]['from'] ); } public function testMissingFrom() { $ob = new Horde_Mail_Transport_Mock(); try { $ob->send(array('foo@example.com'), array(), 'Foo'); $this->fail('Expected Horde_Mail_Exception.'); } catch (Horde_Mail_Exception $e) { } } }