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.List;
044    import railo.runtime.type.Query;
045    import railo.runtime.type.QueryImpl;
046    import railo.runtime.type.Struct;
047    import railo.runtime.type.StructImpl;
048    import railo.runtime.type.util.ArrayUtil;
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                            if(folder != null) {
223                                    folder.close(true);
224                            }
225                    }
226            }
227    
228        /**
229         * return all messages from inbox
230         * @param messageNumbers all messages with this ids
231         * @param uIds all messages with this uids
232         * @param withBody also return body
233         * @return all messages from inbox
234         * @throws MessagingException
235         * @throws IOException
236         */
237        public Query getMails(String[] messageNumbers, String[] uids, boolean all) throws MessagingException, IOException {
238                    Query qry = new QueryImpl(all ? _fldnew : _flddo, 0, "query");
239                    Folder folder = _fldelse.getFolder("INBOX");
240                    folder.open(Folder.READ_ONLY);
241                    try {
242                            getMessages(qry,folder, uids, messageNumbers, startrow, maxrows,all);
243                    }
244                    finally {
245                            folder.close(false);
246                    }
247                    return qry;
248            }
249    
250        private void toQuery(Query qry, Message message, Object uid, boolean all) 
251         {
252                    int row = qry.addRow();
253            // date
254                    try {
255                            qry.setAtEL(DATE, row, Caster.toDate(message.getSentDate(), true,null,null));
256                    } 
257                    catch (MessagingException e) {}
258                    
259                    // subject
260                    try {
261                            qry.setAtEL(SUBJECT, row, message.getSubject());
262                    } catch (MessagingException e) {
263                            qry.setAtEL(SUBJECT, row, "MessagingException:"+e.getMessage());
264                    }
265                    
266                    // size
267                    try {
268                            qry.setAtEL(SIZE, row, new Double(message.getSize()));
269                    } 
270                    catch (MessagingException e) {}
271                    
272                    qry.setAtEL(FROM, row, toList(getHeaderEL(message,"from")));
273                    qry.setAtEL(MESSAGE_NUMBER, row, new Double(message.getMessageNumber()));
274                    qry.setAtEL(MESSAGE_ID, row, toList(getHeaderEL(message,"Message-ID")));
275                    String s = toList(getHeaderEL(message,"reply-to"));
276                    if(s.length() == 0) {
277                            s = Caster.toString(qry.getAt(FROM, row,null), "");
278                    }
279                    qry.setAtEL(REPLYTO, row, s);
280                    qry.setAtEL(CC, row, toList(getHeaderEL(message,"cc")));
281                    qry.setAtEL(BCC, row, toList(getHeaderEL(message,"bcc")));
282                    qry.setAtEL(TO, row, toList(getHeaderEL(message,"to")));
283                    qry.setAtEL(UID, row, uid);
284                    StringBuffer content = new StringBuffer();
285                    try {
286                            for(Enumeration enumeration = message.getAllHeaders(); enumeration.hasMoreElements(); content.append('\n')){
287                                    Header header = (Header) enumeration.nextElement();
288                                    content.append(header.getName());
289                                    content.append(": ");
290                                    content.append(header.getValue());
291                            }
292                    } 
293                    catch (MessagingException e) {}
294                    qry.setAtEL(HEADER, row, content.toString());
295                    
296                    if(all) {
297                            getContentEL(qry, message, row);
298                    }
299            }
300    
301            private String[] getHeaderEL(Message message, String key) {
302                    try {
303                            return message.getHeader(key);
304                    } catch (MessagingException e) {
305                            return null;
306                    }
307            }
308    
309            /**
310         * gets all messages from given Folder that match given criteria
311             * @param qry 
312         * @param folder
313         * @param uIds
314         * @param messageNumbers
315             * @param all 
316         * @param startrow
317         * @param maxrows
318             * @return 
319         * @return matching Messages
320             * @throws MessagingException 
321         */
322        private Map<String,Message> getMessages(Query qry, Folder folder, String[] uids, String[] messageNumbers, int startRow, int maxRow, boolean all) throws MessagingException 
323         {
324                    
325            Message[] messages = folder.getMessages();
326                    Map<String,Message> map = qry==null?new HashMap<String,Message>():null;
327                    int k = 0;
328                    if(uids != null || messageNumbers != null) {
329                            startRow = 0;
330                            maxRow = -1;
331                    }
332                    Message message;
333                    for(int l = startRow; l < messages.length; l++) {
334                            if(maxRow != -1 && k == maxRow) {
335                                    break;
336                            }
337                            message = messages[l];
338                            int messageNumber = message.getMessageNumber();
339                            String id = getId(folder,message);
340                            
341                            if(uids == null ? messageNumbers == null || contains(messageNumbers, messageNumber) : contains(uids, id)) {
342                                    k++;
343                                    if(qry!=null){
344                                            toQuery(qry,message,id,all);
345                                    }
346                                    else map.put(id, message);
347                            }
348                    }
349                    return map;
350            }
351        protected abstract String getId(Folder folder,Message message) throws MessagingException ;
352    
353    
354        private void getContentEL(Query query, Message message, int row) {
355            try {
356                            getContent(query, message, row);
357                    } catch (Exception e) {
358                            String st = ExceptionUtil.getStacktrace(e,true);
359                            
360                            query.setAtEL(BODY, row, st);
361                    }
362        }
363    
364            /**
365         * write content data to query
366         * @param qry
367         * @param content
368         * @param row
369         * @throws MessagingException
370         * @throws IOException
371         */
372        private void getContent(Query query, Message message, int row) throws MessagingException, IOException {
373                    StringBuffer body = new StringBuffer();
374                    Struct cids=new StructImpl();
375                    query.setAtEL(CIDS, row, cids);
376                    if(message.isMimeType("text/plain")) {
377                            String content=getConent(message);
378                    query.setAtEL(TEXT_BODY,row,content);
379                    body.append(content);
380                    }
381                    else if(message.isMimeType("text/html")) {
382                            String content=getConent(message);
383                    query.setAtEL(HTML_BODY,row,content);
384                    body.append(content);
385                    }
386                    else {
387                            Object content = message.getContent();
388                            if(content instanceof MimeMultipart) {
389                                    Array attachments = new ArrayImpl();
390                                    Array attachmentFiles = new ArrayImpl();
391                                    getMultiPart(query, row, attachments, attachmentFiles,cids, (MimeMultipart) content, body);
392            
393                                    if(attachments.size() > 0) {
394                                            try {
395                                                    query.setAtEL(ATTACHMENTS, row, List.arrayToList(attachments, "\t"));
396                                            }
397                                            catch(PageException pageexception) {
398                                            }
399                                    }
400                                    if(attachmentFiles.size() > 0) {
401                                            try {
402                                                    query.setAtEL(ATTACHMENT_FILES, row, List.arrayToList(attachmentFiles, "\t"));
403                                            }
404                                            catch(PageException pageexception1) {
405                                            }
406                                    }
407                                    
408                                    
409                            }
410                    }
411                    query.setAtEL(BODY, row, body.toString());
412            }
413    
414            private void getMultiPart(Query query, int row, Array attachments, Array attachmentFiles,Struct cids, Multipart multiPart, StringBuffer body) throws MessagingException, IOException {
415                    int j = multiPart.getCount();
416    
417                    for(int k = 0; k < j; k++) {
418                            BodyPart bodypart = multiPart.getBodyPart(k);
419                            Object content;
420                            
421    
422                            if(bodypart.getFileName() != null) {
423                                    String filename = bodypart.getFileName();
424                                    try{
425                                            filename=Normalizer.normalize(MimeUtility.decodeText(filename),Normalizer.Form.NFC);
426                                    }
427                                    catch(Throwable t){}
428                                    
429                                    if(bodypart.getHeader("Content-ID") != null) {
430                                            String[] ids = bodypart.getHeader("Content-ID");
431                                            String cid=ids[0].substring(1, ids[0].length() - 1);
432                                            cids.setEL(KeyImpl.init(filename), cid);
433                                    }
434                                    
435                                    if(filename != null && ArrayUtil.find(attachments, filename)==0) {
436                                            
437                                            attachments.appendEL(filename);
438                                            if(attachmentDirectory != null) {
439                                                    Resource file = attachmentDirectory.getRealResource(filename);
440                                                    int l = 1;
441                                                    String s2;
442                                                    for(; uniqueFilenames && file.exists(); file = attachmentDirectory.getRealResource(s2)) {
443                                                            String as[] = ResourceUtil.splitFileName(filename);
444                                                            s2 = as.length != 1 ? as[0] + l++ + '.' + as[1] : as[0] + l++;
445                                                    }
446    
447                                                    IOUtil.copy(bodypart.getInputStream(), file, true);
448                                                    attachmentFiles.appendEL(file.getAbsolutePath());
449                                            }
450                                    }
451                            }
452                            else if(bodypart.isMimeType("text/plain")) {
453                                    content=getConent(bodypart);
454                            query.setAtEL(TEXT_BODY,row,content);
455                            if(body.length()==0)body.append(content);
456                            }
457                            else if(bodypart.isMimeType("text/html")) {
458                                    content=getConent(bodypart);
459                            query.setAtEL(HTML_BODY,row,content);
460                            if(body.length()==0)body.append(content);
461                            }
462                            else if((content=bodypart.getContent()) instanceof Multipart) {
463                                    getMultiPart(query, row, attachments, attachmentFiles,cids, (Multipart) content, body);
464                            }
465                            else if(bodypart.getHeader("Content-ID") != null) {
466                                    String[] ids = bodypart.getHeader("Content-ID");
467                                    String cid=ids[0].substring(1, ids[0].length() - 1);
468                                    String filename = "cid:" + cid;
469                                    if(filename != null) {
470                                            attachments.appendEL(filename);
471                                            if(attachmentDirectory != null) {
472                                                    filename = "_" + Md5.getDigestAsString(filename);
473                                                    Resource file = attachmentDirectory.getRealResource(filename);
474                                                    int l = 1;
475                                                    String s2;
476                                                    for(; uniqueFilenames && file.exists(); file = attachmentDirectory.getRealResource(s2)) {
477                                                            String as[] = ResourceUtil.splitFileName(filename);
478                                                            s2 = as.length != 1 ? as[0] + l++ + '.' + as[1] : as[0] + l++;
479                                                    }
480    
481                                                    IOUtil.copy(bodypart.getInputStream(), file, true);
482                                                    attachmentFiles.appendEL(file.getAbsolutePath());
483                                            }
484                                    }
485                                    cids.setEL(KeyImpl.init(filename), cid);
486                            }
487                    }
488            }
489    
490            /* *
491         * writes BodyTag data to query, if there is a problem with encoding, encoding will removed a do it again
492         * @param qry
493         * @param columnName
494         * @param row
495         * @param bp
496         * @param body
497         * @throws IOException
498         * @throws MessagingException
499         * /
500        private void setBody(Query qry, String columnName, int row, BodyPart bp, StringBuffer body) throws IOException, MessagingException {
501            String content = getConent(bp);
502            
503            qry.setAtEL(columnName,row,content);
504            if(body.length()==0)body.append(content);
505            
506        }*/
507        
508        private String getConent(Part bp) throws MessagingException {
509            InputStream is=null;
510            
511            try {
512                    return getContent(is=bp.getInputStream(), getCharsetFromContentType(bp.getContentType()));
513            }
514            catch(IOException mie) {
515                    IOUtil.closeEL(is);
516                    try {
517                                    return getContent(is=bp.getInputStream(), SystemUtil.getCharset());
518                            } 
519                    catch (IOException e) {
520                            return "Can't read body of this message:"+e.getMessage();
521                            } 
522            }
523            finally {
524                    IOUtil.closeEL(is);
525            }
526        }
527        
528        private String getContent(InputStream is,String charset) throws IOException {
529                    return MailUtil.decode(IOUtil.toString(is, charset));
530        }
531        
532        
533            private static String getCharsetFromContentType(String contentType) {
534                    Array arr=List.listToArrayRemoveEmpty(contentType,"; ");
535                    
536                    for(int i=1;i<=arr.size();i++) {
537                            Array inner = List.listToArray((String)arr.get(i,null),"= ");
538                            if(inner.size()==2 && ((String)inner.get(1,"")).trim().equalsIgnoreCase("charset")) {
539                                    String charset = (String) inner.get(2,"");
540                                    charset=charset.trim();
541                                    if(!StringUtil.isEmpty(charset)) {
542                                            if(StringUtil.startsWith(charset, '"') && StringUtil.endsWith(charset, '"')) {
543                                                    charset=charset.substring(1,charset.length()-1);
544                                            }
545                                            if(StringUtil.startsWith(charset, '\'') && StringUtil.endsWith(charset, '\'')) {
546                                                    charset=charset.substring(1,charset.length()-1);
547                                            }
548                                    }
549                                    return charset;
550                            }
551                    }
552                    return "us-ascii";
553            }
554    
555    
556            /**
557         * checks if a String Array (ids) has one element that is equal to id
558         * @param ids
559         * @param id
560         * @return has element found or not
561         */
562            private boolean contains(String ids[], String id) {
563                    for(int i = 0; i < ids.length; i++) {
564                            if(Operator.compare(ids[i], id) == 0)   return true;
565                    }
566                    return false;
567            }
568    
569            /**
570         * checks if a String Array (ids) has one element that is equal to id
571         * @param ids
572         * @param id
573         * @return has element found or not
574         */
575            private boolean contains(String ids[], int id) {
576                    for(int i = 0; i < ids.length; i++) {
577                            if(Operator.compare(ids[i], id) == 0) return true;
578                    }
579                    return false;
580            }
581    
582            /**
583         * translate a String Array to String List
584         * @param arr Array to translate
585         * @return List from Array
586         */
587            private String toList(String ids[]) {
588                    if(ids == null) return "";
589                    return List.arrayToList(ids, ",");
590            }
591    
592            /**
593         * disconnect without a exception
594         */
595            public void disconnectEL() {
596                    try {
597                            if(_fldelse != null)_fldelse.close();
598                    }
599                    catch(Exception exception) {}
600            }
601    }