001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.mail;
018
019import java.io.UnsupportedEncodingException;
020import java.nio.charset.Charset;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Date;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.Properties;
028
029import javax.mail.Authenticator;
030import javax.mail.Message;
031import javax.mail.MessagingException;
032import javax.mail.Session;
033import javax.mail.Store;
034import javax.mail.Transport;
035import javax.mail.internet.AddressException;
036import javax.mail.internet.InternetAddress;
037import javax.mail.internet.MimeMessage;
038import javax.mail.internet.MimeMultipart;
039import javax.mail.internet.MimeUtility;
040import javax.naming.Context;
041import javax.naming.InitialContext;
042import javax.naming.NamingException;
043
044import org.apache.commons.mail.util.IDNEmailAddressConverter;
045
046/**
047 * The base class for all email messages.  This class sets the
048 * sender's email & name, receiver's email & name, subject, and the
049 * sent date.
050 * <p>
051 * Subclasses are responsible for setting the message body.
052 *
053 * @since 1.0
054 */
055public abstract class Email
056{
057    /** @deprecated since 1.3, use {@link EmailConstants#SENDER_EMAIL} instead */
058    @Deprecated
059    public static final String SENDER_EMAIL = EmailConstants.SENDER_EMAIL;
060
061    /** @deprecated since 1.3, use {@link EmailConstants#SENDER_NAME} instead */
062    @Deprecated
063    public static final String SENDER_NAME = EmailConstants.SENDER_NAME;
064
065    /** @deprecated since 1.3, use {@link EmailConstants#RECEIVER_EMAIL} instead */
066    @Deprecated
067    public static final String RECEIVER_EMAIL = EmailConstants.RECEIVER_EMAIL;
068
069    /** @deprecated since 1.3, use {@link EmailConstants#RECEIVER_NAME} instead */
070    @Deprecated
071    public static final String RECEIVER_NAME = EmailConstants.RECEIVER_NAME;
072
073    /** @deprecated since 1.3, use {@link EmailConstants#EMAIL_SUBJECT} instead */
074    @Deprecated
075    public static final String EMAIL_SUBJECT = EmailConstants.EMAIL_SUBJECT;
076
077    /** @deprecated since 1.3, use {@link EmailConstants#EMAIL_BODY} instead */
078    @Deprecated
079    public static final String EMAIL_BODY = EmailConstants.EMAIL_BODY;
080
081    /** @deprecated since 1.3, use {@link EmailConstants#CONTENT_TYPE} instead */
082    @Deprecated
083    public static final String CONTENT_TYPE = EmailConstants.CONTENT_TYPE;
084
085    /** @deprecated since 1.3, use {@link EmailConstants#ATTACHMENTS} instead */
086    @Deprecated
087    public static final String ATTACHMENTS = EmailConstants.ATTACHMENTS;
088
089    /** @deprecated since 1.3, use {@link EmailConstants#FILE_SERVER} instead */
090    @Deprecated
091    public static final String FILE_SERVER = EmailConstants.FILE_SERVER;
092
093    /** @deprecated since 1.3, use {@link EmailConstants#KOI8_R} instead */
094    @Deprecated
095    public static final String KOI8_R = EmailConstants.KOI8_R;
096
097    /** @deprecated since 1.3, use {@link EmailConstants#ISO_8859_1} instead */
098    @Deprecated
099    public static final String ISO_8859_1 = EmailConstants.ISO_8859_1;
100
101    /** @deprecated since 1.3, use {@link EmailConstants#US_ASCII} instead */
102    @Deprecated
103    public static final String US_ASCII = EmailConstants.US_ASCII;
104
105    /** @deprecated since 1.3, use {@link EmailConstants#MAIL_DEBUG} instead */
106    @Deprecated
107    public static final String MAIL_DEBUG = EmailConstants.MAIL_DEBUG;
108
109    /** @deprecated since 1.3, use {@link EmailConstants#MAIL_HOST} instead */
110    @Deprecated
111    public static final String MAIL_HOST = EmailConstants.MAIL_HOST;
112
113    /** @deprecated since 1.3, use {@link EmailConstants#MAIL_PORT} instead */
114    @Deprecated
115    public static final String MAIL_PORT = EmailConstants.MAIL_PORT;
116
117    /** @deprecated since 1.3, use {@link EmailConstants#MAIL_SMTP_FROM} instead */
118    @Deprecated
119    public static final String MAIL_SMTP_FROM = EmailConstants.MAIL_SMTP_FROM;
120
121    /** @deprecated since 1.3, use {@link EmailConstants#MAIL_SMTP_AUTH} instead */
122    @Deprecated
123    public static final String MAIL_SMTP_AUTH = EmailConstants.MAIL_SMTP_AUTH;
124
125    /** @deprecated since 1.3, use {@link EmailConstants#MAIL_SMTP_USER} instead */
126    @Deprecated
127    public static final String MAIL_SMTP_USER = EmailConstants.MAIL_SMTP_USER;
128
129    /** @deprecated since 1.3, use {@link EmailConstants#MAIL_SMTP_PASSWORD} instead */
130    @Deprecated
131    public static final String MAIL_SMTP_PASSWORD = EmailConstants.MAIL_SMTP_PASSWORD;
132
133    /** @deprecated since 1.3, use {@link EmailConstants#MAIL_TRANSPORT_PROTOCOL} instead */
134    @Deprecated
135    public static final String MAIL_TRANSPORT_PROTOCOL = EmailConstants.MAIL_TRANSPORT_PROTOCOL;
136
137    /** @deprecated since 1.3, use {@link EmailConstants#SMTP} instead */
138    @Deprecated
139    public static final String SMTP = EmailConstants.SMTP;
140
141    /** @deprecated since 1.3, use {@link EmailConstants#TEXT_HTML} instead */
142    @Deprecated
143    public static final String TEXT_HTML = EmailConstants.TEXT_HTML;
144
145    /** @deprecated since 1.3, use {@link EmailConstants#TEXT_PLAIN} instead */
146    @Deprecated
147    public static final String TEXT_PLAIN = EmailConstants.TEXT_PLAIN;
148
149    /** @deprecated since 1.3, use {@link EmailConstants#MAIL_TRANSPORT_TLS} instead */
150    @Deprecated
151    public static final String MAIL_TRANSPORT_TLS = EmailConstants.MAIL_TRANSPORT_TLS;
152
153    /** @deprecated since 1.3, use {@link EmailConstants#MAIL_SMTP_SOCKET_FACTORY_FALLBACK} instead */
154    @Deprecated
155    public static final String MAIL_SMTP_SOCKET_FACTORY_FALLBACK = EmailConstants.MAIL_SMTP_SOCKET_FACTORY_FALLBACK;
156
157    /** @deprecated since 1.3, use {@link EmailConstants#MAIL_SMTP_SOCKET_FACTORY_CLASS} instead */
158    @Deprecated
159    public static final String MAIL_SMTP_SOCKET_FACTORY_CLASS = EmailConstants.MAIL_SMTP_SOCKET_FACTORY_CLASS;
160
161    /** @deprecated since 1.3, use {@link EmailConstants#MAIL_SMTP_SOCKET_FACTORY_PORT} instead */
162    @Deprecated
163    public static final String MAIL_SMTP_SOCKET_FACTORY_PORT = EmailConstants.MAIL_SMTP_SOCKET_FACTORY_PORT;
164
165    /** @deprecated since 1.3, use {@link EmailConstants#MAIL_SMTP_CONNECTIONTIMEOUT} instead */
166    @Deprecated
167    public static final String MAIL_SMTP_CONNECTIONTIMEOUT = EmailConstants.MAIL_SMTP_CONNECTIONTIMEOUT;
168
169    /** @deprecated since 1.3, use {@link EmailConstants#MAIL_SMTP_TIMEOUT} instead */
170    @Deprecated
171    public static final String MAIL_SMTP_TIMEOUT = EmailConstants.MAIL_SMTP_TIMEOUT;
172
173    /** The email message to send. */
174    protected MimeMessage message;
175
176    /** The charset to use for this message. */
177    protected String charset;
178
179    /** The Address of the sending party, mandatory. */
180    protected InternetAddress fromAddress;
181
182    /** The Subject. */
183    protected String subject;
184
185    /** An attachment. */
186    protected MimeMultipart emailBody;
187
188    /** The content. */
189    protected Object content;
190
191    /** The content type. */
192    protected String contentType;
193
194    /** Set session debugging on or off. */
195    protected boolean debug;
196
197    /** Sent date. */
198    protected Date sentDate;
199
200    /**
201     * Instance of an <code>Authenticator</code> object that will be used
202     * when authentication is requested from the mail server.
203     */
204    protected Authenticator authenticator;
205
206    /**
207     * The hostname of the mail server with which to connect. If null will try
208     * to get property from system.properties. If still null, quit.
209     */
210    protected String hostName;
211
212    /**
213     * The port number of the mail server to connect to.
214     * Defaults to the standard port ( 25 ).
215     */
216    protected String smtpPort = "25";
217
218    /**
219     * The port number of the SSL enabled SMTP server;
220     * defaults to the standard port, 465.
221     */
222    protected String sslSmtpPort = "465";
223
224    /** List of "to" email addresses. */
225    protected List<InternetAddress> toList = new ArrayList<InternetAddress>();
226
227    /** List of "cc" email addresses. */
228    protected List<InternetAddress> ccList = new ArrayList<InternetAddress>();
229
230    /** List of "bcc" email addresses. */
231    protected List<InternetAddress> bccList = new ArrayList<InternetAddress>();
232
233    /** List of "replyTo" email addresses. */
234    protected List<InternetAddress> replyList = new ArrayList<InternetAddress>();
235
236    /**
237     * Address to which undeliverable mail should be sent.
238     * Because this is handled by JavaMail as a String property
239     * in the mail session, this property is of type <code>String</code>
240     * rather than <code>InternetAddress</code>.
241     */
242    protected String bounceAddress;
243
244    /**
245     * Used to specify the mail headers.  Example:
246     *
247     * X-Mailer: Sendmail, X-Priority: 1( highest )
248     * or  2( high ) 3( normal ) 4( low ) and 5( lowest )
249     * Disposition-Notification-To: user@domain.net
250     */
251    protected Map<String, String> headers = new HashMap<String, String>();
252
253    /**
254     * Used to determine whether to use pop3 before smtp, and if so the settings.
255     */
256    protected boolean popBeforeSmtp;
257
258    /** the host name of the pop3 server. */
259    protected String popHost;
260
261    /** the user name to log into the pop3 server. */
262    protected String popUsername;
263
264    /** the password to log into the pop3 server. */
265    protected String popPassword;
266
267    /**
268     * Does server require TLS encryption for authentication?
269     * @deprecated  since 1.3, use setStartTLSEnabled() instead
270     */
271    @Deprecated
272    protected boolean tls;
273
274    /**
275     * Does the current transport use SSL/TLS encryption upon connection?
276     * @deprecated since 1.3, use setSSLOnConnect() instead
277     */
278    @Deprecated
279    protected boolean ssl;
280
281    /** socket I/O timeout value in milliseconds. */
282    protected int socketTimeout = EmailConstants.SOCKET_TIMEOUT_MS;
283
284    /** socket connection timeout value in milliseconds. */
285    protected int socketConnectionTimeout = EmailConstants.SOCKET_TIMEOUT_MS;
286
287    /**
288     * If true, enables the use of the STARTTLS command (if supported by
289     * the server) to switch the connection to a TLS-protected connection
290     * before issuing any login commands. Note that an appropriate trust
291     * store must configured so that the client will trust the server's
292     * certificate.
293     * Defaults to false.
294     */
295    private boolean startTlsEnabled;
296
297    /**
298     * If true, requires the use of the STARTTLS command. If the server doesn't
299     * support the STARTTLS command, or the command fails, the connect method
300     * will fail.
301     * Defaults to false.
302     */
303    private boolean startTlsRequired;
304
305    /** does the current transport use SSL/TLS encryption upon connection? */
306    private boolean sslOnConnect;
307
308    /**
309     * If set to true, check the server identity as specified by RFC 2595. These
310     * additional checks based on the content of the server's certificate are
311     * intended to prevent man-in-the-middle attacks.
312     * Defaults to false.
313     */
314    private boolean sslCheckServerIdentity;
315
316    /**
317     * If set to true, and a message has some valid and some invalid addresses, send the message anyway,
318     * reporting the partial failure with a SendFailedException.
319     * If set to false (the default), the message is not sent to any of the recipients
320     * if there is an invalid recipient address.
321     * Defaults to false.
322     */
323    private boolean sendPartial;
324
325    /** The Session to mail with. */
326    private Session session;
327
328    /**
329     * Setting to true will enable the display of debug information.
330     *
331     * @param d A boolean.
332     * @since 1.0
333     */
334    public void setDebug(final boolean d)
335    {
336        this.debug = d;
337    }
338
339    /**
340     * Sets the userName and password if authentication is needed.  If this
341     * method is not used, no authentication will be performed.
342     * <p>
343     * This method will create a new instance of
344     * <code>DefaultAuthenticator</code> using the supplied parameters.
345     *
346     * @param userName User name for the SMTP server
347     * @param password password for the SMTP server
348     * @see DefaultAuthenticator
349     * @see #setAuthenticator
350     * @since 1.0
351     */
352    public void setAuthentication(final String userName, final String password)
353    {
354        this.setAuthenticator(new DefaultAuthenticator(userName, password));
355    }
356
357    /**
358     * Sets the <code>Authenticator</code> to be used when authentication
359     * is requested from the mail server.
360     * <p>
361     * This method should be used when your outgoing mail server requires
362     * authentication.  Your mail server must also support RFC2554.
363     *
364     * @param newAuthenticator the <code>Authenticator</code> object.
365     * @see Authenticator
366     * @since 1.0
367     */
368    public void setAuthenticator(final Authenticator newAuthenticator)
369    {
370        this.authenticator = newAuthenticator;
371    }
372
373    /**
374     * Set the charset of the message. Please note that you should set the charset before
375     * adding the message content.
376     *
377     * @param newCharset A String.
378     * @throws java.nio.charset.IllegalCharsetNameException if the charset name is invalid
379     * @throws java.nio.charset.UnsupportedCharsetException if no support for the named charset
380     * exists in the current JVM
381     * @since 1.0
382     */
383    public void setCharset(final String newCharset)
384    {
385        final Charset set = Charset.forName(newCharset);
386        this.charset = set.name();
387    }
388
389    /**
390     * Set the emailBody to a MimeMultiPart
391     *
392     * @param aMimeMultipart aMimeMultipart
393     * @since 1.0
394     */
395    public void setContent(final MimeMultipart aMimeMultipart)
396    {
397        this.emailBody = aMimeMultipart;
398    }
399
400    /**
401     * Set the content and contentType.
402     *
403     * @param   aObject aObject
404     * @param   aContentType aContentType
405     * @since 1.0
406     */
407    public void setContent(final Object aObject, final String aContentType)
408    {
409        this.content = aObject;
410        this.updateContentType(aContentType);
411    }
412
413    /**
414     * Update the contentType.
415     *
416     * @param   aContentType aContentType
417     * @since 1.2
418     */
419    public void updateContentType(final String aContentType)
420    {
421        if (EmailUtils.isEmpty(aContentType))
422        {
423            this.contentType = null;
424        }
425        else
426        {
427            // set the content type
428            this.contentType = aContentType;
429
430            // set the charset if the input was properly formed
431            final String strMarker = "; charset=";
432            int charsetPos = aContentType.toLowerCase().indexOf(strMarker);
433
434            if (charsetPos != -1)
435            {
436                // find the next space (after the marker)
437                charsetPos += strMarker.length();
438                final int intCharsetEnd =
439                    aContentType.toLowerCase().indexOf(" ", charsetPos);
440
441                if (intCharsetEnd != -1)
442                {
443                    this.charset =
444                        aContentType.substring(charsetPos, intCharsetEnd);
445                }
446                else
447                {
448                    this.charset = aContentType.substring(charsetPos);
449                }
450            }
451            else
452            {
453                // use the default charset, if one exists, for messages
454                // whose content-type is some form of text.
455                if (this.contentType.startsWith("text/") && EmailUtils.isNotEmpty(this.charset))
456                {
457                    final StringBuffer contentTypeBuf = new StringBuffer(this.contentType);
458                    contentTypeBuf.append(strMarker);
459                    contentTypeBuf.append(this.charset);
460                    this.contentType = contentTypeBuf.toString();
461                }
462            }
463        }
464    }
465
466    /**
467     * Set the hostname of the outgoing mail server.
468     *
469     * @param   aHostName aHostName
470     * @throws IllegalStateException if the mail session is already initialized
471     * @since 1.0
472     */
473    public void setHostName(final String aHostName)
474    {
475        checkSessionAlreadyInitialized();
476        this.hostName = aHostName;
477    }
478
479    /**
480     * Set or disable the STARTTLS encryption. Please see EMAIL-105
481     * for the reasons of deprecation.
482     *
483     * @deprecated since 1.3, use setStartTLSEnabled() instead
484     * @param withTLS true if STARTTLS requested, false otherwise
485     * @since 1.1
486     */
487    @Deprecated
488    public void setTLS(final boolean withTLS)
489    {
490        setStartTLSEnabled(withTLS);
491    }
492
493    /**
494     * Set or disable the STARTTLS encryption.
495     *
496     * @param startTlsEnabled true if STARTTLS requested, false otherwise
497     * @return An Email.
498     * @throws IllegalStateException if the mail session is already initialized
499     * @since 1.3
500     */
501    public Email setStartTLSEnabled(final boolean startTlsEnabled)
502    {
503        checkSessionAlreadyInitialized();
504        this.startTlsEnabled = startTlsEnabled;
505        this.tls = startTlsEnabled;
506        return this;
507    }
508
509    /**
510     * Set or disable the required STARTTLS encryption.
511     * <p>
512     * Defaults to {@link #smtpPort}; can be overridden by using {@link #setSmtpPort(int)}
513     *
514     * @param startTlsRequired true if STARTTLS requested, false otherwise
515     * @return An Email.
516     * @throws IllegalStateException if the mail session is already initialized
517     * @since 1.3
518     */
519    public Email setStartTLSRequired(final boolean startTlsRequired)
520    {
521        checkSessionAlreadyInitialized();
522        this.startTlsRequired = startTlsRequired;
523        return this;
524    }
525
526    /**
527     * Set the non-SSL port number of the outgoing mail server.
528     *
529     * @param  aPortNumber aPortNumber
530     * @throws IllegalArgumentException if the port number is &lt; 1
531     * @throws IllegalStateException if the mail session is already initialized
532     * @since 1.0
533     * @see #setSslSmtpPort(String)
534     */
535    public void setSmtpPort(final int aPortNumber)
536    {
537        checkSessionAlreadyInitialized();
538
539        if (aPortNumber < 1)
540        {
541            throw new IllegalArgumentException(
542                "Cannot connect to a port number that is less than 1 ( "
543                    + aPortNumber
544                    + " )");
545        }
546
547        this.smtpPort = Integer.toString(aPortNumber);
548    }
549
550    /**
551     * Supply a mail Session object to use. Please note that passing
552     * a user name and password (in the case of mail authentication) will
553     * create a new mail session with a DefaultAuthenticator. This is a
554     * convenience but might come unexpected.
555     *
556     * If mail authentication is used but NO username and password
557     * is supplied the implementation assumes that you have set a
558     * authenticator and will use the existing mail session (as expected).
559     *
560     * @param aSession mail session to be used
561     * @throws IllegalArgumentException if the session is {@code null}
562     * @since 1.0
563     */
564    public void setMailSession(final Session aSession)
565    {
566        EmailUtils.notNull(aSession, "no mail session supplied");
567
568        final Properties sessionProperties = aSession.getProperties();
569        final String auth = sessionProperties.getProperty(EmailConstants.MAIL_SMTP_AUTH);
570
571        if ("true".equalsIgnoreCase(auth))
572        {
573            final String userName = sessionProperties.getProperty(EmailConstants.MAIL_SMTP_USER);
574            final String password = sessionProperties.getProperty(EmailConstants.MAIL_SMTP_PASSWORD);
575
576            if (EmailUtils.isNotEmpty(userName) && EmailUtils.isNotEmpty(password))
577            {
578                // only create a new mail session with an authenticator if
579                // authentication is required and no user name is given
580                this.authenticator = new DefaultAuthenticator(userName, password);
581                this.session = Session.getInstance(sessionProperties, this.authenticator);
582            }
583            else
584            {
585                // assume that the given mail session contains a working authenticator
586                this.session = aSession;
587            }
588        }
589        else
590        {
591            this.session = aSession;
592        }
593    }
594
595    /**
596     * Supply a mail Session object from a JNDI directory.
597     *
598     * @param jndiName name of JNDI resource (javax.mail.Session type), resource
599     * if searched in java:comp/env if name does not start with "java:"
600     * @throws IllegalArgumentException if the JNDI name is null or empty
601     * @throws NamingException if the resource cannot be retrieved from JNDI directory
602     * @since 1.1
603     */
604    public void setMailSessionFromJNDI(final String jndiName) throws NamingException
605    {
606        if (EmailUtils.isEmpty(jndiName))
607        {
608            throw new IllegalArgumentException("JNDI name missing");
609        }
610        Context ctx = null;
611        if (jndiName.startsWith("java:"))
612        {
613            ctx = new InitialContext();
614        }
615        else
616        {
617            ctx = (Context) new InitialContext().lookup("java:comp/env");
618
619        }
620        this.setMailSession((Session) ctx.lookup(jndiName));
621    }
622
623    /**
624     * Determines the mail session used when sending this Email, creating
625     * the Session if necessary. When a mail session is already
626     * initialized setting the session related properties will cause
627     * an IllegalStateException.
628     *
629     * @return A Session.
630     * @throws EmailException if the host name was not set
631     * @since 1.0
632     */
633    public Session getMailSession() throws EmailException
634    {
635        if (this.session == null)
636        {
637            final Properties properties = new Properties(System.getProperties());
638            properties.setProperty(EmailConstants.MAIL_TRANSPORT_PROTOCOL, EmailConstants.SMTP);
639
640            if (EmailUtils.isEmpty(this.hostName))
641            {
642                this.hostName = properties.getProperty(EmailConstants.MAIL_HOST);
643            }
644
645            if (EmailUtils.isEmpty(this.hostName))
646            {
647                throw new EmailException("Cannot find valid hostname for mail session");
648            }
649
650            properties.setProperty(EmailConstants.MAIL_PORT, this.smtpPort);
651            properties.setProperty(EmailConstants.MAIL_HOST, this.hostName);
652            properties.setProperty(EmailConstants.MAIL_DEBUG, String.valueOf(this.debug));
653
654            properties.setProperty(EmailConstants.MAIL_TRANSPORT_STARTTLS_ENABLE,
655                    isStartTLSEnabled() ? "true" : "false");
656            properties.setProperty(EmailConstants.MAIL_TRANSPORT_STARTTLS_REQUIRED,
657                    isStartTLSRequired() ? "true" : "false");
658
659            properties.setProperty(EmailConstants.MAIL_SMTP_SEND_PARTIAL,
660                    isSendPartial() ? "true" : "false");
661            properties.setProperty(EmailConstants.MAIL_SMTPS_SEND_PARTIAL,
662                    isSendPartial() ? "true" : "false");
663
664            if (this.authenticator != null)
665            {
666                properties.setProperty(EmailConstants.MAIL_SMTP_AUTH, "true");
667            }
668
669            if (isSSLOnConnect())
670            {
671                properties.setProperty(EmailConstants.MAIL_PORT, this.sslSmtpPort);
672                properties.setProperty(EmailConstants.MAIL_SMTP_SOCKET_FACTORY_PORT, this.sslSmtpPort);
673                properties.setProperty(EmailConstants.MAIL_SMTP_SOCKET_FACTORY_CLASS, "javax.net.ssl.SSLSocketFactory");
674                properties.setProperty(EmailConstants.MAIL_SMTP_SOCKET_FACTORY_FALLBACK, "false");
675            }
676
677            if ((isSSLOnConnect() || isStartTLSEnabled()) && isSSLCheckServerIdentity())
678            {
679                properties.setProperty(EmailConstants.MAIL_SMTP_SSL_CHECKSERVERIDENTITY, "true");
680            }
681
682            if (this.bounceAddress != null)
683            {
684                properties.setProperty(EmailConstants.MAIL_SMTP_FROM, this.bounceAddress);
685            }
686
687            if (this.socketTimeout > 0)
688            {
689                properties.setProperty(EmailConstants.MAIL_SMTP_TIMEOUT, Integer.toString(this.socketTimeout));
690            }
691
692            if (this.socketConnectionTimeout > 0)
693            {
694                properties.setProperty(EmailConstants.MAIL_SMTP_CONNECTIONTIMEOUT, Integer.toString(this.socketConnectionTimeout));
695            }
696
697            // changed this (back) to getInstance due to security exceptions
698            // caused when testing using maven
699            this.session = Session.getInstance(properties, this.authenticator);
700        }
701        return this.session;
702    }
703
704    /**
705     * Set the FROM field of the email to use the specified address. The email
706     * address will also be used as the personal name.
707     * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}.
708     * If it is not set, it will be encoded using
709     * the Java platform's default charset (UTF-16) if it contains
710     * non-ASCII characters; otherwise, it is used as is.
711     *
712     * @param email A String.
713     * @return An Email.
714     * @throws EmailException Indicates an invalid email address.
715     * @since 1.0
716     */
717    public Email setFrom(final String email)
718        throws EmailException
719    {
720        return setFrom(email, null);
721    }
722
723    /**
724     * Set the FROM field of the email to use the specified address and the
725     * specified personal name.
726     * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}.
727     * If it is not set, it will be encoded using
728     * the Java platform's default charset (UTF-16) if it contains
729     * non-ASCII characters; otherwise, it is used as is.
730     *
731     * @param email A String.
732     * @param name A String.
733     * @return An Email.
734     * @throws EmailException Indicates an invalid email address.
735     * @since 1.0
736     */
737    public Email setFrom(final String email, final String name)
738        throws EmailException
739    {
740        return setFrom(email, name, this.charset);
741    }
742
743    /**
744     * Set the FROM field of the email to use the specified address, personal
745     * name, and charset encoding for the name.
746     *
747     * @param email A String.
748     * @param name A String.
749     * @param charset The charset to encode the name with.
750     * @return An Email.
751     * @throws EmailException Indicates an invalid email address or charset.
752     * @since 1.1
753     */
754    public Email setFrom(final String email, final String name, final String charset)
755        throws EmailException
756    {
757        this.fromAddress = createInternetAddress(email, name, charset);
758        return this;
759    }
760
761    /**
762     * Add a recipient TO to the email. The email
763     * address will also be used as the personal name.
764     * The name will be encoded by the charset of
765     * {@link #setCharset(java.lang.String) setCharset()}.
766     * If it is not set, it will be encoded using
767     * the Java platform's default charset (UTF-16) if it contains
768     * non-ASCII characters; otherwise, it is used as is.
769     *
770     * @param email A String.
771     * @return An Email.
772     * @throws EmailException Indicates an invalid email address.
773     * @since 1.0
774     */
775    public Email addTo(final String email)
776        throws EmailException
777    {
778        return addTo(email, null);
779    }
780
781    /**
782     * Add a list of TO recipients to the email. The email
783     * addresses will also be used as the personal names.
784     * The names will be encoded by the charset of
785     * {@link #setCharset(java.lang.String) setCharset()}.
786     * If it is not set, it will be encoded using
787     * the Java platform's default charset (UTF-16) if it contains
788     * non-ASCII characters; otherwise, it is used as is.
789     *
790     * @param emails A String array.
791     * @return An Email.
792     * @throws EmailException Indicates an invalid email address.
793     * @since 1.3
794     */
795    public Email addTo(final String... emails)
796        throws EmailException
797    {
798        if (emails == null || emails.length == 0)
799        {
800            throw new EmailException("Address List provided was invalid");
801        }
802
803        for (final String email : emails)
804        {
805            addTo(email, null);
806        }
807
808        return this;
809    }
810
811    /**
812     * Add a recipient TO to the email using the specified address and the
813     * specified personal name.
814     * The name will be encoded by the charset of
815     * {@link #setCharset(java.lang.String) setCharset()}.
816     * If it is not set, it will be encoded using
817     * the Java platform's default charset (UTF-16) if it contains
818     * non-ASCII characters; otherwise, it is used as is.
819     *
820     * @param email A String.
821     * @param name A String.
822     * @return An Email.
823     * @throws EmailException Indicates an invalid email address.
824     * @since 1.0
825     */
826    public Email addTo(final String email, final String name)
827        throws EmailException
828    {
829        return addTo(email, name, this.charset);
830    }
831
832    /**
833     * Add a recipient TO to the email using the specified address, personal
834     * name, and charset encoding for the name.
835     *
836     * @param email A String.
837     * @param name A String.
838     * @param charset The charset to encode the name with.
839     * @return An Email.
840     * @throws EmailException Indicates an invalid email address or charset.
841     * @since 1.1
842     */
843    public Email addTo(final String email, final String name, final String charset)
844        throws EmailException
845    {
846        this.toList.add(createInternetAddress(email, name, charset));
847        return this;
848    }
849
850    /**
851     * Set a list of "TO" addresses. All elements in the specified
852     * <code>Collection</code> are expected to be of type
853     * <code>java.mail.internet.InternetAddress</code>.
854     *
855     * @param  aCollection collection of <code>InternetAddress</code> objects.
856     * @return An Email.
857     * @throws EmailException Indicates an invalid email address.
858     * @see javax.mail.internet.InternetAddress
859     * @since 1.0
860     */
861    public Email setTo(final Collection<InternetAddress> aCollection) throws EmailException
862    {
863        if (aCollection == null || aCollection.isEmpty())
864        {
865            throw new EmailException("Address List provided was invalid");
866        }
867
868        this.toList = new ArrayList<InternetAddress>(aCollection);
869        return this;
870    }
871
872    /**
873     * Add a recipient CC to the email. The email
874     * address will also be used as the personal name.
875     * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}.
876     * If it is not set, it will be encoded using
877     * the Java platform's default charset (UTF-16) if it contains
878     * non-ASCII characters; otherwise, it is used as is.
879     *
880     * @param email A String.
881     * @return An Email.
882     * @throws EmailException Indicates an invalid email address.
883     * @since 1.0
884     */
885    public Email addCc(final String email)
886        throws EmailException
887    {
888        return this.addCc(email, null);
889    }
890
891    /**
892     * Add an array of CC recipients to the email. The email
893     * addresses will also be used as the personal name.
894     * The names will be encoded by the charset of
895     * {@link #setCharset(java.lang.String) setCharset()}.
896     * If it is not set, it will be encoded using
897     * the Java platform's default charset (UTF-16) if it contains
898     * non-ASCII characters; otherwise, it is used as is.
899     *
900     * @param emails A String array.
901     * @return An Email.
902     * @throws EmailException Indicates an invalid email address.
903     * @since 1.3
904     */
905    public Email addCc(final String... emails)
906        throws EmailException
907    {
908        if (emails == null || emails.length == 0)
909        {
910            throw new EmailException("Address List provided was invalid");
911        }
912
913        for (final String email : emails)
914        {
915            addCc(email, null);
916        }
917
918        return this;
919    }
920
921    /**
922     * Add a recipient CC to the email using the specified address and the
923     * specified personal name.
924     * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}.
925     * If it is not set, it will be encoded using
926     * the Java platform's default charset (UTF-16) if it contains
927     * non-ASCII characters; otherwise, it is used as is.
928     *
929     * @param email A String.
930     * @param name A String.
931     * @return An Email.
932     * @throws EmailException Indicates an invalid email address.
933     * @since 1.0
934     */
935    public Email addCc(final String email, final String name)
936        throws EmailException
937    {
938        return addCc(email, name, this.charset);
939    }
940
941    /**
942     * Add a recipient CC to the email using the specified address, personal
943     * name, and charset encoding for the name.
944     *
945     * @param email A String.
946     * @param name A String.
947     * @param charset The charset to encode the name with.
948     * @return An Email.
949     * @throws EmailException Indicates an invalid email address or charset.
950     * @since 1.1
951     */
952    public Email addCc(final String email, final String name, final String charset)
953        throws EmailException
954    {
955        this.ccList.add(createInternetAddress(email, name, charset));
956        return this;
957    }
958
959    /**
960     * Set a list of "CC" addresses. All elements in the specified
961     * <code>Collection</code> are expected to be of type
962     * <code>java.mail.internet.InternetAddress</code>.
963     *
964     * @param aCollection collection of <code>InternetAddress</code> objects.
965     * @return An Email.
966     * @throws EmailException Indicates an invalid email address.
967     * @see javax.mail.internet.InternetAddress
968     * @since 1.0
969     */
970    public Email setCc(final Collection<InternetAddress> aCollection) throws EmailException
971    {
972        if (aCollection == null || aCollection.isEmpty())
973        {
974            throw new EmailException("Address List provided was invalid");
975        }
976
977        this.ccList = new ArrayList<InternetAddress>(aCollection);
978        return this;
979    }
980
981    /**
982     * Add a blind BCC recipient to the email. The email
983     * address will also be used as the personal name.
984     * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}.
985     * If it is not set, it will be encoded using
986     * the Java platform's default charset (UTF-16) if it contains
987     * non-ASCII characters; otherwise, it is used as is.
988     *
989     * @param email A String.
990     * @return An Email.
991     * @throws EmailException Indicates an invalid email address
992     * @since 1.0
993     */
994    public Email addBcc(final String email)
995        throws EmailException
996    {
997        return this.addBcc(email, null);
998    }
999
1000    /**
1001     * Add an array of blind BCC recipients to the email. The email
1002     * addresses will also be used as the personal name.
1003     * The names will be encoded by the charset of
1004     * {@link #setCharset(java.lang.String) setCharset()}.
1005     * If it is not set, it will be encoded using
1006     * the Java platform's default charset (UTF-16) if it contains
1007     * non-ASCII characters; otherwise, it is used as is.
1008     *
1009     * @param emails A String array.
1010     * @return An Email.
1011     * @throws EmailException Indicates an invalid email address
1012     * @since 1.3
1013     */
1014    public Email addBcc(final String... emails)
1015        throws EmailException
1016    {
1017        if (emails == null || emails.length == 0)
1018        {
1019            throw new EmailException("Address List provided was invalid");
1020        }
1021
1022        for (final String email : emails)
1023        {
1024            addBcc(email, null);
1025        }
1026
1027        return this;
1028    }
1029
1030    /**
1031     * Add a blind BCC recipient to the email using the specified address and
1032     * the specified personal name.
1033     * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}.
1034     * If it is not set, it will be encoded using
1035     * the Java platform's default charset (UTF-16) if it contains
1036     * non-ASCII characters; otherwise, it is used as is.
1037     *
1038     * @param email A String.
1039     * @param name A String.
1040     * @return An Email.
1041     * @throws EmailException Indicates an invalid email address
1042     * @since 1.0
1043     */
1044    public Email addBcc(final String email, final String name)
1045        throws EmailException
1046    {
1047        return addBcc(email, name, this.charset);
1048    }
1049
1050    /**
1051     * Add a blind BCC recipient to the email using the specified address,
1052     * personal name, and charset encoding for the name.
1053     *
1054     * @param email A String.
1055     * @param name A String.
1056     * @param charset The charset to encode the name with.
1057     * @return An Email.
1058     * @throws EmailException Indicates an invalid email address
1059     * @since 1.1
1060     */
1061    public Email addBcc(final String email, final String name, final String charset)
1062        throws EmailException
1063    {
1064        this.bccList.add(createInternetAddress(email, name, charset));
1065        return this;
1066    }
1067
1068    /**
1069     * Set a list of "BCC" addresses. All elements in the specified
1070     * <code>Collection</code> are expected to be of type
1071     * <code>java.mail.internet.InternetAddress</code>.
1072     *
1073     * @param  aCollection collection of <code>InternetAddress</code> objects
1074     * @return An Email.
1075     * @throws EmailException Indicates an invalid email address
1076     * @see javax.mail.internet.InternetAddress
1077     * @since 1.0
1078     */
1079    public Email setBcc(final Collection<InternetAddress> aCollection) throws EmailException
1080    {
1081        if (aCollection == null || aCollection.isEmpty())
1082        {
1083            throw new EmailException("Address List provided was invalid");
1084        }
1085
1086        this.bccList = new ArrayList<InternetAddress>(aCollection);
1087        return this;
1088    }
1089
1090    /**
1091     * Add a reply to address to the email. The email
1092     * address will also be used as the personal name.
1093     * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}.
1094     * If it is not set, it will be encoded using
1095     * the Java platform's default charset (UTF-16) if it contains
1096     * non-ASCII characters; otherwise, it is used as is.
1097     *
1098     * @param email A String.
1099     * @return An Email.
1100     * @throws EmailException Indicates an invalid email address
1101     * @since 1.0
1102     */
1103    public Email addReplyTo(final String email)
1104        throws EmailException
1105    {
1106        return this.addReplyTo(email, null);
1107    }
1108
1109    /**
1110     * Add a reply to address to the email using the specified address and
1111     * the specified personal name.
1112     * The name will be encoded by the charset of {@link #setCharset(java.lang.String) setCharset()}.
1113     * If it is not set, it will be encoded using
1114     * the Java platform's default charset (UTF-16) if it contains
1115     * non-ASCII characters; otherwise, it is used as is.
1116     *
1117     * @param email A String.
1118     * @param name A String.
1119     * @return An Email.
1120     * @throws EmailException Indicates an invalid email address
1121     * @since 1.0
1122     */
1123    public Email addReplyTo(final String email, final String name)
1124        throws EmailException
1125    {
1126        return addReplyTo(email, name, this.charset);
1127    }
1128
1129    /**
1130     * Add a reply to address to the email using the specified address,
1131     * personal name, and charset encoding for the name.
1132     *
1133     * @param email A String.
1134     * @param name A String.
1135     * @param charset The charset to encode the name with.
1136     * @return An Email.
1137     * @throws EmailException Indicates an invalid email address or charset.
1138     * @since 1.1
1139     */
1140    public Email addReplyTo(final String email, final String name, final String charset)
1141        throws EmailException
1142    {
1143        this.replyList.add(createInternetAddress(email, name, charset));
1144        return this;
1145    }
1146
1147    /**
1148     * Set a list of reply to addresses. All elements in the specified
1149     * <code>Collection</code> are expected to be of type
1150     * <code>java.mail.internet.InternetAddress</code>.
1151     *
1152     * @param   aCollection collection of <code>InternetAddress</code> objects
1153     * @return  An Email.
1154     * @throws EmailException Indicates an invalid email address
1155     * @see javax.mail.internet.InternetAddress
1156     * @since 1.1
1157     */
1158    public Email setReplyTo(final Collection<InternetAddress> aCollection) throws EmailException
1159    {
1160        if (aCollection == null || aCollection.isEmpty())
1161        {
1162            throw new EmailException("Address List provided was invalid");
1163        }
1164
1165        this.replyList = new ArrayList<InternetAddress>(aCollection);
1166        return this;
1167    }
1168
1169    /**
1170     * Used to specify the mail headers.  Example:
1171     *
1172     * X-Mailer: Sendmail, X-Priority: 1( highest )
1173     * or  2( high ) 3( normal ) 4( low ) and 5( lowest )
1174     * Disposition-Notification-To: user@domain.net
1175     *
1176     * @param map A Map.
1177     * @throws IllegalArgumentException if either of the provided header / value is null or empty
1178     * @since 1.0
1179     */
1180    public void setHeaders(final Map<String, String> map)
1181    {
1182        this.headers.clear();
1183
1184        for (final Map.Entry<String, String> entry : map.entrySet())
1185        {
1186            addHeader(entry.getKey(), entry.getValue());
1187        }
1188    }
1189
1190    /**
1191     * Adds a header ( name, value ) to the headers Map.
1192     *
1193     * @param name A String with the name.
1194     * @param value A String with the value.
1195     * @since 1.0
1196     * @throws IllegalArgumentException if either {@code name} or {@code value} is null or empty
1197     */
1198    public void addHeader(final String name, final String value)
1199    {
1200        if (EmailUtils.isEmpty(name))
1201        {
1202            throw new IllegalArgumentException("name can not be null or empty");
1203        }
1204        if (EmailUtils.isEmpty(value))
1205        {
1206            throw new IllegalArgumentException("value can not be null or empty");
1207        }
1208
1209        this.headers.put(name, value);
1210    }
1211
1212    /**
1213     * Gets the specified header.
1214     *
1215     * @param header A string with the header.
1216     * @return The value of the header, or null if no such header.
1217     * @since 1.5
1218     */
1219    public String getHeader(final String header)
1220    {
1221        return this.headers.get(header);
1222    }
1223
1224    /**
1225     * Gets all headers on an Email.
1226     *
1227     * @return a Map of all headers.
1228     * @since 1.5
1229     */
1230    public Map<String, String> getHeaders()
1231    {
1232        return this.headers;
1233    }
1234
1235    /**
1236     * Sets the email subject. Replaces end-of-line characters with spaces.
1237     *
1238     * @param aSubject A String.
1239     * @return An Email.
1240     * @since 1.0
1241     */
1242    public Email setSubject(final String aSubject)
1243    {
1244        this.subject = EmailUtils.replaceEndOfLineCharactersWithSpaces(aSubject);
1245        return this;
1246    }
1247
1248    /**
1249     * Gets the "bounce address" of this email.
1250     *
1251     * @return the bounce address as string
1252     * @since 1.4
1253     */
1254    public String getBounceAddress()
1255    {
1256        return this.bounceAddress;
1257    }
1258
1259    /**
1260     * Set the "bounce address" - the address to which undeliverable messages
1261     * will be returned.  If this value is never set, then the message will be
1262     * sent to the address specified with the System property "mail.smtp.from",
1263     * or if that value is not set, then to the "from" address.
1264     *
1265     * @param email A String.
1266     * @return An Email.
1267     * @throws IllegalStateException if the mail session is already initialized
1268     * @since 1.0
1269     */
1270    public Email setBounceAddress(final String email)
1271    {
1272        checkSessionAlreadyInitialized();
1273
1274        if (email != null && !email.isEmpty())
1275        {
1276            try
1277            {
1278                this.bounceAddress = createInternetAddress(email, null, this.charset).getAddress();
1279            }
1280            catch (final EmailException e)
1281            {
1282                // Can't throw 'EmailException' to keep backward-compatibility
1283                throw new IllegalArgumentException("Failed to set the bounce address : " + email, e);
1284            }
1285        }
1286        else
1287        {
1288            this.bounceAddress = email;
1289        }
1290
1291        return this;
1292    }
1293
1294    /**
1295     * Define the content of the mail. It should be overridden by the
1296     * subclasses.
1297     *
1298     * @param msg A String.
1299     * @return An Email.
1300     * @throws EmailException generic exception.
1301     * @since 1.0
1302     */
1303    public abstract Email setMsg(String msg) throws EmailException;
1304
1305    /**
1306     * Does the work of actually building the MimeMessage. Please note that
1307     * a user rarely calls this method directly and only if he/she is
1308     * interested in the sending the underlying MimeMessage without
1309     * commons-email.
1310     *
1311     * @throws IllegalStateException if the MimeMessage was already built
1312     * @throws EmailException if there was an error.
1313     * @since 1.0
1314     */
1315    public void buildMimeMessage() throws EmailException
1316    {
1317        if (this.message != null)
1318        {
1319            // [EMAIL-95] we assume that an email is not reused therefore invoking
1320            // buildMimeMessage() more than once is illegal.
1321            throw new IllegalStateException("The MimeMessage is already built.");
1322        }
1323
1324        try
1325        {
1326            this.message = this.createMimeMessage(this.getMailSession());
1327
1328            if (EmailUtils.isNotEmpty(this.subject))
1329            {
1330                if (EmailUtils.isNotEmpty(this.charset))
1331                {
1332                    this.message.setSubject(this.subject, this.charset);
1333                }
1334                else
1335                {
1336                    this.message.setSubject(this.subject);
1337                }
1338            }
1339
1340            // update content type (and encoding)
1341            this.updateContentType(this.contentType);
1342
1343            if (this.content != null)
1344            {
1345                if (EmailConstants.TEXT_PLAIN.equalsIgnoreCase(this.contentType)
1346                        && this.content instanceof String)
1347                {
1348                    // EMAIL-104: call explicitly setText to use default mime charset
1349                    //            (property "mail.mime.charset") in case none has been set
1350                    this.message.setText(this.content.toString(), this.charset);
1351                }
1352                else
1353                {
1354                    this.message.setContent(this.content, this.contentType);
1355                }
1356            }
1357            else if (this.emailBody != null)
1358            {
1359                if (this.contentType == null)
1360                {
1361                    this.message.setContent(this.emailBody);
1362                }
1363                else
1364                {
1365                    this.message.setContent(this.emailBody, this.contentType);
1366                }
1367            }
1368            else
1369            {
1370                this.message.setText("");
1371            }
1372
1373            if (this.fromAddress != null)
1374            {
1375                this.message.setFrom(this.fromAddress);
1376            }
1377            else
1378            {
1379                if (session.getProperty(EmailConstants.MAIL_SMTP_FROM) == null
1380                        && session.getProperty(EmailConstants.MAIL_FROM) == null)
1381                {
1382                    throw new EmailException("From address required");
1383                }
1384            }
1385
1386            if (this.toList.size() + this.ccList.size() + this.bccList.size() == 0)
1387            {
1388                throw new EmailException("At least one receiver address required");
1389            }
1390
1391            if (this.toList.size() > 0)
1392            {
1393                this.message.setRecipients(
1394                    Message.RecipientType.TO,
1395                    this.toInternetAddressArray(this.toList));
1396            }
1397
1398            if (this.ccList.size() > 0)
1399            {
1400                this.message.setRecipients(
1401                    Message.RecipientType.CC,
1402                    this.toInternetAddressArray(this.ccList));
1403            }
1404
1405            if (this.bccList.size() > 0)
1406            {
1407                this.message.setRecipients(
1408                    Message.RecipientType.BCC,
1409                    this.toInternetAddressArray(this.bccList));
1410            }
1411
1412            if (this.replyList.size() > 0)
1413            {
1414                this.message.setReplyTo(
1415                    this.toInternetAddressArray(this.replyList));
1416            }
1417
1418
1419            if (this.headers.size() > 0)
1420            {
1421                for (final Map.Entry<String, String> entry : this.headers.entrySet())
1422                {
1423                    final String foldedValue = createFoldedHeaderValue(entry.getKey(), entry.getValue());
1424                    this.message.addHeader(entry.getKey(), foldedValue);
1425                }
1426            }
1427
1428            if (this.message.getSentDate() == null)
1429            {
1430                this.message.setSentDate(getSentDate());
1431            }
1432
1433            if (this.popBeforeSmtp)
1434            {
1435                final Store store = session.getStore("pop3");
1436                store.connect(this.popHost, this.popUsername, this.popPassword);
1437            }
1438        }
1439        catch (final MessagingException me)
1440        {
1441            throw new EmailException(me);
1442        }
1443    }
1444
1445    /**
1446     * Sends the previously created MimeMessage to the SMTP server.
1447     *
1448     * @return the message id of the underlying MimeMessage
1449     * @throws IllegalArgumentException if the MimeMessage has not been created
1450     * @throws EmailException the sending failed
1451     */
1452    public String sendMimeMessage()
1453       throws EmailException
1454    {
1455        EmailUtils.notNull(this.message, "MimeMessage has not been created yet");
1456
1457        try
1458        {
1459            Transport.send(this.message);
1460            return this.message.getMessageID();
1461        }
1462        catch (final Throwable t)
1463        {
1464            final String msg = "Sending the email to the following server failed : "
1465                + this.getHostName()
1466                + ":"
1467                + this.getSmtpPort();
1468
1469            throw new EmailException(msg, t);
1470        }
1471    }
1472
1473    /**
1474     * Returns the internal MimeMessage. Please note that the
1475     * MimeMessage is built by the buildMimeMessage() method.
1476     *
1477     * @return the MimeMessage
1478     */
1479    public MimeMessage getMimeMessage()
1480    {
1481        return this.message;
1482    }
1483
1484    /**
1485     * Sends the email. Internally we build a MimeMessage
1486     * which is afterwards sent to the SMTP server.
1487     *
1488     * @return the message id of the underlying MimeMessage
1489     * @throws IllegalStateException if the MimeMessage was already built, ie {@link #buildMimeMessage()}
1490     *   was already called
1491     * @throws EmailException the sending failed
1492     */
1493    public String send() throws EmailException
1494    {
1495        this.buildMimeMessage();
1496        return this.sendMimeMessage();
1497    }
1498
1499    /**
1500     * Sets the sent date for the email.  The sent date will default to the
1501     * current date if not explicitly set.
1502     *
1503     * @param date Date to use as the sent date on the email
1504     * @since 1.0
1505     */
1506    public void setSentDate(final Date date)
1507    {
1508        if (date != null)
1509        {
1510            // create a separate instance to keep findbugs happy
1511            this.sentDate = new Date(date.getTime());
1512        }
1513    }
1514
1515    /**
1516     * Gets the sent date for the email.
1517     *
1518     * @return date to be used as the sent date for the email
1519     * @since 1.0
1520     */
1521    public Date getSentDate()
1522    {
1523        if (this.sentDate == null)
1524        {
1525            return new Date();
1526        }
1527        return new Date(this.sentDate.getTime());
1528    }
1529
1530    /**
1531     * Gets the subject of the email.
1532     *
1533     * @return email subject
1534     */
1535    public String getSubject()
1536    {
1537        return this.subject;
1538    }
1539
1540    /**
1541     * Gets the sender of the email.
1542     *
1543     * @return from address
1544     */
1545    public InternetAddress getFromAddress()
1546    {
1547        return this.fromAddress;
1548    }
1549
1550    /**
1551     * Gets the host name of the SMTP server,
1552     *
1553     * @return host name
1554     */
1555    public String getHostName()
1556    {
1557        if (this.session != null)
1558        {
1559            return this.session.getProperty(EmailConstants.MAIL_HOST);
1560        }
1561        else if (EmailUtils.isNotEmpty(this.hostName))
1562        {
1563            return this.hostName;
1564        }
1565        return null;
1566    }
1567
1568    /**
1569     * Gets the listening port of the SMTP server.
1570     *
1571     * @return smtp port
1572     */
1573    public String getSmtpPort()
1574    {
1575        if (this.session != null)
1576        {
1577            return this.session.getProperty(EmailConstants.MAIL_PORT);
1578        }
1579        else if (EmailUtils.isNotEmpty(this.smtpPort))
1580        {
1581            return this.smtpPort;
1582        }
1583        return null;
1584    }
1585
1586    /**
1587     * Gets whether the client is configured to require STARTTLS.
1588     *
1589     * @return true if using STARTTLS for authentication, false otherwise
1590     * @since 1.3
1591     */
1592    public boolean isStartTLSRequired()
1593    {
1594        return this.startTlsRequired;
1595    }
1596
1597    /**
1598     * Gets whether the client is configured to try to enable STARTTLS.
1599     *
1600     * @return true if using STARTTLS for authentication, false otherwise
1601     * @since 1.3
1602     */
1603    public boolean isStartTLSEnabled()
1604    {
1605        return this.startTlsEnabled || tls;
1606    }
1607
1608    /**
1609     * Gets whether the client is configured to try to enable STARTTLS.
1610     * See EMAIL-105 for reason of deprecation.
1611     *
1612     * @deprecated since 1.3, use isStartTLSEnabled() instead
1613     * @return true if using STARTTLS for authentication, false otherwise
1614     * @since 1.1
1615     */
1616    @Deprecated
1617    public boolean isTLS()
1618    {
1619        return isStartTLSEnabled();
1620    }
1621
1622    /**
1623     * Utility to copy List of known InternetAddress objects into an
1624     * array.
1625     *
1626     * @param list A List.
1627     * @return An InternetAddress[].
1628     * @since 1.0
1629     */
1630    protected InternetAddress[] toInternetAddressArray(final List<InternetAddress> list)
1631    {
1632        return list.toArray(new InternetAddress[list.size()]);
1633    }
1634
1635    /**
1636     * Set details regarding "pop3 before smtp" authentication.
1637     *
1638     * @param newPopBeforeSmtp Whether or not to log into pop3 server before sending mail.
1639     * @param newPopHost The pop3 host to use.
1640     * @param newPopUsername The pop3 username.
1641     * @param newPopPassword The pop3 password.
1642     * @since 1.0
1643     */
1644    public void setPopBeforeSmtp(
1645        final boolean newPopBeforeSmtp,
1646        final String newPopHost,
1647        final String newPopUsername,
1648        final String newPopPassword)
1649    {
1650        this.popBeforeSmtp = newPopBeforeSmtp;
1651        this.popHost = newPopHost;
1652        this.popUsername = newPopUsername;
1653        this.popPassword = newPopPassword;
1654    }
1655
1656    /**
1657     * Returns whether SSL/TLS encryption for the transport is currently enabled (SMTPS/POPS).
1658     * See EMAIL-105 for reason of deprecation.
1659     *
1660     * @deprecated since 1.3, use isSSLOnConnect() instead
1661     * @return true if SSL enabled for the transport
1662     */
1663    @Deprecated
1664    public boolean isSSL()
1665    {
1666        return isSSLOnConnect();
1667    }
1668
1669    /**
1670     * Returns whether SSL/TLS encryption for the transport is currently enabled (SMTPS/POPS).
1671     *
1672     * @return true if SSL enabled for the transport
1673     * @since 1.3
1674     */
1675    public boolean isSSLOnConnect()
1676    {
1677        return sslOnConnect || ssl;
1678    }
1679
1680    /**
1681     * Sets whether SSL/TLS encryption should be enabled for the SMTP transport upon connection (SMTPS/POPS).
1682     * See EMAIL-105 for reason of deprecation.
1683     *
1684     * @deprecated since 1.3, use setSSLOnConnect() instead
1685     * @param ssl whether to enable the SSL transport
1686     */
1687    @Deprecated
1688    public void setSSL(final boolean ssl)
1689    {
1690        setSSLOnConnect(ssl);
1691    }
1692
1693    /**
1694     * Sets whether SSL/TLS encryption should be enabled for the SMTP transport upon connection (SMTPS/POPS).
1695     * Takes precedence over {@link #setStartTLSRequired(boolean)}
1696     * <p>
1697     * Defaults to {@link #sslSmtpPort}; can be overridden by using {@link #setSslSmtpPort(String)}
1698     *
1699     * @param ssl whether to enable the SSL transport
1700     * @return An Email.
1701     * @throws IllegalStateException if the mail session is already initialized
1702     * @since 1.3
1703     */
1704    public Email setSSLOnConnect(final boolean ssl)
1705    {
1706        checkSessionAlreadyInitialized();
1707        this.sslOnConnect = ssl;
1708        this.ssl = ssl;
1709        return this;
1710    }
1711
1712    /**
1713    * Is the server identity checked as specified by RFC 2595
1714    *
1715    * @return true if the server identity is checked
1716    * @since 1.3
1717    */
1718    public boolean isSSLCheckServerIdentity()
1719    {
1720        return sslCheckServerIdentity;
1721    }
1722
1723    /**
1724     * Sets whether the server identity is checked as specified by RFC 2595
1725     *
1726     * @param sslCheckServerIdentity whether to enable server identity check
1727     * @return An Email.
1728     * @throws IllegalStateException if the mail session is already initialized
1729     * @since 1.3
1730     */
1731    public Email setSSLCheckServerIdentity(final boolean sslCheckServerIdentity)
1732    {
1733        checkSessionAlreadyInitialized();
1734        this.sslCheckServerIdentity = sslCheckServerIdentity;
1735        return this;
1736    }
1737
1738    /**
1739     * Returns the current SSL port used by the SMTP transport.
1740     *
1741     * @return the current SSL port used by the SMTP transport
1742     */
1743    public String getSslSmtpPort()
1744    {
1745        if (this.session != null)
1746        {
1747            return this.session.getProperty(EmailConstants.MAIL_SMTP_SOCKET_FACTORY_PORT);
1748        }
1749        else if (EmailUtils.isNotEmpty(this.sslSmtpPort))
1750        {
1751            return this.sslSmtpPort;
1752        }
1753        return null;
1754    }
1755
1756    /**
1757     * Sets the SSL port to use for the SMTP transport. Defaults to the standard
1758     * port, 465.
1759     *
1760     * @param sslSmtpPort the SSL port to use for the SMTP transport
1761     * @throws IllegalStateException if the mail session is already initialized
1762     * @see #setSmtpPort(int)
1763     */
1764    public void setSslSmtpPort(final String sslSmtpPort)
1765    {
1766        checkSessionAlreadyInitialized();
1767        this.sslSmtpPort = sslSmtpPort;
1768    }
1769
1770    /**
1771    * If partial sending of email enabled.
1772    *
1773    * @return true if sending partial email is enabled
1774    * @since 1.3.2
1775    */
1776    public boolean isSendPartial()
1777    {
1778        return sendPartial;
1779    }
1780
1781    /**
1782     * Sets whether the email is partially send in case of invalid addresses.
1783     * <p>
1784     * In case the mail server rejects an address as invalid, the call to {@link #send()}
1785     * may throw a {@link javax.mail.SendFailedException}, even if partial send mode is enabled (emails
1786     * to valid addresses will be transmitted). In case the email server does not reject
1787     * invalid addresses immediately, but return a bounce message, no exception will be thrown
1788     * by the {@link #send()} method.
1789     *
1790     * @param sendPartial whether to enable partial send mode
1791     * @return An Email.
1792     * @throws IllegalStateException if the mail session is already initialized
1793     * @since 1.3.2
1794     */
1795    public Email setSendPartial(final boolean sendPartial)
1796    {
1797        checkSessionAlreadyInitialized();
1798        this.sendPartial = sendPartial;
1799        return this;
1800    }
1801
1802    /**
1803     * Get the list of "To" addresses.
1804     *
1805     * @return List addresses
1806     */
1807    public List<InternetAddress> getToAddresses()
1808    {
1809        return this.toList;
1810    }
1811
1812    /**
1813     * Get the list of "CC" addresses.
1814     *
1815     * @return List addresses
1816     */
1817    public List<InternetAddress> getCcAddresses()
1818    {
1819        return this.ccList;
1820    }
1821
1822    /**
1823     * Get the list of "Bcc" addresses.
1824     *
1825     * @return List addresses
1826     */
1827    public List<InternetAddress> getBccAddresses()
1828    {
1829        return this.bccList;
1830    }
1831
1832    /**
1833     * Get the list of "Reply-To" addresses.
1834     *
1835     * @return List addresses
1836     */
1837    public List<InternetAddress> getReplyToAddresses()
1838    {
1839        return this.replyList;
1840    }
1841
1842    /**
1843     * Get the socket connection timeout value in milliseconds.
1844     *
1845     * @return the timeout in milliseconds.
1846     * @since 1.2
1847     */
1848    public int getSocketConnectionTimeout()
1849    {
1850        return this.socketConnectionTimeout;
1851    }
1852
1853    /**
1854     * Set the socket connection timeout value in milliseconds.
1855     * Default is a 60 second timeout.
1856     *
1857     * @param socketConnectionTimeout the connection timeout
1858     * @throws IllegalStateException if the mail session is already initialized
1859     * @since 1.2
1860     */
1861    public void setSocketConnectionTimeout(final int socketConnectionTimeout)
1862    {
1863        checkSessionAlreadyInitialized();
1864        this.socketConnectionTimeout = socketConnectionTimeout;
1865    }
1866
1867    /**
1868     * Get the socket I/O timeout value in milliseconds.
1869     *
1870     * @return the socket I/O timeout
1871     * @since 1.2
1872     */
1873    public int getSocketTimeout()
1874    {
1875        return this.socketTimeout;
1876    }
1877
1878    /**
1879     * Set the socket I/O timeout value in milliseconds.
1880     * Default is 60 second timeout.
1881     *
1882     * @param socketTimeout the socket I/O timeout
1883     * @throws IllegalStateException if the mail session is already initialized
1884     * @since 1.2
1885     */
1886    public void setSocketTimeout(final int socketTimeout)
1887    {
1888        checkSessionAlreadyInitialized();
1889        this.socketTimeout = socketTimeout;
1890    }
1891
1892    /**
1893     * Factory method to create a customized MimeMessage which can be
1894     * implemented by a derived class, e.g. to set the message id.
1895     *
1896     * @param aSession mail session to be used
1897     * @return the newly created message
1898     */
1899    protected MimeMessage createMimeMessage(final Session aSession)
1900    {
1901        return new MimeMessage(aSession);
1902    }
1903
1904    /**
1905     * Create a folded header value containing 76 character chunks.
1906     *
1907     * @param name the name of the header
1908     * @param value the value of the header
1909     * @return the folded header value
1910     * @throws IllegalArgumentException if either the name or value is null or empty
1911     */
1912    private String createFoldedHeaderValue(final String name, final String value)
1913    {
1914        if (EmailUtils.isEmpty(name))
1915        {
1916            throw new IllegalArgumentException("name can not be null or empty");
1917        }
1918        if (value == null || EmailUtils.isEmpty(value))
1919        {
1920            throw new IllegalArgumentException("value can not be null or empty");
1921        }
1922
1923        try
1924        {
1925            return MimeUtility.fold(name.length() + 2, MimeUtility.encodeText(value, this.charset, null));
1926        }
1927        catch (final UnsupportedEncodingException e)
1928        {
1929            return value;
1930        }
1931    }
1932
1933    /**
1934     * Creates a InternetAddress.
1935     *
1936     * @param email An email address.
1937     * @param name A name.
1938     * @param charsetName The name of the charset to encode the name with.
1939     * @return An internet address.
1940     * @throws EmailException Thrown when the supplied address, name or charset were invalid.
1941     */
1942    private InternetAddress createInternetAddress(final String email, final String name, final String charsetName)
1943        throws EmailException
1944    {
1945        InternetAddress address;
1946
1947        try
1948        {
1949            address = new InternetAddress(new IDNEmailAddressConverter().toASCII(email));
1950
1951            // check name input
1952            if (EmailUtils.isNotEmpty(name))
1953            {
1954                // check charset input.
1955                if (EmailUtils.isEmpty(charsetName))
1956                {
1957                    address.setPersonal(name);
1958                }
1959                else
1960                {
1961                    // canonicalize the charset name and make sure
1962                    // the current platform supports it.
1963                    final Charset set = Charset.forName(charsetName);
1964                    address.setPersonal(name, set.name());
1965                }
1966            }
1967
1968            // run sanity check on new InternetAddress object; if this fails
1969            // it will throw AddressException.
1970            address.validate();
1971        }
1972        catch (final AddressException e)
1973        {
1974            throw new EmailException(e);
1975        }
1976        catch (final UnsupportedEncodingException e)
1977        {
1978            throw new EmailException(e);
1979        }
1980        return address;
1981    }
1982
1983    /**
1984     * When a mail session is already initialized setting the
1985     * session properties has no effect. In order to flag the
1986     * problem throw an IllegalStateException.
1987     *
1988     * @throws IllegalStateException when the mail session is already initialized
1989     */
1990    private void checkSessionAlreadyInitialized()
1991    {
1992        if (this.session != null)
1993        {
1994            throw new IllegalStateException("The mail session is already initialized");
1995        }
1996    }
1997}