001    package railo.runtime.net.mail;
002    
003    import java.io.IOException;
004    import java.io.InputStream;
005    import java.text.Normalizer;
006    import java.util.Enumeration;
007    import java.util.HashMap;
008    import java.util.Iterator;
009    import java.util.Map;
010    import java.util.Properties;
011    
012    import javax.mail.Authenticator;
013    import javax.mail.BodyPart;
014    import javax.mail.Flags;
015    import javax.mail.Folder;
016    import javax.mail.Header;
017    import javax.mail.Message;
018    import javax.mail.MessagingException;
019    import javax.mail.Multipart;
020    import javax.mail.Part;
021    import javax.mail.PasswordAuthentication;
022    import javax.mail.Session;
023    import javax.mail.Store;
024    import javax.mail.internet.MimeMultipart;
025    import javax.mail.internet.MimeUtility;
026    
027    import railo.commons.io.IOUtil;
028    import railo.commons.io.SystemUtil;
029    import railo.commons.io.res.Resource;
030    import railo.commons.io.res.util.ResourceUtil;
031    import railo.commons.lang.ExceptionUtil;
032    import railo.commons.lang.Md5;
033    import railo.commons.lang.StringUtil;
034    import railo.runtime.exp.PageException;
035    import railo.runtime.net.imap.ImapClient;
036    import railo.runtime.net.pop.PopClient;
037    import railo.runtime.op.Caster;
038    import railo.runtime.op.Operator;
039    import railo.runtime.type.Array;
040    import railo.runtime.type.ArrayImpl;
041    import railo.runtime.type.Collection;
042    import railo.runtime.type.KeyImpl;
043    import railo.runtime.type.Query;
044    import railo.runtime.type.QueryImpl;
045    import railo.runtime.type.Struct;
046    import railo.runtime.type.StructImpl;
047    import railo.runtime.type.util.ArrayUtil;
048    import railo.runtime.type.util.ListUtil;
049    
050    public abstract class MailClient {
051            
052            /**
053             * Simple authenicator implmentation
054             */
055            private final class _Authenticator extends Authenticator {
056    
057                    private String _fldif = null;
058                    private String a = null;
059    
060                    protected PasswordAuthentication getPasswordAuthentication() {
061                            return new PasswordAuthentication(_fldif, a);
062                    }
063    
064                    public _Authenticator(String s, String s1) {
065                            _fldif = s;
066                            a = s1;
067                    }
068            }
069            
070    
071            private static final Collection.Key DATE = KeyImpl.init("date");
072            private static final Collection.Key SUBJECT = KeyImpl.init("subject");
073            private static final Collection.Key SIZE = KeyImpl.init("size");
074            private static final Collection.Key FROM = KeyImpl.init("from");
075            private static final Collection.Key MESSAGE_NUMBER = KeyImpl.init("messagenumber");
076            private static final Collection.Key MESSAGE_ID = KeyImpl.init("messageid");
077            private static final Collection.Key REPLYTO = KeyImpl.init("replyto");
078            private static final Collection.Key CC = KeyImpl.init("cc");
079            private static final Collection.Key BCC = KeyImpl.init("bcc");
080            private static final Collection.Key TO = KeyImpl.init("to");
081            private static final Collection.Key UID = KeyImpl.init("uid");
082            private static final Collection.Key HEADER = KeyImpl.init("header");
083            private static final Collection.Key BODY = KeyImpl.init("body");
084            private static final Collection.Key CIDS = KeyImpl.init("cids");
085            private static final Collection.Key TEXT_BODY = KeyImpl.init("textBody");
086            private static final Collection.Key HTML_BODY = KeyImpl.init("HTMLBody");
087            private static final Collection.Key ATTACHMENTS = KeyImpl.init("attachments");
088            private static final Collection.Key ATTACHMENT_FILES = KeyImpl.init("attachmentfiles");
089    
090    
091            public static final int TYPE_POP3 = 0;
092            public static final int TYPE_IMAP = 1;
093    
094    
095            private String _flddo[] = {"date", "from", "messagenumber", "messageid", "replyto", "subject", "cc", "to", "size", "header", "uid"};
096            private String _fldnew[] = {"date", "from", "messagenumber", "messageid", "replyto", "subject", "cc", "to", "size", "header", "uid", "body", "textBody", "HTMLBody", "attachments", "attachmentfiles","cids"};
097            private String server = null;
098            private String username = null;
099            private String password = null;
100            private Session _fldtry = null;
101            private Store _fldelse = null;
102            private int port = 0;
103            private int timeout = 0;
104            private int startrow = 0;
105            private int maxrows = 0;
106            private boolean uniqueFilenames = false;
107            private Resource attachmentDirectory = null;
108    
109            
110            public static MailClient getInstance(int type,String server, int port, String username, String password){
111                    if(TYPE_POP3==type)
112                            return new PopClient(server,port,username,password);
113                    if(TYPE_IMAP==type)
114                            return new ImapClient(server,port,username,password);
115                    return null;
116            }
117            
118            /**
119             * constructor of the class
120             * @param server
121             * @param port
122             * @param username
123             * @param password
124             */
125            public MailClient(String server, int port, String username, String password) {
126                    timeout = 60000;
127                    startrow = 0;
128                    maxrows = -1;
129                    uniqueFilenames = false;
130                    this.server = server;
131                    this.port = port;
132                    this.username = username;
133                    this.password = password;
134            }
135    
136    
137        /**
138         * @param maxrows The maxrows to set.
139         */
140        public void setMaxrows(int maxrows) {
141            this.maxrows = maxrows;
142        }
143    
144        /**
145         * @param startrow The startrow to set.
146         */
147        public void setStartrow(int startrow) {
148            this.startrow = startrow;
149        }
150    
151    
152        /**
153         * @param timeout The timeout to set.
154         */
155        public void setTimeout(int timeout) {
156            this.timeout = timeout;
157        }
158    
159        /**
160         * @param uniqueFilenames The uniqueFilenames to set.
161         */
162        public void setUniqueFilenames(boolean uniqueFilenames) {
163            this.uniqueFilenames = uniqueFilenames;
164        }
165    
166        /**
167         * @param attachmentDirectory The attachmentDirectory to set.
168         */
169        public void setAttachmentDirectory(Resource attachmentDirectory) {
170            this.attachmentDirectory = attachmentDirectory;
171        }
172    
173        /**
174         * connects to pop server
175         * @throws MessagingException
176         */
177        public void connect() throws MessagingException {
178                    Properties properties = new Properties();
179                    String type=getTypeAsString();
180                    properties.put("mail."+type+".host", server);
181                    properties.put("mail."+type+".port", new Double(port));
182                    properties.put("mail."+type+".connectiontimeout", String.valueOf(timeout));
183                    properties.put("mail."+type+".timeout", String.valueOf(timeout));
184                    //properties.put("mail.mime.charset", "UTF-8");
185                    
186                    
187                    if(TYPE_IMAP==getType())properties.put("mail.imap.partialfetch", "false" );
188                    
189                    _fldtry = username != null ? Session.getInstance(properties, new _Authenticator(username, password)) : Session.getInstance(properties);
190                    _fldelse = _fldtry.getStore(type);
191                    if(!StringUtil.isEmpty(username))_fldelse.connect(server,username,password);
192                    else _fldelse.connect();
193            }
194    
195        protected abstract String getTypeAsString();
196        protected abstract int getType();
197    
198    
199            /**
200         * delete all message in ibox that match given criteria
201         * @param messageNumbers
202         * @param uIds
203         * @throws MessagingException
204             * @throws IOException 
205         */
206        public void deleteMails(String as[], String as1[]) throws MessagingException, IOException {
207                    Folder folder;
208                    Message amessage[];
209                    folder = _fldelse.getFolder("INBOX");
210                    folder.open(2);
211                    Map<String, Message> map = getMessages(null,folder, as1, as, startrow, maxrows,false);
212                    Iterator<String> iterator = map.keySet().iterator();
213                    amessage = new Message[map.size()];
214                    int i = 0;
215                    while(iterator.hasNext()) {
216                            amessage[i++] =  map.get(iterator.next());
217                    }
218                    try {
219                            folder.setFlags(amessage, new Flags(javax.mail.Flags.Flag.DELETED), true);
220                    }
221                    finally {
222                            folder.close(true);
223                    }
224            }
225    
226        /**
227         * return all messages from inbox
228         * @param messageNumbers all messages with this ids
229         * @param uIds all messages with this uids
230         * @param withBody also return body
231         * @return all messages from inbox
232         * @throws MessagingException
233         * @throws IOException
234         */
235        public Query getMails(String[] messageNumbers, String[] uids, boolean all) throws MessagingException, IOException {
236                    Query qry = new QueryImpl(all ? _fldnew : _flddo, 0, "query");
237                    Folder folder = _fldelse.getFolder("INBOX");
238                    folder.open(Folder.READ_ONLY);
239                    try {
240                            getMessages(qry,folder, uids, messageNumbers, startrow, maxrows,all);
241                    }
242                    finally {
243                            folder.close(false);
244                    }
245                    return qry;
246            }
247    
248        private void toQuery(Query qry, Message message, Object uid, boolean all) 
249         {
250                    int row = qry.addRow();
251            // date
252                    try {
253                            qry.setAtEL(DATE, row, Caster.toDate(message.getSentDate(), true,null,null));
254                    } 
255                    catch (MessagingException e) {}
256                    
257                    // subject
258                    try {
259                            qry.setAtEL(SUBJECT, row, message.getSubject());
260                    } catch (MessagingException e) {
261                            qry.setAtEL(SUBJECT, row, "MessagingException:"+e.getMessage());
262                    }
263                    
264                    // size
265                    try {
266                            qry.setAtEL(SIZE, row, new Double(message.getSize()));
267                    } 
268                    catch (MessagingException e) {}
269                    
270                    qry.setAtEL(FROM, row, toList(getHeaderEL(message,"from")));
271                    qry.setAtEL(MESSAGE_NUMBER, row, new Double(message.getMessageNumber()));
272                    qry.setAtEL(MESSAGE_ID, row, toList(getHeaderEL(message,"Message-ID")));
273                    String s = toList(getHeaderEL(message,"reply-to"));
274                    if(s.length() == 0) {
275                            s = Caster.toString(qry.getAt(FROM, row,null), "");
276                    }
277                    qry.setAtEL(REPLYTO, row, s);
278                    qry.setAtEL(CC, row, toList(getHeaderEL(message,"cc")));
279                    qry.setAtEL(BCC, row, toList(getHeaderEL(message,"bcc")));
280                    qry.setAtEL(TO, row, toList(getHeaderEL(message,"to")));
281                    qry.setAtEL(UID, row, uid);
282                    StringBuffer content = new StringBuffer();
283                    try {
284                            for(Enumeration enumeration = message.getAllHeaders(); enumeration.hasMoreElements(); content.append('\n')){
285                                    Header header = (Header) enumeration.nextElement();
286                                    content.append(header.getName());
287                                    content.append(": ");
288                                    content.append(header.getValue());
289                            }
290                    } 
291                    catch (MessagingException e) {}
292                    qry.setAtEL(HEADER, row, content.toString());
293                    
294                    if(all) {
295                            getContentEL(qry, message, row);
296                    }
297            }
298    
299            private String[] getHeaderEL(Message message, String key) {
300                    try {
301                            return message.getHeader(key);
302                    } catch (MessagingException e) {
303                            return null;
304                    }
305            }
306    
307            /**
308         * gets all messages from given Folder that match given criteria
309             * @param qry 
310         * @param folder
311         * @param uIds
312         * @param messageNumbers
313             * @param all 
314         * @param startrow
315         * @param maxrows
316             * @return 
317         * @return matching Messages
318             * @throws MessagingException 
319         */
320        private Map<String,Message> getMessages(Query qry, Folder folder, String[] uids, String[] messageNumbers, int startRow, int maxRow, boolean all) throws MessagingException 
321         {
322                    
323            Message[] messages = folder.getMessages();
324                    Map<String,Message> map = qry==null?new HashMap<String,Message>():null;
325                    int k = 0;
326                    if(uids != null || messageNumbers != null) {
327                            startRow = 0;
328                            maxRow = -1;
329                    }
330                    Message message;
331                    for(int l = startRow; l < messages.length; l++) {
332                            if(maxRow != -1 && k == maxRow) {
333                                    break;
334                            }
335                            message = messages[l];
336                            int messageNumber = message.getMessageNumber();
337                            String id = getId(folder,message);
338                            
339                            if(uids == null ? messageNumbers == null || contains(messageNumbers, messageNumber) : contains(uids, id)) {
340                                    k++;
341                                    if(qry!=null){
342                                            toQuery(qry,message,id,all);
343                                    }
344                                    else map.put(id, message);
345                            }
346                    }
347                    return map;
348            }
349        protected abstract String getId(Folder folder,Message message) throws MessagingException ;
350    
351    
352        private void getContentEL(Query query, Message message, int row) {
353            try {
354                            getContent(query, message, row);
355                    } catch (Exception e) {
356                            String st = ExceptionUtil.getStacktrace(e,true);
357                            
358                            query.setAtEL(BODY, row, st);
359                    }
360        }
361    
362            /**
363         * write content data to query
364         * @param qry
365         * @param content
366         * @param row
367         * @throws MessagingException
368         * @throws IOException
369         */
370        private void getContent(Query query, Message message, int row) throws MessagingException, IOException {
371                    StringBuffer body = new StringBuffer();
372                    Struct cids=new StructImpl();
373                    query.setAtEL(CIDS, row, cids);
374                    if(message.isMimeType("text/plain")) {
375                            String content=getConent(message);
376                    query.setAtEL(TEXT_BODY,row,content);
377                    body.append(content);
378                    }
379                    else if(message.isMimeType("text/html")) {
380                            String content=getConent(message);
381                    query.setAtEL(HTML_BODY,row,content);
382                    body.append(content);
383                    }
384                    else {
385                            Object content = message.getContent();
386                            if(content instanceof MimeMultipart) {
387                                    Array attachments = new ArrayImpl();
388                                    Array attachmentFiles = new ArrayImpl();
389                                    getMultiPart(query, row, attachments, attachmentFiles,cids, (MimeMultipart) content, body);
390            
391                                    if(attachments.size() > 0) {
392                                            try {
393                                                    query.setAtEL(ATTACHMENTS, row, ListUtil.arrayToList(attachments, "\t"));
394                                            }
395                                            catch(PageException pageexception) {
396                                            }
397                                    }
398                                    if(attachmentFiles.size() > 0) {
399                                            try {
400                                                    query.setAtEL(ATTACHMENT_FILES, row, ListUtil.arrayToList(attachmentFiles, "\t"));
401                                            }
402                                            catch(PageException pageexception1) {
403                                            }
404                                    }
405                                    
406                                    
407                            }
408                    }
409                    query.setAtEL(BODY, row, body.toString());
410            }
411    
412            private void getMultiPart(Query query, int row, Array attachments, Array attachmentFiles,Struct cids, Multipart multiPart, StringBuffer body) throws MessagingException, IOException {
413                    int j = multiPart.getCount();
414    
415                    for(int k = 0; k < j; k++) {
416                            BodyPart bodypart = multiPart.getBodyPart(k);
417                            Object content;
418                            
419    
420                            if(bodypart.getFileName() != null) {
421                                    String filename = bodypart.getFileName();
422                                    try{
423                                            filename=Normalizer.normalize(MimeUtility.decodeText(filename),Normalizer.Form.NFC);
424                                    }
425                                    catch(Throwable t){}
426                                    
427                                    if(bodypart.getHeader("Content-ID") != null) {
428                                            String[] ids = bodypart.getHeader("Content-ID");
429                                            String cid=ids[0].substring(1, ids[0].length() - 1);
430                                            cids.setEL(KeyImpl.init(filename), cid);
431                                    }
432                                    
433                                    if(filename != null && ArrayUtil.find(attachments, filename)==0) {
434                                            
435                                            attachments.appendEL(filename);
436                                            if(attachmentDirectory != null) {
437                                                    Resource file = attachmentDirectory.getRealResource(filename);
438                                                    int l = 1;
439                                                    String s2;
440                                                    for(; uniqueFilenames && file.exists(); file = attachmentDirectory.getRealResource(s2)) {
441                                                            String as[] = ResourceUtil.splitFileName(filename);
442                                                            s2 = as.length != 1 ? as[0] + l++ + '.' + as[1] : as[0] + l++;
443                                                    }
444    
445                                                    IOUtil.copy(bodypart.getInputStream(), file, true);
446                                                    attachmentFiles.appendEL(file.getAbsolutePath());
447                                            }
448                                    }
449                            }
450                            else if(bodypart.isMimeType("text/plain")) {
451                                    content=getConent(bodypart);
452                            query.setAtEL(TEXT_BODY,row,content);
453                            if(body.length()==0)body.append(content);
454                            }
455                            else if(bodypart.isMimeType("text/html")) {
456                                    content=getConent(bodypart);
457                            query.setAtEL(HTML_BODY,row,content);
458                            if(body.length()==0)body.append(content);
459                            }
460                            else if((content=bodypart.getContent()) instanceof Multipart) {
461                                    getMultiPart(query, row, attachments, attachmentFiles,cids, (Multipart) content, body);
462                            }
463                            else if(bodypart.getHeader("Content-ID") != null) {
464                                    String[] ids = bodypart.getHeader("Content-ID");
465                                    String cid=ids[0].substring(1, ids[0].length() - 1);
466                                    String filename = "cid:" + cid;
467                                    
468                                            attachments.appendEL(filename);
469                                            if(attachmentDirectory != null) {
470                                                    filename = "_" + Md5.getDigestAsString(filename);
471                                                    Resource file = attachmentDirectory.getRealResource(filename);
472                                                    int l = 1;
473                                                    String s2;
474                                                    for(; uniqueFilenames && file.exists(); file = attachmentDirectory.getRealResource(s2)) {
475                                                            String as[] = ResourceUtil.splitFileName(filename);
476                                                            s2 = as.length != 1 ? as[0] + l++ + '.' + as[1] : as[0] + l++;
477                                                    }
478    
479                                                    IOUtil.copy(bodypart.getInputStream(), file, true);
480                                                    attachmentFiles.appendEL(file.getAbsolutePath());
481                                            }
482                                    
483                                    cids.setEL(KeyImpl.init(filename), cid);
484                            }
485                    }
486            }
487    
488            /* *
489         * writes BodyTag data to query, if there is a problem with encoding, encoding will removed a do it again
490         * @param qry
491         * @param columnName
492         * @param row
493         * @param bp
494         * @param body
495         * @throws IOException
496         * @throws MessagingException
497         * /
498        private void setBody(Query qry, String columnName, int row, BodyPart bp, StringBuffer body) throws IOException, MessagingException {
499            String content = getConent(bp);
500            
501            qry.setAtEL(columnName,row,content);
502            if(body.length()==0)body.append(content);
503            
504        }*/
505        
506        private String getConent(Part bp) throws MessagingException {
507            InputStream is=null;
508            
509            try {
510                    return getContent(is=bp.getInputStream(), getCharsetFromContentType(bp.getContentType()));
511            }
512            catch(IOException mie) {
513                    IOUtil.closeEL(is);
514                    try {
515                                    return getContent(is=bp.getInputStream(), SystemUtil.getCharset());
516                            } 
517                    catch (IOException e) {
518                            return "Can't read body of this message:"+e.getMessage();
519                            } 
520            }
521            finally {
522                    IOUtil.closeEL(is);
523            }
524        }
525        
526        private String getContent(InputStream is,String charset) throws IOException {
527                    return MailUtil.decode(IOUtil.toString(is, charset));
528        }
529        
530        
531            private static String getCharsetFromContentType(String contentType) {
532                    Array arr=ListUtil.listToArrayRemoveEmpty(contentType,"; ");
533                    
534                    for(int i=1;i<=arr.size();i++) {
535                            Array inner = ListUtil.listToArray((String)arr.get(i,null),"= ");
536                            if(inner.size()==2 && ((String)inner.get(1,"")).trim().equalsIgnoreCase("charset")) {
537                                    String charset = (String) inner.get(2,"");
538                                    charset=charset.trim();
539                                    if(!StringUtil.isEmpty(charset)) {
540                                            if(StringUtil.startsWith(charset, '"') && StringUtil.endsWith(charset, '"')) {
541                                                    charset=charset.substring(1,charset.length()-1);
542                                            }
543                                            if(StringUtil.startsWith(charset, '\'') && StringUtil.endsWith(charset, '\'')) {
544                                                    charset=charset.substring(1,charset.length()-1);
545                                            }
546                                    }
547                                    return charset;
548                            }
549                    }
550                    return "us-ascii";
551            }
552    
553    
554            /**
555         * checks if a String Array (ids) has one element that is equal to id
556         * @param ids
557         * @param id
558         * @return has element found or not
559         */
560            private boolean contains(String ids[], String id) {
561                    for(int i = 0; i < ids.length; i++) {
562                            if(Operator.compare(ids[i], id) == 0)   return true;
563                    }
564                    return false;
565            }
566    
567            /**
568         * checks if a String Array (ids) has one element that is equal to id
569         * @param ids
570         * @param id
571         * @return has element found or not
572         */
573            private boolean contains(String ids[], int id) {
574                    for(int i = 0; i < ids.length; i++) {
575                            if(Operator.compare(ids[i], id) == 0) return true;
576                    }
577                    return false;
578            }
579    
580            /**
581         * translate a String Array to String List
582         * @param arr Array to translate
583         * @return List from Array
584         */
585            private String toList(String ids[]) {
586                    if(ids == null) return "";
587                    return ListUtil.arrayToList(ids, ",");
588            }
589    
590            /**
591         * disconnect without a exception
592         */
593            public void disconnectEL() {
594                    try {
595                            if(_fldelse != null)_fldelse.close();
596                    }
597                    catch(Exception exception) {}
598            }
599    }