MailAddress.class.php

Go to the documentation of this file.
00001 <?php
00002 /***************************************************************************
00003  *   Copyright (C) 2009 by Ivan Y. Khvostishkov                            *
00004  *                                                                         *
00005  *   This program is free software; you can redistribute it and/or modify  *
00006  *   it under the terms of the GNU Lesser General Public License as        *
00007  *   published by the Free Software Foundation; either version 3 of the    *
00008  *   License, or (at your option) any later version.                       *
00009  *                                                                         *
00010  ***************************************************************************/
00011 
00019     final class MailAddress
00020     {
00021         const RFC_MAX_ENCODED_WORD_LENGTH = 75;
00022         
00023         private $address    = null;
00024         private $person     = null;
00025         private $charset    = 'UTF-8';
00026         
00027         public static function create()
00028         {
00029             return new self;
00030         }
00031         
00032         public function setAddress($address)
00033         {
00034             $this->address = $address;
00035             
00036             return $this;
00037         }
00038         
00039         public function getAddress()
00040         {
00041             return $this->address;
00042         }
00043         
00044         public function setPerson($person)
00045         {
00046             $this->person = $person;
00047             
00048             return $this;
00049         }
00050         
00051         public function getPerson()
00052         {
00053             return $this->person;
00054         }
00055         
00056         public function setCharset($charset)
00057         {
00058             $this->charset = $charset;
00059             
00060             return $this;
00061         }
00062         
00063         public function getCharset()
00064         {
00065             return $this->charset;
00066         }
00067         
00068         public function toString()
00069         {
00070             $specials = preg_quote('()<>@,;:".[]\\', '/');
00071             $cr = '\015';
00072             $space = '\ ';
00073             
00074             $ctls = '\000-\037\177';
00075             $char = '\000-\177';
00076             
00077             $atom = "[^{$specials}{$space}{$ctls}]+";
00078             $asciiAtom = "[$char]+";
00079             
00080             if (
00081                 !preg_match($this->getAddressRegExp($asciiAtom), $this->address)
00082                 || !preg_match($this->getAddressRegExp($atom), $this->address)
00083             )
00084                 throw new WrongArgumentException(
00085                     'wrongly formatted address is encountered'
00086                     .' or local-part contains quoted-string'
00087                     .' or domain contains domain-literal (unimplemented yet)'
00088                 );
00089             
00090             if (!$this->person)
00091                 return $this->address;
00092             
00093             
00094             $person = $this->person;
00095             
00096             if (
00097                 !preg_match($this->getUnquotedPhraseRegexp($asciiAtom), $person)
00098                 || !preg_match($this->getUnquotedPhraseRegexp($atom), $person)
00099             ) {
00100                 // TODO: linear-white-space instead of simple space may be here
00101                 $qtextWithNoExceptions = "[$char\ ]";
00102                 $qtextExceptions = "[$cr\"\\\\]";
00103                 
00104                 // TODO: quoted-pair inside quoted-string is allowed too
00105                 if (
00106                     preg_match("/^{$qtextWithNoExceptions}*$/u", $person)
00107                     && !preg_match("/{$qtextExceptions}/", $person)
00108                 ) {
00109                     $person = '"'.$person.'"';
00110                     
00111                 } else {
00112                     $person = $this->getEncodedPerson();
00113                 }
00114             }
00115             
00116             return "$person <{$this->address}>";
00117         }
00118         
00119         private function getAddressRegExp($atom)
00120         {
00121             // TODO: word may be a quoted-string also
00122             $word = $atom;
00123             
00124             $localPart = "{$word}(\.{$word})*";
00125             
00126             $domainRef = $atom;
00127             
00128             // TODO: sub-domain may be a domain-literal also
00129             $subDomain = $domainRef;
00130             
00131             $domain = "{$subDomain}(\.{$subDomain})*";
00132             
00133             $addrSpec = "{$localPart}@{$domain}";
00134             
00135             return "/^{$addrSpec}$/";
00136         }
00137         
00138         private function getUnquotedPhraseRegexp($atom)
00139         {
00140             return "/^({$atom})(\ {$atom})*$/u";
00141         }
00142         
00143         private function getEncodedWord($word)
00144         {
00145             return "=?{$this->charset}?B?".base64_encode($word)."?=";
00146         }
00147         
00148         private function getEncodedPerson()
00149         {
00150             $result = null;
00151             
00152             $personChunk = null;
00153             
00154             for ($i = 0; $i < mb_strlen($this->person); $i++) {
00155                 $symbol = mb_substr($this->person, $i, 1);
00156                 
00157                 $newLength = strlen(
00158                     $this->getEncodedWord($personChunk.$symbol)
00159                 );
00160                 
00161                 if ($newLength >= self::RFC_MAX_ENCODED_WORD_LENGTH) {
00162                     $result = $this->appendChunk($result, $personChunk);
00163                     
00164                     $personChunk = null;
00165                 }
00166                 
00167                 $personChunk .= $symbol;
00168             }
00169             
00170             if ($personChunk)
00171                 $result = $this->appendChunk($result, $personChunk);
00172             
00173             return $result;
00174         }
00175         
00176         private function appendChunk($encodedPerson, $personChunk)
00177         {
00178             $crlfSpace = "\015\012 ";
00179             
00180             return
00181                 $encodedPerson
00182                 .($encodedPerson ? $crlfSpace : null)
00183                 .$this->getEncodedWord($personChunk);
00184         }
00185     }
00186 ?>