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 }