001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.net.mail;
020
021import java.io.UnsupportedEncodingException;
022import java.net.IDN;
023import java.util.ArrayList;
024import java.util.Iterator;
025
026import javax.mail.internet.AddressException;
027import javax.mail.internet.InternetAddress;
028import javax.mail.internet.MimeUtility;
029
030import lucee.commons.lang.StringUtil;
031import lucee.runtime.exp.PageException;
032import lucee.runtime.op.Caster;
033import lucee.runtime.op.Decision;
034import lucee.runtime.type.Array;
035import lucee.runtime.type.Struct;
036import lucee.runtime.type.util.ListUtil;
037
038public final class MailUtil {
039
040        public static String encode(String text,String encoding) throws UnsupportedEncodingException {
041                //print.ln(StringUtil.changeCharset(text,encoding));
042                return MimeUtility.encodeText(text,encoding,"Q");
043        }
044        
045        public static String decode(String text) throws UnsupportedEncodingException {
046                return MimeUtility.decodeText(text);
047        }
048
049
050
051    public static InternetAddress toInternetAddress(Object emails) throws MailException, UnsupportedEncodingException, PageException {
052
053        if ( emails instanceof String )
054            return parseEmail( emails );
055
056        InternetAddress[] addresses = toInternetAddresses( emails );
057        if ( addresses != null && addresses.length > 0 )
058            return addresses[0];
059
060        return null;
061    }
062
063
064    public static InternetAddress[] toInternetAddresses(Object emails) throws MailException, UnsupportedEncodingException, PageException {
065
066        if (emails instanceof String )
067            return fromList((String) emails);
068        
069        else if ( Decision.isArray(emails) ) 
070            return fromArray(Caster.toArray(emails));
071        
072        else if ( Decision.isStruct(emails) ) 
073            return new InternetAddress[]{ fromStruct(Caster.toStruct(emails)) };
074        
075        else
076            throw new MailException("e-mail defintions must be one of the following types [string,array,struct], not ["+emails.getClass().getName()+"]");
077    }
078
079
080    private static InternetAddress[] fromArray(Array array) throws MailException, PageException, UnsupportedEncodingException {
081
082        Iterator it = array.valueIterator();
083        Object el;
084        ArrayList<InternetAddress> pairs = new ArrayList();
085
086        while(it.hasNext()){
087            el=it.next();
088            if ( Decision.isStruct( el ) ) {
089
090                pairs.add( fromStruct(Caster.toStruct(el)) );
091            }
092            else {
093
094                InternetAddress addr = parseEmail( Caster.toString(el) );
095                if ( addr != null )
096                    pairs.add( addr );
097            }
098        }
099
100        return pairs.toArray( new InternetAddress[ pairs.size() ] );
101    }
102
103
104    private static InternetAddress fromStruct( Struct sct ) throws MailException, UnsupportedEncodingException {
105
106        String name = Caster.toString(sct.get("label",null),null);
107        if ( name == null )
108            name=Caster.toString(sct.get("name",null),null);
109
110        String email = Caster.toString(sct.get("email",null),null);
111        if ( email == null )
112            email = Caster.toString(sct.get("e-mail",null),null);
113        if ( email == null )
114            email = Caster.toString(sct.get("mail",null),null);
115
116        if( StringUtil.isEmpty(email) )
117            throw new MailException("missing e-mail definition in struct");
118
119        if(name==null) name="";
120
121        return new InternetAddress( email, name );
122    }
123
124
125    private static InternetAddress[] fromList( String strEmails ) {
126
127        if ( StringUtil.isEmpty( strEmails, true ) )
128            return new InternetAddress[0];
129
130        Array raw = ListUtil.listWithQuotesToArray(strEmails, ",;", "\"");
131
132        Iterator<Object> it = raw.valueIterator();
133        ArrayList<InternetAddress> al = new ArrayList();
134
135        while( it.hasNext() ) {
136
137            InternetAddress addr = parseEmail( it.next() );
138
139            if( addr != null )
140                al.add( addr );
141        }
142
143        return al.toArray( new InternetAddress[ al.size() ] );
144    }
145
146
147    /**
148     * returns true if the passed value is a in valid email address format
149     * @param value
150     * @return
151     */
152    public static boolean isValidEmail( Object value ) {
153
154        InternetAddress addr = parseEmail( value );
155
156        if ( addr != null ) {
157
158                String address = addr.getAddress();
159
160                if ( address.contains( ".." ) )
161                        return false;
162
163                int pos = address.indexOf( '@' );
164
165                if ( pos < 1 || pos == address.length() - 1 )
166                        return false;
167
168                String local  = address.substring(0, pos);
169                String domain = address.substring(pos + 1);
170
171                if ( domain.charAt( 0 ) == '.'
172                          || local.charAt(  0 ) == '.'
173                          || local.charAt( local.length() - 1 ) == '.' )
174                        return false;
175
176                pos = domain.lastIndexOf( '.' );
177
178                if ( pos > 0 && pos < domain.length() - 2 )         // test TLD to be at least 2 chars all alpha characters
179                    return StringUtil.isAllAlpha( domain.substring( pos + 1 ) );
180        }
181
182        return false;
183    }
184
185
186    /**
187     * returns an InternetAddress object or null if the parsing fails.  to be be used in multiple places.
188     * @param value
189     * @return
190     */
191    public static InternetAddress parseEmail( Object value ) {
192
193        String str = Caster.toString( value, "" );
194
195        if ( str.indexOf( '@' ) > -1 ) {
196
197            try {
198
199                InternetAddress addr = new InternetAddress( str );
200
201                fixIDN( addr );
202                return addr;
203            }
204            catch ( AddressException ex ) {}
205        }
206
207        return null;
208    }
209
210
211    /**
212     * converts IDN to ASCII if needed
213     * @param addr
214     */
215    public static void fixIDN( InternetAddress addr ) {
216
217        String address = addr.getAddress();
218        int pos = address.indexOf( '@' );
219
220        if ( pos > 0 && pos < address.length() - 1 ) {
221
222            String domain = address.substring( pos + 1 );
223
224            if ( !StringUtil.isAscii( domain ) ) {
225
226                domain = IDN.toASCII( domain );
227                addr.setAddress( address.substring( 0, pos ) + "@" + domain );
228            }
229        }
230    }
231
232}