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.tag;
020
021
022import javax.mail.internet.InternetAddress;
023
024import lucee.commons.io.res.Resource;
025import lucee.commons.io.res.util.ResourceUtil;
026import lucee.commons.lang.StringUtil;
027import lucee.runtime.exp.ApplicationException;
028import lucee.runtime.exp.ExpressionException;
029import lucee.runtime.exp.PageException;
030import lucee.runtime.ext.tag.BodyTagImpl;
031import lucee.runtime.net.mail.MailException;
032import lucee.runtime.net.mail.MailPart;
033import lucee.runtime.net.smtp.SMTPClient;
034import lucee.runtime.op.Caster;
035import lucee.runtime.op.Decision;
036import lucee.runtime.type.dt.DateTime;
037// TODO test proxy
038/**
039 * 
040* Sends e-mail messages by an SMTP server.
041*
042*
043*
044**/
045public final class Mail extends BodyTagImpl {
046
047        /** Specifies the query column to use when you group sets of records together to send as an e-mail 
048        **              message. For example, if you send a set of billing statements to customers, you might group on 
049        **              "Customer_ID." The group attribute, which is case sensitive, eliminates adjacent duplicates when the 
050        **              data is sorted by the specified field. See the Usage section for exceptions. */
051        private String group;
052
053        /** Boolean indicating whether to group with regard to case or not. The default value is YES; 
054        **              case is considered while grouping. If the query attribute specifies a query object that was generated 
055        **              by a case-insensitive SQL query, set the groupCaseSensitive attribute to NO to keep the recordset 
056        **              intact. */
057        private boolean groupcasesensitive;
058
059        /** The name of the cfquery from which to draw data for message(s) to send. Specify this 
060        **              attribute to send more than one mail message, or to send the results of a query within a message. */
061        private String query;
062
063        /** Specifies the maximum number of e-mail messages to send. */
064        private double maxrows;
065
066        /** Specifies the row in the query to start from. */
067        private double startrow;
068
069
070        //private lucee.runtime.mail.Mail mail=new lucee.runtime.mail.Mail();
071        private SMTPClient smtp=new SMTPClient();
072        private lucee.runtime.net.mail.MailPart part=null;//new lucee.runtime.mail.MailPart("UTF-8");
073
074        private String charset;
075        private int priority;
076        private boolean remove;
077
078        /** specify the time for the message to be sent when using the spooler */
079        private DateTime sendTime;
080        
081
082        @Override
083        public void release()   {
084                super.release();
085//       do not clear because spooler
086        //mail=new lucee.runtime.mail.Mail();   
087                smtp=new SMTPClient();
088        part=null;//new lucee.runtime.mail.MailPart("UTF-8");
089                group=null;
090                groupcasesensitive=false;
091                query=null;
092                maxrows=0d;
093                startrow=0d;
094                charset=null;
095                remove=false;
096                sendTime=null;
097        }
098        
099        
100        /**
101         * @param remove the remove to set
102         */
103        public void setRemove(boolean remove) {
104                this.remove = remove;
105        }
106
107
108        /**
109     * @param proxyserver The proxyserver to set.
110         * @throws ApplicationException 
111     */
112    public void setProxyserver(String proxyserver) throws ApplicationException {
113                try {
114                        smtp.getProxyData().setServer(proxyserver);
115                } catch (Exception e) {
116                        throw new ApplicationException("attribute [proxyserver] of the tag [mail] is invalid",e.getMessage());
117                }
118    }
119        
120        /** set the value proxyport
121        *  The port number on the proxy server from which the object is requested. Default is 80. When 
122        *       used with resolveURL, the URLs of retrieved documents that specify a port number are automatically 
123        *       resolved to preserve links in the retrieved document.
124        * @param proxyport value to set
125         * @throws ApplicationException 
126        **/
127        public void setProxyport(double proxyport) throws ApplicationException  {
128                try {
129                        smtp.getProxyData().setPort((int)proxyport);
130                } catch (Exception e) {
131                        throw new ApplicationException("attribute [proxyport] of the tag [mail] is invalid",e.getMessage());
132                }
133        }
134
135        /** set the value username
136        *  When required by a proxy server, a valid username.
137        * @param proxyuser value to set
138         * @throws ApplicationException 
139        **/
140        public void setProxyuser(String proxyuser) throws ApplicationException  {
141                try {
142                        smtp.getProxyData().setUsername(proxyuser);
143                } catch (Exception e) {
144                        throw new ApplicationException("attribute [proxyuser] of the tag [mail] is invalid",e.getMessage());
145                }
146        }
147
148    
149        /** set the value password
150        *  When required by a proxy server, a valid password.
151        * @param proxypassword value to set
152         * @throws ApplicationException 
153        **/
154        public void setProxypassword(String proxypassword) throws ApplicationException  {
155                try {
156                        smtp.getProxyData().setPassword(proxypassword);
157                } catch (Exception e) {
158                        throw new ApplicationException("attribute [proxypassword] of the tag [mail] is invalid",e.getMessage());
159                }
160        }
161
162        
163
164        /** set the value from
165        *  The sender of the e-mail message.
166        * @param strForm value to set
167         * @throws PageException 
168        **/
169        public void setFrom(Object from) throws PageException   {
170                if(StringUtil.isEmpty(from)) return;
171                try {
172                        smtp.setFrom(from);
173                } catch (Exception e) {
174                        throw Caster.toPageException(e);
175                }
176        }
177
178        /** set the value to
179        *  The name of the e-mail message recipient.
180        * @param strTo value to set
181         * @throws ApplicationException
182        **/
183        public void setTo(Object to) throws ApplicationException        {
184                if(StringUtil.isEmpty(to)) return;
185                try {
186                        smtp.addTo(to);
187                } catch (Exception e) {
188                        throw new ApplicationException("attribute [to] of the tag [mail] is invalid",e.getMessage());
189                }
190        }
191
192        /** set the value cc
193        *  Indicates addresses to copy the e-mail message to; "cc" stands for "carbon copy."
194        * @param strCc value to set
195         * @throws ApplicationException
196        **/
197        public void setCc(Object cc) throws ApplicationException        {
198                if(StringUtil.isEmpty(cc)) return;
199                try {
200                        smtp.addCC(cc);
201                } catch (Exception e) {
202                        throw new ApplicationException("attribute [cc] of the tag [mail] is invalid",e.getMessage());
203                }
204        }
205
206
207
208        /** set the value bcc
209        *  Indicates addresses to copy the e-mail message to, without listing them in the message header. 
210        *               "bcc" stands for "blind carbon copy."
211        * @param strBcc value to set
212         * @throws ApplicationException
213        **/
214        public void setBcc(Object bcc) throws ApplicationException      {
215                if(StringUtil.isEmpty(bcc)) return;
216                try {
217                        smtp.addBCC(bcc);
218                } catch (Exception e) {
219                        throw new ApplicationException("attribute [bcc] of the tag [mail] is invalid",e.getMessage());
220                }
221        }       
222        
223        /**
224         * @param strFailto The failto to set.
225         * @throws ApplicationException
226         */
227        public void setFailto(Object failto) throws ApplicationException {
228                if(StringUtil.isEmpty(failto)) return;
229                try {
230                        smtp.addFailTo(failto);
231                } catch (Exception e) {
232                        throw new ApplicationException("attribute [failto] of the tag [mail] is invalid",e.getMessage());
233                }
234        }
235        /**
236         * @param strReplyto The replyto to set.
237         * @throws ApplicationException
238         */
239        public void setReplyto(Object replyto) throws ApplicationException {
240                if(StringUtil.isEmpty(replyto)) return;
241                try {
242                        smtp.addReplyTo(replyto);
243                } catch (Exception e) {
244                        throw new ApplicationException("attribute [replyto] of the tag [mail] is invalid",e.getMessage());
245                }
246        }
247        
248        /** set the value type
249        *  Specifies extended type attributes for the message.
250        * @param type value to set
251         * @throws ApplicationException
252        **/
253        public void setType(String type) throws ApplicationException    {
254                type=type.toLowerCase().trim();
255                if(type.equals("text/plain") || type.equals("plain") || type.equals("text"))
256                    getPart().isHTML(false);
257                    //mail.setType(lucee.runtime.mail.Mail.TYPE_TEXT);
258                else if(type.equals("text/html") || type.equals("html") || type.equals("htm"))
259                        getPart().isHTML(true);
260                else
261                        throw new ApplicationException("attribute type of tag mail has an invalid values","valid values are [plain,text,html] but value is now ["+type+"]");
262                        //throw new ApplicationException(("invalid type "+type);
263        }
264
265        /** set the value subject
266        *  The subject of the mail message. This field may be driven dynamically on 
267        *               a message-by-message basis
268        * @param subject value to set
269        **/
270        public void setSubject(String subject)  {
271                smtp.setSubject(subject);
272        }
273        /**
274         * @param username The username to set.
275         */
276        public void setUsername(String username) {
277                smtp.setUsername(username);
278        }
279        /**
280         * @param password The password to set.
281         */
282        public void setPassword(String password) {
283                smtp.setPassword(password);
284        }
285        
286        /** set the value mimeattach
287        *  Specifies the path of the file to be attached to the e-mail message. An attached file 
288        *               is MIME-encoded.
289        * @param strMimeattach value to set
290         * @param type mimetype of the file
291         * @param contentID 
292         * @param disposition 
293         * @throws PageException 
294        **/
295        public void setMimeattach(String strMimeattach, String type, String disposition, String contentID,boolean removeAfterSend) throws PageException {
296                Resource file=ResourceUtil.toResourceNotExisting(pageContext,strMimeattach);
297        pageContext.getConfig().getSecurityManager().checkFileLocation(file);
298                if(!file.exists())
299                        throw new ApplicationException("can't attach file "+strMimeattach+", this file doesn't exist");
300                
301
302        smtp.addAttachment(file,type,disposition,contentID,removeAfterSend);
303                
304        }
305        public void setMimeattach(String strMimeattach) throws PageException    {
306                setMimeattach(strMimeattach, "", null, null,false);
307        }
308        
309        /**
310         * @param spoolenable The spoolenable to set.
311         */
312        public void setAsync(boolean spoolenable) {
313                smtp.setSpoolenable(spoolenable);
314        }
315        
316
317        // old function for backward compatiblity
318        public void setSpoolenable(boolean async){
319                setAsync(async);
320        }
321        
322        
323        /** set the value server
324        * @param strServer value to set
325         * @throws PageException 
326        **/
327        public void setServer(String strServer) throws PageException {
328                smtp.setHost(strServer);
329        }
330 
331        /** set the value mailerid
332        * @param mailerid value to set
333        **/
334        public void setMailerid(String mailerid)        {
335                smtp.setXMailer(mailerid);
336        }
337        
338        /** set the value port
339        *  The TCP/IP port on which the SMTP server listens for requests. This is normally 25.
340        * @param port value to set
341        **/
342        public void setPort(double port)        {
343                smtp.setPort((int)port);
344        }
345        
346        /**
347         * @param wraptext The wraptext to set.
348         */
349        public void setWraptext(double wraptext) {
350                getPart().setWraptext((int)wraptext);
351        }
352
353        /** set the value timeout
354        *  The number of seconds to wait before timing out the connection to the SMTP server.
355        * @param timeout value to set
356        **/
357        public void setTimeout(double timeout)  {
358                smtp.setTimeout((int)(timeout*1000));
359        }
360        
361        /**
362         * @param charset The charset to set.
363         */
364        public void setCharset(String charset) {
365                this.charset=charset;
366        }
367
368        /** set the value group
369        *  Specifies the query column to use when you group sets of records together to send as an e-mail 
370        *               message. For example, if you send a set of billing statements to customers, you might group on 
371        *               "Customer_ID." The group attribute, which is case sensitive, eliminates adjacent duplicates when the 
372        *               data is sorted by the specified field. See the Usage section for exceptions.
373        * @param group value to set
374        **/
375        public void setGroup(String group)      {
376                this.group=group;
377        }
378
379        /** set the value groupcasesensitive
380        *  Boolean indicating whether to group with regard to case or not. The default value is YES; 
381        *               case is considered while grouping. If the query attribute specifies a query object that was generated 
382        *               by a case-insensitive SQL query, set the groupCaseSensitive attribute to NO to keep the recordset 
383        *               intact.
384        * @param groupcasesensitive value to set
385        **/
386        public void setGroupcasesensitive(boolean groupcasesensitive)   {
387                this.groupcasesensitive=groupcasesensitive;
388        }
389
390        /** set the value query
391        *  The name of the cfquery from which to draw data for message(s) to send. Specify this 
392        *               attribute to send more than one mail message, or to send the results of a query within a message.
393        * @param query value to set
394        **/
395        public void setQuery(String query)      {
396                this.query=query;
397        }
398
399        /** set the value maxrows
400        *  Specifies the maximum number of e-mail messages to send.
401        * @param maxrows value to set
402        **/
403        public void setMaxrows(double maxrows)  {
404                this.maxrows=maxrows;
405        }
406        
407
408        public void setTls(boolean tls) {
409                smtp.setTLS(tls);
410        }       
411        
412        public void setUsetls(boolean tls)      {
413                smtp.setTLS(tls);
414        }       
415        
416        public void setStarttls(boolean tls)    {
417                smtp.setTLS(tls);
418        }
419
420        public void setSsl(boolean ssl) {
421                smtp.setSSL(ssl);
422        }
423
424        public void setUsessl(boolean ssl)      {
425                smtp.setSSL(ssl);
426        }
427
428        public void setSecure(boolean ssl)      {
429                smtp.setSSL(ssl);
430        }
431        public void setPriority(String strPriority) throws ExpressionException  {
432                strPriority=strPriority.trim().toLowerCase();
433                boolean valid=true;
434                if(Decision.isNumeric(strPriority)) {
435                        int p=Caster.toIntValue(strPriority,-1);
436                        if(p<1 || p>5)valid=false;
437                        else this.priority=p;
438                }
439                else {
440                        if("highest".equals(strPriority))priority=1;
441                        else if("urgent".equals(strPriority))priority=1;
442                        else if("high".equals(strPriority))priority=2;
443                        else if("normal".equals(strPriority))priority=3;
444                        else if("low".equals(strPriority))priority=4;
445                        else if("lowest".equals(strPriority))priority=5;
446                        else if("non-urgent".equals(strPriority))priority=5;
447                        else if("none-urgent".equals(strPriority))priority=5;
448                        else valid=false;
449                }
450                
451                if(!valid)throw new ExpressionException("the value of attribute priority is invalid ["+strPriority+"], " +
452                                "the value should be an integer between [1-5] or " +
453                                "one of the following [highest,urgent,high,normal,low,lowest,non-urgent]");
454                
455        }
456
457        /** set the value startrow
458        *  Specifies the row in the query to start from.
459        * @param startrow value to set
460        **/
461        public void setStartrow(double startrow)        {
462                this.startrow=startrow;
463        }
464
465    /**
466     * @param part
467     */
468    public void addPart(MailPart part) {
469        String type = part.getType();
470                if(StringUtil.isEmpty(part.getCharset())) part.setCharset(getCharset());
471                if(type!=null && (type.equals("text/plain") || type.equals("plain") || type.equals("text"))){
472                        part.isHTML(false);
473                        addClassicBodyPart(part);
474                }
475                else if(type!=null && (type.equals("text/html") || type.equals("html") || type.equals("htm"))){
476                        part.isHTML(true);
477                        addClassicBodyPart(part);
478                }   
479                else {
480                        addBodyPart(part);
481                }
482    }
483        
484    // this was not supported in prior releases
485        private void addBodyPart(MailPart part) {
486                smtp.setPart(part);
487        }
488
489        /**
490     * @param part
491     */
492    private void addClassicBodyPart(MailPart part) {
493        if(part.isHTML()) {
494            if(!smtp.hasHTMLText())smtp.setHTMLText(part.getBody(), part.getCharset());
495        }
496        else {
497            if(!smtp.hasPlainText())smtp.setPlainText(part.getBody(), part.getCharset());
498        }
499    }
500
501
502    @Override
503        public int doStartTag() throws ApplicationException     {
504                if(isEmpty(smtp.getTos()) && isEmpty(smtp.getCcs()) && isEmpty(smtp.getBccs())) 
505                        throw new ApplicationException("One of the following attribtues must be defined [to, cc, bcc]");
506                        
507                return EVAL_BODY_BUFFERED;
508        }
509
510        private boolean isEmpty(InternetAddress[] addrs) {
511                return addrs==null || addrs.length==0;
512        }
513
514
515        @Override
516        public void doInitBody()        {
517                
518        }
519
520        @Override
521        public int doAfterBody()        {
522                getPart().setBody(bodyContent.getString());
523                smtp.setCharset(getCharset());
524                getPart().setCharset(getCharset());
525                addClassicBodyPart(getPart());
526                return SKIP_BODY;
527        }
528        
529        @Override
530        public int doEndTag() throws PageException      {
531                smtp.setTimeZone(pageContext.getTimeZone());
532                try {
533                        smtp.send(pageContext.getConfig(), sendTime!=null ? sendTime.getTime() : 0);
534                } 
535                catch (MailException e) {
536                        throw Caster.toPageException(e);
537                }
538                return EVAL_PAGE;
539        }
540
541        /**
542         * sets a mail param
543         * @param type
544         * @param file
545         * @param name
546         * @param value
547         * @param contentID 
548         * @param disposition 
549         * @throws PageException 
550         */
551        public void setParam(String type, String file, String name, String value, String disposition, String contentID,Boolean oRemoveAfterSend) throws PageException {
552                if(file!=null){
553                        boolean removeAfterSend=(oRemoveAfterSend==null)?remove:oRemoveAfterSend.booleanValue();
554                                
555                        setMimeattach(file,type,disposition,contentID,removeAfterSend);
556                }
557                else {
558                        if(name.equalsIgnoreCase("bcc"))                        setBcc(value);
559                        else if(name.equalsIgnoreCase("cc"))            setCc(value);
560                        else if(name.equalsIgnoreCase("charset"))       setCharset(value);
561                        else if(name.equalsIgnoreCase("failto"))        setFailto(value);
562                        else if(name.equalsIgnoreCase("from"))          setFrom(value);
563                        else if(name.equalsIgnoreCase("mailerid"))      setMailerid(value);
564                        else if(name.equalsIgnoreCase("mimeattach"))setMimeattach(value);
565                        else if(name.equalsIgnoreCase("priority"))      setPriority(value);
566                        else if(name.equalsIgnoreCase("replyto"))       setReplyto(value);
567                        else if(name.equalsIgnoreCase("subject"))       setSubject(value);
568                        else if(name.equalsIgnoreCase("to"))            setTo(value);
569                        
570                        else smtp.addHeader(name,value);
571                }
572        }       
573        
574        private lucee.runtime.net.mail.MailPart getPart() {
575                if(part==null)part=new lucee.runtime.net.mail.MailPart(pageContext.getConfig().getMailDefaultEncoding());
576                return part;
577        }
578
579
580        /**
581         * @return the charset
582         */
583        public String getCharset() {
584                if(charset==null)charset=pageContext.getConfig().getMailDefaultEncoding();
585                return charset;
586        }
587
588
589        public void setSendtime(DateTime dt) {
590
591                this.sendTime = dt;
592        }
593
594}