Overview

Packages

  • Mime

Classes

  • Horde_Mime
  • Horde_Mime_Address
  • Horde_Mime_Exception
  • Horde_Mime_Headers
  • Horde_Mime_Magic
  • Horde_Mime_Mail
  • Horde_Mime_Mdn
  • Horde_Mime_Part
  • Horde_Mime_Translation
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * The Horde_Mime_Address:: class provides methods for dealing with email
  4:  * address standards (RFC 822/2822/3490/5322).
  5:  *
  6:  * Copyright 2008-2012 Horde LLC (http://www.horde.org/)
  7:  *
  8:  * See the enclosed file COPYING for license information (LGPL). If you
  9:  * did not receive this file, see http://www.horde.org/licenses/lgpl21.
 10:  *
 11:  * @author   Chuck Hagenbuch <chuck@horde.org>
 12:  * @author   Michael Slusarz <slusarz@horde.org>
 13:  * @category Horde
 14:  * @license  http://www.horde.org/licenses/lgpl21 LGPL 2.1
 15:  * @package  Mime
 16:  */
 17: class Horde_Mime_Address
 18: {
 19:     /**
 20:      * Builds an RFC compliant email address.
 21:      *
 22:      * @param string $mailbox   Mailbox name.
 23:      * @param string $host      Domain name of mailbox's host.
 24:      * @param string $personal  Personal name phrase.
 25:      * @param array $opts       Additional options:
 26:      *   - idn: (boolean) If true, decode IDN domain names (Punycode/RFC 3490).
 27:      *          If false, convert domain names into IDN if necessary (@since
 28:      *          1.5.0).
 29:      *          If null, does no conversion.
 30:      *          Requires the idn or intl PHP module.
 31:      *          DEFAULT: true
 32:      *
 33:      * @return string  The correctly escaped and quoted
 34:      *                 "$personal <$mailbox@$host>" string.
 35:      */
 36:     static public function writeAddress($mailbox, $host, $personal = '',
 37:                                         $opts = array())
 38:     {
 39:         $host = ltrim($host, '@');
 40:         if (isset($opts['idn'])) {
 41:             switch ($opts['idn']) {
 42:             case true:
 43:                 if (function_exists('idn_to_utf8')) {
 44:                     $host = idn_to_utf8($host);
 45:                 }
 46:                 break;
 47: 
 48:             case false:
 49:                 if (function_exists('idn_to_ascii')) {
 50:                     $host = idn_to_ascii($host);
 51:                 }
 52:                 break;
 53:             }
 54:         }
 55: 
 56:         $address = self::encode($mailbox, 'address') . '@' . $host;
 57: 
 58:         return (strlen($personal) && ($personal != $address))
 59:             ? self::encode($personal, 'personal') . ' <' . $address . '>'
 60:             : $address;
 61:     }
 62: 
 63:     /**
 64:      * Write an RFC compliant group address, given the group name and a list
 65:      * of email addresses.
 66:      *
 67:      * @param string $groupname  The name of the group.
 68:      * @param array $addresses   The component email addresses. These e-mail
 69:      *                           addresses must be in RFC format.
 70:      *
 71:      * @return string  The correctly quoted group string.
 72:      */
 73:     static public function writeGroupAddress($groupname, $addresses = array())
 74:     {
 75:         return self::encode($groupname, 'address') . ':' . (empty($addresses) ? '' : (' ' . implode(', ', $addresses)) . ';');
 76:     }
 77: 
 78:     /**
 79:      * If an email address has no personal information, get rid of any angle
 80:      * brackets (<>) around it.
 81:      *
 82:      * @param string $address  The address to trim.
 83:      *
 84:      * @return string  The trimmed address.
 85:      */
 86:     static public function trimAddress($address)
 87:     {
 88:         $address = trim($address);
 89: 
 90:         if (($address[0] == '<') && (substr($address, -1) == '>')) {
 91:             $address = substr($address, 1, -1);
 92:         }
 93: 
 94:         return $address;
 95:     }
 96: 
 97:     /**
 98:      * Explodes an RFC string, ignoring a delimiter if preceded by a "\"
 99:      * character, or if the delimiter is inside single or double quotes.
100:      *
101:      * @param string $string      The RFC compliant string.
102:      * @param string $delimiters  A string containing valid delimiters.
103:      *                            Defaults to ','.
104:      *
105:      * @return array  The exploded string in an array.
106:      */
107:     static public function explode($string, $delimiters = ',')
108:     {
109:         if (!strlen($string)) {
110:             return array($string);
111:         }
112: 
113:         $emails = array();
114:         $pos = 0;
115:         $in_group = $in_quote = false;
116: 
117:         for ($i = 0, $iMax = strlen($string); $i < $iMax; ++$i) {
118:             $char = $string[$i];
119:             if ($char == '"') {
120:                 if (!$i || ($prev !== '\\')) {
121:                     $in_quote = !$in_quote;
122:                 }
123:             } elseif ($in_group) {
124:                 if ($char == ';') {
125:                     $emails[] = substr($string, $pos, $i - $pos + 1);
126:                     $pos = $i + 1;
127:                     $in_group = false;
128:                 }
129:             } elseif (!$in_quote) {
130:                 if ($char == ':') {
131:                     $in_group = true;
132:                 } elseif ((strpos($delimiters, $char) !== false) &&
133:                           (!$i || ($prev !== '\\'))) {
134:                     $emails[] = $i ? substr($string, $pos, $i - $pos) : '';
135:                     $pos = $i + 1;
136:                 }
137:             }
138:             $prev = $char;
139:         }
140: 
141:         if ($pos != $i) {
142:             /* The string ended without a delimiter. */
143:             $emails[] = substr($string, $pos, $i - $pos);
144:         }
145: 
146:         return array_map('trim', $emails);
147:     }
148: 
149:     /**
150:      * Takes an address object array and formats it as a string.
151:      *
152:      * Object array format for the address "John Doe <john_doe@example.com>"
153:      * is:
154:      *   - host: The host the mailbox is on ("example.com")
155:      *   - mailbox: The user's mailbox ("john_doe")
156:      *   - personal: Personal name ("John Doe")
157:      *
158:      * @param array $ob    The address object to be turned into a string.
159:      * @param array $opts  Additional options:
160:      *   - charset: (string) The local charset.
161:      *              DEFAULT: NONE
162:      *   - filter: (mixed) A user@example.com style bare address to ignore.
163:      *             Either single string or an array of strings. If the address
164:      *             matches $filter, an empty string will be returned.
165:      *             DEFAULT: No filter
166:      *   - idn: (boolean) Convert IDN domain names (Punycode/RFC 3490) into
167:      *          the local charset.
168:      *          Requires the idn or intl PHP module.
169:      *          DEFAULT: true
170:      *
171:      * @return string  The formatted address.
172:      */
173:     static public function addrObject2String($ob, $opts = array())
174:     {
175:         $opts = array_merge(array(
176:             'charset' => null
177:         ), $opts);
178: 
179:         /* If the personal name is set, decode it. */
180:         $ob['personal'] = isset($ob['personal'])
181:             ? Horde_Mime::decode($ob['personal'], $opts['charset'])
182:             : '';
183: 
184:         /* If both the mailbox and the host are empty, return an empty string.
185:          * If we just let this case fall through, the call to writeAddress()
186:          * will end up return just a '@', which is undesirable. */
187:         if (empty($ob['mailbox']) && empty($ob['host'])) {
188:             return '';
189:         }
190: 
191:         /* Make sure these two variables have some sort of value. */
192:         if (!isset($ob['mailbox'])) {
193:             $ob['mailbox'] = '';
194:         }
195:         if (!isset($ob['host'])) {
196:             $ob['host'] = '';
197:         }
198: 
199:         /* Filter out unwanted addresses based on the $filter string. */
200:         if (!empty($opts['filter'])) {
201:             $filter = is_array($opts['filter'])
202:                 ? $opts['filter']
203:                 : array($opts['filter']);
204:             foreach ($filter as $f) {
205:                 if (strcasecmp($f, $ob['mailbox'] . '@' . $ob['host']) == 0) {
206:                     return '';
207:                 }
208:             }
209:         }
210: 
211:         /* Return the formatted email address. */
212:         return self::writeAddress($ob['mailbox'], $ob['host'], $ob['personal'], $opts);
213:     }
214: 
215:     /**
216:      * Takes an array of address object arrays and passes each of them through
217:      * addrObject2String().
218:      *
219:      * @param array $addresses  The array of address objects.
220:      * @param array $opts       Additional options:
221:      *   - charset: (string) The local charset.
222:      *              DEFAULT: NONE
223:      *   - filter: (mixed) A user@example.com style bare address to ignore.
224:      *             Either single string or an array of strings.
225:      *             DEFAULT: No filter
226:      *   - idn: (boolean) Convert IDN domain names (Punycode/RFC 3490) into
227:      *          the local charset.
228:      *          Requires the idn or intl PHP module.
229:      *          DEFAULT: true
230:      *
231:      * @return string  All of the addresses in a comma-delimited string.
232:      *                 Returns the empty string on error/no addresses found.
233:      */
234:     static public function addrArray2String($addresses, $opts = array())
235:     {
236:         if (!is_array($addresses)) {
237:             return '';
238:         }
239: 
240:         $addrList = array();
241: 
242:         foreach ($addresses as $addr) {
243:             $val = self::addrObject2String($addr, $opts);
244:             if (!empty($val)) {
245:                 $addrList[Horde_String::lower(self::bareAddress($val))] = $val;
246:             }
247:         }
248: 
249:         return implode(', ', $addrList);
250:     }
251: 
252:     /**
253:      * Return the list of addresses for a header object.
254:      *
255:      * @todo Replace with built-in Horde_Mail_Rfc822_Address function.
256:      *
257:      * @param array $obs   An array of header objects.
258:      * @param array $opts  Additional options:
259:      *   - charset: (string) The local charset.
260:      *              DEFAULT: NONE
261:      *   - filter: (mixed) A user@example.com style bare address to ignore.
262:      *             Either single string or an array of strings.
263:      *             DEFAULT: No filter
264:      *   - idn: (boolean) Convert IDN domain names (Punycode/RFC 3490) into
265:      *          the local charset.
266:      *          Requires the idn or intl PHP module.
267:      *          DEFAULT: true
268:      *
269:      * @return array  An array of address information. Array elements:
270:      *   - address: (string) Full address
271:      *   - display: (string) A displayable version of the address
272:      *   - groupname: (string) The group name.
273:      *   - host: (string) Hostname
274:      *   - inner: (string) Trimmed, bare address
275:      *   - personal: (string) Personal string
276:      */
277:     static public function getAddressesFromObject($obs, $opts = array())
278:     {
279:         $opts = array_merge(array(
280:             'charset' => null
281:         ), $opts);
282: 
283:         $ret = array();
284: 
285:         if (!is_array($obs) || empty($obs)) {
286:             return $ret;
287:         }
288: 
289:         foreach ($obs as $ob) {
290:             if (isset($ob['groupname'])) {
291:                 $ret[] = array(
292:                     'addresses' => self::getAddressesFromObject($ob['addresses'], $opts),
293:                     'groupname' => $ob['groupname']
294:                 );
295:                 continue;
296:             }
297: 
298:             if (is_array($ob)) {
299:                 $ob = array_merge(array(
300:                     'host' => '',
301:                     'mailbox' => '',
302:                     'personal' => ''
303:                 ), $ob);
304:             }
305: 
306:             /* Ensure we're working with initialized values. */
307:             if (!empty($ob['personal'])) {
308:                 $ob['personal'] = trim(stripslashes(Horde_Mime::decode($ob['personal'], $opts['charset'])), '"');
309:             }
310: 
311:             $inner = self::writeAddress($ob['mailbox'], $ob['host']);
312: 
313:             $addr_string = self::addrObject2String($ob, $opts);
314: 
315:             if (!empty($addr_string)) {
316:                 /* Generate the new object. */
317:                 $ret[] = array(
318:                     'address' => $addr_string,
319:                     'display' => (empty($ob['personal']) ? '' : $ob['personal'] . ' <') . $inner . (empty($ob['personal']) ? '' : '>'),
320:                     'host' => $ob['host'],
321:                     'inner' => $inner,
322:                     'personal' => $ob['personal']
323:                 );
324:             }
325:         }
326: 
327:         return $ret;
328:     }
329: 
330:     /**
331:      * Returns the bare address.
332:      *
333:      * @param string $address    The address string.
334:      * @param string $defserver  The default domain to append to mailboxes.
335:      * @param boolean $multiple  Should we return multiple results?
336:      *
337:      * @return mixed  If $multiple is false, returns the mailbox@host e-mail
338:      *                address.  If $multiple is true, returns an array of
339:      *                these addresses.
340:      */
341:     static public function bareAddress($address, $defserver = null,
342:                                        $multiple = false)
343:     {
344:         $addressList = array();
345: 
346:         try {
347:             $from = self::parseAddressList($address, array(
348:                 'defserver' => $defserver
349:             ));
350:         } catch (Horde_Mime_Exception $e) {
351:             return $multiple ? array() : '';
352:         }
353: 
354:         foreach ($from as $entry) {
355:             if (!empty($entry['mailbox'])) {
356:                 $addressList[] = $entry['mailbox'] . (isset($entry['host']) ? '@' . $entry['host'] : '');
357:             }
358:         }
359: 
360:         return $multiple ? $addressList : array_pop($addressList);
361:     }
362: 
363:     /**
364:      * Parses a list of email addresses into its parts. Handles distribution
365:      * lists.
366:      *
367:      * @param string $address  The address string.
368:      * @param array $opts   Additional options:
369:      *   - defserver: (string) The default domain to append to mailboxes.
370:      *                DEFAULT: No domain appended.
371:      *   - nestgroups: (boolean) Nest the groups? (Will appear under the
372:      *                 'groupname' key)
373:      *                 DEFAULT: No.
374:      *   - validate: (boolean) Validate the address(es)?
375:      *               DEFAULT: No.
376:      *
377:      * @return array  A list of arrays with the possible keys: 'mailbox',
378:      *                'host', 'personal', 'adl', 'groupname', and 'comment'.
379:      * @throws Horde_Mime_Exception
380:      */
381:     static public function parseAddressList($address, array $opts = array())
382:     {
383:         $opts = array_merge(array(
384:             'defserver' => null,
385:             'nestgroups' => false,
386:             'validate' => false
387:         ), $opts);
388: 
389:         $rfc822 = new Horde_Mail_Rfc822();
390: 
391:         try {
392:             $ret = $rfc822->parseAddressList($address, array(
393:                 'default_domain' => $opts['defserver'],
394:                 'nest_groups' => $opts['nestgroups'],
395:                 'validate' => $opts['validate']
396:             ));
397:         } catch (Horde_Mail_Exception $e) {
398:             throw new Horde_Mime_Exception($e);
399:         }
400: 
401:         /* Convert objects to arrays. */
402:         foreach (array_keys($ret) as $key) {
403:             $ret[$key] = (array)$ret[$key];
404:             if (isset($ret[$key]['addresses'])) {
405:                 $ptr = &$ret[$key]['addresses'];
406:                 foreach (array_keys($ptr) as $key2) {
407:                     $ptr[$key2] = (array)$ptr[$key2];
408:                 }
409:             }
410:         }
411: 
412:         return $ret;
413:     }
414: 
415:     /**
416:      * Quotes and escapes the given string if necessary using rules contained
417:      * in RFC 2822 [3.2.5].
418:      *
419:      * @param string $str   The string to be quoted and escaped.
420:      * @param string $type  Either 'address', or 'personal';
421:      *
422:      * @return string  The correctly quoted and escaped string.
423:      */
424:     static public function encode($str, $type = 'address')
425:     {
426:         // Excluded (in ASCII): 0-8, 10-31, 34, 40-41, 44, 58-60, 62, 64,
427:         // 91-93, 127
428:         $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";
429: 
430:         switch ($type) {
431:         case 'personal':
432:             // RFC 2822 [3.4]: Period not allowed in display name
433:             $filter .= '.';
434:             break;
435: 
436:         case 'address':
437:         default:
438:             // RFC 2822 [3.4.1]: (HTAB, SPACE) not allowed in address
439:             $filter .= "\11\40";
440:             break;
441:         }
442: 
443:         // Strip double quotes if they are around the string already.
444:         // If quoted, we know that the contents are already escaped, so
445:         // unescape now.
446:         $str = trim($str);
447:         if ($str && ($str[0] == '"') && (substr($str, -1) == '"')) {
448:             $str = stripslashes(substr($str, 1, -1));
449:         }
450: 
451:         return (strcspn($str, $filter) != strlen($str))
452:             ? '"' . addcslashes($str, '\\"') . '"'
453:             : $str;
454:     }
455: 
456: }
457: 
API documentation generated by ApiGen