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.IOException;
022import java.io.InputStream;
023import java.net.URL;
024import java.util.ArrayList;
025import java.util.Iterator;
026import java.util.List;
027
028import javax.activation.DataHandler;
029import javax.activation.URLDataSource;
030import javax.mail.BodyPart;
031import javax.mail.MessagingException;
032import javax.mail.internet.MimeBodyPart;
033import javax.mail.internet.MimeMultipart;
034
035import lucee.commons.lang.StringUtil;
036
037import org.apache.commons.mail.Email;
038import org.apache.commons.mail.EmailException;
039import org.apache.commons.mail.MultiPartEmail;
040
041/**
042 * An HTML multipart email.
043 *
044 * <p>This class is used to send HTML formatted email.  A text message
045 * can also be set for HTML unaware email clients, such as text-based
046 * email clients.
047 *
048 * <p>This class also inherits from MultiPartEmail, so it is easy to
049 * add attachments to the email.
050 *
051 * <p>To send an email in HTML, one should create a HtmlEmail, then
052 * use the setFrom, addTo, etc. methods.  The HTML content can be set
053 * with the setHtmlMsg method.  The alternative text content can be set
054 * with setTextMsg.
055 *
056 * <p>Either the text or HTML can be omitted, in which case the "main"
057 * part of the multipart becomes whichever is supplied rather than a
058 * multipart/alternative.
059 *
060 *
061 */
062public final class HtmlEmailImpl extends MultiPartEmail
063{
064    /** Definition of the length of generated CID's */
065    public static final int CID_LENGTH = 10;
066
067    /**
068     * Text part of the message.  This will be used as alternative text if
069     * the email client does not support HTML messages.
070     */
071    protected String text;
072
073    /** Html part of the message */
074    protected String html;
075
076    /** Embedded images */
077    protected List inlineImages = new ArrayList();
078
079    /**
080     * Set the text content.
081     *
082     * @param aText A String.
083     * @return An HtmlEmail.
084     * @throws EmailException see javax.mail.internet.MimeBodyPart
085     *  for definitions
086     *
087     */
088    public HtmlEmailImpl setTextMsg(String aText) throws EmailException {
089        if (StringUtil.isEmpty(aText)) {
090            throw new EmailException("Invalid message supplied");
091        }
092        this.text = aText;
093        return this;
094    }
095
096    /**
097     * Set the HTML content.
098     *
099     * @param aHtml A String.
100     * @return An HtmlEmail.
101     * @throws EmailException see javax.mail.internet.MimeBodyPart
102     *  for definitions
103     *
104     */
105    public HtmlEmailImpl setHtmlMsg(String aHtml) throws EmailException {
106        if (StringUtil.isEmpty(aHtml)) {
107            throw new EmailException("Invalid message supplied");
108        }
109        this.html = aHtml;
110        return this;
111    }
112
113    /**
114     * Set the message.
115     *
116     * <p>This method overrides the MultiPartEmail setMsg() method in
117     * order to send an HTML message instead of a full text message in
118     * the mail body. The message is formatted in HTML for the HTML
119     * part of the message, it is let as is in the alternate text
120     * part.
121     *
122     * @param msg A String.
123     * @return An Email.
124     * @throws EmailException see javax.mail.internet.MimeBodyPart
125     *  for definitions
126     *
127     */
128    public Email setMsg(String msg) throws EmailException {
129        if (StringUtil.isEmpty(msg)) {
130            throw new EmailException("Invalid message supplied");
131        }
132
133        setTextMsg(msg);
134
135        setHtmlMsg(
136            new StringBuffer()
137                .append("<html><body><pre>")
138                .append(msg)
139                .append("</pre></body></html>")
140                .toString());
141
142        return this;
143    }
144
145    /**
146     * Embeds an URL in the HTML.
147     *
148     * <p>This method allows to embed a file located by an URL into
149     * the mail body.  It allows, for instance, to add inline images
150     * to the email.  Inline files may be referenced with a
151     * <code>cid:xxxxxx</code> URL, where xxxxxx is the Content-ID
152     * returned by the embed function.
153     *
154     * <p>Example of use:<br><code><pre>
155     * HtmlEmail he = new HtmlEmail();
156     * he.setHtmlMsg("&lt;html&gt;&lt;img src=cid:" +
157     *  embed("file:/my/image.gif","image.gif") +
158     *  "&gt;&lt;/html&gt;");
159     * // code to set the others email fields (not shown)
160     * </pre></code>
161     *
162     * @param url The URL of the file.
163     * @param cid A String with the Content-ID of the file.
164     * @param name The name that will be set in the filename header
165     * field.
166     * @throws EmailException when URL supplied is invalid
167     *  also see javax.mail.internet.MimeBodyPart for definitions
168     *
169     */
170    public void embed(URL url, String cid, String name) throws EmailException {
171        // verify that the URL is valid
172        try {
173            InputStream is = url.openStream();
174            is.close();
175        }
176        catch (IOException e) {
177            throw new EmailException("Invalid URL");
178        }
179
180        MimeBodyPart mbp = new MimeBodyPart();
181
182        try {
183            mbp.setDataHandler(new DataHandler(new URLDataSource(url)));
184            mbp.setFileName(name);
185            mbp.setDisposition("inline");
186            mbp.addHeader("Content-ID", "<" + cid + ">");
187            this.inlineImages.add(mbp);
188        }
189        catch (MessagingException me) {
190            throw new EmailException(me);
191        }
192    }
193
194    /**
195     * Does the work of actually building the email.
196     *
197     * @exception EmailException if there was an error.
198     *
199     */
200    public void buildMimeMessage() throws EmailException {
201        try {
202            // if the email has attachments then the base type is mixed,
203            // otherwise it should be related
204            if (this.isBoolHasAttachments()) {
205                this.buildAttachments();
206            }
207            else {
208                this.buildNoAttachments();
209            }
210
211        }
212        catch (MessagingException me)
213        {
214            throw new EmailException(me);
215        }
216        super.buildMimeMessage();
217    }
218
219    /**
220     * @throws EmailException EmailException
221     * @throws MessagingException MessagingException
222     */
223    private void buildAttachments() throws MessagingException, EmailException
224    {
225        MimeMultipart container = this.getContainer();
226        MimeMultipart subContainer = null;
227        MimeMultipart subContainerHTML = new MimeMultipart("related");
228        BodyPart msgHtml = null;
229        BodyPart msgText = null;
230
231        container.setSubType("mixed");
232        subContainer = new MimeMultipart("alternative");
233
234        if (!StringUtil.isEmpty(this.text)) {
235            msgText = new MimeBodyPart();
236            subContainer.addBodyPart(msgText);
237
238            if (!StringUtil.isEmpty(this.charset)) {
239                msgText.setContent(
240                    this.text,
241                    Email.TEXT_PLAIN + "; charset=" + this.charset);
242            }
243            else {
244                msgText.setContent(this.text, Email.TEXT_PLAIN);
245            }
246        }
247
248        if (!StringUtil.isEmpty(this.html))
249        {
250            if (this.inlineImages.size() > 0)
251            {
252                msgHtml = new MimeBodyPart();
253                subContainerHTML.addBodyPart(msgHtml);
254            }
255            else
256            {
257                msgHtml = new MimeBodyPart();
258                subContainer.addBodyPart(msgHtml);
259            }
260
261            if (!StringUtil.isEmpty(this.charset))
262            {
263                msgHtml.setContent(
264                    this.html,
265                    Email.TEXT_HTML + "; charset=" + this.charset);
266            }
267            else
268            {
269                msgHtml.setContent(this.html, Email.TEXT_HTML);
270            }
271
272            Iterator iter = this.inlineImages.iterator();
273            while (iter.hasNext())
274            {
275                subContainerHTML.addBodyPart((BodyPart) iter.next());
276            }
277        }
278
279        // add sub containers to message
280        this.addPart(subContainer, 0);
281
282        if (this.inlineImages.size() > 0)
283        {
284            // add sub container to message
285            this.addPart(subContainerHTML, 1);
286        }
287    }
288
289    /**
290     * @throws EmailException EmailException
291     * @throws MessagingException MessagingException
292     */
293    private void buildNoAttachments() throws MessagingException, EmailException
294    {
295        MimeMultipart container = this.getContainer();
296        MimeMultipart subContainerHTML = new MimeMultipart("related");
297
298        container.setSubType("alternative");
299
300        BodyPart msgText = null;
301        BodyPart msgHtml = null;
302
303        if(!StringUtil.isEmpty(this.text)) {
304            msgText = this.getPrimaryBodyPart();
305            if (!StringUtil.isEmpty(this.charset)) {
306                msgText.setContent(
307                    this.text,
308                    Email.TEXT_PLAIN + "; charset=" + this.charset);
309            }
310            else {
311                msgText.setContent(this.text, Email.TEXT_PLAIN);
312            }
313        }
314
315        if(!StringUtil.isEmpty(this.html)) {
316            // if the txt part of the message was null, then the html part
317            // will become the primary body part
318            if (msgText == null) {
319                msgHtml = getPrimaryBodyPart();
320            }
321            else {
322                if (this.inlineImages.size() > 0) {
323                    msgHtml = new MimeBodyPart();
324                    subContainerHTML.addBodyPart(msgHtml);
325                }
326                else {
327                    msgHtml = new MimeBodyPart();
328                    container.addBodyPart(msgHtml, 1);
329                }
330            }
331
332            if(!StringUtil.isEmpty(this.charset)) {
333                msgHtml.setContent(
334                    this.html,
335                    Email.TEXT_HTML + "; charset=" + this.charset);
336            }
337            else {
338                msgHtml.setContent(this.html, Email.TEXT_HTML);
339            }
340
341            Iterator iter = this.inlineImages.iterator();
342            while (iter.hasNext())
343            {
344                subContainerHTML.addBodyPart((BodyPart) iter.next());
345            }
346
347            if (this.inlineImages.size() > 0)
348            {
349                // add sub container to message
350                this.addPart(subContainerHTML);
351            }
352        }
353    }
354}